mirror of https://github.com/grpc/grpc-node.git
Compare commits
38 Commits
@grpc/grpc
...
master
Author | SHA1 | Date |
---|---|---|
|
179dbfaecc | |
|
d22becc98e | |
|
987735920e | |
|
972bb23101 | |
|
7548f413a5 | |
|
110a273a06 | |
|
7ab3da2431 | |
|
5756fe7672 | |
|
eef4b080f5 | |
|
82b331d9e1 | |
|
21d40b0247 | |
|
1605b71a02 | |
|
0157776059 | |
|
537b32f116 | |
|
7905a76494 | |
|
c2b914d4fd | |
|
38e00726f4 | |
|
9843648afb | |
|
9b7402ffab | |
|
af53efcb85 | |
|
2e39783b36 | |
|
6f81b4ef9b | |
|
ff679ae473 | |
|
c4580fa80b | |
|
4a0f4cf5c8 | |
|
e6da4ad1d8 | |
|
024d5d8fdf | |
|
86aa0f2f8b | |
|
9e35cacfe5 | |
|
7a735ce062 | |
|
b74de954cf | |
|
4f0610338f | |
|
78f194be6e | |
|
6c7abfe4a8 | |
|
68bfa3b5e7 | |
|
c5b96a9054 | |
|
b43225d6a6 | |
|
8499c7b20f |
|
@ -0,0 +1,27 @@
|
|||
# Debugging
|
||||
|
||||
Currently, grpc provides two major tools to help user debug issues, which are logging and channelz.
|
||||
|
||||
## Logs
|
||||
|
||||
gRPC has put substantial logging instruments on critical paths of gRPC to help users debug issues. The [Environment Variables](https://github.com/grpc/grpc-node/blob/master/doc/environment_variables.md) doc describes the environment variables that control debug logging.
|
||||
|
||||
To enable full debug logging, run the code with the following environment variables: `GRPC_TRACE=all GRPC_VERBOSITY=DEBUG`.
|
||||
|
||||
## Channelz
|
||||
|
||||
We also provide a runtime debugging tool, Channelz, to help users with live debugging.
|
||||
|
||||
See the channelz blog post here ([link](https://grpc.io/blog/a-short-introduction-to-channelz/)) for details about how to use channelz service to debug live program.
|
||||
|
||||
## Try it
|
||||
|
||||
The example is able to showcase how logging and channelz can help with debugging. See the channelz blog post linked above for full explanation.
|
||||
|
||||
```
|
||||
node server.js
|
||||
```
|
||||
|
||||
```
|
||||
node client.js
|
||||
```
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
*
|
||||
* Copyright 2025 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.
|
||||
*
|
||||
*/
|
||||
|
||||
const grpc = require('@grpc/grpc-js');
|
||||
const protoLoader = require('@grpc/proto-loader');
|
||||
const parseArgs = require('minimist');
|
||||
|
||||
var PROTO_PATH = __dirname + '/../protos/helloworld.proto';
|
||||
|
||||
const packageDefinition = protoLoader.loadSync(
|
||||
PROTO_PATH,
|
||||
{keepCase: true,
|
||||
longs: String,
|
||||
enums: String,
|
||||
defaults: true,
|
||||
oneofs: true
|
||||
});
|
||||
var helloProto = grpc.loadPackageDefinition(packageDefinition).helloworld;
|
||||
|
||||
function serverBindPort(server, port) {
|
||||
return new Promise((resolve, reject) => {
|
||||
server.bindAsync(port, grpc.ServerCredentials.createInsecure(), (error, port) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(port);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
const addressString = 'ipv4:///127.0.0.1:10001,127.0.0.1:10002,127.0.0.1:10003';
|
||||
|
||||
function callSayHello(client, name) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const deadline = new Date();
|
||||
deadline.setMilliseconds(deadline.getMilliseconds() + 150);
|
||||
client.sayHello({name}, {deadline}, (error, response) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(response);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const argv = parseArgs(process.argv.slice(2), {
|
||||
string: ['addr', 'name'],
|
||||
default: {addr: 'localhost:50051', name: 'world'}
|
||||
});
|
||||
|
||||
// Set up the server serving channelz service.
|
||||
const channelzServer = new grpc.Server();
|
||||
grpc.addAdminServicesToServer(channelzServer);
|
||||
await serverBindPort(channelzServer, argv.addr);
|
||||
|
||||
const roundRobinServiceConfig = {
|
||||
methodConfig: [],
|
||||
loadBalancingConfig: [{ round_robin: {} }]
|
||||
};
|
||||
const client = new helloProto.Greeter(addressString, grpc.credentials.createInsecure(), {'grpc.service_config': JSON.stringify(roundRobinServiceConfig)});
|
||||
|
||||
// Contact the server and print out its response
|
||||
|
||||
// Make 100 SayHello RPCs
|
||||
for (let i = 0; i < 100; i++) {
|
||||
try {
|
||||
const response = await callSayHello(client, argv.name);
|
||||
console.log(`Greeting: ${response.message}`);
|
||||
} catch (e) {
|
||||
console.log(`could not greet: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Unless you exit the program (e.g. CTRL+C), channelz data will be available for querying.
|
||||
// Users can take time to examine and learn about the info provided by channelz.
|
||||
setInterval(() => {}, 10000);
|
||||
}
|
||||
|
||||
main();
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
*
|
||||
* Copyright 2025 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.
|
||||
*
|
||||
*/
|
||||
|
||||
const grpc = require('@grpc/grpc-js');
|
||||
const protoLoader = require('@grpc/proto-loader');
|
||||
|
||||
var PROTO_PATH = __dirname + '/../protos/helloworld.proto';
|
||||
|
||||
const packageDefinition = protoLoader.loadSync(
|
||||
PROTO_PATH,
|
||||
{keepCase: true,
|
||||
longs: String,
|
||||
enums: String,
|
||||
defaults: true,
|
||||
oneofs: true
|
||||
});
|
||||
var helloProto = grpc.loadPackageDefinition(packageDefinition).helloworld;
|
||||
|
||||
const greeterImplementation = {
|
||||
sayHello: (call, callback) => {
|
||||
callback(null, { message: `Hello ${call.request.name}`});
|
||||
}
|
||||
};
|
||||
|
||||
const slowGreeterImplementation = {
|
||||
sayHello: (call, callback) => {
|
||||
const waitTimeMs = 100 + (Math.random() * 100)|0;
|
||||
setTimeout(() => {
|
||||
callback(null, { message: `Hello ${call.request.name}`});
|
||||
}, waitTimeMs);
|
||||
}
|
||||
}
|
||||
|
||||
function serverBindPort(server, port) {
|
||||
return new Promise((resolve, reject) => {
|
||||
server.bindAsync(`0.0.0.0:${port}`, grpc.ServerCredentials.createInsecure(), (error, port) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(port);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const channelzServer = new grpc.Server();
|
||||
grpc.addAdminServicesToServer(channelzServer);
|
||||
await serverBindPort(channelzServer, 50052);
|
||||
|
||||
const server1 = new grpc.Server();
|
||||
server1.addService(helloProto.Greeter.service, greeterImplementation);
|
||||
await serverBindPort(server1, 10001);
|
||||
|
||||
const server2 = new grpc.Server();
|
||||
server2.addService(helloProto.Greeter.service, greeterImplementation);
|
||||
await serverBindPort(server2, 10002);
|
||||
|
||||
const server3 = new grpc.Server();
|
||||
server3.addService(helloProto.Greeter.service, slowGreeterImplementation);
|
||||
await serverBindPort(server3, 10003);
|
||||
}
|
||||
|
||||
main();
|
|
@ -0,0 +1,57 @@
|
|||
# Load balancing
|
||||
|
||||
This examples shows how `Client` can pick different load balancing policies.
|
||||
|
||||
## Try it
|
||||
|
||||
```
|
||||
node server.js
|
||||
```
|
||||
|
||||
```
|
||||
node client.js
|
||||
```
|
||||
|
||||
## Explanation
|
||||
|
||||
Two echo servers are serving on "0.0.0.0:50051" and "0.0.0.0:50052". They will include their serving address in the response. So the server on "0.0.0.0:50051" will reply to the RPC with this is examples/load_balancing (from 0.0.0.0:50051).
|
||||
|
||||
Two clients are created, to connect to both of these servers. Each client picks a different load balancer (using the `grpc.service_config` option): `pick_first` or `round_robin`.
|
||||
|
||||
Note that balancers can also be switched using service config, which allows service owners (instead of client owners) to pick the balancer to use. Service config doc is available at https://github.com/grpc/grpc/blob/master/doc/service_config.md.
|
||||
|
||||
### pick_first
|
||||
|
||||
The first client is configured to use `pick_first`. `pick_first` tries to connect to the first address, uses it for all RPCs if it connects, or try the next address if it fails (and keep doing that until one connection is successful). Because of this, all the RPCs will be sent to the same backend. The responses received all show the same backend address.
|
||||
|
||||
```
|
||||
this is examples/load_balancing (from 0.0.0.0:50051)
|
||||
this is examples/load_balancing (from 0.0.0.0:50051)
|
||||
this is examples/load_balancing (from 0.0.0.0:50051)
|
||||
this is examples/load_balancing (from 0.0.0.0:50051)
|
||||
this is examples/load_balancing (from 0.0.0.0:50051)
|
||||
this is examples/load_balancing (from 0.0.0.0:50051)
|
||||
this is examples/load_balancing (from 0.0.0.0:50051)
|
||||
this is examples/load_balancing (from 0.0.0.0:50051)
|
||||
this is examples/load_balancing (from 0.0.0.0:50051)
|
||||
this is examples/load_balancing (from 0.0.0.0:50051)
|
||||
```
|
||||
|
||||
### round_robin
|
||||
|
||||
The second client is configured to use `round_robin`. `round_robin` connects to all the addresses it sees, and sends an RPC to each backend one at a time in order. E.g. the first RPC will be sent to backend-1, the second RPC will be sent to backend-2, and the third RPC will be sent to backend-1 again.
|
||||
|
||||
```
|
||||
this is examples/load_balancing (from 0.0.0.0:50051)
|
||||
this is examples/load_balancing (from 0.0.0.0:50051)
|
||||
this is examples/load_balancing (from 0.0.0.0:50052)
|
||||
this is examples/load_balancing (from 0.0.0.0:50051)
|
||||
this is examples/load_balancing (from 0.0.0.0:50052)
|
||||
this is examples/load_balancing (from 0.0.0.0:50051)
|
||||
this is examples/load_balancing (from 0.0.0.0:50052)
|
||||
this is examples/load_balancing (from 0.0.0.0:50051)
|
||||
this is examples/load_balancing (from 0.0.0.0:50052)
|
||||
this is examples/load_balancing (from 0.0.0.0:50051)
|
||||
```
|
||||
|
||||
Note that it's possible to see two consecutive RPC sent to the same backend. That's because `round_robin` only picks the connections ready for RPCs. So if one of the two connections is not ready for some reason, all RPCs will be sent to the ready connection.
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
*
|
||||
* Copyright 2025 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.
|
||||
*
|
||||
*/
|
||||
|
||||
const grpc = require('@grpc/grpc-js');
|
||||
const protoLoader = require('@grpc/proto-loader');
|
||||
|
||||
const PROTO_PATH = __dirname + '/../protos/echo.proto';
|
||||
|
||||
const packageDefinition = protoLoader.loadSync(
|
||||
PROTO_PATH,
|
||||
{keepCase: true,
|
||||
longs: String,
|
||||
enums: String,
|
||||
defaults: true,
|
||||
oneofs: true
|
||||
});
|
||||
const echoProto = grpc.loadPackageDefinition(packageDefinition).grpc.examples.echo;
|
||||
|
||||
const addressString = 'ipv4:///127.0.0.1:50051,127.0.0.1:50052';
|
||||
|
||||
function callUnaryEcho(client, message) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const deadline = new Date();
|
||||
deadline.setSeconds(deadline.getSeconds() + 1);
|
||||
client.unaryEcho({message}, {deadline}, (error, response) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
console.log(response.message);
|
||||
resolve(response);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function makeRPCs(client, count) {
|
||||
for (let i = 0; i < count; i++) {
|
||||
await callUnaryEcho(client, "this is examples/load_balancing");
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// "pick_first" is the default, so there's no need to set the load balancing policy.
|
||||
const pickFirstClient = new echoProto.Echo(addressString, grpc.credentials.createInsecure());
|
||||
console.log("--- calling helloworld.Greeter/SayHello with pick_first ---");
|
||||
await makeRPCs(pickFirstClient, 10);
|
||||
console.log();
|
||||
|
||||
const roundRobinServiceConfig = {
|
||||
methodConfig: [],
|
||||
loadBalancingConfig: [{ round_robin: {} }]
|
||||
};
|
||||
const roundRobinClient = new echoProto.Echo(addressString, grpc.credentials.createInsecure(), {'grpc.service_config': JSON.stringify(roundRobinServiceConfig)});
|
||||
console.log("--- calling helloworld.Greeter/SayHello with round_robin ---");
|
||||
await makeRPCs(roundRobinClient, 10);
|
||||
pickFirstClient.close();
|
||||
roundRobinClient.close();
|
||||
}
|
||||
|
||||
main();
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
*
|
||||
* Copyright 2025 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.
|
||||
*
|
||||
*/
|
||||
|
||||
const grpc = require('@grpc/grpc-js');
|
||||
const protoLoader = require('@grpc/proto-loader');
|
||||
|
||||
const PROTO_PATH = __dirname + '/../protos/echo.proto';
|
||||
|
||||
const packageDefinition = protoLoader.loadSync(
|
||||
PROTO_PATH,
|
||||
{keepCase: true,
|
||||
longs: String,
|
||||
enums: String,
|
||||
defaults: true,
|
||||
oneofs: true
|
||||
});
|
||||
const echoProto = grpc.loadPackageDefinition(packageDefinition).grpc.examples.echo;
|
||||
|
||||
function startServer(address) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const server = new grpc.Server();
|
||||
server.addService(echoProto.Echo.service, {
|
||||
unaryEcho: (call, callback) => {
|
||||
callback(null, {message: `${call.request.message} (from ${address})`});
|
||||
}
|
||||
});
|
||||
server.bindAsync(address, grpc.ServerCredentials.createInsecure(), (error, port) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(server);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const addresses = ['0.0.0.0:50051', '0.0.0.0:50052'];
|
||||
|
||||
async function main() {
|
||||
for (const address of addresses) {
|
||||
await startServer(address)
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
|
@ -25,6 +25,13 @@ option java_multiple_files = true;
|
|||
option java_outer_classname = "HealthProto";
|
||||
option java_package = "io.grpc.health.v1";
|
||||
|
||||
message HealthListRequest {}
|
||||
|
||||
message HealthListResponse {
|
||||
// statuses contains all the services and their respective status.
|
||||
map<string, HealthCheckResponse> statuses = 1;
|
||||
}
|
||||
|
||||
message HealthCheckRequest {
|
||||
string service = 1;
|
||||
}
|
||||
|
@ -70,4 +77,17 @@ service Health {
|
|||
// call. If the call terminates with any other status (including OK),
|
||||
// clients should retry the call with appropriate exponential backoff.
|
||||
rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse);
|
||||
|
||||
// List provides a non-atomic snapshot of the health of all the available
|
||||
// services.
|
||||
//
|
||||
// The server may respond with a RESOURCE_EXHAUSTED error if too many services
|
||||
// exist.
|
||||
//
|
||||
// Clients should set a deadline when calling List, and can declare the server
|
||||
// unhealthy if they do not receive a timely response.
|
||||
//
|
||||
// Clients should keep in mind that the list of health services exposed by an
|
||||
// application can change over the lifetime of the process.
|
||||
rpc List(HealthListRequest) returns (HealthListResponse);
|
||||
}
|
||||
|
|
|
@ -71,6 +71,7 @@ const copyTestFixtures = checkTask(() =>
|
|||
const runTests = checkTask(() => {
|
||||
process.env.GRPC_EXPERIMENTAL_XDS_FEDERATION = 'true';
|
||||
process.env.GRPC_EXPERIMENTAL_PICKFIRST_LB_CONFIG = 'true';
|
||||
process.env.GRPC_XDS_EXPERIMENTAL_RBAC = 'true';
|
||||
if (Number(process.versions.node.split('.')[0]) <= 14) {
|
||||
process.env.GRPC_XDS_EXPERIMENTAL_ENABLE_RING_HASH = 'false';
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ COPY --from=build /node/src/grpc-node/packages/grpc-js ./packages/grpc-js/
|
|||
COPY --from=build /node/src/grpc-node/packages/grpc-js-xds ./packages/grpc-js-xds/
|
||||
|
||||
ENV GRPC_VERBOSITY="DEBUG"
|
||||
ENV GRPC_TRACE=xds_client,server,xds_server,http_filter,certificate_provider
|
||||
ENV GRPC_TRACE=xds_client,server,xds_server,http_filter,certificate_provider,rbac_filter
|
||||
|
||||
# tini serves as PID 1 and enables the server to properly respond to signals.
|
||||
COPY --from=build /tini /tini
|
||||
|
|
|
@ -41,6 +41,7 @@ import PickResult = grpc.experimental.PickResult;
|
|||
import PickResultType = grpc.experimental.PickResultType;
|
||||
import createChildChannelControlHelper = grpc.experimental.createChildChannelControlHelper;
|
||||
import parseLoadBalancingConfig = grpc.experimental.parseLoadBalancingConfig;
|
||||
import StatusOr = grpc.experimental.StatusOr;
|
||||
import { ChannelOptions } from '@grpc/grpc-js';
|
||||
|
||||
grpc_xds.register();
|
||||
|
@ -100,12 +101,12 @@ class RpcBehaviorLoadBalancer implements LoadBalancer {
|
|||
});
|
||||
this.child = new ChildLoadBalancerHandler(childChannelControlHelper);
|
||||
}
|
||||
updateAddressList(endpointList: Endpoint[], lbConfig: TypedLoadBalancingConfig, options: ChannelOptions): void {
|
||||
updateAddressList(endpointList: StatusOr<Endpoint[]>, lbConfig: TypedLoadBalancingConfig, options: ChannelOptions, resolutionNote: string): boolean {
|
||||
if (!(lbConfig instanceof RpcBehaviorLoadBalancingConfig)) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
this.latestConfig = lbConfig;
|
||||
this.child.updateAddressList(endpointList, RPC_BEHAVIOR_CHILD_CONFIG, options);
|
||||
return this.child.updateAddressList(endpointList, RPC_BEHAVIOR_CHILD_CONFIG, options, resolutionNote);
|
||||
}
|
||||
exitIdle(): void {
|
||||
this.child.exitIdle();
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
"prepare": "npm run generate-types && npm run compile",
|
||||
"pretest": "npm run compile",
|
||||
"posttest": "npm run check",
|
||||
"generate-types": "proto-loader-gen-types --keepCase --longs String --enums String --defaults --oneofs --includeComments --includeDirs deps/envoy-api/ deps/xds/ deps/googleapis/ deps/protoc-gen-validate/ -O src/generated/ --grpcLib @grpc/grpc-js envoy/service/discovery/v3/ads.proto envoy/service/load_stats/v3/lrs.proto envoy/config/listener/v3/listener.proto envoy/config/route/v3/route.proto envoy/config/cluster/v3/cluster.proto envoy/config/endpoint/v3/endpoint.proto envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto udpa/type/v1/typed_struct.proto xds/type/v3/typed_struct.proto envoy/extensions/filters/http/fault/v3/fault.proto envoy/service/status/v3/csds.proto envoy/extensions/load_balancing_policies/wrr_locality/v3/wrr_locality.proto envoy/extensions/load_balancing_policies/ring_hash/v3/ring_hash.proto envoy/extensions/load_balancing_policies/pick_first/v3/pick_first.proto envoy/extensions/clusters/aggregate/v3/cluster.proto envoy/extensions/transport_sockets/tls/v3/tls.proto",
|
||||
"generate-types": "proto-loader-gen-types --keepCase --longs String --enums String --defaults --oneofs --includeComments --includeDirs deps/envoy-api/ deps/xds/ deps/googleapis/ deps/protoc-gen-validate/ -O src/generated/ --grpcLib @grpc/grpc-js envoy/service/discovery/v3/ads.proto envoy/service/load_stats/v3/lrs.proto envoy/config/listener/v3/listener.proto envoy/config/route/v3/route.proto envoy/config/cluster/v3/cluster.proto envoy/config/endpoint/v3/endpoint.proto envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto udpa/type/v1/typed_struct.proto xds/type/v3/typed_struct.proto envoy/extensions/filters/http/fault/v3/fault.proto envoy/service/status/v3/csds.proto envoy/extensions/load_balancing_policies/wrr_locality/v3/wrr_locality.proto envoy/extensions/load_balancing_policies/ring_hash/v3/ring_hash.proto envoy/extensions/load_balancing_policies/pick_first/v3/pick_first.proto envoy/extensions/clusters/aggregate/v3/cluster.proto envoy/extensions/transport_sockets/tls/v3/tls.proto envoy/config/rbac/v3/rbac.proto envoy/extensions/filters/http/rbac/v3/rbac.proto",
|
||||
"generate-interop-types": "proto-loader-gen-types --keep-case --longs String --enums String --defaults --oneofs --json --includeComments --includeDirs proto/ -O interop/generated --grpcLib @grpc/grpc-js grpc/testing/test.proto",
|
||||
"generate-test-types": "proto-loader-gen-types --keep-case --longs String --enums String --defaults --oneofs --json --includeComments --includeDirs proto/ -O test/generated --grpcLib @grpc/grpc-js grpc/testing/echo.proto"
|
||||
},
|
||||
|
|
|
@ -27,3 +27,4 @@ export const EXPERIMENTAL_RING_HASH = (process.env.GRPC_XDS_EXPERIMENTAL_ENABLE_
|
|||
export const EXPERIMENTAL_PICK_FIRST = (process.env.GRPC_EXPERIMENTAL_PICKFIRST_LB_CONFIG ?? 'false') === 'true';
|
||||
export const EXPERIMENTAL_DUALSTACK_ENDPOINTS = (process.env.GRPC_EXPERIMENTAL_XDS_DUALSTACK_ENDPOINTS ?? 'true') === 'true';
|
||||
export const AGGREGATE_CLUSTER_BACKWARDS_COMPAT = (process.env.GRPC_XDS_AGGREGATE_CLUSTER_BACKWARD_COMPAT ?? 'false') === 'true';
|
||||
export const EXPERIMENTAL_RBAC = (process.env.GRPC_XDS_EXPERIMENTAL_RBAC ?? 'false') === 'true';
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
// Original file: deps/envoy-api/envoy/config/rbac/v3/rbac.proto
|
||||
|
||||
import type { _envoy_config_rbac_v3_RBAC_Action, _envoy_config_rbac_v3_RBAC_Action__Output } from '../../../../envoy/config/rbac/v3/RBAC';
|
||||
|
||||
/**
|
||||
* Action defines the result of allowance or denial when a request matches the matcher.
|
||||
*/
|
||||
export interface Action {
|
||||
/**
|
||||
* The name indicates the policy name.
|
||||
*/
|
||||
'name'?: (string);
|
||||
/**
|
||||
* The action to take if the matcher matches. Every action either allows or denies a request,
|
||||
* and can also carry out action-specific operations.
|
||||
*
|
||||
* Actions:
|
||||
*
|
||||
* * ``ALLOW``: If the request gets matched on ALLOW, it is permitted.
|
||||
* * ``DENY``: If the request gets matched on DENY, it is not permitted.
|
||||
* * ``LOG``: If the request gets matched on LOG, it is permitted. Besides, the
|
||||
* dynamic metadata key ``access_log_hint`` under the shared key namespace
|
||||
* ``envoy.common`` will be set to the value ``true``.
|
||||
* * If the request cannot get matched, it will fallback to ``DENY``.
|
||||
*
|
||||
* Log behavior:
|
||||
*
|
||||
* If the RBAC matcher contains at least one LOG action, the dynamic
|
||||
* metadata key ``access_log_hint`` will be set based on if the request
|
||||
* get matched on the LOG action.
|
||||
*/
|
||||
'action'?: (_envoy_config_rbac_v3_RBAC_Action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Action defines the result of allowance or denial when a request matches the matcher.
|
||||
*/
|
||||
export interface Action__Output {
|
||||
/**
|
||||
* The name indicates the policy name.
|
||||
*/
|
||||
'name': (string);
|
||||
/**
|
||||
* The action to take if the matcher matches. Every action either allows or denies a request,
|
||||
* and can also carry out action-specific operations.
|
||||
*
|
||||
* Actions:
|
||||
*
|
||||
* * ``ALLOW``: If the request gets matched on ALLOW, it is permitted.
|
||||
* * ``DENY``: If the request gets matched on DENY, it is not permitted.
|
||||
* * ``LOG``: If the request gets matched on LOG, it is permitted. Besides, the
|
||||
* dynamic metadata key ``access_log_hint`` under the shared key namespace
|
||||
* ``envoy.common`` will be set to the value ``true``.
|
||||
* * If the request cannot get matched, it will fallback to ``DENY``.
|
||||
*
|
||||
* Log behavior:
|
||||
*
|
||||
* If the RBAC matcher contains at least one LOG action, the dynamic
|
||||
* metadata key ``access_log_hint`` will be set based on if the request
|
||||
* get matched on the LOG action.
|
||||
*/
|
||||
'action': (_envoy_config_rbac_v3_RBAC_Action__Output);
|
||||
}
|
|
@ -0,0 +1,198 @@
|
|||
// Original file: deps/envoy-api/envoy/config/rbac/v3/rbac.proto
|
||||
|
||||
import type { HeaderMatcher as _envoy_config_route_v3_HeaderMatcher, HeaderMatcher__Output as _envoy_config_route_v3_HeaderMatcher__Output } from '../../../../envoy/config/route/v3/HeaderMatcher';
|
||||
import type { CidrRange as _envoy_config_core_v3_CidrRange, CidrRange__Output as _envoy_config_core_v3_CidrRange__Output } from '../../../../envoy/config/core/v3/CidrRange';
|
||||
import type { MetadataMatcher as _envoy_type_matcher_v3_MetadataMatcher, MetadataMatcher__Output as _envoy_type_matcher_v3_MetadataMatcher__Output } from '../../../../envoy/type/matcher/v3/MetadataMatcher';
|
||||
import type { Permission as _envoy_config_rbac_v3_Permission, Permission__Output as _envoy_config_rbac_v3_Permission__Output } from '../../../../envoy/config/rbac/v3/Permission';
|
||||
import type { StringMatcher as _envoy_type_matcher_v3_StringMatcher, StringMatcher__Output as _envoy_type_matcher_v3_StringMatcher__Output } from '../../../../envoy/type/matcher/v3/StringMatcher';
|
||||
import type { PathMatcher as _envoy_type_matcher_v3_PathMatcher, PathMatcher__Output as _envoy_type_matcher_v3_PathMatcher__Output } from '../../../../envoy/type/matcher/v3/PathMatcher';
|
||||
import type { Int32Range as _envoy_type_v3_Int32Range, Int32Range__Output as _envoy_type_v3_Int32Range__Output } from '../../../../envoy/type/v3/Int32Range';
|
||||
import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../envoy/config/core/v3/TypedExtensionConfig';
|
||||
|
||||
/**
|
||||
* Used in the ``and_rules`` and ``or_rules`` fields in the ``rule`` oneof. Depending on the context,
|
||||
* each are applied with the associated behavior.
|
||||
*/
|
||||
export interface _envoy_config_rbac_v3_Permission_Set {
|
||||
'rules'?: (_envoy_config_rbac_v3_Permission)[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Used in the ``and_rules`` and ``or_rules`` fields in the ``rule`` oneof. Depending on the context,
|
||||
* each are applied with the associated behavior.
|
||||
*/
|
||||
export interface _envoy_config_rbac_v3_Permission_Set__Output {
|
||||
'rules': (_envoy_config_rbac_v3_Permission__Output)[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Permission defines an action (or actions) that a principal can take.
|
||||
* [#next-free-field: 14]
|
||||
*/
|
||||
export interface Permission {
|
||||
/**
|
||||
* A set of rules that all must match in order to define the action.
|
||||
*/
|
||||
'and_rules'?: (_envoy_config_rbac_v3_Permission_Set | null);
|
||||
/**
|
||||
* A set of rules where at least one must match in order to define the action.
|
||||
*/
|
||||
'or_rules'?: (_envoy_config_rbac_v3_Permission_Set | null);
|
||||
/**
|
||||
* When any is set, it matches any action.
|
||||
*/
|
||||
'any'?: (boolean);
|
||||
/**
|
||||
* A header (or pseudo-header such as :path or :method) on the incoming HTTP request. Only
|
||||
* available for HTTP request.
|
||||
* Note: the pseudo-header :path includes the query and fragment string. Use the ``url_path``
|
||||
* field if you want to match the URL path without the query and fragment string.
|
||||
*/
|
||||
'header'?: (_envoy_config_route_v3_HeaderMatcher | null);
|
||||
/**
|
||||
* A CIDR block that describes the destination IP.
|
||||
*/
|
||||
'destination_ip'?: (_envoy_config_core_v3_CidrRange | null);
|
||||
/**
|
||||
* A port number that describes the destination port connecting to.
|
||||
*/
|
||||
'destination_port'?: (number);
|
||||
/**
|
||||
* Metadata that describes additional information about the action.
|
||||
*/
|
||||
'metadata'?: (_envoy_type_matcher_v3_MetadataMatcher | null);
|
||||
/**
|
||||
* Negates matching the provided permission. For instance, if the value of
|
||||
* ``not_rule`` would match, this permission would not match. Conversely, if
|
||||
* the value of ``not_rule`` would not match, this permission would match.
|
||||
*/
|
||||
'not_rule'?: (_envoy_config_rbac_v3_Permission | null);
|
||||
/**
|
||||
* The request server from the client's connection request. This is
|
||||
* typically TLS SNI.
|
||||
*
|
||||
* .. attention::
|
||||
*
|
||||
* The behavior of this field may be affected by how Envoy is configured
|
||||
* as explained below.
|
||||
*
|
||||
* * If the :ref:`TLS Inspector <config_listener_filters_tls_inspector>`
|
||||
* filter is not added, and if a ``FilterChainMatch`` is not defined for
|
||||
* the :ref:`server name
|
||||
* <envoy_v3_api_field_config.listener.v3.FilterChainMatch.server_names>`,
|
||||
* a TLS connection's requested SNI server name will be treated as if it
|
||||
* wasn't present.
|
||||
*
|
||||
* * A :ref:`listener filter <arch_overview_listener_filters>` may
|
||||
* overwrite a connection's requested server name within Envoy.
|
||||
*
|
||||
* Please refer to :ref:`this FAQ entry <faq_how_to_setup_sni>` to learn to
|
||||
* setup SNI.
|
||||
*/
|
||||
'requested_server_name'?: (_envoy_type_matcher_v3_StringMatcher | null);
|
||||
/**
|
||||
* A URL path on the incoming HTTP request. Only available for HTTP.
|
||||
*/
|
||||
'url_path'?: (_envoy_type_matcher_v3_PathMatcher | null);
|
||||
/**
|
||||
* A port number range that describes a range of destination ports connecting to.
|
||||
*/
|
||||
'destination_port_range'?: (_envoy_type_v3_Int32Range | null);
|
||||
/**
|
||||
* Extension for configuring custom matchers for RBAC.
|
||||
* [#extension-category: envoy.rbac.matchers]
|
||||
*/
|
||||
'matcher'?: (_envoy_config_core_v3_TypedExtensionConfig | null);
|
||||
/**
|
||||
* URI template path matching.
|
||||
* [#extension-category: envoy.path.match]
|
||||
*/
|
||||
'uri_template'?: (_envoy_config_core_v3_TypedExtensionConfig | null);
|
||||
'rule'?: "and_rules"|"or_rules"|"any"|"header"|"url_path"|"destination_ip"|"destination_port"|"destination_port_range"|"metadata"|"not_rule"|"requested_server_name"|"matcher"|"uri_template";
|
||||
}
|
||||
|
||||
/**
|
||||
* Permission defines an action (or actions) that a principal can take.
|
||||
* [#next-free-field: 14]
|
||||
*/
|
||||
export interface Permission__Output {
|
||||
/**
|
||||
* A set of rules that all must match in order to define the action.
|
||||
*/
|
||||
'and_rules'?: (_envoy_config_rbac_v3_Permission_Set__Output | null);
|
||||
/**
|
||||
* A set of rules where at least one must match in order to define the action.
|
||||
*/
|
||||
'or_rules'?: (_envoy_config_rbac_v3_Permission_Set__Output | null);
|
||||
/**
|
||||
* When any is set, it matches any action.
|
||||
*/
|
||||
'any'?: (boolean);
|
||||
/**
|
||||
* A header (or pseudo-header such as :path or :method) on the incoming HTTP request. Only
|
||||
* available for HTTP request.
|
||||
* Note: the pseudo-header :path includes the query and fragment string. Use the ``url_path``
|
||||
* field if you want to match the URL path without the query and fragment string.
|
||||
*/
|
||||
'header'?: (_envoy_config_route_v3_HeaderMatcher__Output | null);
|
||||
/**
|
||||
* A CIDR block that describes the destination IP.
|
||||
*/
|
||||
'destination_ip'?: (_envoy_config_core_v3_CidrRange__Output | null);
|
||||
/**
|
||||
* A port number that describes the destination port connecting to.
|
||||
*/
|
||||
'destination_port'?: (number);
|
||||
/**
|
||||
* Metadata that describes additional information about the action.
|
||||
*/
|
||||
'metadata'?: (_envoy_type_matcher_v3_MetadataMatcher__Output | null);
|
||||
/**
|
||||
* Negates matching the provided permission. For instance, if the value of
|
||||
* ``not_rule`` would match, this permission would not match. Conversely, if
|
||||
* the value of ``not_rule`` would not match, this permission would match.
|
||||
*/
|
||||
'not_rule'?: (_envoy_config_rbac_v3_Permission__Output | null);
|
||||
/**
|
||||
* The request server from the client's connection request. This is
|
||||
* typically TLS SNI.
|
||||
*
|
||||
* .. attention::
|
||||
*
|
||||
* The behavior of this field may be affected by how Envoy is configured
|
||||
* as explained below.
|
||||
*
|
||||
* * If the :ref:`TLS Inspector <config_listener_filters_tls_inspector>`
|
||||
* filter is not added, and if a ``FilterChainMatch`` is not defined for
|
||||
* the :ref:`server name
|
||||
* <envoy_v3_api_field_config.listener.v3.FilterChainMatch.server_names>`,
|
||||
* a TLS connection's requested SNI server name will be treated as if it
|
||||
* wasn't present.
|
||||
*
|
||||
* * A :ref:`listener filter <arch_overview_listener_filters>` may
|
||||
* overwrite a connection's requested server name within Envoy.
|
||||
*
|
||||
* Please refer to :ref:`this FAQ entry <faq_how_to_setup_sni>` to learn to
|
||||
* setup SNI.
|
||||
*/
|
||||
'requested_server_name'?: (_envoy_type_matcher_v3_StringMatcher__Output | null);
|
||||
/**
|
||||
* A URL path on the incoming HTTP request. Only available for HTTP.
|
||||
*/
|
||||
'url_path'?: (_envoy_type_matcher_v3_PathMatcher__Output | null);
|
||||
/**
|
||||
* A port number range that describes a range of destination ports connecting to.
|
||||
*/
|
||||
'destination_port_range'?: (_envoy_type_v3_Int32Range__Output | null);
|
||||
/**
|
||||
* Extension for configuring custom matchers for RBAC.
|
||||
* [#extension-category: envoy.rbac.matchers]
|
||||
*/
|
||||
'matcher'?: (_envoy_config_core_v3_TypedExtensionConfig__Output | null);
|
||||
/**
|
||||
* URI template path matching.
|
||||
* [#extension-category: envoy.path.match]
|
||||
*/
|
||||
'uri_template'?: (_envoy_config_core_v3_TypedExtensionConfig__Output | null);
|
||||
'rule'?: "and_rules"|"or_rules"|"any"|"header"|"url_path"|"destination_ip"|"destination_port"|"destination_port_range"|"metadata"|"not_rule"|"requested_server_name"|"matcher"|"uri_template";
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
// Original file: deps/envoy-api/envoy/config/rbac/v3/rbac.proto
|
||||
|
||||
import type { Permission as _envoy_config_rbac_v3_Permission, Permission__Output as _envoy_config_rbac_v3_Permission__Output } from '../../../../envoy/config/rbac/v3/Permission';
|
||||
import type { Principal as _envoy_config_rbac_v3_Principal, Principal__Output as _envoy_config_rbac_v3_Principal__Output } from '../../../../envoy/config/rbac/v3/Principal';
|
||||
import type { Expr as _google_api_expr_v1alpha1_Expr, Expr__Output as _google_api_expr_v1alpha1_Expr__Output } from '../../../../google/api/expr/v1alpha1/Expr';
|
||||
import type { CheckedExpr as _google_api_expr_v1alpha1_CheckedExpr, CheckedExpr__Output as _google_api_expr_v1alpha1_CheckedExpr__Output } from '../../../../google/api/expr/v1alpha1/CheckedExpr';
|
||||
|
||||
/**
|
||||
* Policy specifies a role and the principals that are assigned/denied the role.
|
||||
* A policy matches if and only if at least one of its permissions match the
|
||||
* action taking place AND at least one of its principals match the downstream
|
||||
* AND the condition is true if specified.
|
||||
*/
|
||||
export interface Policy {
|
||||
/**
|
||||
* Required. The set of permissions that define a role. Each permission is
|
||||
* matched with OR semantics. To match all actions for this policy, a single
|
||||
* Permission with the ``any`` field set to true should be used.
|
||||
*/
|
||||
'permissions'?: (_envoy_config_rbac_v3_Permission)[];
|
||||
/**
|
||||
* Required. The set of principals that are assigned/denied the role based on
|
||||
* “action”. Each principal is matched with OR semantics. To match all
|
||||
* downstreams for this policy, a single Principal with the ``any`` field set to
|
||||
* true should be used.
|
||||
*/
|
||||
'principals'?: (_envoy_config_rbac_v3_Principal)[];
|
||||
/**
|
||||
* An optional symbolic expression specifying an access control
|
||||
* :ref:`condition <arch_overview_condition>`. The condition is combined
|
||||
* with the permissions and the principals as a clause with AND semantics.
|
||||
* Only be used when checked_condition is not used.
|
||||
*/
|
||||
'condition'?: (_google_api_expr_v1alpha1_Expr | null);
|
||||
/**
|
||||
* [#not-implemented-hide:]
|
||||
* An optional symbolic expression that has been successfully type checked.
|
||||
* Only be used when condition is not used.
|
||||
*/
|
||||
'checked_condition'?: (_google_api_expr_v1alpha1_CheckedExpr | null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Policy specifies a role and the principals that are assigned/denied the role.
|
||||
* A policy matches if and only if at least one of its permissions match the
|
||||
* action taking place AND at least one of its principals match the downstream
|
||||
* AND the condition is true if specified.
|
||||
*/
|
||||
export interface Policy__Output {
|
||||
/**
|
||||
* Required. The set of permissions that define a role. Each permission is
|
||||
* matched with OR semantics. To match all actions for this policy, a single
|
||||
* Permission with the ``any`` field set to true should be used.
|
||||
*/
|
||||
'permissions': (_envoy_config_rbac_v3_Permission__Output)[];
|
||||
/**
|
||||
* Required. The set of principals that are assigned/denied the role based on
|
||||
* “action”. Each principal is matched with OR semantics. To match all
|
||||
* downstreams for this policy, a single Principal with the ``any`` field set to
|
||||
* true should be used.
|
||||
*/
|
||||
'principals': (_envoy_config_rbac_v3_Principal__Output)[];
|
||||
/**
|
||||
* An optional symbolic expression specifying an access control
|
||||
* :ref:`condition <arch_overview_condition>`. The condition is combined
|
||||
* with the permissions and the principals as a clause with AND semantics.
|
||||
* Only be used when checked_condition is not used.
|
||||
*/
|
||||
'condition': (_google_api_expr_v1alpha1_Expr__Output | null);
|
||||
/**
|
||||
* [#not-implemented-hide:]
|
||||
* An optional symbolic expression that has been successfully type checked.
|
||||
* Only be used when condition is not used.
|
||||
*/
|
||||
'checked_condition': (_google_api_expr_v1alpha1_CheckedExpr__Output | null);
|
||||
}
|
|
@ -0,0 +1,209 @@
|
|||
// Original file: deps/envoy-api/envoy/config/rbac/v3/rbac.proto
|
||||
|
||||
import type { CidrRange as _envoy_config_core_v3_CidrRange, CidrRange__Output as _envoy_config_core_v3_CidrRange__Output } from '../../../../envoy/config/core/v3/CidrRange';
|
||||
import type { HeaderMatcher as _envoy_config_route_v3_HeaderMatcher, HeaderMatcher__Output as _envoy_config_route_v3_HeaderMatcher__Output } from '../../../../envoy/config/route/v3/HeaderMatcher';
|
||||
import type { MetadataMatcher as _envoy_type_matcher_v3_MetadataMatcher, MetadataMatcher__Output as _envoy_type_matcher_v3_MetadataMatcher__Output } from '../../../../envoy/type/matcher/v3/MetadataMatcher';
|
||||
import type { Principal as _envoy_config_rbac_v3_Principal, Principal__Output as _envoy_config_rbac_v3_Principal__Output } from '../../../../envoy/config/rbac/v3/Principal';
|
||||
import type { PathMatcher as _envoy_type_matcher_v3_PathMatcher, PathMatcher__Output as _envoy_type_matcher_v3_PathMatcher__Output } from '../../../../envoy/type/matcher/v3/PathMatcher';
|
||||
import type { FilterStateMatcher as _envoy_type_matcher_v3_FilterStateMatcher, FilterStateMatcher__Output as _envoy_type_matcher_v3_FilterStateMatcher__Output } from '../../../../envoy/type/matcher/v3/FilterStateMatcher';
|
||||
import type { StringMatcher as _envoy_type_matcher_v3_StringMatcher, StringMatcher__Output as _envoy_type_matcher_v3_StringMatcher__Output } from '../../../../envoy/type/matcher/v3/StringMatcher';
|
||||
|
||||
/**
|
||||
* Authentication attributes for a downstream.
|
||||
*/
|
||||
export interface _envoy_config_rbac_v3_Principal_Authenticated {
|
||||
/**
|
||||
* The name of the principal. If set, The URI SAN or DNS SAN in that order
|
||||
* is used from the certificate, otherwise the subject field is used. If
|
||||
* unset, it applies to any user that is authenticated.
|
||||
*/
|
||||
'principal_name'?: (_envoy_type_matcher_v3_StringMatcher | null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Authentication attributes for a downstream.
|
||||
*/
|
||||
export interface _envoy_config_rbac_v3_Principal_Authenticated__Output {
|
||||
/**
|
||||
* The name of the principal. If set, The URI SAN or DNS SAN in that order
|
||||
* is used from the certificate, otherwise the subject field is used. If
|
||||
* unset, it applies to any user that is authenticated.
|
||||
*/
|
||||
'principal_name': (_envoy_type_matcher_v3_StringMatcher__Output | null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used in the ``and_ids`` and ``or_ids`` fields in the ``identifier`` oneof.
|
||||
* Depending on the context, each are applied with the associated behavior.
|
||||
*/
|
||||
export interface _envoy_config_rbac_v3_Principal_Set {
|
||||
'ids'?: (_envoy_config_rbac_v3_Principal)[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Used in the ``and_ids`` and ``or_ids`` fields in the ``identifier`` oneof.
|
||||
* Depending on the context, each are applied with the associated behavior.
|
||||
*/
|
||||
export interface _envoy_config_rbac_v3_Principal_Set__Output {
|
||||
'ids': (_envoy_config_rbac_v3_Principal__Output)[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Principal defines an identity or a group of identities for a downstream
|
||||
* subject.
|
||||
* [#next-free-field: 13]
|
||||
*/
|
||||
export interface Principal {
|
||||
/**
|
||||
* A set of identifiers that all must match in order to define the
|
||||
* downstream.
|
||||
*/
|
||||
'and_ids'?: (_envoy_config_rbac_v3_Principal_Set | null);
|
||||
/**
|
||||
* A set of identifiers at least one must match in order to define the
|
||||
* downstream.
|
||||
*/
|
||||
'or_ids'?: (_envoy_config_rbac_v3_Principal_Set | null);
|
||||
/**
|
||||
* When any is set, it matches any downstream.
|
||||
*/
|
||||
'any'?: (boolean);
|
||||
/**
|
||||
* Authenticated attributes that identify the downstream.
|
||||
*/
|
||||
'authenticated'?: (_envoy_config_rbac_v3_Principal_Authenticated | null);
|
||||
/**
|
||||
* A CIDR block that describes the downstream IP.
|
||||
* This address will honor proxy protocol, but will not honor XFF.
|
||||
*
|
||||
* This field is deprecated; either use :ref:`remote_ip
|
||||
* <envoy_v3_api_field_config.rbac.v3.Principal.remote_ip>` for the same
|
||||
* behavior, or use
|
||||
* :ref:`direct_remote_ip <envoy_v3_api_field_config.rbac.v3.Principal.direct_remote_ip>`.
|
||||
* @deprecated
|
||||
*/
|
||||
'source_ip'?: (_envoy_config_core_v3_CidrRange | null);
|
||||
/**
|
||||
* A header (or pseudo-header such as :path or :method) on the incoming HTTP
|
||||
* request. Only available for HTTP request. Note: the pseudo-header :path
|
||||
* includes the query and fragment string. Use the ``url_path`` field if you
|
||||
* want to match the URL path without the query and fragment string.
|
||||
*/
|
||||
'header'?: (_envoy_config_route_v3_HeaderMatcher | null);
|
||||
/**
|
||||
* Metadata that describes additional information about the principal.
|
||||
*/
|
||||
'metadata'?: (_envoy_type_matcher_v3_MetadataMatcher | null);
|
||||
/**
|
||||
* Negates matching the provided principal. For instance, if the value of
|
||||
* ``not_id`` would match, this principal would not match. Conversely, if the
|
||||
* value of ``not_id`` would not match, this principal would match.
|
||||
*/
|
||||
'not_id'?: (_envoy_config_rbac_v3_Principal | null);
|
||||
/**
|
||||
* A URL path on the incoming HTTP request. Only available for HTTP.
|
||||
*/
|
||||
'url_path'?: (_envoy_type_matcher_v3_PathMatcher | null);
|
||||
/**
|
||||
* A CIDR block that describes the downstream remote/origin address.
|
||||
* Note: This is always the physical peer even if the
|
||||
* :ref:`remote_ip <envoy_v3_api_field_config.rbac.v3.Principal.remote_ip>` is
|
||||
* inferred from for example the x-forwarder-for header, proxy protocol,
|
||||
* etc.
|
||||
*/
|
||||
'direct_remote_ip'?: (_envoy_config_core_v3_CidrRange | null);
|
||||
/**
|
||||
* A CIDR block that describes the downstream remote/origin address.
|
||||
* Note: This may not be the physical peer and could be different from the
|
||||
* :ref:`direct_remote_ip
|
||||
* <envoy_v3_api_field_config.rbac.v3.Principal.direct_remote_ip>`. E.g, if the
|
||||
* remote ip is inferred from for example the x-forwarder-for header, proxy
|
||||
* protocol, etc.
|
||||
*/
|
||||
'remote_ip'?: (_envoy_config_core_v3_CidrRange | null);
|
||||
/**
|
||||
* Identifies the principal using a filter state object.
|
||||
*/
|
||||
'filter_state'?: (_envoy_type_matcher_v3_FilterStateMatcher | null);
|
||||
'identifier'?: "and_ids"|"or_ids"|"any"|"authenticated"|"source_ip"|"direct_remote_ip"|"remote_ip"|"header"|"url_path"|"metadata"|"filter_state"|"not_id";
|
||||
}
|
||||
|
||||
/**
|
||||
* Principal defines an identity or a group of identities for a downstream
|
||||
* subject.
|
||||
* [#next-free-field: 13]
|
||||
*/
|
||||
export interface Principal__Output {
|
||||
/**
|
||||
* A set of identifiers that all must match in order to define the
|
||||
* downstream.
|
||||
*/
|
||||
'and_ids'?: (_envoy_config_rbac_v3_Principal_Set__Output | null);
|
||||
/**
|
||||
* A set of identifiers at least one must match in order to define the
|
||||
* downstream.
|
||||
*/
|
||||
'or_ids'?: (_envoy_config_rbac_v3_Principal_Set__Output | null);
|
||||
/**
|
||||
* When any is set, it matches any downstream.
|
||||
*/
|
||||
'any'?: (boolean);
|
||||
/**
|
||||
* Authenticated attributes that identify the downstream.
|
||||
*/
|
||||
'authenticated'?: (_envoy_config_rbac_v3_Principal_Authenticated__Output | null);
|
||||
/**
|
||||
* A CIDR block that describes the downstream IP.
|
||||
* This address will honor proxy protocol, but will not honor XFF.
|
||||
*
|
||||
* This field is deprecated; either use :ref:`remote_ip
|
||||
* <envoy_v3_api_field_config.rbac.v3.Principal.remote_ip>` for the same
|
||||
* behavior, or use
|
||||
* :ref:`direct_remote_ip <envoy_v3_api_field_config.rbac.v3.Principal.direct_remote_ip>`.
|
||||
* @deprecated
|
||||
*/
|
||||
'source_ip'?: (_envoy_config_core_v3_CidrRange__Output | null);
|
||||
/**
|
||||
* A header (or pseudo-header such as :path or :method) on the incoming HTTP
|
||||
* request. Only available for HTTP request. Note: the pseudo-header :path
|
||||
* includes the query and fragment string. Use the ``url_path`` field if you
|
||||
* want to match the URL path without the query and fragment string.
|
||||
*/
|
||||
'header'?: (_envoy_config_route_v3_HeaderMatcher__Output | null);
|
||||
/**
|
||||
* Metadata that describes additional information about the principal.
|
||||
*/
|
||||
'metadata'?: (_envoy_type_matcher_v3_MetadataMatcher__Output | null);
|
||||
/**
|
||||
* Negates matching the provided principal. For instance, if the value of
|
||||
* ``not_id`` would match, this principal would not match. Conversely, if the
|
||||
* value of ``not_id`` would not match, this principal would match.
|
||||
*/
|
||||
'not_id'?: (_envoy_config_rbac_v3_Principal__Output | null);
|
||||
/**
|
||||
* A URL path on the incoming HTTP request. Only available for HTTP.
|
||||
*/
|
||||
'url_path'?: (_envoy_type_matcher_v3_PathMatcher__Output | null);
|
||||
/**
|
||||
* A CIDR block that describes the downstream remote/origin address.
|
||||
* Note: This is always the physical peer even if the
|
||||
* :ref:`remote_ip <envoy_v3_api_field_config.rbac.v3.Principal.remote_ip>` is
|
||||
* inferred from for example the x-forwarder-for header, proxy protocol,
|
||||
* etc.
|
||||
*/
|
||||
'direct_remote_ip'?: (_envoy_config_core_v3_CidrRange__Output | null);
|
||||
/**
|
||||
* A CIDR block that describes the downstream remote/origin address.
|
||||
* Note: This may not be the physical peer and could be different from the
|
||||
* :ref:`direct_remote_ip
|
||||
* <envoy_v3_api_field_config.rbac.v3.Principal.direct_remote_ip>`. E.g, if the
|
||||
* remote ip is inferred from for example the x-forwarder-for header, proxy
|
||||
* protocol, etc.
|
||||
*/
|
||||
'remote_ip'?: (_envoy_config_core_v3_CidrRange__Output | null);
|
||||
/**
|
||||
* Identifies the principal using a filter state object.
|
||||
*/
|
||||
'filter_state'?: (_envoy_type_matcher_v3_FilterStateMatcher__Output | null);
|
||||
'identifier'?: "and_ids"|"or_ids"|"any"|"authenticated"|"source_ip"|"direct_remote_ip"|"remote_ip"|"header"|"url_path"|"metadata"|"filter_state"|"not_id";
|
||||
}
|
|
@ -0,0 +1,335 @@
|
|||
// Original file: deps/envoy-api/envoy/config/rbac/v3/rbac.proto
|
||||
|
||||
import type { Policy as _envoy_config_rbac_v3_Policy, Policy__Output as _envoy_config_rbac_v3_Policy__Output } from '../../../../envoy/config/rbac/v3/Policy';
|
||||
import type { TypedExtensionConfig as _envoy_config_core_v3_TypedExtensionConfig, TypedExtensionConfig__Output as _envoy_config_core_v3_TypedExtensionConfig__Output } from '../../../../envoy/config/core/v3/TypedExtensionConfig';
|
||||
|
||||
// Original file: deps/envoy-api/envoy/config/rbac/v3/rbac.proto
|
||||
|
||||
/**
|
||||
* Should we do safe-list or block-list style access control?
|
||||
*/
|
||||
export const _envoy_config_rbac_v3_RBAC_Action = {
|
||||
/**
|
||||
* The policies grant access to principals. The rest are denied. This is safe-list style
|
||||
* access control. This is the default type.
|
||||
*/
|
||||
ALLOW: 'ALLOW',
|
||||
/**
|
||||
* The policies deny access to principals. The rest are allowed. This is block-list style
|
||||
* access control.
|
||||
*/
|
||||
DENY: 'DENY',
|
||||
/**
|
||||
* The policies set the ``access_log_hint`` dynamic metadata key based on if requests match.
|
||||
* All requests are allowed.
|
||||
*/
|
||||
LOG: 'LOG',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Should we do safe-list or block-list style access control?
|
||||
*/
|
||||
export type _envoy_config_rbac_v3_RBAC_Action =
|
||||
/**
|
||||
* The policies grant access to principals. The rest are denied. This is safe-list style
|
||||
* access control. This is the default type.
|
||||
*/
|
||||
| 'ALLOW'
|
||||
| 0
|
||||
/**
|
||||
* The policies deny access to principals. The rest are allowed. This is block-list style
|
||||
* access control.
|
||||
*/
|
||||
| 'DENY'
|
||||
| 1
|
||||
/**
|
||||
* The policies set the ``access_log_hint`` dynamic metadata key based on if requests match.
|
||||
* All requests are allowed.
|
||||
*/
|
||||
| 'LOG'
|
||||
| 2
|
||||
|
||||
/**
|
||||
* Should we do safe-list or block-list style access control?
|
||||
*/
|
||||
export type _envoy_config_rbac_v3_RBAC_Action__Output = typeof _envoy_config_rbac_v3_RBAC_Action[keyof typeof _envoy_config_rbac_v3_RBAC_Action]
|
||||
|
||||
// Original file: deps/envoy-api/envoy/config/rbac/v3/rbac.proto
|
||||
|
||||
/**
|
||||
* Deny and allow here refer to RBAC decisions, not actions.
|
||||
*/
|
||||
export const _envoy_config_rbac_v3_RBAC_AuditLoggingOptions_AuditCondition = {
|
||||
/**
|
||||
* Never audit.
|
||||
*/
|
||||
NONE: 'NONE',
|
||||
/**
|
||||
* Audit when RBAC denies the request.
|
||||
*/
|
||||
ON_DENY: 'ON_DENY',
|
||||
/**
|
||||
* Audit when RBAC allows the request.
|
||||
*/
|
||||
ON_ALLOW: 'ON_ALLOW',
|
||||
/**
|
||||
* Audit whether RBAC allows or denies the request.
|
||||
*/
|
||||
ON_DENY_AND_ALLOW: 'ON_DENY_AND_ALLOW',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Deny and allow here refer to RBAC decisions, not actions.
|
||||
*/
|
||||
export type _envoy_config_rbac_v3_RBAC_AuditLoggingOptions_AuditCondition =
|
||||
/**
|
||||
* Never audit.
|
||||
*/
|
||||
| 'NONE'
|
||||
| 0
|
||||
/**
|
||||
* Audit when RBAC denies the request.
|
||||
*/
|
||||
| 'ON_DENY'
|
||||
| 1
|
||||
/**
|
||||
* Audit when RBAC allows the request.
|
||||
*/
|
||||
| 'ON_ALLOW'
|
||||
| 2
|
||||
/**
|
||||
* Audit whether RBAC allows or denies the request.
|
||||
*/
|
||||
| 'ON_DENY_AND_ALLOW'
|
||||
| 3
|
||||
|
||||
/**
|
||||
* Deny and allow here refer to RBAC decisions, not actions.
|
||||
*/
|
||||
export type _envoy_config_rbac_v3_RBAC_AuditLoggingOptions_AuditCondition__Output = typeof _envoy_config_rbac_v3_RBAC_AuditLoggingOptions_AuditCondition[keyof typeof _envoy_config_rbac_v3_RBAC_AuditLoggingOptions_AuditCondition]
|
||||
|
||||
/**
|
||||
* [#not-implemented-hide:]
|
||||
*/
|
||||
export interface _envoy_config_rbac_v3_RBAC_AuditLoggingOptions_AuditLoggerConfig {
|
||||
/**
|
||||
* Typed logger configuration.
|
||||
*
|
||||
* [#extension-category: envoy.rbac.audit_loggers]
|
||||
*/
|
||||
'audit_logger'?: (_envoy_config_core_v3_TypedExtensionConfig | null);
|
||||
/**
|
||||
* If true, when the logger is not supported, the data plane will not NACK but simply ignore it.
|
||||
*/
|
||||
'is_optional'?: (boolean);
|
||||
}
|
||||
|
||||
/**
|
||||
* [#not-implemented-hide:]
|
||||
*/
|
||||
export interface _envoy_config_rbac_v3_RBAC_AuditLoggingOptions_AuditLoggerConfig__Output {
|
||||
/**
|
||||
* Typed logger configuration.
|
||||
*
|
||||
* [#extension-category: envoy.rbac.audit_loggers]
|
||||
*/
|
||||
'audit_logger': (_envoy_config_core_v3_TypedExtensionConfig__Output | null);
|
||||
/**
|
||||
* If true, when the logger is not supported, the data plane will not NACK but simply ignore it.
|
||||
*/
|
||||
'is_optional': (boolean);
|
||||
}
|
||||
|
||||
export interface _envoy_config_rbac_v3_RBAC_AuditLoggingOptions {
|
||||
/**
|
||||
* Condition for the audit logging to happen.
|
||||
* If this condition is met, all the audit loggers configured here will be invoked.
|
||||
*
|
||||
* [#not-implemented-hide:]
|
||||
*/
|
||||
'audit_condition'?: (_envoy_config_rbac_v3_RBAC_AuditLoggingOptions_AuditCondition);
|
||||
/**
|
||||
* Configurations for RBAC-based authorization audit loggers.
|
||||
*
|
||||
* [#not-implemented-hide:]
|
||||
*/
|
||||
'logger_configs'?: (_envoy_config_rbac_v3_RBAC_AuditLoggingOptions_AuditLoggerConfig)[];
|
||||
}
|
||||
|
||||
export interface _envoy_config_rbac_v3_RBAC_AuditLoggingOptions__Output {
|
||||
/**
|
||||
* Condition for the audit logging to happen.
|
||||
* If this condition is met, all the audit loggers configured here will be invoked.
|
||||
*
|
||||
* [#not-implemented-hide:]
|
||||
*/
|
||||
'audit_condition': (_envoy_config_rbac_v3_RBAC_AuditLoggingOptions_AuditCondition__Output);
|
||||
/**
|
||||
* Configurations for RBAC-based authorization audit loggers.
|
||||
*
|
||||
* [#not-implemented-hide:]
|
||||
*/
|
||||
'logger_configs': (_envoy_config_rbac_v3_RBAC_AuditLoggingOptions_AuditLoggerConfig__Output)[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Role Based Access Control (RBAC) provides service-level and method-level access control for a
|
||||
* service. Requests are allowed or denied based on the ``action`` and whether a matching policy is
|
||||
* found. For instance, if the action is ALLOW and a matching policy is found the request should be
|
||||
* allowed.
|
||||
*
|
||||
* RBAC can also be used to make access logging decisions by communicating with access loggers
|
||||
* through dynamic metadata. When the action is LOG and at least one policy matches, the
|
||||
* ``access_log_hint`` value in the shared key namespace 'envoy.common' is set to ``true`` indicating
|
||||
* the request should be logged.
|
||||
*
|
||||
* Here is an example of RBAC configuration. It has two policies:
|
||||
*
|
||||
* * Service account ``cluster.local/ns/default/sa/admin`` has full access to the service, and so
|
||||
* does "cluster.local/ns/default/sa/superuser".
|
||||
*
|
||||
* * Any user can read (``GET``) the service at paths with prefix ``/products``, so long as the
|
||||
* destination port is either 80 or 443.
|
||||
*
|
||||
* .. code-block:: yaml
|
||||
*
|
||||
* action: ALLOW
|
||||
* policies:
|
||||
* "service-admin":
|
||||
* permissions:
|
||||
* - any: true
|
||||
* principals:
|
||||
* - authenticated:
|
||||
* principal_name:
|
||||
* exact: "cluster.local/ns/default/sa/admin"
|
||||
* - authenticated:
|
||||
* principal_name:
|
||||
* exact: "cluster.local/ns/default/sa/superuser"
|
||||
* "product-viewer":
|
||||
* permissions:
|
||||
* - and_rules:
|
||||
* rules:
|
||||
* - header:
|
||||
* name: ":method"
|
||||
* string_match:
|
||||
* exact: "GET"
|
||||
* - url_path:
|
||||
* path: { prefix: "/products" }
|
||||
* - or_rules:
|
||||
* rules:
|
||||
* - destination_port: 80
|
||||
* - destination_port: 443
|
||||
* principals:
|
||||
* - any: true
|
||||
*/
|
||||
export interface RBAC {
|
||||
/**
|
||||
* The action to take if a policy matches. Every action either allows or denies a request,
|
||||
* and can also carry out action-specific operations.
|
||||
*
|
||||
* Actions:
|
||||
*
|
||||
* * ``ALLOW``: Allows the request if and only if there is a policy that matches
|
||||
* the request.
|
||||
* * ``DENY``: Allows the request if and only if there are no policies that
|
||||
* match the request.
|
||||
* * ``LOG``: Allows all requests. If at least one policy matches, the dynamic
|
||||
* metadata key ``access_log_hint`` is set to the value ``true`` under the shared
|
||||
* key namespace ``envoy.common``. If no policies match, it is set to ``false``.
|
||||
* Other actions do not modify this key.
|
||||
*/
|
||||
'action'?: (_envoy_config_rbac_v3_RBAC_Action);
|
||||
/**
|
||||
* Maps from policy name to policy. A match occurs when at least one policy matches the request.
|
||||
* The policies are evaluated in lexicographic order of the policy name.
|
||||
*/
|
||||
'policies'?: ({[key: string]: _envoy_config_rbac_v3_Policy});
|
||||
/**
|
||||
* Audit logging options that include the condition for audit logging to happen
|
||||
* and audit logger configurations.
|
||||
*
|
||||
* [#not-implemented-hide:]
|
||||
*/
|
||||
'audit_logging_options'?: (_envoy_config_rbac_v3_RBAC_AuditLoggingOptions | null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Role Based Access Control (RBAC) provides service-level and method-level access control for a
|
||||
* service. Requests are allowed or denied based on the ``action`` and whether a matching policy is
|
||||
* found. For instance, if the action is ALLOW and a matching policy is found the request should be
|
||||
* allowed.
|
||||
*
|
||||
* RBAC can also be used to make access logging decisions by communicating with access loggers
|
||||
* through dynamic metadata. When the action is LOG and at least one policy matches, the
|
||||
* ``access_log_hint`` value in the shared key namespace 'envoy.common' is set to ``true`` indicating
|
||||
* the request should be logged.
|
||||
*
|
||||
* Here is an example of RBAC configuration. It has two policies:
|
||||
*
|
||||
* * Service account ``cluster.local/ns/default/sa/admin`` has full access to the service, and so
|
||||
* does "cluster.local/ns/default/sa/superuser".
|
||||
*
|
||||
* * Any user can read (``GET``) the service at paths with prefix ``/products``, so long as the
|
||||
* destination port is either 80 or 443.
|
||||
*
|
||||
* .. code-block:: yaml
|
||||
*
|
||||
* action: ALLOW
|
||||
* policies:
|
||||
* "service-admin":
|
||||
* permissions:
|
||||
* - any: true
|
||||
* principals:
|
||||
* - authenticated:
|
||||
* principal_name:
|
||||
* exact: "cluster.local/ns/default/sa/admin"
|
||||
* - authenticated:
|
||||
* principal_name:
|
||||
* exact: "cluster.local/ns/default/sa/superuser"
|
||||
* "product-viewer":
|
||||
* permissions:
|
||||
* - and_rules:
|
||||
* rules:
|
||||
* - header:
|
||||
* name: ":method"
|
||||
* string_match:
|
||||
* exact: "GET"
|
||||
* - url_path:
|
||||
* path: { prefix: "/products" }
|
||||
* - or_rules:
|
||||
* rules:
|
||||
* - destination_port: 80
|
||||
* - destination_port: 443
|
||||
* principals:
|
||||
* - any: true
|
||||
*/
|
||||
export interface RBAC__Output {
|
||||
/**
|
||||
* The action to take if a policy matches. Every action either allows or denies a request,
|
||||
* and can also carry out action-specific operations.
|
||||
*
|
||||
* Actions:
|
||||
*
|
||||
* * ``ALLOW``: Allows the request if and only if there is a policy that matches
|
||||
* the request.
|
||||
* * ``DENY``: Allows the request if and only if there are no policies that
|
||||
* match the request.
|
||||
* * ``LOG``: Allows all requests. If at least one policy matches, the dynamic
|
||||
* metadata key ``access_log_hint`` is set to the value ``true`` under the shared
|
||||
* key namespace ``envoy.common``. If no policies match, it is set to ``false``.
|
||||
* Other actions do not modify this key.
|
||||
*/
|
||||
'action': (_envoy_config_rbac_v3_RBAC_Action__Output);
|
||||
/**
|
||||
* Maps from policy name to policy. A match occurs when at least one policy matches the request.
|
||||
* The policies are evaluated in lexicographic order of the policy name.
|
||||
*/
|
||||
'policies': ({[key: string]: _envoy_config_rbac_v3_Policy__Output});
|
||||
/**
|
||||
* Audit logging options that include the condition for audit logging to happen
|
||||
* and audit logger configurations.
|
||||
*
|
||||
* [#not-implemented-hide:]
|
||||
*/
|
||||
'audit_logging_options': (_envoy_config_rbac_v3_RBAC_AuditLoggingOptions__Output | null);
|
||||
}
|
104
packages/grpc-js-xds/src/generated/envoy/extensions/filters/http/rbac/v3/RBAC.ts
generated
Normal file
104
packages/grpc-js-xds/src/generated/envoy/extensions/filters/http/rbac/v3/RBAC.ts
generated
Normal file
|
@ -0,0 +1,104 @@
|
|||
// Original file: deps/envoy-api/envoy/extensions/filters/http/rbac/v3/rbac.proto
|
||||
|
||||
import type { RBAC as _envoy_config_rbac_v3_RBAC, RBAC__Output as _envoy_config_rbac_v3_RBAC__Output } from '../../../../../../envoy/config/rbac/v3/RBAC';
|
||||
import type { Matcher as _xds_type_matcher_v3_Matcher, Matcher__Output as _xds_type_matcher_v3_Matcher__Output } from '../../../../../../xds/type/matcher/v3/Matcher';
|
||||
|
||||
/**
|
||||
* RBAC filter config.
|
||||
* [#next-free-field: 8]
|
||||
*/
|
||||
export interface RBAC {
|
||||
/**
|
||||
* Specify the RBAC rules to be applied globally.
|
||||
* If absent, no enforcing RBAC policy will be applied.
|
||||
* If present and empty, DENY.
|
||||
* If both rules and matcher are configured, rules will be ignored.
|
||||
*/
|
||||
'rules'?: (_envoy_config_rbac_v3_RBAC | null);
|
||||
/**
|
||||
* Shadow rules are not enforced by the filter (i.e., returning a 403)
|
||||
* but will emit stats and logs and can be used for rule testing.
|
||||
* If absent, no shadow RBAC policy will be applied.
|
||||
* If both shadow rules and shadow matcher are configured, shadow rules will be ignored.
|
||||
*/
|
||||
'shadow_rules'?: (_envoy_config_rbac_v3_RBAC | null);
|
||||
/**
|
||||
* If specified, shadow rules will emit stats with the given prefix.
|
||||
* This is useful to distinguish the stat when there are more than 1 RBAC filter configured with
|
||||
* shadow rules.
|
||||
*/
|
||||
'shadow_rules_stat_prefix'?: (string);
|
||||
/**
|
||||
* The match tree to use when resolving RBAC action for incoming requests. Requests do not
|
||||
* match any matcher will be denied.
|
||||
* If absent, no enforcing RBAC matcher will be applied.
|
||||
* If present and empty, deny all requests.
|
||||
*/
|
||||
'matcher'?: (_xds_type_matcher_v3_Matcher | null);
|
||||
/**
|
||||
* The match tree to use for emitting stats and logs which can be used for rule testing for
|
||||
* incoming requests.
|
||||
* If absent, no shadow matcher will be applied.
|
||||
*/
|
||||
'shadow_matcher'?: (_xds_type_matcher_v3_Matcher | null);
|
||||
/**
|
||||
* If specified, rules will emit stats with the given prefix.
|
||||
* This is useful to distinguish the stat when there are more than 1 RBAC filter configured with
|
||||
* rules.
|
||||
*/
|
||||
'rules_stat_prefix'?: (string);
|
||||
/**
|
||||
* If track_per_rule_stats is true, counters will be published for each rule and shadow rule.
|
||||
*/
|
||||
'track_per_rule_stats'?: (boolean);
|
||||
}
|
||||
|
||||
/**
|
||||
* RBAC filter config.
|
||||
* [#next-free-field: 8]
|
||||
*/
|
||||
export interface RBAC__Output {
|
||||
/**
|
||||
* Specify the RBAC rules to be applied globally.
|
||||
* If absent, no enforcing RBAC policy will be applied.
|
||||
* If present and empty, DENY.
|
||||
* If both rules and matcher are configured, rules will be ignored.
|
||||
*/
|
||||
'rules': (_envoy_config_rbac_v3_RBAC__Output | null);
|
||||
/**
|
||||
* Shadow rules are not enforced by the filter (i.e., returning a 403)
|
||||
* but will emit stats and logs and can be used for rule testing.
|
||||
* If absent, no shadow RBAC policy will be applied.
|
||||
* If both shadow rules and shadow matcher are configured, shadow rules will be ignored.
|
||||
*/
|
||||
'shadow_rules': (_envoy_config_rbac_v3_RBAC__Output | null);
|
||||
/**
|
||||
* If specified, shadow rules will emit stats with the given prefix.
|
||||
* This is useful to distinguish the stat when there are more than 1 RBAC filter configured with
|
||||
* shadow rules.
|
||||
*/
|
||||
'shadow_rules_stat_prefix': (string);
|
||||
/**
|
||||
* The match tree to use when resolving RBAC action for incoming requests. Requests do not
|
||||
* match any matcher will be denied.
|
||||
* If absent, no enforcing RBAC matcher will be applied.
|
||||
* If present and empty, deny all requests.
|
||||
*/
|
||||
'matcher': (_xds_type_matcher_v3_Matcher__Output | null);
|
||||
/**
|
||||
* The match tree to use for emitting stats and logs which can be used for rule testing for
|
||||
* incoming requests.
|
||||
* If absent, no shadow matcher will be applied.
|
||||
*/
|
||||
'shadow_matcher': (_xds_type_matcher_v3_Matcher__Output | null);
|
||||
/**
|
||||
* If specified, rules will emit stats with the given prefix.
|
||||
* This is useful to distinguish the stat when there are more than 1 RBAC filter configured with
|
||||
* rules.
|
||||
*/
|
||||
'rules_stat_prefix': (string);
|
||||
/**
|
||||
* If track_per_rule_stats is true, counters will be published for each rule and shadow rule.
|
||||
*/
|
||||
'track_per_rule_stats': (boolean);
|
||||
}
|
19
packages/grpc-js-xds/src/generated/envoy/extensions/filters/http/rbac/v3/RBACPerRoute.ts
generated
Normal file
19
packages/grpc-js-xds/src/generated/envoy/extensions/filters/http/rbac/v3/RBACPerRoute.ts
generated
Normal file
|
@ -0,0 +1,19 @@
|
|||
// Original file: deps/envoy-api/envoy/extensions/filters/http/rbac/v3/rbac.proto
|
||||
|
||||
import type { RBAC as _envoy_extensions_filters_http_rbac_v3_RBAC, RBAC__Output as _envoy_extensions_filters_http_rbac_v3_RBAC__Output } from '../../../../../../envoy/extensions/filters/http/rbac/v3/RBAC';
|
||||
|
||||
export interface RBACPerRoute {
|
||||
/**
|
||||
* Override the global configuration of the filter with this new config.
|
||||
* If absent, the global RBAC policy will be disabled for this route.
|
||||
*/
|
||||
'rbac'?: (_envoy_extensions_filters_http_rbac_v3_RBAC | null);
|
||||
}
|
||||
|
||||
export interface RBACPerRoute__Output {
|
||||
/**
|
||||
* Override the global configuration of the filter with this new config.
|
||||
* If absent, the global RBAC policy will be disabled for this route.
|
||||
*/
|
||||
'rbac': (_envoy_extensions_filters_http_rbac_v3_RBAC__Output | null);
|
||||
}
|
33
packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/FilterStateMatcher.ts
generated
Normal file
33
packages/grpc-js-xds/src/generated/envoy/type/matcher/v3/FilterStateMatcher.ts
generated
Normal file
|
@ -0,0 +1,33 @@
|
|||
// Original file: deps/envoy-api/envoy/type/matcher/v3/filter_state.proto
|
||||
|
||||
import type { StringMatcher as _envoy_type_matcher_v3_StringMatcher, StringMatcher__Output as _envoy_type_matcher_v3_StringMatcher__Output } from '../../../../envoy/type/matcher/v3/StringMatcher';
|
||||
|
||||
/**
|
||||
* FilterStateMatcher provides a general interface for matching the filter state objects.
|
||||
*/
|
||||
export interface FilterStateMatcher {
|
||||
/**
|
||||
* The filter state key to retrieve the object.
|
||||
*/
|
||||
'key'?: (string);
|
||||
/**
|
||||
* Matches the filter state object as a string value.
|
||||
*/
|
||||
'string_match'?: (_envoy_type_matcher_v3_StringMatcher | null);
|
||||
'matcher'?: "string_match";
|
||||
}
|
||||
|
||||
/**
|
||||
* FilterStateMatcher provides a general interface for matching the filter state objects.
|
||||
*/
|
||||
export interface FilterStateMatcher__Output {
|
||||
/**
|
||||
* The filter state key to retrieve the object.
|
||||
*/
|
||||
'key': (string);
|
||||
/**
|
||||
* Matches the filter state object as a string value.
|
||||
*/
|
||||
'string_match'?: (_envoy_type_matcher_v3_StringMatcher__Output | null);
|
||||
'matcher'?: "string_match";
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
// Original file: deps/envoy-api/envoy/type/matcher/v3/path.proto
|
||||
|
||||
import type { StringMatcher as _envoy_type_matcher_v3_StringMatcher, StringMatcher__Output as _envoy_type_matcher_v3_StringMatcher__Output } from '../../../../envoy/type/matcher/v3/StringMatcher';
|
||||
|
||||
/**
|
||||
* Specifies the way to match a path on HTTP request.
|
||||
*/
|
||||
export interface PathMatcher {
|
||||
/**
|
||||
* The ``path`` must match the URL path portion of the :path header. The query and fragment
|
||||
* string (if present) are removed in the URL path portion.
|
||||
* For example, the path ``/data`` will match the ``:path`` header ``/data#fragment?param=value``.
|
||||
*/
|
||||
'path'?: (_envoy_type_matcher_v3_StringMatcher | null);
|
||||
'rule'?: "path";
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the way to match a path on HTTP request.
|
||||
*/
|
||||
export interface PathMatcher__Output {
|
||||
/**
|
||||
* The ``path`` must match the URL path portion of the :path header. The query and fragment
|
||||
* string (if present) are removed in the URL path portion.
|
||||
* For example, the path ``/data`` will match the ``:path`` header ``/data#fragment?param=value``.
|
||||
*/
|
||||
'path'?: (_envoy_type_matcher_v3_StringMatcher__Output | null);
|
||||
'rule'?: "path";
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
// Original file: deps/googleapis/google/api/expr/v1alpha1/checked.proto
|
||||
|
||||
import type { Reference as _google_api_expr_v1alpha1_Reference, Reference__Output as _google_api_expr_v1alpha1_Reference__Output } from '../../../../google/api/expr/v1alpha1/Reference';
|
||||
import type { Type as _google_api_expr_v1alpha1_Type, Type__Output as _google_api_expr_v1alpha1_Type__Output } from '../../../../google/api/expr/v1alpha1/Type';
|
||||
import type { Expr as _google_api_expr_v1alpha1_Expr, Expr__Output as _google_api_expr_v1alpha1_Expr__Output } from '../../../../google/api/expr/v1alpha1/Expr';
|
||||
import type { SourceInfo as _google_api_expr_v1alpha1_SourceInfo, SourceInfo__Output as _google_api_expr_v1alpha1_SourceInfo__Output } from '../../../../google/api/expr/v1alpha1/SourceInfo';
|
||||
|
||||
/**
|
||||
* A CEL expression which has been successfully type checked.
|
||||
*/
|
||||
export interface CheckedExpr {
|
||||
/**
|
||||
* A map from expression ids to resolved references.
|
||||
*
|
||||
* The following entries are in this table:
|
||||
*
|
||||
* - An Ident or Select expression is represented here if it resolves to a
|
||||
* declaration. For instance, if `a.b.c` is represented by
|
||||
* `select(select(id(a), b), c)`, and `a.b` resolves to a declaration,
|
||||
* while `c` is a field selection, then the reference is attached to the
|
||||
* nested select expression (but not to the id or or the outer select).
|
||||
* In turn, if `a` resolves to a declaration and `b.c` are field selections,
|
||||
* the reference is attached to the ident expression.
|
||||
* - Every Call expression has an entry here, identifying the function being
|
||||
* called.
|
||||
* - Every CreateStruct expression for a message has an entry, identifying
|
||||
* the message.
|
||||
*/
|
||||
'reference_map'?: ({[key: number]: _google_api_expr_v1alpha1_Reference});
|
||||
/**
|
||||
* A map from expression ids to types.
|
||||
*
|
||||
* Every expression node which has a type different than DYN has a mapping
|
||||
* here. If an expression has type DYN, it is omitted from this map to save
|
||||
* space.
|
||||
*/
|
||||
'type_map'?: ({[key: number]: _google_api_expr_v1alpha1_Type});
|
||||
/**
|
||||
* The checked expression. Semantically equivalent to the parsed `expr`, but
|
||||
* may have structural differences.
|
||||
*/
|
||||
'expr'?: (_google_api_expr_v1alpha1_Expr | null);
|
||||
/**
|
||||
* The source info derived from input that generated the parsed `expr` and
|
||||
* any optimizations made during the type-checking pass.
|
||||
*/
|
||||
'source_info'?: (_google_api_expr_v1alpha1_SourceInfo | null);
|
||||
}
|
||||
|
||||
/**
|
||||
* A CEL expression which has been successfully type checked.
|
||||
*/
|
||||
export interface CheckedExpr__Output {
|
||||
/**
|
||||
* A map from expression ids to resolved references.
|
||||
*
|
||||
* The following entries are in this table:
|
||||
*
|
||||
* - An Ident or Select expression is represented here if it resolves to a
|
||||
* declaration. For instance, if `a.b.c` is represented by
|
||||
* `select(select(id(a), b), c)`, and `a.b` resolves to a declaration,
|
||||
* while `c` is a field selection, then the reference is attached to the
|
||||
* nested select expression (but not to the id or or the outer select).
|
||||
* In turn, if `a` resolves to a declaration and `b.c` are field selections,
|
||||
* the reference is attached to the ident expression.
|
||||
* - Every Call expression has an entry here, identifying the function being
|
||||
* called.
|
||||
* - Every CreateStruct expression for a message has an entry, identifying
|
||||
* the message.
|
||||
*/
|
||||
'reference_map': ({[key: number]: _google_api_expr_v1alpha1_Reference__Output});
|
||||
/**
|
||||
* A map from expression ids to types.
|
||||
*
|
||||
* Every expression node which has a type different than DYN has a mapping
|
||||
* here. If an expression has type DYN, it is omitted from this map to save
|
||||
* space.
|
||||
*/
|
||||
'type_map': ({[key: number]: _google_api_expr_v1alpha1_Type__Output});
|
||||
/**
|
||||
* The checked expression. Semantically equivalent to the parsed `expr`, but
|
||||
* may have structural differences.
|
||||
*/
|
||||
'expr': (_google_api_expr_v1alpha1_Expr__Output | null);
|
||||
/**
|
||||
* The source info derived from input that generated the parsed `expr` and
|
||||
* any optimizations made during the type-checking pass.
|
||||
*/
|
||||
'source_info': (_google_api_expr_v1alpha1_SourceInfo__Output | null);
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
// Original file: deps/googleapis/google/api/expr/v1alpha1/syntax.proto
|
||||
|
||||
import type { NullValue as _google_protobuf_NullValue, NullValue__Output as _google_protobuf_NullValue__Output } from '../../../../google/protobuf/NullValue';
|
||||
import type { Duration as _google_protobuf_Duration, Duration__Output as _google_protobuf_Duration__Output } from '../../../../google/protobuf/Duration';
|
||||
import type { Timestamp as _google_protobuf_Timestamp, Timestamp__Output as _google_protobuf_Timestamp__Output } from '../../../../google/protobuf/Timestamp';
|
||||
import type { Long } from '@grpc/proto-loader';
|
||||
|
||||
/**
|
||||
* Represents a primitive literal.
|
||||
*
|
||||
* Named 'Constant' here for backwards compatibility.
|
||||
*
|
||||
* This is similar as the primitives supported in the well-known type
|
||||
* `google.protobuf.Value`, but richer so it can represent CEL's full range of
|
||||
* primitives.
|
||||
*
|
||||
* Lists and structs are not included as constants as these aggregate types may
|
||||
* contain [Expr][google.api.expr.v1alpha1.Expr] elements which require evaluation and are thus not constant.
|
||||
*
|
||||
* Examples of literals include: `"hello"`, `b'bytes'`, `1u`, `4.2`, `-2`,
|
||||
* `true`, `null`.
|
||||
*/
|
||||
export interface Constant {
|
||||
/**
|
||||
* null value.
|
||||
*/
|
||||
'null_value'?: (_google_protobuf_NullValue);
|
||||
/**
|
||||
* boolean value.
|
||||
*/
|
||||
'bool_value'?: (boolean);
|
||||
/**
|
||||
* int64 value.
|
||||
*/
|
||||
'int64_value'?: (number | string | Long);
|
||||
/**
|
||||
* uint64 value.
|
||||
*/
|
||||
'uint64_value'?: (number | string | Long);
|
||||
/**
|
||||
* double value.
|
||||
*/
|
||||
'double_value'?: (number | string);
|
||||
/**
|
||||
* string value.
|
||||
*/
|
||||
'string_value'?: (string);
|
||||
/**
|
||||
* bytes value.
|
||||
*/
|
||||
'bytes_value'?: (Buffer | Uint8Array | string);
|
||||
/**
|
||||
* protobuf.Duration value.
|
||||
*
|
||||
* Deprecated: duration is no longer considered a builtin cel type.
|
||||
* @deprecated
|
||||
*/
|
||||
'duration_value'?: (_google_protobuf_Duration | null);
|
||||
/**
|
||||
* protobuf.Timestamp value.
|
||||
*
|
||||
* Deprecated: timestamp is no longer considered a builtin cel type.
|
||||
* @deprecated
|
||||
*/
|
||||
'timestamp_value'?: (_google_protobuf_Timestamp | null);
|
||||
/**
|
||||
* Required. The valid constant kinds.
|
||||
*/
|
||||
'constant_kind'?: "null_value"|"bool_value"|"int64_value"|"uint64_value"|"double_value"|"string_value"|"bytes_value"|"duration_value"|"timestamp_value";
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a primitive literal.
|
||||
*
|
||||
* Named 'Constant' here for backwards compatibility.
|
||||
*
|
||||
* This is similar as the primitives supported in the well-known type
|
||||
* `google.protobuf.Value`, but richer so it can represent CEL's full range of
|
||||
* primitives.
|
||||
*
|
||||
* Lists and structs are not included as constants as these aggregate types may
|
||||
* contain [Expr][google.api.expr.v1alpha1.Expr] elements which require evaluation and are thus not constant.
|
||||
*
|
||||
* Examples of literals include: `"hello"`, `b'bytes'`, `1u`, `4.2`, `-2`,
|
||||
* `true`, `null`.
|
||||
*/
|
||||
export interface Constant__Output {
|
||||
/**
|
||||
* null value.
|
||||
*/
|
||||
'null_value'?: (_google_protobuf_NullValue__Output);
|
||||
/**
|
||||
* boolean value.
|
||||
*/
|
||||
'bool_value'?: (boolean);
|
||||
/**
|
||||
* int64 value.
|
||||
*/
|
||||
'int64_value'?: (string);
|
||||
/**
|
||||
* uint64 value.
|
||||
*/
|
||||
'uint64_value'?: (string);
|
||||
/**
|
||||
* double value.
|
||||
*/
|
||||
'double_value'?: (number);
|
||||
/**
|
||||
* string value.
|
||||
*/
|
||||
'string_value'?: (string);
|
||||
/**
|
||||
* bytes value.
|
||||
*/
|
||||
'bytes_value'?: (Buffer);
|
||||
/**
|
||||
* protobuf.Duration value.
|
||||
*
|
||||
* Deprecated: duration is no longer considered a builtin cel type.
|
||||
* @deprecated
|
||||
*/
|
||||
'duration_value'?: (_google_protobuf_Duration__Output | null);
|
||||
/**
|
||||
* protobuf.Timestamp value.
|
||||
*
|
||||
* Deprecated: timestamp is no longer considered a builtin cel type.
|
||||
* @deprecated
|
||||
*/
|
||||
'timestamp_value'?: (_google_protobuf_Timestamp__Output | null);
|
||||
/**
|
||||
* Required. The valid constant kinds.
|
||||
*/
|
||||
'constant_kind'?: "null_value"|"bool_value"|"int64_value"|"uint64_value"|"double_value"|"string_value"|"bytes_value"|"duration_value"|"timestamp_value";
|
||||
}
|
|
@ -0,0 +1,266 @@
|
|||
// Original file: deps/googleapis/google/api/expr/v1alpha1/checked.proto
|
||||
|
||||
import type { Type as _google_api_expr_v1alpha1_Type, Type__Output as _google_api_expr_v1alpha1_Type__Output } from '../../../../google/api/expr/v1alpha1/Type';
|
||||
import type { Constant as _google_api_expr_v1alpha1_Constant, Constant__Output as _google_api_expr_v1alpha1_Constant__Output } from '../../../../google/api/expr/v1alpha1/Constant';
|
||||
|
||||
/**
|
||||
* Function declaration specifies one or more overloads which indicate the
|
||||
* function's parameter types and return type, and may optionally specify a
|
||||
* function definition in terms of CEL expressions.
|
||||
*
|
||||
* Functions have no observable side-effects (there may be side-effects like
|
||||
* logging which are not observable from CEL).
|
||||
*/
|
||||
export interface _google_api_expr_v1alpha1_Decl_FunctionDecl {
|
||||
/**
|
||||
* Required. List of function overloads, must contain at least one overload.
|
||||
*/
|
||||
'overloads'?: (_google_api_expr_v1alpha1_Decl_FunctionDecl_Overload)[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Function declaration specifies one or more overloads which indicate the
|
||||
* function's parameter types and return type, and may optionally specify a
|
||||
* function definition in terms of CEL expressions.
|
||||
*
|
||||
* Functions have no observable side-effects (there may be side-effects like
|
||||
* logging which are not observable from CEL).
|
||||
*/
|
||||
export interface _google_api_expr_v1alpha1_Decl_FunctionDecl__Output {
|
||||
/**
|
||||
* Required. List of function overloads, must contain at least one overload.
|
||||
*/
|
||||
'overloads': (_google_api_expr_v1alpha1_Decl_FunctionDecl_Overload__Output)[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifier declaration which specifies its type and optional `Expr` value.
|
||||
*
|
||||
* An identifier without a value is a declaration that must be provided at
|
||||
* evaluation time. An identifier with a value should resolve to a constant,
|
||||
* but may be used in conjunction with other identifiers bound at evaluation
|
||||
* time.
|
||||
*/
|
||||
export interface _google_api_expr_v1alpha1_Decl_IdentDecl {
|
||||
/**
|
||||
* Required. The type of the identifier.
|
||||
*/
|
||||
'type'?: (_google_api_expr_v1alpha1_Type | null);
|
||||
/**
|
||||
* The constant value of the identifier. If not specified, the identifier
|
||||
* must be supplied at evaluation time.
|
||||
*/
|
||||
'value'?: (_google_api_expr_v1alpha1_Constant | null);
|
||||
/**
|
||||
* Documentation string for the identifier.
|
||||
*/
|
||||
'doc'?: (string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifier declaration which specifies its type and optional `Expr` value.
|
||||
*
|
||||
* An identifier without a value is a declaration that must be provided at
|
||||
* evaluation time. An identifier with a value should resolve to a constant,
|
||||
* but may be used in conjunction with other identifiers bound at evaluation
|
||||
* time.
|
||||
*/
|
||||
export interface _google_api_expr_v1alpha1_Decl_IdentDecl__Output {
|
||||
/**
|
||||
* Required. The type of the identifier.
|
||||
*/
|
||||
'type': (_google_api_expr_v1alpha1_Type__Output | null);
|
||||
/**
|
||||
* The constant value of the identifier. If not specified, the identifier
|
||||
* must be supplied at evaluation time.
|
||||
*/
|
||||
'value': (_google_api_expr_v1alpha1_Constant__Output | null);
|
||||
/**
|
||||
* Documentation string for the identifier.
|
||||
*/
|
||||
'doc': (string);
|
||||
}
|
||||
|
||||
/**
|
||||
* An overload indicates a function's parameter types and return type, and
|
||||
* may optionally include a function body described in terms of [Expr][google.api.expr.v1alpha1.Expr]
|
||||
* values.
|
||||
*
|
||||
* Functions overloads are declared in either a function or method
|
||||
* call-style. For methods, the `params[0]` is the expected type of the
|
||||
* target receiver.
|
||||
*
|
||||
* Overloads must have non-overlapping argument types after erasure of all
|
||||
* parameterized type variables (similar as type erasure in Java).
|
||||
*/
|
||||
export interface _google_api_expr_v1alpha1_Decl_FunctionDecl_Overload {
|
||||
/**
|
||||
* Required. Globally unique overload name of the function which reflects
|
||||
* the function name and argument types.
|
||||
*
|
||||
* This will be used by a [Reference][google.api.expr.v1alpha1.Reference] to indicate the `overload_id` that
|
||||
* was resolved for the function `name`.
|
||||
*/
|
||||
'overload_id'?: (string);
|
||||
/**
|
||||
* List of function parameter [Type][google.api.expr.v1alpha1.Type] values.
|
||||
*
|
||||
* Param types are disjoint after generic type parameters have been
|
||||
* replaced with the type `DYN`. Since the `DYN` type is compatible with
|
||||
* any other type, this means that if `A` is a type parameter, the
|
||||
* function types `int<A>` and `int<int>` are not disjoint. Likewise,
|
||||
* `map<string, string>` is not disjoint from `map<K, V>`.
|
||||
*
|
||||
* When the `result_type` of a function is a generic type param, the
|
||||
* type param name also appears as the `type` of on at least one params.
|
||||
*/
|
||||
'params'?: (_google_api_expr_v1alpha1_Type)[];
|
||||
/**
|
||||
* The type param names associated with the function declaration.
|
||||
*
|
||||
* For example, `function ex<K,V>(K key, map<K, V> map) : V` would yield
|
||||
* the type params of `K, V`.
|
||||
*/
|
||||
'type_params'?: (string)[];
|
||||
/**
|
||||
* Required. The result type of the function. For example, the operator
|
||||
* `string.isEmpty()` would have `result_type` of `kind: BOOL`.
|
||||
*/
|
||||
'result_type'?: (_google_api_expr_v1alpha1_Type | null);
|
||||
/**
|
||||
* Whether the function is to be used in a method call-style `x.f(...)`
|
||||
* of a function call-style `f(x, ...)`.
|
||||
*
|
||||
* For methods, the first parameter declaration, `params[0]` is the
|
||||
* expected type of the target receiver.
|
||||
*/
|
||||
'is_instance_function'?: (boolean);
|
||||
/**
|
||||
* Documentation string for the overload.
|
||||
*/
|
||||
'doc'?: (string);
|
||||
}
|
||||
|
||||
/**
|
||||
* An overload indicates a function's parameter types and return type, and
|
||||
* may optionally include a function body described in terms of [Expr][google.api.expr.v1alpha1.Expr]
|
||||
* values.
|
||||
*
|
||||
* Functions overloads are declared in either a function or method
|
||||
* call-style. For methods, the `params[0]` is the expected type of the
|
||||
* target receiver.
|
||||
*
|
||||
* Overloads must have non-overlapping argument types after erasure of all
|
||||
* parameterized type variables (similar as type erasure in Java).
|
||||
*/
|
||||
export interface _google_api_expr_v1alpha1_Decl_FunctionDecl_Overload__Output {
|
||||
/**
|
||||
* Required. Globally unique overload name of the function which reflects
|
||||
* the function name and argument types.
|
||||
*
|
||||
* This will be used by a [Reference][google.api.expr.v1alpha1.Reference] to indicate the `overload_id` that
|
||||
* was resolved for the function `name`.
|
||||
*/
|
||||
'overload_id': (string);
|
||||
/**
|
||||
* List of function parameter [Type][google.api.expr.v1alpha1.Type] values.
|
||||
*
|
||||
* Param types are disjoint after generic type parameters have been
|
||||
* replaced with the type `DYN`. Since the `DYN` type is compatible with
|
||||
* any other type, this means that if `A` is a type parameter, the
|
||||
* function types `int<A>` and `int<int>` are not disjoint. Likewise,
|
||||
* `map<string, string>` is not disjoint from `map<K, V>`.
|
||||
*
|
||||
* When the `result_type` of a function is a generic type param, the
|
||||
* type param name also appears as the `type` of on at least one params.
|
||||
*/
|
||||
'params': (_google_api_expr_v1alpha1_Type__Output)[];
|
||||
/**
|
||||
* The type param names associated with the function declaration.
|
||||
*
|
||||
* For example, `function ex<K,V>(K key, map<K, V> map) : V` would yield
|
||||
* the type params of `K, V`.
|
||||
*/
|
||||
'type_params': (string)[];
|
||||
/**
|
||||
* Required. The result type of the function. For example, the operator
|
||||
* `string.isEmpty()` would have `result_type` of `kind: BOOL`.
|
||||
*/
|
||||
'result_type': (_google_api_expr_v1alpha1_Type__Output | null);
|
||||
/**
|
||||
* Whether the function is to be used in a method call-style `x.f(...)`
|
||||
* of a function call-style `f(x, ...)`.
|
||||
*
|
||||
* For methods, the first parameter declaration, `params[0]` is the
|
||||
* expected type of the target receiver.
|
||||
*/
|
||||
'is_instance_function': (boolean);
|
||||
/**
|
||||
* Documentation string for the overload.
|
||||
*/
|
||||
'doc': (string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a declaration of a named value or function.
|
||||
*
|
||||
* A declaration is part of the contract between the expression, the agent
|
||||
* evaluating that expression, and the caller requesting evaluation.
|
||||
*/
|
||||
export interface Decl {
|
||||
/**
|
||||
* The fully qualified name of the declaration.
|
||||
*
|
||||
* Declarations are organized in containers and this represents the full path
|
||||
* to the declaration in its container, as in `google.api.expr.Decl`.
|
||||
*
|
||||
* Declarations used as [FunctionDecl.Overload][google.api.expr.v1alpha1.Decl.FunctionDecl.Overload] parameters may or may not
|
||||
* have a name depending on whether the overload is function declaration or a
|
||||
* function definition containing a result [Expr][google.api.expr.v1alpha1.Expr].
|
||||
*/
|
||||
'name'?: (string);
|
||||
/**
|
||||
* Identifier declaration.
|
||||
*/
|
||||
'ident'?: (_google_api_expr_v1alpha1_Decl_IdentDecl | null);
|
||||
/**
|
||||
* Function declaration.
|
||||
*/
|
||||
'function'?: (_google_api_expr_v1alpha1_Decl_FunctionDecl | null);
|
||||
/**
|
||||
* Required. The declaration kind.
|
||||
*/
|
||||
'decl_kind'?: "ident"|"function";
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a declaration of a named value or function.
|
||||
*
|
||||
* A declaration is part of the contract between the expression, the agent
|
||||
* evaluating that expression, and the caller requesting evaluation.
|
||||
*/
|
||||
export interface Decl__Output {
|
||||
/**
|
||||
* The fully qualified name of the declaration.
|
||||
*
|
||||
* Declarations are organized in containers and this represents the full path
|
||||
* to the declaration in its container, as in `google.api.expr.Decl`.
|
||||
*
|
||||
* Declarations used as [FunctionDecl.Overload][google.api.expr.v1alpha1.Decl.FunctionDecl.Overload] parameters may or may not
|
||||
* have a name depending on whether the overload is function declaration or a
|
||||
* function definition containing a result [Expr][google.api.expr.v1alpha1.Expr].
|
||||
*/
|
||||
'name': (string);
|
||||
/**
|
||||
* Identifier declaration.
|
||||
*/
|
||||
'ident'?: (_google_api_expr_v1alpha1_Decl_IdentDecl__Output | null);
|
||||
/**
|
||||
* Function declaration.
|
||||
*/
|
||||
'function'?: (_google_api_expr_v1alpha1_Decl_FunctionDecl__Output | null);
|
||||
/**
|
||||
* Required. The declaration kind.
|
||||
*/
|
||||
'decl_kind'?: "ident"|"function";
|
||||
}
|
|
@ -0,0 +1,493 @@
|
|||
// Original file: deps/googleapis/google/api/expr/v1alpha1/syntax.proto
|
||||
|
||||
import type { Constant as _google_api_expr_v1alpha1_Constant, Constant__Output as _google_api_expr_v1alpha1_Constant__Output } from '../../../../google/api/expr/v1alpha1/Constant';
|
||||
import type { Expr as _google_api_expr_v1alpha1_Expr, Expr__Output as _google_api_expr_v1alpha1_Expr__Output } from '../../../../google/api/expr/v1alpha1/Expr';
|
||||
import type { Long } from '@grpc/proto-loader';
|
||||
|
||||
/**
|
||||
* A call expression, including calls to predefined functions and operators.
|
||||
*
|
||||
* For example, `value == 10`, `size(map_value)`.
|
||||
*/
|
||||
export interface _google_api_expr_v1alpha1_Expr_Call {
|
||||
/**
|
||||
* The target of an method call-style expression. For example, `x` in
|
||||
* `x.f()`.
|
||||
*/
|
||||
'target'?: (_google_api_expr_v1alpha1_Expr | null);
|
||||
/**
|
||||
* Required. The name of the function or method being called.
|
||||
*/
|
||||
'function'?: (string);
|
||||
/**
|
||||
* The arguments.
|
||||
*/
|
||||
'args'?: (_google_api_expr_v1alpha1_Expr)[];
|
||||
}
|
||||
|
||||
/**
|
||||
* A call expression, including calls to predefined functions and operators.
|
||||
*
|
||||
* For example, `value == 10`, `size(map_value)`.
|
||||
*/
|
||||
export interface _google_api_expr_v1alpha1_Expr_Call__Output {
|
||||
/**
|
||||
* The target of an method call-style expression. For example, `x` in
|
||||
* `x.f()`.
|
||||
*/
|
||||
'target': (_google_api_expr_v1alpha1_Expr__Output | null);
|
||||
/**
|
||||
* Required. The name of the function or method being called.
|
||||
*/
|
||||
'function': (string);
|
||||
/**
|
||||
* The arguments.
|
||||
*/
|
||||
'args': (_google_api_expr_v1alpha1_Expr__Output)[];
|
||||
}
|
||||
|
||||
/**
|
||||
* A comprehension expression applied to a list or map.
|
||||
*
|
||||
* Comprehensions are not part of the core syntax, but enabled with macros.
|
||||
* A macro matches a specific call signature within a parsed AST and replaces
|
||||
* the call with an alternate AST block. Macro expansion happens at parse
|
||||
* time.
|
||||
*
|
||||
* The following macros are supported within CEL:
|
||||
*
|
||||
* Aggregate type macros may be applied to all elements in a list or all keys
|
||||
* in a map:
|
||||
*
|
||||
* * `all`, `exists`, `exists_one` - test a predicate expression against
|
||||
* the inputs and return `true` if the predicate is satisfied for all,
|
||||
* any, or only one value `list.all(x, x < 10)`.
|
||||
* * `filter` - test a predicate expression against the inputs and return
|
||||
* the subset of elements which satisfy the predicate:
|
||||
* `payments.filter(p, p > 1000)`.
|
||||
* * `map` - apply an expression to all elements in the input and return the
|
||||
* output aggregate type: `[1, 2, 3].map(i, i * i)`.
|
||||
*
|
||||
* The `has(m.x)` macro tests whether the property `x` is present in struct
|
||||
* `m`. The semantics of this macro depend on the type of `m`. For proto2
|
||||
* messages `has(m.x)` is defined as 'defined, but not set`. For proto3, the
|
||||
* macro tests whether the property is set to its default. For map and struct
|
||||
* types, the macro tests whether the property `x` is defined on `m`.
|
||||
*/
|
||||
export interface _google_api_expr_v1alpha1_Expr_Comprehension {
|
||||
/**
|
||||
* The name of the iteration variable.
|
||||
*/
|
||||
'iter_var'?: (string);
|
||||
/**
|
||||
* The range over which var iterates.
|
||||
*/
|
||||
'iter_range'?: (_google_api_expr_v1alpha1_Expr | null);
|
||||
/**
|
||||
* The name of the variable used for accumulation of the result.
|
||||
*/
|
||||
'accu_var'?: (string);
|
||||
/**
|
||||
* The initial value of the accumulator.
|
||||
*/
|
||||
'accu_init'?: (_google_api_expr_v1alpha1_Expr | null);
|
||||
/**
|
||||
* An expression which can contain iter_var and accu_var.
|
||||
*
|
||||
* Returns false when the result has been computed and may be used as
|
||||
* a hint to short-circuit the remainder of the comprehension.
|
||||
*/
|
||||
'loop_condition'?: (_google_api_expr_v1alpha1_Expr | null);
|
||||
/**
|
||||
* An expression which can contain iter_var and accu_var.
|
||||
*
|
||||
* Computes the next value of accu_var.
|
||||
*/
|
||||
'loop_step'?: (_google_api_expr_v1alpha1_Expr | null);
|
||||
/**
|
||||
* An expression which can contain accu_var.
|
||||
*
|
||||
* Computes the result.
|
||||
*/
|
||||
'result'?: (_google_api_expr_v1alpha1_Expr | null);
|
||||
}
|
||||
|
||||
/**
|
||||
* A comprehension expression applied to a list or map.
|
||||
*
|
||||
* Comprehensions are not part of the core syntax, but enabled with macros.
|
||||
* A macro matches a specific call signature within a parsed AST and replaces
|
||||
* the call with an alternate AST block. Macro expansion happens at parse
|
||||
* time.
|
||||
*
|
||||
* The following macros are supported within CEL:
|
||||
*
|
||||
* Aggregate type macros may be applied to all elements in a list or all keys
|
||||
* in a map:
|
||||
*
|
||||
* * `all`, `exists`, `exists_one` - test a predicate expression against
|
||||
* the inputs and return `true` if the predicate is satisfied for all,
|
||||
* any, or only one value `list.all(x, x < 10)`.
|
||||
* * `filter` - test a predicate expression against the inputs and return
|
||||
* the subset of elements which satisfy the predicate:
|
||||
* `payments.filter(p, p > 1000)`.
|
||||
* * `map` - apply an expression to all elements in the input and return the
|
||||
* output aggregate type: `[1, 2, 3].map(i, i * i)`.
|
||||
*
|
||||
* The `has(m.x)` macro tests whether the property `x` is present in struct
|
||||
* `m`. The semantics of this macro depend on the type of `m`. For proto2
|
||||
* messages `has(m.x)` is defined as 'defined, but not set`. For proto3, the
|
||||
* macro tests whether the property is set to its default. For map and struct
|
||||
* types, the macro tests whether the property `x` is defined on `m`.
|
||||
*/
|
||||
export interface _google_api_expr_v1alpha1_Expr_Comprehension__Output {
|
||||
/**
|
||||
* The name of the iteration variable.
|
||||
*/
|
||||
'iter_var': (string);
|
||||
/**
|
||||
* The range over which var iterates.
|
||||
*/
|
||||
'iter_range': (_google_api_expr_v1alpha1_Expr__Output | null);
|
||||
/**
|
||||
* The name of the variable used for accumulation of the result.
|
||||
*/
|
||||
'accu_var': (string);
|
||||
/**
|
||||
* The initial value of the accumulator.
|
||||
*/
|
||||
'accu_init': (_google_api_expr_v1alpha1_Expr__Output | null);
|
||||
/**
|
||||
* An expression which can contain iter_var and accu_var.
|
||||
*
|
||||
* Returns false when the result has been computed and may be used as
|
||||
* a hint to short-circuit the remainder of the comprehension.
|
||||
*/
|
||||
'loop_condition': (_google_api_expr_v1alpha1_Expr__Output | null);
|
||||
/**
|
||||
* An expression which can contain iter_var and accu_var.
|
||||
*
|
||||
* Computes the next value of accu_var.
|
||||
*/
|
||||
'loop_step': (_google_api_expr_v1alpha1_Expr__Output | null);
|
||||
/**
|
||||
* An expression which can contain accu_var.
|
||||
*
|
||||
* Computes the result.
|
||||
*/
|
||||
'result': (_google_api_expr_v1alpha1_Expr__Output | null);
|
||||
}
|
||||
|
||||
/**
|
||||
* A list creation expression.
|
||||
*
|
||||
* Lists may either be homogenous, e.g. `[1, 2, 3]`, or heterogenous, e.g.
|
||||
* `dyn([1, 'hello', 2.0])`
|
||||
*/
|
||||
export interface _google_api_expr_v1alpha1_Expr_CreateList {
|
||||
/**
|
||||
* The elements part of the list.
|
||||
*/
|
||||
'elements'?: (_google_api_expr_v1alpha1_Expr)[];
|
||||
}
|
||||
|
||||
/**
|
||||
* A list creation expression.
|
||||
*
|
||||
* Lists may either be homogenous, e.g. `[1, 2, 3]`, or heterogenous, e.g.
|
||||
* `dyn([1, 'hello', 2.0])`
|
||||
*/
|
||||
export interface _google_api_expr_v1alpha1_Expr_CreateList__Output {
|
||||
/**
|
||||
* The elements part of the list.
|
||||
*/
|
||||
'elements': (_google_api_expr_v1alpha1_Expr__Output)[];
|
||||
}
|
||||
|
||||
/**
|
||||
* A map or message creation expression.
|
||||
*
|
||||
* Maps are constructed as `{'key_name': 'value'}`. Message construction is
|
||||
* similar, but prefixed with a type name and composed of field ids:
|
||||
* `types.MyType{field_id: 'value'}`.
|
||||
*/
|
||||
export interface _google_api_expr_v1alpha1_Expr_CreateStruct {
|
||||
/**
|
||||
* The type name of the message to be created, empty when creating map
|
||||
* literals.
|
||||
*/
|
||||
'message_name'?: (string);
|
||||
/**
|
||||
* The entries in the creation expression.
|
||||
*/
|
||||
'entries'?: (_google_api_expr_v1alpha1_Expr_CreateStruct_Entry)[];
|
||||
}
|
||||
|
||||
/**
|
||||
* A map or message creation expression.
|
||||
*
|
||||
* Maps are constructed as `{'key_name': 'value'}`. Message construction is
|
||||
* similar, but prefixed with a type name and composed of field ids:
|
||||
* `types.MyType{field_id: 'value'}`.
|
||||
*/
|
||||
export interface _google_api_expr_v1alpha1_Expr_CreateStruct__Output {
|
||||
/**
|
||||
* The type name of the message to be created, empty when creating map
|
||||
* literals.
|
||||
*/
|
||||
'message_name': (string);
|
||||
/**
|
||||
* The entries in the creation expression.
|
||||
*/
|
||||
'entries': (_google_api_expr_v1alpha1_Expr_CreateStruct_Entry__Output)[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an entry.
|
||||
*/
|
||||
export interface _google_api_expr_v1alpha1_Expr_CreateStruct_Entry {
|
||||
/**
|
||||
* Required. An id assigned to this node by the parser which is unique
|
||||
* in a given expression tree. This is used to associate type
|
||||
* information and other attributes to the node.
|
||||
*/
|
||||
'id'?: (number | string | Long);
|
||||
/**
|
||||
* The field key for a message creator statement.
|
||||
*/
|
||||
'field_key'?: (string);
|
||||
/**
|
||||
* The key expression for a map creation statement.
|
||||
*/
|
||||
'map_key'?: (_google_api_expr_v1alpha1_Expr | null);
|
||||
/**
|
||||
* Required. The value assigned to the key.
|
||||
*/
|
||||
'value'?: (_google_api_expr_v1alpha1_Expr | null);
|
||||
/**
|
||||
* The `Entry` key kinds.
|
||||
*/
|
||||
'key_kind'?: "field_key"|"map_key";
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an entry.
|
||||
*/
|
||||
export interface _google_api_expr_v1alpha1_Expr_CreateStruct_Entry__Output {
|
||||
/**
|
||||
* Required. An id assigned to this node by the parser which is unique
|
||||
* in a given expression tree. This is used to associate type
|
||||
* information and other attributes to the node.
|
||||
*/
|
||||
'id': (string);
|
||||
/**
|
||||
* The field key for a message creator statement.
|
||||
*/
|
||||
'field_key'?: (string);
|
||||
/**
|
||||
* The key expression for a map creation statement.
|
||||
*/
|
||||
'map_key'?: (_google_api_expr_v1alpha1_Expr__Output | null);
|
||||
/**
|
||||
* Required. The value assigned to the key.
|
||||
*/
|
||||
'value': (_google_api_expr_v1alpha1_Expr__Output | null);
|
||||
/**
|
||||
* The `Entry` key kinds.
|
||||
*/
|
||||
'key_kind'?: "field_key"|"map_key";
|
||||
}
|
||||
|
||||
/**
|
||||
* An identifier expression. e.g. `request`.
|
||||
*/
|
||||
export interface _google_api_expr_v1alpha1_Expr_Ident {
|
||||
/**
|
||||
* Required. Holds a single, unqualified identifier, possibly preceded by a
|
||||
* '.'.
|
||||
*
|
||||
* Qualified names are represented by the [Expr.Select][google.api.expr.v1alpha1.Expr.Select] expression.
|
||||
*/
|
||||
'name'?: (string);
|
||||
}
|
||||
|
||||
/**
|
||||
* An identifier expression. e.g. `request`.
|
||||
*/
|
||||
export interface _google_api_expr_v1alpha1_Expr_Ident__Output {
|
||||
/**
|
||||
* Required. Holds a single, unqualified identifier, possibly preceded by a
|
||||
* '.'.
|
||||
*
|
||||
* Qualified names are represented by the [Expr.Select][google.api.expr.v1alpha1.Expr.Select] expression.
|
||||
*/
|
||||
'name': (string);
|
||||
}
|
||||
|
||||
/**
|
||||
* A field selection expression. e.g. `request.auth`.
|
||||
*/
|
||||
export interface _google_api_expr_v1alpha1_Expr_Select {
|
||||
/**
|
||||
* Required. The target of the selection expression.
|
||||
*
|
||||
* For example, in the select expression `request.auth`, the `request`
|
||||
* portion of the expression is the `operand`.
|
||||
*/
|
||||
'operand'?: (_google_api_expr_v1alpha1_Expr | null);
|
||||
/**
|
||||
* Required. The name of the field to select.
|
||||
*
|
||||
* For example, in the select expression `request.auth`, the `auth` portion
|
||||
* of the expression would be the `field`.
|
||||
*/
|
||||
'field'?: (string);
|
||||
/**
|
||||
* Whether the select is to be interpreted as a field presence test.
|
||||
*
|
||||
* This results from the macro `has(request.auth)`.
|
||||
*/
|
||||
'test_only'?: (boolean);
|
||||
}
|
||||
|
||||
/**
|
||||
* A field selection expression. e.g. `request.auth`.
|
||||
*/
|
||||
export interface _google_api_expr_v1alpha1_Expr_Select__Output {
|
||||
/**
|
||||
* Required. The target of the selection expression.
|
||||
*
|
||||
* For example, in the select expression `request.auth`, the `request`
|
||||
* portion of the expression is the `operand`.
|
||||
*/
|
||||
'operand': (_google_api_expr_v1alpha1_Expr__Output | null);
|
||||
/**
|
||||
* Required. The name of the field to select.
|
||||
*
|
||||
* For example, in the select expression `request.auth`, the `auth` portion
|
||||
* of the expression would be the `field`.
|
||||
*/
|
||||
'field': (string);
|
||||
/**
|
||||
* Whether the select is to be interpreted as a field presence test.
|
||||
*
|
||||
* This results from the macro `has(request.auth)`.
|
||||
*/
|
||||
'test_only': (boolean);
|
||||
}
|
||||
|
||||
/**
|
||||
* An abstract representation of a common expression.
|
||||
*
|
||||
* Expressions are abstractly represented as a collection of identifiers,
|
||||
* select statements, function calls, literals, and comprehensions. All
|
||||
* operators with the exception of the '.' operator are modelled as function
|
||||
* calls. This makes it easy to represent new operators into the existing AST.
|
||||
*
|
||||
* All references within expressions must resolve to a [Decl][google.api.expr.v1alpha1.Decl] provided at
|
||||
* type-check for an expression to be valid. A reference may either be a bare
|
||||
* identifier `name` or a qualified identifier `google.api.name`. References
|
||||
* may either refer to a value or a function declaration.
|
||||
*
|
||||
* For example, the expression `google.api.name.startsWith('expr')` references
|
||||
* the declaration `google.api.name` within a [Expr.Select][google.api.expr.v1alpha1.Expr.Select] expression, and
|
||||
* the function declaration `startsWith`.
|
||||
*/
|
||||
export interface Expr {
|
||||
/**
|
||||
* Required. An id assigned to this node by the parser which is unique in a
|
||||
* given expression tree. This is used to associate type information and other
|
||||
* attributes to a node in the parse tree.
|
||||
*/
|
||||
'id'?: (number | string | Long);
|
||||
/**
|
||||
* A literal expression.
|
||||
*/
|
||||
'const_expr'?: (_google_api_expr_v1alpha1_Constant | null);
|
||||
/**
|
||||
* An identifier expression.
|
||||
*/
|
||||
'ident_expr'?: (_google_api_expr_v1alpha1_Expr_Ident | null);
|
||||
/**
|
||||
* A field selection expression, e.g. `request.auth`.
|
||||
*/
|
||||
'select_expr'?: (_google_api_expr_v1alpha1_Expr_Select | null);
|
||||
/**
|
||||
* A call expression, including calls to predefined functions and operators.
|
||||
*/
|
||||
'call_expr'?: (_google_api_expr_v1alpha1_Expr_Call | null);
|
||||
/**
|
||||
* A list creation expression.
|
||||
*/
|
||||
'list_expr'?: (_google_api_expr_v1alpha1_Expr_CreateList | null);
|
||||
/**
|
||||
* A map or message creation expression.
|
||||
*/
|
||||
'struct_expr'?: (_google_api_expr_v1alpha1_Expr_CreateStruct | null);
|
||||
/**
|
||||
* A comprehension expression.
|
||||
*/
|
||||
'comprehension_expr'?: (_google_api_expr_v1alpha1_Expr_Comprehension | null);
|
||||
/**
|
||||
* Required. Variants of expressions.
|
||||
*/
|
||||
'expr_kind'?: "const_expr"|"ident_expr"|"select_expr"|"call_expr"|"list_expr"|"struct_expr"|"comprehension_expr";
|
||||
}
|
||||
|
||||
/**
|
||||
* An abstract representation of a common expression.
|
||||
*
|
||||
* Expressions are abstractly represented as a collection of identifiers,
|
||||
* select statements, function calls, literals, and comprehensions. All
|
||||
* operators with the exception of the '.' operator are modelled as function
|
||||
* calls. This makes it easy to represent new operators into the existing AST.
|
||||
*
|
||||
* All references within expressions must resolve to a [Decl][google.api.expr.v1alpha1.Decl] provided at
|
||||
* type-check for an expression to be valid. A reference may either be a bare
|
||||
* identifier `name` or a qualified identifier `google.api.name`. References
|
||||
* may either refer to a value or a function declaration.
|
||||
*
|
||||
* For example, the expression `google.api.name.startsWith('expr')` references
|
||||
* the declaration `google.api.name` within a [Expr.Select][google.api.expr.v1alpha1.Expr.Select] expression, and
|
||||
* the function declaration `startsWith`.
|
||||
*/
|
||||
export interface Expr__Output {
|
||||
/**
|
||||
* Required. An id assigned to this node by the parser which is unique in a
|
||||
* given expression tree. This is used to associate type information and other
|
||||
* attributes to a node in the parse tree.
|
||||
*/
|
||||
'id': (string);
|
||||
/**
|
||||
* A literal expression.
|
||||
*/
|
||||
'const_expr'?: (_google_api_expr_v1alpha1_Constant__Output | null);
|
||||
/**
|
||||
* An identifier expression.
|
||||
*/
|
||||
'ident_expr'?: (_google_api_expr_v1alpha1_Expr_Ident__Output | null);
|
||||
/**
|
||||
* A field selection expression, e.g. `request.auth`.
|
||||
*/
|
||||
'select_expr'?: (_google_api_expr_v1alpha1_Expr_Select__Output | null);
|
||||
/**
|
||||
* A call expression, including calls to predefined functions and operators.
|
||||
*/
|
||||
'call_expr'?: (_google_api_expr_v1alpha1_Expr_Call__Output | null);
|
||||
/**
|
||||
* A list creation expression.
|
||||
*/
|
||||
'list_expr'?: (_google_api_expr_v1alpha1_Expr_CreateList__Output | null);
|
||||
/**
|
||||
* A map or message creation expression.
|
||||
*/
|
||||
'struct_expr'?: (_google_api_expr_v1alpha1_Expr_CreateStruct__Output | null);
|
||||
/**
|
||||
* A comprehension expression.
|
||||
*/
|
||||
'comprehension_expr'?: (_google_api_expr_v1alpha1_Expr_Comprehension__Output | null);
|
||||
/**
|
||||
* Required. Variants of expressions.
|
||||
*/
|
||||
'expr_kind'?: "const_expr"|"ident_expr"|"select_expr"|"call_expr"|"list_expr"|"struct_expr"|"comprehension_expr";
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
// Original file: deps/googleapis/google/api/expr/v1alpha1/syntax.proto
|
||||
|
||||
import type { Expr as _google_api_expr_v1alpha1_Expr, Expr__Output as _google_api_expr_v1alpha1_Expr__Output } from '../../../../google/api/expr/v1alpha1/Expr';
|
||||
import type { SourceInfo as _google_api_expr_v1alpha1_SourceInfo, SourceInfo__Output as _google_api_expr_v1alpha1_SourceInfo__Output } from '../../../../google/api/expr/v1alpha1/SourceInfo';
|
||||
|
||||
/**
|
||||
* An expression together with source information as returned by the parser.
|
||||
*/
|
||||
export interface ParsedExpr {
|
||||
/**
|
||||
* The parsed expression.
|
||||
*/
|
||||
'expr'?: (_google_api_expr_v1alpha1_Expr | null);
|
||||
/**
|
||||
* The source info derived from input that generated the parsed `expr`.
|
||||
*/
|
||||
'source_info'?: (_google_api_expr_v1alpha1_SourceInfo | null);
|
||||
}
|
||||
|
||||
/**
|
||||
* An expression together with source information as returned by the parser.
|
||||
*/
|
||||
export interface ParsedExpr__Output {
|
||||
/**
|
||||
* The parsed expression.
|
||||
*/
|
||||
'expr': (_google_api_expr_v1alpha1_Expr__Output | null);
|
||||
/**
|
||||
* The source info derived from input that generated the parsed `expr`.
|
||||
*/
|
||||
'source_info': (_google_api_expr_v1alpha1_SourceInfo__Output | null);
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
// Original file: deps/googleapis/google/api/expr/v1alpha1/checked.proto
|
||||
|
||||
import type { Constant as _google_api_expr_v1alpha1_Constant, Constant__Output as _google_api_expr_v1alpha1_Constant__Output } from '../../../../google/api/expr/v1alpha1/Constant';
|
||||
|
||||
/**
|
||||
* Describes a resolved reference to a declaration.
|
||||
*/
|
||||
export interface Reference {
|
||||
/**
|
||||
* The fully qualified name of the declaration.
|
||||
*/
|
||||
'name'?: (string);
|
||||
/**
|
||||
* For references to functions, this is a list of `Overload.overload_id`
|
||||
* values which match according to typing rules.
|
||||
*
|
||||
* If the list has more than one element, overload resolution among the
|
||||
* presented candidates must happen at runtime because of dynamic types. The
|
||||
* type checker attempts to narrow down this list as much as possible.
|
||||
*
|
||||
* Empty if this is not a reference to a [Decl.FunctionDecl][google.api.expr.v1alpha1.Decl.FunctionDecl].
|
||||
*/
|
||||
'overload_id'?: (string)[];
|
||||
/**
|
||||
* For references to constants, this may contain the value of the
|
||||
* constant if known at compile time.
|
||||
*/
|
||||
'value'?: (_google_api_expr_v1alpha1_Constant | null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes a resolved reference to a declaration.
|
||||
*/
|
||||
export interface Reference__Output {
|
||||
/**
|
||||
* The fully qualified name of the declaration.
|
||||
*/
|
||||
'name': (string);
|
||||
/**
|
||||
* For references to functions, this is a list of `Overload.overload_id`
|
||||
* values which match according to typing rules.
|
||||
*
|
||||
* If the list has more than one element, overload resolution among the
|
||||
* presented candidates must happen at runtime because of dynamic types. The
|
||||
* type checker attempts to narrow down this list as much as possible.
|
||||
*
|
||||
* Empty if this is not a reference to a [Decl.FunctionDecl][google.api.expr.v1alpha1.Decl.FunctionDecl].
|
||||
*/
|
||||
'overload_id': (string)[];
|
||||
/**
|
||||
* For references to constants, this may contain the value of the
|
||||
* constant if known at compile time.
|
||||
*/
|
||||
'value': (_google_api_expr_v1alpha1_Constant__Output | null);
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
// Original file: deps/googleapis/google/api/expr/v1alpha1/syntax.proto
|
||||
|
||||
import type { Expr as _google_api_expr_v1alpha1_Expr, Expr__Output as _google_api_expr_v1alpha1_Expr__Output } from '../../../../google/api/expr/v1alpha1/Expr';
|
||||
|
||||
/**
|
||||
* Source information collected at parse time.
|
||||
*/
|
||||
export interface SourceInfo {
|
||||
/**
|
||||
* The syntax version of the source, e.g. `cel1`.
|
||||
*/
|
||||
'syntax_version'?: (string);
|
||||
/**
|
||||
* The location name. All position information attached to an expression is
|
||||
* relative to this location.
|
||||
*
|
||||
* The location could be a file, UI element, or similar. For example,
|
||||
* `acme/app/AnvilPolicy.cel`.
|
||||
*/
|
||||
'location'?: (string);
|
||||
/**
|
||||
* Monotonically increasing list of character offsets where newlines appear.
|
||||
*
|
||||
* The line number of a given position is the index `i` where for a given
|
||||
* `id` the `line_offsets[i] < id_positions[id] < line_offsets[i+1]`. The
|
||||
* column may be derivd from `id_positions[id] - line_offsets[i]`.
|
||||
*/
|
||||
'line_offsets'?: (number)[];
|
||||
/**
|
||||
* A map from the parse node id (e.g. `Expr.id`) to the character offset
|
||||
* within source.
|
||||
*/
|
||||
'positions'?: ({[key: number]: number});
|
||||
/**
|
||||
* A map from the parse node id where a macro replacement was made to the
|
||||
* call `Expr` that resulted in a macro expansion.
|
||||
*
|
||||
* For example, `has(value.field)` is a function call that is replaced by a
|
||||
* `test_only` field selection in the AST. Likewise, the call
|
||||
* `list.exists(e, e > 10)` translates to a comprehension expression. The key
|
||||
* in the map corresponds to the expression id of the expanded macro, and the
|
||||
* value is the call `Expr` that was replaced.
|
||||
*/
|
||||
'macro_calls'?: ({[key: number]: _google_api_expr_v1alpha1_Expr});
|
||||
}
|
||||
|
||||
/**
|
||||
* Source information collected at parse time.
|
||||
*/
|
||||
export interface SourceInfo__Output {
|
||||
/**
|
||||
* The syntax version of the source, e.g. `cel1`.
|
||||
*/
|
||||
'syntax_version': (string);
|
||||
/**
|
||||
* The location name. All position information attached to an expression is
|
||||
* relative to this location.
|
||||
*
|
||||
* The location could be a file, UI element, or similar. For example,
|
||||
* `acme/app/AnvilPolicy.cel`.
|
||||
*/
|
||||
'location': (string);
|
||||
/**
|
||||
* Monotonically increasing list of character offsets where newlines appear.
|
||||
*
|
||||
* The line number of a given position is the index `i` where for a given
|
||||
* `id` the `line_offsets[i] < id_positions[id] < line_offsets[i+1]`. The
|
||||
* column may be derivd from `id_positions[id] - line_offsets[i]`.
|
||||
*/
|
||||
'line_offsets': (number)[];
|
||||
/**
|
||||
* A map from the parse node id (e.g. `Expr.id`) to the character offset
|
||||
* within source.
|
||||
*/
|
||||
'positions': ({[key: number]: number});
|
||||
/**
|
||||
* A map from the parse node id where a macro replacement was made to the
|
||||
* call `Expr` that resulted in a macro expansion.
|
||||
*
|
||||
* For example, `has(value.field)` is a function call that is replaced by a
|
||||
* `test_only` field selection in the AST. Likewise, the call
|
||||
* `list.exists(e, e > 10)` translates to a comprehension expression. The key
|
||||
* in the map corresponds to the expression id of the expanded macro, and the
|
||||
* value is the call `Expr` that was replaced.
|
||||
*/
|
||||
'macro_calls': ({[key: number]: _google_api_expr_v1alpha1_Expr__Output});
|
||||
}
|
50
packages/grpc-js-xds/src/generated/google/api/expr/v1alpha1/SourcePosition.ts
generated
Normal file
50
packages/grpc-js-xds/src/generated/google/api/expr/v1alpha1/SourcePosition.ts
generated
Normal file
|
@ -0,0 +1,50 @@
|
|||
// Original file: deps/googleapis/google/api/expr/v1alpha1/syntax.proto
|
||||
|
||||
|
||||
/**
|
||||
* A specific position in source.
|
||||
*/
|
||||
export interface SourcePosition {
|
||||
/**
|
||||
* The soucre location name (e.g. file name).
|
||||
*/
|
||||
'location'?: (string);
|
||||
/**
|
||||
* The character offset.
|
||||
*/
|
||||
'offset'?: (number);
|
||||
/**
|
||||
* The 1-based index of the starting line in the source text
|
||||
* where the issue occurs, or 0 if unknown.
|
||||
*/
|
||||
'line'?: (number);
|
||||
/**
|
||||
* The 0-based index of the starting position within the line of source text
|
||||
* where the issue occurs. Only meaningful if line is nonzero.
|
||||
*/
|
||||
'column'?: (number);
|
||||
}
|
||||
|
||||
/**
|
||||
* A specific position in source.
|
||||
*/
|
||||
export interface SourcePosition__Output {
|
||||
/**
|
||||
* The soucre location name (e.g. file name).
|
||||
*/
|
||||
'location': (string);
|
||||
/**
|
||||
* The character offset.
|
||||
*/
|
||||
'offset': (number);
|
||||
/**
|
||||
* The 1-based index of the starting line in the source text
|
||||
* where the issue occurs, or 0 if unknown.
|
||||
*/
|
||||
'line': (number);
|
||||
/**
|
||||
* The 0-based index of the starting position within the line of source text
|
||||
* where the issue occurs. Only meaningful if line is nonzero.
|
||||
*/
|
||||
'column': (number);
|
||||
}
|
|
@ -0,0 +1,416 @@
|
|||
// Original file: deps/googleapis/google/api/expr/v1alpha1/checked.proto
|
||||
|
||||
import type { Empty as _google_protobuf_Empty, Empty__Output as _google_protobuf_Empty__Output } from '../../../../google/protobuf/Empty';
|
||||
import type { NullValue as _google_protobuf_NullValue, NullValue__Output as _google_protobuf_NullValue__Output } from '../../../../google/protobuf/NullValue';
|
||||
import type { Type as _google_api_expr_v1alpha1_Type, Type__Output as _google_api_expr_v1alpha1_Type__Output } from '../../../../google/api/expr/v1alpha1/Type';
|
||||
|
||||
/**
|
||||
* Application defined abstract type.
|
||||
*/
|
||||
export interface _google_api_expr_v1alpha1_Type_AbstractType {
|
||||
/**
|
||||
* The fully qualified name of this abstract type.
|
||||
*/
|
||||
'name'?: (string);
|
||||
/**
|
||||
* Parameter types for this abstract type.
|
||||
*/
|
||||
'parameter_types'?: (_google_api_expr_v1alpha1_Type)[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Application defined abstract type.
|
||||
*/
|
||||
export interface _google_api_expr_v1alpha1_Type_AbstractType__Output {
|
||||
/**
|
||||
* The fully qualified name of this abstract type.
|
||||
*/
|
||||
'name': (string);
|
||||
/**
|
||||
* Parameter types for this abstract type.
|
||||
*/
|
||||
'parameter_types': (_google_api_expr_v1alpha1_Type__Output)[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Function type with result and arg types.
|
||||
*/
|
||||
export interface _google_api_expr_v1alpha1_Type_FunctionType {
|
||||
/**
|
||||
* Result type of the function.
|
||||
*/
|
||||
'result_type'?: (_google_api_expr_v1alpha1_Type | null);
|
||||
/**
|
||||
* Argument types of the function.
|
||||
*/
|
||||
'arg_types'?: (_google_api_expr_v1alpha1_Type)[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Function type with result and arg types.
|
||||
*/
|
||||
export interface _google_api_expr_v1alpha1_Type_FunctionType__Output {
|
||||
/**
|
||||
* Result type of the function.
|
||||
*/
|
||||
'result_type': (_google_api_expr_v1alpha1_Type__Output | null);
|
||||
/**
|
||||
* Argument types of the function.
|
||||
*/
|
||||
'arg_types': (_google_api_expr_v1alpha1_Type__Output)[];
|
||||
}
|
||||
|
||||
/**
|
||||
* List type with typed elements, e.g. `list<example.proto.MyMessage>`.
|
||||
*/
|
||||
export interface _google_api_expr_v1alpha1_Type_ListType {
|
||||
/**
|
||||
* The element type.
|
||||
*/
|
||||
'elem_type'?: (_google_api_expr_v1alpha1_Type | null);
|
||||
}
|
||||
|
||||
/**
|
||||
* List type with typed elements, e.g. `list<example.proto.MyMessage>`.
|
||||
*/
|
||||
export interface _google_api_expr_v1alpha1_Type_ListType__Output {
|
||||
/**
|
||||
* The element type.
|
||||
*/
|
||||
'elem_type': (_google_api_expr_v1alpha1_Type__Output | null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map type with parameterized key and value types, e.g. `map<string, int>`.
|
||||
*/
|
||||
export interface _google_api_expr_v1alpha1_Type_MapType {
|
||||
/**
|
||||
* The type of the key.
|
||||
*/
|
||||
'key_type'?: (_google_api_expr_v1alpha1_Type | null);
|
||||
/**
|
||||
* The type of the value.
|
||||
*/
|
||||
'value_type'?: (_google_api_expr_v1alpha1_Type | null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map type with parameterized key and value types, e.g. `map<string, int>`.
|
||||
*/
|
||||
export interface _google_api_expr_v1alpha1_Type_MapType__Output {
|
||||
/**
|
||||
* The type of the key.
|
||||
*/
|
||||
'key_type': (_google_api_expr_v1alpha1_Type__Output | null);
|
||||
/**
|
||||
* The type of the value.
|
||||
*/
|
||||
'value_type': (_google_api_expr_v1alpha1_Type__Output | null);
|
||||
}
|
||||
|
||||
// Original file: deps/googleapis/google/api/expr/v1alpha1/checked.proto
|
||||
|
||||
/**
|
||||
* CEL primitive types.
|
||||
*/
|
||||
export const _google_api_expr_v1alpha1_Type_PrimitiveType = {
|
||||
/**
|
||||
* Unspecified type.
|
||||
*/
|
||||
PRIMITIVE_TYPE_UNSPECIFIED: 'PRIMITIVE_TYPE_UNSPECIFIED',
|
||||
/**
|
||||
* Boolean type.
|
||||
*/
|
||||
BOOL: 'BOOL',
|
||||
/**
|
||||
* Int64 type.
|
||||
*
|
||||
* Proto-based integer values are widened to int64.
|
||||
*/
|
||||
INT64: 'INT64',
|
||||
/**
|
||||
* Uint64 type.
|
||||
*
|
||||
* Proto-based unsigned integer values are widened to uint64.
|
||||
*/
|
||||
UINT64: 'UINT64',
|
||||
/**
|
||||
* Double type.
|
||||
*
|
||||
* Proto-based float values are widened to double values.
|
||||
*/
|
||||
DOUBLE: 'DOUBLE',
|
||||
/**
|
||||
* String type.
|
||||
*/
|
||||
STRING: 'STRING',
|
||||
/**
|
||||
* Bytes type.
|
||||
*/
|
||||
BYTES: 'BYTES',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* CEL primitive types.
|
||||
*/
|
||||
export type _google_api_expr_v1alpha1_Type_PrimitiveType =
|
||||
/**
|
||||
* Unspecified type.
|
||||
*/
|
||||
| 'PRIMITIVE_TYPE_UNSPECIFIED'
|
||||
| 0
|
||||
/**
|
||||
* Boolean type.
|
||||
*/
|
||||
| 'BOOL'
|
||||
| 1
|
||||
/**
|
||||
* Int64 type.
|
||||
*
|
||||
* Proto-based integer values are widened to int64.
|
||||
*/
|
||||
| 'INT64'
|
||||
| 2
|
||||
/**
|
||||
* Uint64 type.
|
||||
*
|
||||
* Proto-based unsigned integer values are widened to uint64.
|
||||
*/
|
||||
| 'UINT64'
|
||||
| 3
|
||||
/**
|
||||
* Double type.
|
||||
*
|
||||
* Proto-based float values are widened to double values.
|
||||
*/
|
||||
| 'DOUBLE'
|
||||
| 4
|
||||
/**
|
||||
* String type.
|
||||
*/
|
||||
| 'STRING'
|
||||
| 5
|
||||
/**
|
||||
* Bytes type.
|
||||
*/
|
||||
| 'BYTES'
|
||||
| 6
|
||||
|
||||
/**
|
||||
* CEL primitive types.
|
||||
*/
|
||||
export type _google_api_expr_v1alpha1_Type_PrimitiveType__Output = typeof _google_api_expr_v1alpha1_Type_PrimitiveType[keyof typeof _google_api_expr_v1alpha1_Type_PrimitiveType]
|
||||
|
||||
// Original file: deps/googleapis/google/api/expr/v1alpha1/checked.proto
|
||||
|
||||
/**
|
||||
* Well-known protobuf types treated with first-class support in CEL.
|
||||
*/
|
||||
export const _google_api_expr_v1alpha1_Type_WellKnownType = {
|
||||
/**
|
||||
* Unspecified type.
|
||||
*/
|
||||
WELL_KNOWN_TYPE_UNSPECIFIED: 'WELL_KNOWN_TYPE_UNSPECIFIED',
|
||||
/**
|
||||
* Well-known protobuf.Any type.
|
||||
*
|
||||
* Any types are a polymorphic message type. During type-checking they are
|
||||
* treated like `DYN` types, but at runtime they are resolved to a specific
|
||||
* message type specified at evaluation time.
|
||||
*/
|
||||
ANY: 'ANY',
|
||||
/**
|
||||
* Well-known protobuf.Timestamp type, internally referenced as `timestamp`.
|
||||
*/
|
||||
TIMESTAMP: 'TIMESTAMP',
|
||||
/**
|
||||
* Well-known protobuf.Duration type, internally referenced as `duration`.
|
||||
*/
|
||||
DURATION: 'DURATION',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Well-known protobuf types treated with first-class support in CEL.
|
||||
*/
|
||||
export type _google_api_expr_v1alpha1_Type_WellKnownType =
|
||||
/**
|
||||
* Unspecified type.
|
||||
*/
|
||||
| 'WELL_KNOWN_TYPE_UNSPECIFIED'
|
||||
| 0
|
||||
/**
|
||||
* Well-known protobuf.Any type.
|
||||
*
|
||||
* Any types are a polymorphic message type. During type-checking they are
|
||||
* treated like `DYN` types, but at runtime they are resolved to a specific
|
||||
* message type specified at evaluation time.
|
||||
*/
|
||||
| 'ANY'
|
||||
| 1
|
||||
/**
|
||||
* Well-known protobuf.Timestamp type, internally referenced as `timestamp`.
|
||||
*/
|
||||
| 'TIMESTAMP'
|
||||
| 2
|
||||
/**
|
||||
* Well-known protobuf.Duration type, internally referenced as `duration`.
|
||||
*/
|
||||
| 'DURATION'
|
||||
| 3
|
||||
|
||||
/**
|
||||
* Well-known protobuf types treated with first-class support in CEL.
|
||||
*/
|
||||
export type _google_api_expr_v1alpha1_Type_WellKnownType__Output = typeof _google_api_expr_v1alpha1_Type_WellKnownType[keyof typeof _google_api_expr_v1alpha1_Type_WellKnownType]
|
||||
|
||||
/**
|
||||
* Represents a CEL type.
|
||||
*/
|
||||
export interface Type {
|
||||
/**
|
||||
* Dynamic type.
|
||||
*/
|
||||
'dyn'?: (_google_protobuf_Empty | null);
|
||||
/**
|
||||
* Null value.
|
||||
*/
|
||||
'null'?: (_google_protobuf_NullValue);
|
||||
/**
|
||||
* Primitive types: `true`, `1u`, `-2.0`, `'string'`, `b'bytes'`.
|
||||
*/
|
||||
'primitive'?: (_google_api_expr_v1alpha1_Type_PrimitiveType);
|
||||
/**
|
||||
* Wrapper of a primitive type, e.g. `google.protobuf.Int64Value`.
|
||||
*/
|
||||
'wrapper'?: (_google_api_expr_v1alpha1_Type_PrimitiveType);
|
||||
/**
|
||||
* Well-known protobuf type such as `google.protobuf.Timestamp`.
|
||||
*/
|
||||
'well_known'?: (_google_api_expr_v1alpha1_Type_WellKnownType);
|
||||
/**
|
||||
* Parameterized list with elements of `list_type`, e.g. `list<timestamp>`.
|
||||
*/
|
||||
'list_type'?: (_google_api_expr_v1alpha1_Type_ListType | null);
|
||||
/**
|
||||
* Parameterized map with typed keys and values.
|
||||
*/
|
||||
'map_type'?: (_google_api_expr_v1alpha1_Type_MapType | null);
|
||||
/**
|
||||
* Function type.
|
||||
*/
|
||||
'function'?: (_google_api_expr_v1alpha1_Type_FunctionType | null);
|
||||
/**
|
||||
* Protocol buffer message type.
|
||||
*
|
||||
* The `message_type` string specifies the qualified message type name. For
|
||||
* example, `google.plus.Profile`.
|
||||
*/
|
||||
'message_type'?: (string);
|
||||
/**
|
||||
* Type param type.
|
||||
*
|
||||
* The `type_param` string specifies the type parameter name, e.g. `list<E>`
|
||||
* would be a `list_type` whose element type was a `type_param` type
|
||||
* named `E`.
|
||||
*/
|
||||
'type_param'?: (string);
|
||||
/**
|
||||
* Type type.
|
||||
*
|
||||
* The `type` value specifies the target type. e.g. int is type with a
|
||||
* target type of `Primitive.INT`.
|
||||
*/
|
||||
'type'?: (_google_api_expr_v1alpha1_Type | null);
|
||||
/**
|
||||
* Error type.
|
||||
*
|
||||
* During type-checking if an expression is an error, its type is propagated
|
||||
* as the `ERROR` type. This permits the type-checker to discover other
|
||||
* errors present in the expression.
|
||||
*/
|
||||
'error'?: (_google_protobuf_Empty | null);
|
||||
/**
|
||||
* Abstract, application defined type.
|
||||
*/
|
||||
'abstract_type'?: (_google_api_expr_v1alpha1_Type_AbstractType | null);
|
||||
/**
|
||||
* The kind of type.
|
||||
*/
|
||||
'type_kind'?: "dyn"|"null"|"primitive"|"wrapper"|"well_known"|"list_type"|"map_type"|"function"|"message_type"|"type_param"|"type"|"error"|"abstract_type";
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a CEL type.
|
||||
*/
|
||||
export interface Type__Output {
|
||||
/**
|
||||
* Dynamic type.
|
||||
*/
|
||||
'dyn'?: (_google_protobuf_Empty__Output | null);
|
||||
/**
|
||||
* Null value.
|
||||
*/
|
||||
'null'?: (_google_protobuf_NullValue__Output);
|
||||
/**
|
||||
* Primitive types: `true`, `1u`, `-2.0`, `'string'`, `b'bytes'`.
|
||||
*/
|
||||
'primitive'?: (_google_api_expr_v1alpha1_Type_PrimitiveType__Output);
|
||||
/**
|
||||
* Wrapper of a primitive type, e.g. `google.protobuf.Int64Value`.
|
||||
*/
|
||||
'wrapper'?: (_google_api_expr_v1alpha1_Type_PrimitiveType__Output);
|
||||
/**
|
||||
* Well-known protobuf type such as `google.protobuf.Timestamp`.
|
||||
*/
|
||||
'well_known'?: (_google_api_expr_v1alpha1_Type_WellKnownType__Output);
|
||||
/**
|
||||
* Parameterized list with elements of `list_type`, e.g. `list<timestamp>`.
|
||||
*/
|
||||
'list_type'?: (_google_api_expr_v1alpha1_Type_ListType__Output | null);
|
||||
/**
|
||||
* Parameterized map with typed keys and values.
|
||||
*/
|
||||
'map_type'?: (_google_api_expr_v1alpha1_Type_MapType__Output | null);
|
||||
/**
|
||||
* Function type.
|
||||
*/
|
||||
'function'?: (_google_api_expr_v1alpha1_Type_FunctionType__Output | null);
|
||||
/**
|
||||
* Protocol buffer message type.
|
||||
*
|
||||
* The `message_type` string specifies the qualified message type name. For
|
||||
* example, `google.plus.Profile`.
|
||||
*/
|
||||
'message_type'?: (string);
|
||||
/**
|
||||
* Type param type.
|
||||
*
|
||||
* The `type_param` string specifies the type parameter name, e.g. `list<E>`
|
||||
* would be a `list_type` whose element type was a `type_param` type
|
||||
* named `E`.
|
||||
*/
|
||||
'type_param'?: (string);
|
||||
/**
|
||||
* Type type.
|
||||
*
|
||||
* The `type` value specifies the target type. e.g. int is type with a
|
||||
* target type of `Primitive.INT`.
|
||||
*/
|
||||
'type'?: (_google_api_expr_v1alpha1_Type__Output | null);
|
||||
/**
|
||||
* Error type.
|
||||
*
|
||||
* During type-checking if an expression is an error, its type is propagated
|
||||
* as the `ERROR` type. This permits the type-checker to discover other
|
||||
* errors present in the expression.
|
||||
*/
|
||||
'error'?: (_google_protobuf_Empty__Output | null);
|
||||
/**
|
||||
* Abstract, application defined type.
|
||||
*/
|
||||
'abstract_type'?: (_google_api_expr_v1alpha1_Type_AbstractType__Output | null);
|
||||
/**
|
||||
* The kind of type.
|
||||
*/
|
||||
'type_kind'?: "dyn"|"null"|"primitive"|"wrapper"|"well_known"|"list_type"|"map_type"|"function"|"message_type"|"type_param"|"type"|"error"|"abstract_type";
|
||||
}
|
|
@ -50,7 +50,6 @@ export interface FieldOptions {
|
|||
'weak'?: (boolean);
|
||||
'uninterpretedOption'?: (_google_protobuf_UninterpretedOption)[];
|
||||
'.validate.rules'?: (_validate_FieldRules | null);
|
||||
'.udpa.annotations.sensitive'?: (boolean);
|
||||
'.envoy.annotations.deprecated_at_minor_version'?: (string);
|
||||
'.udpa.annotations.field_migrate'?: (_udpa_annotations_FieldMigrateAnnotation | null);
|
||||
'.envoy.annotations.disallowed_by_default'?: (boolean);
|
||||
|
@ -66,7 +65,6 @@ export interface FieldOptions__Output {
|
|||
'weak': (boolean);
|
||||
'uninterpretedOption': (_google_protobuf_UninterpretedOption__Output)[];
|
||||
'.validate.rules': (_validate_FieldRules__Output | null);
|
||||
'.udpa.annotations.sensitive': (boolean);
|
||||
'.envoy.annotations.deprecated_at_minor_version': (string);
|
||||
'.udpa.annotations.field_migrate': (_udpa_annotations_FieldMigrateAnnotation__Output | null);
|
||||
'.envoy.annotations.disallowed_by_default': (boolean);
|
||||
|
|
|
@ -0,0 +1,265 @@
|
|||
import type * as grpc from '@grpc/grpc-js';
|
||||
import type { EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader';
|
||||
|
||||
|
||||
type SubtypeConstructor<Constructor extends new (...args: any) => any, Subtype> = {
|
||||
new(...args: ConstructorParameters<Constructor>): Subtype;
|
||||
};
|
||||
|
||||
export interface ProtoGrpcType {
|
||||
envoy: {
|
||||
annotations: {
|
||||
}
|
||||
config: {
|
||||
core: {
|
||||
v3: {
|
||||
Address: MessageTypeDefinition
|
||||
AsyncDataSource: MessageTypeDefinition
|
||||
BackoffStrategy: MessageTypeDefinition
|
||||
BindConfig: MessageTypeDefinition
|
||||
BuildVersion: MessageTypeDefinition
|
||||
CidrRange: MessageTypeDefinition
|
||||
ControlPlane: MessageTypeDefinition
|
||||
DataSource: MessageTypeDefinition
|
||||
EnvoyInternalAddress: MessageTypeDefinition
|
||||
Extension: MessageTypeDefinition
|
||||
ExtraSourceAddress: MessageTypeDefinition
|
||||
HeaderMap: MessageTypeDefinition
|
||||
HeaderValue: MessageTypeDefinition
|
||||
HeaderValueOption: MessageTypeDefinition
|
||||
HttpUri: MessageTypeDefinition
|
||||
KeyValue: MessageTypeDefinition
|
||||
KeyValueAppend: MessageTypeDefinition
|
||||
KeyValueMutation: MessageTypeDefinition
|
||||
Locality: MessageTypeDefinition
|
||||
Metadata: MessageTypeDefinition
|
||||
Node: MessageTypeDefinition
|
||||
Pipe: MessageTypeDefinition
|
||||
ProxyProtocolConfig: MessageTypeDefinition
|
||||
ProxyProtocolPassThroughTLVs: MessageTypeDefinition
|
||||
QueryParameter: MessageTypeDefinition
|
||||
RemoteDataSource: MessageTypeDefinition
|
||||
RequestMethod: EnumTypeDefinition
|
||||
RetryPolicy: MessageTypeDefinition
|
||||
RoutingPriority: EnumTypeDefinition
|
||||
RuntimeDouble: MessageTypeDefinition
|
||||
RuntimeFeatureFlag: MessageTypeDefinition
|
||||
RuntimeFractionalPercent: MessageTypeDefinition
|
||||
RuntimePercent: MessageTypeDefinition
|
||||
RuntimeUInt32: MessageTypeDefinition
|
||||
SocketAddress: MessageTypeDefinition
|
||||
SocketOption: MessageTypeDefinition
|
||||
SocketOptionsOverride: MessageTypeDefinition
|
||||
TcpKeepalive: MessageTypeDefinition
|
||||
TrafficDirection: EnumTypeDefinition
|
||||
TransportSocket: MessageTypeDefinition
|
||||
TypedExtensionConfig: MessageTypeDefinition
|
||||
WatchedDirectory: MessageTypeDefinition
|
||||
}
|
||||
}
|
||||
rbac: {
|
||||
v3: {
|
||||
Action: MessageTypeDefinition
|
||||
Permission: MessageTypeDefinition
|
||||
Policy: MessageTypeDefinition
|
||||
Principal: MessageTypeDefinition
|
||||
RBAC: MessageTypeDefinition
|
||||
}
|
||||
}
|
||||
route: {
|
||||
v3: {
|
||||
ClusterSpecifierPlugin: MessageTypeDefinition
|
||||
CorsPolicy: MessageTypeDefinition
|
||||
Decorator: MessageTypeDefinition
|
||||
DirectResponseAction: MessageTypeDefinition
|
||||
FilterAction: MessageTypeDefinition
|
||||
FilterConfig: MessageTypeDefinition
|
||||
HeaderMatcher: MessageTypeDefinition
|
||||
HedgePolicy: MessageTypeDefinition
|
||||
InternalRedirectPolicy: MessageTypeDefinition
|
||||
NonForwardingAction: MessageTypeDefinition
|
||||
QueryParameterMatcher: MessageTypeDefinition
|
||||
RateLimit: MessageTypeDefinition
|
||||
RedirectAction: MessageTypeDefinition
|
||||
RetryPolicy: MessageTypeDefinition
|
||||
Route: MessageTypeDefinition
|
||||
RouteAction: MessageTypeDefinition
|
||||
RouteList: MessageTypeDefinition
|
||||
RouteMatch: MessageTypeDefinition
|
||||
Tracing: MessageTypeDefinition
|
||||
VirtualCluster: MessageTypeDefinition
|
||||
VirtualHost: MessageTypeDefinition
|
||||
WeightedCluster: MessageTypeDefinition
|
||||
}
|
||||
}
|
||||
}
|
||||
extensions: {
|
||||
filters: {
|
||||
http: {
|
||||
rbac: {
|
||||
v3: {
|
||||
RBAC: MessageTypeDefinition
|
||||
RBACPerRoute: MessageTypeDefinition
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
type: {
|
||||
matcher: {
|
||||
v3: {
|
||||
DoubleMatcher: MessageTypeDefinition
|
||||
FilterStateMatcher: MessageTypeDefinition
|
||||
ListMatcher: MessageTypeDefinition
|
||||
ListStringMatcher: MessageTypeDefinition
|
||||
MetadataMatcher: MessageTypeDefinition
|
||||
OrMatcher: MessageTypeDefinition
|
||||
PathMatcher: MessageTypeDefinition
|
||||
RegexMatchAndSubstitute: MessageTypeDefinition
|
||||
RegexMatcher: MessageTypeDefinition
|
||||
StringMatcher: MessageTypeDefinition
|
||||
ValueMatcher: MessageTypeDefinition
|
||||
}
|
||||
}
|
||||
metadata: {
|
||||
v3: {
|
||||
MetadataKey: MessageTypeDefinition
|
||||
MetadataKind: MessageTypeDefinition
|
||||
}
|
||||
}
|
||||
tracing: {
|
||||
v3: {
|
||||
CustomTag: MessageTypeDefinition
|
||||
}
|
||||
}
|
||||
v3: {
|
||||
DoubleRange: MessageTypeDefinition
|
||||
FractionalPercent: MessageTypeDefinition
|
||||
Int32Range: MessageTypeDefinition
|
||||
Int64Range: MessageTypeDefinition
|
||||
Percent: MessageTypeDefinition
|
||||
SemanticVersion: MessageTypeDefinition
|
||||
}
|
||||
}
|
||||
}
|
||||
google: {
|
||||
api: {
|
||||
expr: {
|
||||
v1alpha1: {
|
||||
CheckedExpr: MessageTypeDefinition
|
||||
Constant: MessageTypeDefinition
|
||||
Decl: MessageTypeDefinition
|
||||
Expr: MessageTypeDefinition
|
||||
ParsedExpr: MessageTypeDefinition
|
||||
Reference: MessageTypeDefinition
|
||||
SourceInfo: MessageTypeDefinition
|
||||
SourcePosition: MessageTypeDefinition
|
||||
Type: MessageTypeDefinition
|
||||
}
|
||||
}
|
||||
}
|
||||
protobuf: {
|
||||
Any: MessageTypeDefinition
|
||||
BoolValue: MessageTypeDefinition
|
||||
BytesValue: MessageTypeDefinition
|
||||
DescriptorProto: MessageTypeDefinition
|
||||
DoubleValue: MessageTypeDefinition
|
||||
Duration: MessageTypeDefinition
|
||||
Empty: MessageTypeDefinition
|
||||
EnumDescriptorProto: MessageTypeDefinition
|
||||
EnumOptions: MessageTypeDefinition
|
||||
EnumValueDescriptorProto: MessageTypeDefinition
|
||||
EnumValueOptions: MessageTypeDefinition
|
||||
FieldDescriptorProto: MessageTypeDefinition
|
||||
FieldOptions: MessageTypeDefinition
|
||||
FileDescriptorProto: MessageTypeDefinition
|
||||
FileDescriptorSet: MessageTypeDefinition
|
||||
FileOptions: MessageTypeDefinition
|
||||
FloatValue: MessageTypeDefinition
|
||||
GeneratedCodeInfo: MessageTypeDefinition
|
||||
Int32Value: MessageTypeDefinition
|
||||
Int64Value: MessageTypeDefinition
|
||||
ListValue: MessageTypeDefinition
|
||||
MessageOptions: MessageTypeDefinition
|
||||
MethodDescriptorProto: MessageTypeDefinition
|
||||
MethodOptions: MessageTypeDefinition
|
||||
NullValue: EnumTypeDefinition
|
||||
OneofDescriptorProto: MessageTypeDefinition
|
||||
OneofOptions: MessageTypeDefinition
|
||||
ServiceDescriptorProto: MessageTypeDefinition
|
||||
ServiceOptions: MessageTypeDefinition
|
||||
SourceCodeInfo: MessageTypeDefinition
|
||||
StringValue: MessageTypeDefinition
|
||||
Struct: MessageTypeDefinition
|
||||
Timestamp: MessageTypeDefinition
|
||||
UInt32Value: MessageTypeDefinition
|
||||
UInt64Value: MessageTypeDefinition
|
||||
UninterpretedOption: MessageTypeDefinition
|
||||
Value: MessageTypeDefinition
|
||||
}
|
||||
}
|
||||
udpa: {
|
||||
annotations: {
|
||||
FieldMigrateAnnotation: MessageTypeDefinition
|
||||
FileMigrateAnnotation: MessageTypeDefinition
|
||||
MigrateAnnotation: MessageTypeDefinition
|
||||
PackageVersionStatus: EnumTypeDefinition
|
||||
StatusAnnotation: MessageTypeDefinition
|
||||
VersioningAnnotation: MessageTypeDefinition
|
||||
}
|
||||
}
|
||||
validate: {
|
||||
AnyRules: MessageTypeDefinition
|
||||
BoolRules: MessageTypeDefinition
|
||||
BytesRules: MessageTypeDefinition
|
||||
DoubleRules: MessageTypeDefinition
|
||||
DurationRules: MessageTypeDefinition
|
||||
EnumRules: MessageTypeDefinition
|
||||
FieldRules: MessageTypeDefinition
|
||||
Fixed32Rules: MessageTypeDefinition
|
||||
Fixed64Rules: MessageTypeDefinition
|
||||
FloatRules: MessageTypeDefinition
|
||||
Int32Rules: MessageTypeDefinition
|
||||
Int64Rules: MessageTypeDefinition
|
||||
KnownRegex: EnumTypeDefinition
|
||||
MapRules: MessageTypeDefinition
|
||||
MessageRules: MessageTypeDefinition
|
||||
RepeatedRules: MessageTypeDefinition
|
||||
SFixed32Rules: MessageTypeDefinition
|
||||
SFixed64Rules: MessageTypeDefinition
|
||||
SInt32Rules: MessageTypeDefinition
|
||||
SInt64Rules: MessageTypeDefinition
|
||||
StringRules: MessageTypeDefinition
|
||||
TimestampRules: MessageTypeDefinition
|
||||
UInt32Rules: MessageTypeDefinition
|
||||
UInt64Rules: MessageTypeDefinition
|
||||
}
|
||||
xds: {
|
||||
annotations: {
|
||||
v3: {
|
||||
FieldStatusAnnotation: MessageTypeDefinition
|
||||
FileStatusAnnotation: MessageTypeDefinition
|
||||
MessageStatusAnnotation: MessageTypeDefinition
|
||||
PackageVersionStatus: EnumTypeDefinition
|
||||
StatusAnnotation: MessageTypeDefinition
|
||||
}
|
||||
}
|
||||
core: {
|
||||
v3: {
|
||||
ContextParams: MessageTypeDefinition
|
||||
TypedExtensionConfig: MessageTypeDefinition
|
||||
}
|
||||
}
|
||||
type: {
|
||||
matcher: {
|
||||
v3: {
|
||||
ListStringMatcher: MessageTypeDefinition
|
||||
Matcher: MessageTypeDefinition
|
||||
RegexMatcher: MessageTypeDefinition
|
||||
StringMatcher: MessageTypeDefinition
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
// This is a non-public, unstable API, but it's very convenient
|
||||
import { loadProtosWithOptionsSync } from '@grpc/proto-loader/build/src/util';
|
||||
import { experimental, logVerbosity } from '@grpc/grpc-js';
|
||||
import { experimental, logVerbosity, ServerInterceptor } from '@grpc/grpc-js';
|
||||
import { Any__Output } from './generated/google/protobuf/Any';
|
||||
import Filter = experimental.Filter;
|
||||
import FilterFactory = experimental.FilterFactory;
|
||||
|
@ -64,7 +64,8 @@ export interface HttpFilterFactoryConstructor<FilterType extends Filter> {
|
|||
export interface HttpFilterRegistryEntry {
|
||||
parseTopLevelFilterConfig(encodedConfig: Any__Output): HttpFilterConfig | null;
|
||||
parseOverrideFilterConfig(encodedConfig: Any__Output): HttpFilterConfig | null;
|
||||
httpFilterConstructor: HttpFilterFactoryConstructor<Filter>;
|
||||
httpFilterConstructor?: HttpFilterFactoryConstructor<Filter> | undefined;
|
||||
createServerFilter?: ((config: HttpFilterConfig, overrideConfigMap: Map<string, HttpFilterConfig>) => ServerInterceptor) | undefined;
|
||||
}
|
||||
|
||||
const FILTER_REGISTRY = new Map<string, HttpFilterRegistryEntry>();
|
||||
|
@ -106,7 +107,7 @@ export function getTopLevelFilterUrl(encodedConfig: Any__Output): string {
|
|||
}
|
||||
}
|
||||
|
||||
export function validateTopLevelFilter(httpFilter: HttpFilter__Output): boolean {
|
||||
export function validateTopLevelFilter(httpFilter: HttpFilter__Output, client: boolean): boolean {
|
||||
if (!httpFilter.typed_config) {
|
||||
trace(httpFilter.name + ' validation failed: typed_config unset');
|
||||
return false;
|
||||
|
@ -121,6 +122,17 @@ export function validateTopLevelFilter(httpFilter: HttpFilter__Output): boolean
|
|||
}
|
||||
const registryEntry = FILTER_REGISTRY.get(typeUrl);
|
||||
if (registryEntry) {
|
||||
if (!httpFilter.is_optional) {
|
||||
if (client) {
|
||||
if (!registryEntry.httpFilterConstructor) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!registryEntry.createServerFilter) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
const parsedConfig = registryEntry.parseTopLevelFilterConfig(encodedConfig);
|
||||
if (parsedConfig === null) {
|
||||
trace(httpFilter.name + ' validation failed: config parsing failed');
|
||||
|
@ -185,7 +197,7 @@ export function validateOverrideFilter(encodedConfig: Any__Output): boolean {
|
|||
}
|
||||
}
|
||||
|
||||
export function parseTopLevelFilterConfig(encodedConfig: Any__Output) {
|
||||
export function parseTopLevelFilterConfig(encodedConfig: Any__Output, client: boolean) {
|
||||
let typeUrl: string;
|
||||
try {
|
||||
typeUrl = getTopLevelFilterUrl(encodedConfig);
|
||||
|
@ -194,6 +206,15 @@ export function parseTopLevelFilterConfig(encodedConfig: Any__Output) {
|
|||
}
|
||||
const registryEntry = FILTER_REGISTRY.get(typeUrl);
|
||||
if (registryEntry) {
|
||||
if (client) {
|
||||
if (!registryEntry.httpFilterConstructor) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
if (!registryEntry.createServerFilter) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return registryEntry.parseTopLevelFilterConfig(encodedConfig);
|
||||
} else {
|
||||
// Filter type URL not found in registry
|
||||
|
@ -236,11 +257,20 @@ export function parseOverrideFilterConfig(encodedConfig: Any__Output) {
|
|||
}
|
||||
}
|
||||
|
||||
export function createHttpFilter(config: HttpFilterConfig, overrideConfig?: HttpFilterConfig): FilterFactory<Filter> | null {
|
||||
export function createClientHttpFilter(config: HttpFilterConfig, overrideConfig?: HttpFilterConfig): FilterFactory<Filter> | null {
|
||||
const registryEntry = FILTER_REGISTRY.get(config.typeUrl);
|
||||
if (registryEntry) {
|
||||
if (registryEntry && registryEntry.httpFilterConstructor) {
|
||||
return new registryEntry.httpFilterConstructor(config, overrideConfig);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function createServerHttpFilter(config: HttpFilterConfig, overrideConfigMap: Map<string, HttpFilterConfig>): ServerInterceptor | null {
|
||||
const registryEntry = FILTER_REGISTRY.get(config.typeUrl);
|
||||
if (registryEntry && registryEntry.createServerFilter) {
|
||||
return registryEntry.createServerFilter(config, overrideConfigMap);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -213,8 +213,8 @@ function asyncTimeout(timeMs: number): Promise<void> {
|
|||
|
||||
/**
|
||||
* Returns true with probability numerator/denominator.
|
||||
* @param numerator
|
||||
* @param denominator
|
||||
* @param numerator
|
||||
* @param denominator
|
||||
*/
|
||||
function rollRandomPercentage(numerator: number, denominator: number): boolean {
|
||||
return Math.random() * denominator < numerator;
|
||||
|
@ -344,4 +344,4 @@ export function setup() {
|
|||
parseOverrideFilterConfig: parseHTTPFaultConfig,
|
||||
httpFilterConstructor: FaultInjectionFilterFactory
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,175 @@
|
|||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
// This is a non-public, unstable API, but it's very convenient
|
||||
import { loadProtosWithOptionsSync } from '@grpc/proto-loader/build/src/util';
|
||||
import { experimental, logVerbosity, ServerInterceptingCall, ServerInterceptor, ServerListener, status } from '@grpc/grpc-js';
|
||||
import { Any__Output } from '../generated/google/protobuf/Any';
|
||||
import { HttpFilterConfig, registerHttpFilter } from '../http-filter';
|
||||
import { RbacPolicyGroup, UnifiedInfo as UnifiedRbacInfo } from '../rbac';
|
||||
import { RBAC__Output } from '../generated/envoy/extensions/filters/http/rbac/v3/RBAC';
|
||||
import { RBACPerRoute__Output } from '../generated/envoy/extensions/filters/http/rbac/v3/RBACPerRoute';
|
||||
import { parseConfig as parseRbacConfig } from '../rbac';
|
||||
import { EXPERIMENTAL_RBAC } from '../environment';
|
||||
|
||||
const TRACER_NAME = 'rbac_filter';
|
||||
|
||||
function trace(text: string): void {
|
||||
experimental.trace(logVerbosity.DEBUG, TRACER_NAME, text);
|
||||
}
|
||||
|
||||
const resourceRoot = loadProtosWithOptionsSync([
|
||||
'envoy/extensions/filters/http/rbac/v3/rbac.proto'], {
|
||||
keepCase: true,
|
||||
includeDirs: [
|
||||
// Paths are relative to src/build/http-filter
|
||||
__dirname + '/../../../deps/xds/',
|
||||
__dirname + '/../../../deps/envoy-api/',
|
||||
__dirname + '/../../../deps/protoc-gen-validate/',
|
||||
__dirname + '/../../../deps/googleapis/'
|
||||
],
|
||||
}
|
||||
);
|
||||
|
||||
const RBAC_FILTER_URL = 'type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC';
|
||||
const RBAC_FILTER_OVERRIDE_URL ='type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBACPerRoute';
|
||||
|
||||
const toObjectOptions = {
|
||||
longs: String,
|
||||
enums: String,
|
||||
defaults: true,
|
||||
oneofs: true
|
||||
}
|
||||
|
||||
function parseAnyMessage<MessageType>(message: Any__Output): MessageType | null {
|
||||
const typeName = message.type_url.substring(message.type_url.lastIndexOf('/') + 1);
|
||||
const messageType = resourceRoot.lookup(typeName);
|
||||
if (messageType) {
|
||||
const decodedMessage = (messageType as any).decode(message.value);
|
||||
return decodedMessage.$type.toObject(decodedMessage, toObjectOptions) as MessageType;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
interface RbacFilterConfig extends HttpFilterConfig {
|
||||
typeUrl: 'type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC';
|
||||
config: RbacPolicyGroup;
|
||||
}
|
||||
|
||||
function parseTopLevelRbacConfig(encodedConfig: Any__Output): RbacFilterConfig | null {
|
||||
if (encodedConfig.type_url !== RBAC_FILTER_URL) {
|
||||
trace('Config parsing failed: unexpected type URL: ' + encodedConfig.type_url);
|
||||
return null;
|
||||
}
|
||||
const parsedMessage = parseAnyMessage<RBAC__Output>(encodedConfig);
|
||||
if (parsedMessage === null) {
|
||||
trace('Config parsing failed: failed to parse RBAC message');
|
||||
return null;
|
||||
}
|
||||
trace('Parsing RBAC message ' + JSON.stringify(parsedMessage, undefined, 2));
|
||||
if (!parsedMessage.rules) {
|
||||
trace('Config parsing failed: no rules found');
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return {
|
||||
typeUrl: RBAC_FILTER_URL,
|
||||
config: parseRbacConfig(parsedMessage.rules)
|
||||
};
|
||||
} catch (e) {
|
||||
trace('Config parsing failed: ' + (e as Error).message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function parseOverrideRbacConfig(encodedConfig: Any__Output): RbacFilterConfig | null {
|
||||
if (encodedConfig.type_url !== RBAC_FILTER_OVERRIDE_URL) {
|
||||
trace('Config parsing failed: unexpected type URL: ' + encodedConfig.type_url);
|
||||
return null;
|
||||
}
|
||||
const parsedMessage = parseAnyMessage<RBACPerRoute__Output>(encodedConfig);
|
||||
if (parsedMessage === null) {
|
||||
trace('Config parsing failed: failed to parse RBACPerRoute message');
|
||||
return null;
|
||||
}
|
||||
trace('Parsing RBAC message ' + JSON.stringify(parsedMessage, undefined, 2));
|
||||
if (!parsedMessage.rbac?.rules) {
|
||||
trace('Config parsing failed: no rules found');
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return {
|
||||
typeUrl: RBAC_FILTER_URL,
|
||||
config: parseRbacConfig(parsedMessage.rbac.rules)
|
||||
};
|
||||
} catch (e) {
|
||||
trace('Config parsing failed: ' + (e as Error).message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function createRbacServerFilter(config: HttpFilterConfig, overrideConfigMap: Map<string, HttpFilterConfig>): ServerInterceptor {
|
||||
return function rbacServerFilter(methodDescriptor, call): ServerInterceptingCall {
|
||||
const listener: ServerListener = {
|
||||
onReceiveMetadata: (metadata, next) => {
|
||||
let activeConfig = config;
|
||||
const routeName = metadata.get('grpc-route')[0];
|
||||
if (routeName) {
|
||||
const overrideConfig = overrideConfigMap.get(routeName as string);
|
||||
if (overrideConfig) {
|
||||
activeConfig = overrideConfig;
|
||||
}
|
||||
}
|
||||
const rbacMetadata = metadata.clone();
|
||||
rbacMetadata.set(':method', 'POST');
|
||||
rbacMetadata.set(':authority', call.getHost());
|
||||
rbacMetadata.set(':path', methodDescriptor.path);
|
||||
const connectionInfo = call.getConnectionInfo();
|
||||
const authContext = call.getAuthContext();
|
||||
const info: UnifiedRbacInfo = {
|
||||
destinationIp: connectionInfo.localAddress!,
|
||||
destinationPort: connectionInfo.localPort!,
|
||||
sourceIp: connectionInfo.remoteAddress!,
|
||||
headers: rbacMetadata,
|
||||
tls: authContext.transportSecurityType !== undefined,
|
||||
peerCertificate: authContext.sslPeerCertificate ?? null,
|
||||
urlPath: methodDescriptor.path
|
||||
};
|
||||
if ((activeConfig as RbacFilterConfig).config.apply(info)) {
|
||||
next(metadata);
|
||||
} else {
|
||||
call.sendStatus({code: status.PERMISSION_DENIED, details: 'Unauthorized RPC rejected'});
|
||||
}
|
||||
}
|
||||
};
|
||||
return new ServerInterceptingCall(call, {
|
||||
start: next => {
|
||||
next(listener);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function setup() {
|
||||
if (EXPERIMENTAL_RBAC) {
|
||||
registerHttpFilter(RBAC_FILTER_URL, {
|
||||
parseTopLevelFilterConfig: parseTopLevelRbacConfig,
|
||||
parseOverrideFilterConfig: parseOverrideRbacConfig,
|
||||
createServerFilter: createRbacServerFilter
|
||||
});
|
||||
}
|
||||
}
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { experimental } from '@grpc/grpc-js';
|
||||
import { experimental, ServerInterceptingCall, ServerInterceptor } from '@grpc/grpc-js';
|
||||
import { Any__Output } from '../generated/google/protobuf/Any';
|
||||
import { HttpFilterConfig, registerHttpFilter } from '../http-filter';
|
||||
import Filter = experimental.Filter;
|
||||
|
@ -31,6 +31,21 @@ class RouterFilterFactory implements FilterFactory<RouterFilter> {
|
|||
}
|
||||
}
|
||||
|
||||
function createServerHttpFilter(config: HttpFilterConfig, overrideConfigMap: Map<string, HttpFilterConfig>): ServerInterceptor {
|
||||
return (methodDescriptor, call) => {
|
||||
return new ServerInterceptingCall(call, {
|
||||
start: next => {
|
||||
next({
|
||||
onReceiveMetadata: (metadata, next) => {
|
||||
metadata.remove('grpc-route');
|
||||
next(metadata);
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
const ROUTER_FILTER_URL = 'type.googleapis.com/envoy.extensions.filters.http.router.v3.Router';
|
||||
|
||||
function parseConfig(encodedConfig: Any__Output): HttpFilterConfig | null {
|
||||
|
@ -44,6 +59,7 @@ export function setup() {
|
|||
registerHttpFilter(ROUTER_FILTER_URL, {
|
||||
parseTopLevelFilterConfig: parseConfig,
|
||||
parseOverrideFilterConfig: parseConfig,
|
||||
httpFilterConstructor: RouterFilterFactory
|
||||
httpFilterConstructor: RouterFilterFactory,
|
||||
createServerFilter: createServerHttpFilter
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import * as xds_wrr_locality from './load-balancer-xds-wrr-locality';
|
|||
import * as ring_hash from './load-balancer-ring-hash';
|
||||
import * as router_filter from './http-filter/router-filter';
|
||||
import * as fault_injection_filter from './http-filter/fault-injection-filter';
|
||||
import * as rbac_filter from './http-filter/rbac-filter';
|
||||
import * as csds from './csds';
|
||||
import * as round_robin_lb from './lb-policy-registry/round-robin';
|
||||
import * as typed_struct_lb from './lb-policy-registry/typed-struct';
|
||||
|
@ -53,6 +54,7 @@ export function register() {
|
|||
ring_hash.setup();
|
||||
router_filter.setup();
|
||||
fault_injection_filter.setup();
|
||||
rbac_filter.setup();
|
||||
csds.setup();
|
||||
round_robin_lb.setup();
|
||||
typed_struct_lb.setup();
|
||||
|
|
|
@ -26,6 +26,8 @@ import ChannelControlHelper = experimental.ChannelControlHelper;
|
|||
import registerLoadBalancerType = experimental.registerLoadBalancerType;
|
||||
import TypedLoadBalancingConfig = experimental.TypedLoadBalancingConfig;
|
||||
import parseLoadBalancingConfig = experimental.parseLoadBalancingConfig;
|
||||
import StatusOr = experimental.StatusOr;
|
||||
import statusOrFromValue = experimental.statusOrFromValue;
|
||||
import { XdsConfig } from './xds-dependency-manager';
|
||||
import { LocalityEndpoint, PriorityChildRaw } from './load-balancer-priority';
|
||||
import { Locality__Output } from './generated/envoy/config/core/v3/Locality';
|
||||
|
@ -205,7 +207,7 @@ function getLeafClusters(xdsConfig: XdsConfig, rootCluster: string, depth = 0):
|
|||
if (!maybeClusterConfig) {
|
||||
return [];
|
||||
}
|
||||
if (!maybeClusterConfig.success) {
|
||||
if (!maybeClusterConfig.ok) {
|
||||
return [rootCluster];
|
||||
}
|
||||
if (maybeClusterConfig.value.children.type === 'aggregate') {
|
||||
|
@ -240,13 +242,14 @@ export class CdsLoadBalancer implements LoadBalancer {
|
|||
}
|
||||
|
||||
updateAddressList(
|
||||
endpointList: Endpoint[],
|
||||
endpointList: StatusOr<Endpoint[]>,
|
||||
lbConfig: TypedLoadBalancingConfig,
|
||||
options: ChannelOptions
|
||||
): void {
|
||||
options: ChannelOptions,
|
||||
resolutionNote: string
|
||||
): boolean {
|
||||
if (!(lbConfig instanceof CdsLoadBalancingConfig)) {
|
||||
trace('Discarding address list update with unrecognized config ' + JSON.stringify(lbConfig, undefined, 2));
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
trace('Received update with config ' + JSON.stringify(lbConfig, undefined, 2));
|
||||
const xdsConfig = options[XDS_CONFIG_KEY] as XdsConfig;
|
||||
|
@ -254,12 +257,12 @@ export class CdsLoadBalancer implements LoadBalancer {
|
|||
const maybeClusterConfig = xdsConfig.clusters.get(clusterName);
|
||||
if (!maybeClusterConfig) {
|
||||
trace('Received update with no config for cluster ' + clusterName);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if (!maybeClusterConfig.success) {
|
||||
if (!maybeClusterConfig.ok) {
|
||||
this.childBalancer.destroy();
|
||||
this.channelControlHelper.updateState(connectivityState.TRANSIENT_FAILURE, new UnavailablePicker(maybeClusterConfig.error), maybeClusterConfig.error.details);
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
const clusterConfig = maybeClusterConfig.value;
|
||||
|
||||
|
@ -270,8 +273,8 @@ export class CdsLoadBalancer implements LoadBalancer {
|
|||
} catch (e) {
|
||||
trace('xDS config parsing failed with error ' + (e as Error).message);
|
||||
const errorMessage = `xDS config parsing failed with error ${(e as Error).message}`;
|
||||
this.channelControlHelper.updateState(connectivityState.TRANSIENT_FAILURE, new UnavailablePicker({code: status.UNAVAILABLE, details: errorMessage}), errorMessage);
|
||||
return;
|
||||
this.channelControlHelper.updateState(connectivityState.TRANSIENT_FAILURE, new UnavailablePicker({code: status.UNAVAILABLE, details: `${errorMessage} Resolution note: ${resolutionNote}`}), errorMessage);
|
||||
return true;
|
||||
}
|
||||
const priorityChildren: {[name: string]: PriorityChildRaw} = {};
|
||||
for (const cluster of leafClusters) {
|
||||
|
@ -296,16 +299,16 @@ export class CdsLoadBalancer implements LoadBalancer {
|
|||
} catch (e) {
|
||||
trace('LB policy config parsing failed with error ' + (e as Error).message);
|
||||
const errorMessage = `LB policy config parsing failed with error ${(e as Error).message}`;
|
||||
this.channelControlHelper.updateState(connectivityState.TRANSIENT_FAILURE, new UnavailablePicker({code: status.UNAVAILABLE, details: errorMessage}), errorMessage);
|
||||
return;
|
||||
this.channelControlHelper.updateState(connectivityState.TRANSIENT_FAILURE, new UnavailablePicker({code: status.UNAVAILABLE, details: `${errorMessage} Resolution note: ${resolutionNote}`}), errorMessage);
|
||||
return true;
|
||||
}
|
||||
this.childBalancer.updateAddressList(endpointList, typedChildConfig, {...options, [ROOT_CLUSTER_KEY]: clusterName});
|
||||
this.childBalancer.updateAddressList(endpointList, typedChildConfig, {...options, [ROOT_CLUSTER_KEY]: clusterName}, resolutionNote);
|
||||
} else {
|
||||
if (!clusterConfig.children.endpoints) {
|
||||
trace('Received update with no resolved endpoints for cluster ' + clusterName);
|
||||
const errorMessage = `Cluster ${clusterName} resolution failed: ${clusterConfig.children.resolutionNote}`;
|
||||
this.channelControlHelper.updateState(connectivityState.TRANSIENT_FAILURE, new UnavailablePicker({code: status.UNAVAILABLE, details: errorMessage}), errorMessage);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
const newPriorityNames: string[] = [];
|
||||
const newLocalityPriorities = new Map<string, number>();
|
||||
|
@ -317,7 +320,7 @@ export class CdsLoadBalancer implements LoadBalancer {
|
|||
if (AGGREGATE_CLUSTER_BACKWARDS_COMPAT) {
|
||||
if (typeof options[ROOT_CLUSTER_KEY] === 'string') {
|
||||
const maybeRootClusterConfig = xdsConfig.clusters.get(options[ROOT_CLUSTER_KEY]);
|
||||
if (maybeRootClusterConfig?.success) {
|
||||
if (maybeRootClusterConfig?.ok) {
|
||||
endpointPickingPolicy = maybeRootClusterConfig.value.cluster.lbPolicyConfig;
|
||||
}
|
||||
}
|
||||
|
@ -409,9 +412,9 @@ export class CdsLoadBalancer implements LoadBalancer {
|
|||
typedChildConfig = parseLoadBalancingConfig(childConfig);
|
||||
} catch (e) {
|
||||
trace('LB policy config parsing failed with error ' + (e as Error).message);
|
||||
const errorMessage = `LB policy config parsing failed with error ${(e as Error).message}`;
|
||||
const errorMessage = `LB policy config parsing failed with error ${(e as Error).message}. Resolution note: ${resolutionNote}`;
|
||||
this.channelControlHelper.updateState(connectivityState.TRANSIENT_FAILURE, new UnavailablePicker({code: status.UNAVAILABLE, details: errorMessage}), errorMessage);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
const childOptions: ChannelOptions = {...options};
|
||||
if (clusterConfig.cluster.securityUpdate) {
|
||||
|
@ -419,16 +422,16 @@ export class CdsLoadBalancer implements LoadBalancer {
|
|||
const xdsClient = options[XDS_CLIENT_KEY] as XdsClient;
|
||||
const caCertProvider = xdsClient.getCertificateProvider(securityUpdate.caCertificateProviderInstance);
|
||||
if (!caCertProvider) {
|
||||
const errorMessage = `Cluster ${clusterName} configured with CA certificate provider ${securityUpdate.caCertificateProviderInstance} not in bootstrap`;
|
||||
const errorMessage = `Cluster ${clusterName} configured with CA certificate provider ${securityUpdate.caCertificateProviderInstance} not in bootstrap. Resolution note: ${resolutionNote}`;
|
||||
this.channelControlHelper.updateState(connectivityState.TRANSIENT_FAILURE, new UnavailablePicker({code: status.UNAVAILABLE, details: errorMessage}), errorMessage);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if (securityUpdate.identityCertificateProviderInstance) {
|
||||
const identityCertProvider = xdsClient.getCertificateProvider(securityUpdate.identityCertificateProviderInstance);
|
||||
if (!identityCertProvider) {
|
||||
const errorMessage = `Cluster ${clusterName} configured with identity certificate provider ${securityUpdate.identityCertificateProviderInstance} not in bootstrap`;
|
||||
const errorMessage = `Cluster ${clusterName} configured with identity certificate provider ${securityUpdate.identityCertificateProviderInstance} not in bootstrap. Resolution note: ${resolutionNote}`;
|
||||
this.channelControlHelper.updateState(connectivityState.TRANSIENT_FAILURE, new UnavailablePicker({code: status.UNAVAILABLE, details: errorMessage}), errorMessage);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
childOptions[IDENTITY_CERT_PROVIDER_KEY] = identityCertProvider;
|
||||
}
|
||||
|
@ -440,8 +443,9 @@ export class CdsLoadBalancer implements LoadBalancer {
|
|||
trace('Configured subject alternative name matcher: ' + sanMatcher);
|
||||
childOptions[SAN_MATCHER_KEY] = this.latestSanMatcher;
|
||||
}
|
||||
this.childBalancer.updateAddressList(childEndpointList, typedChildConfig, childOptions);
|
||||
this.childBalancer.updateAddressList(statusOrFromValue(childEndpointList), typedChildConfig, childOptions, resolutionNote);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
exitIdle(): void {
|
||||
this.childBalancer.exitIdle();
|
||||
|
|
|
@ -27,6 +27,8 @@ import QueuePicker = experimental.QueuePicker;
|
|||
import UnavailablePicker = experimental.UnavailablePicker;
|
||||
import ChildLoadBalancerHandler = experimental.ChildLoadBalancerHandler;
|
||||
import selectLbConfigFromList = experimental.selectLbConfigFromList;
|
||||
import StatusOr = experimental.StatusOr;
|
||||
import statusOrFromValue = experimental.statusOrFromValue;
|
||||
import { Locality__Output } from './generated/envoy/config/core/v3/Locality';
|
||||
|
||||
const TRACER_NAME = 'priority';
|
||||
|
@ -155,9 +157,10 @@ class PriorityLoadBalancingConfig implements TypedLoadBalancingConfig {
|
|||
|
||||
interface PriorityChildBalancer {
|
||||
updateAddressList(
|
||||
endpointList: Endpoint[],
|
||||
endpointList: StatusOr<Endpoint[]>,
|
||||
lbConfig: TypedLoadBalancingConfig,
|
||||
attributes: { [key: string]: unknown }
|
||||
attributes: { [key: string]: unknown },
|
||||
resolutionNote: string
|
||||
): void;
|
||||
exitIdle(): void;
|
||||
resetBackoff(): void;
|
||||
|
@ -240,11 +243,12 @@ export class PriorityLoadBalancer implements LoadBalancer {
|
|||
}
|
||||
|
||||
updateAddressList(
|
||||
endpointList: Endpoint[],
|
||||
endpointList: StatusOr<Endpoint[]>,
|
||||
lbConfig: TypedLoadBalancingConfig,
|
||||
attributes: { [key: string]: unknown }
|
||||
attributes: { [key: string]: unknown },
|
||||
resolutionNote: string
|
||||
): void {
|
||||
this.childBalancer.updateAddressList(endpointList, lbConfig, attributes);
|
||||
this.childBalancer.updateAddressList(endpointList, lbConfig, attributes, resolutionNote);
|
||||
}
|
||||
|
||||
exitIdle() {
|
||||
|
@ -332,6 +336,8 @@ export class PriorityLoadBalancer implements LoadBalancer {
|
|||
|
||||
private updatesPaused = false;
|
||||
|
||||
private latestResolutionNote: string = '';
|
||||
|
||||
constructor(private channelControlHelper: ChannelControlHelper) {}
|
||||
|
||||
private updateState(state: ConnectivityState, picker: Picker, errorMessage: string | null) {
|
||||
|
@ -401,9 +407,10 @@ export class PriorityLoadBalancer implements LoadBalancer {
|
|||
child = new this.PriorityChildImpl(this, childName, childUpdate.ignoreReresolutionRequests);
|
||||
this.children.set(childName, child);
|
||||
child.updateAddressList(
|
||||
childUpdate.subchannelAddress,
|
||||
statusOrFromValue(childUpdate.subchannelAddress),
|
||||
childUpdate.lbConfig,
|
||||
this.latestOptions
|
||||
this.latestOptions,
|
||||
this.latestResolutionNote
|
||||
);
|
||||
} else {
|
||||
/* We're going to try to use this child, so reactivate it if it has been
|
||||
|
@ -440,14 +447,21 @@ export class PriorityLoadBalancer implements LoadBalancer {
|
|||
}
|
||||
|
||||
updateAddressList(
|
||||
endpointList: Endpoint[],
|
||||
endpointList: StatusOr<Endpoint[]>,
|
||||
lbConfig: TypedLoadBalancingConfig,
|
||||
options: ChannelOptions
|
||||
): void {
|
||||
options: ChannelOptions,
|
||||
resolutionNote: string
|
||||
): boolean {
|
||||
if (!(lbConfig instanceof PriorityLoadBalancingConfig)) {
|
||||
// Reject a config of the wrong type
|
||||
trace('Discarding address list update with unrecognized config ' + JSON.stringify(lbConfig.toJsonObject(), undefined, 2));
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if (!endpointList.ok) {
|
||||
if (this.latestUpdates.size === 0) {
|
||||
this.updateState(ConnectivityState.TRANSIENT_FAILURE, new UnavailablePicker(endpointList.error), endpointList.error.details);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/* For each address, the first element of its localityPath array determines
|
||||
* which child it belongs to. So we bucket those addresses by that first
|
||||
|
@ -457,14 +471,14 @@ export class PriorityLoadBalancer implements LoadBalancer {
|
|||
string,
|
||||
LocalityEndpoint[]
|
||||
>();
|
||||
for (const endpoint of endpointList) {
|
||||
for (const endpoint of endpointList.value) {
|
||||
if (!isLocalityEndpoint(endpoint)) {
|
||||
// Reject address that cannot be prioritized
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if (endpoint.localityPath.length < 1) {
|
||||
// Reject address that cannot be prioritized
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
const childName = endpoint.localityPath[0];
|
||||
const childAddress: LocalityEndpoint = {
|
||||
|
@ -495,9 +509,10 @@ export class PriorityLoadBalancer implements LoadBalancer {
|
|||
const existingChild = this.children.get(childName);
|
||||
if (existingChild !== undefined) {
|
||||
existingChild.updateAddressList(
|
||||
childAddresses,
|
||||
statusOrFromValue(childAddresses),
|
||||
childConfig.config,
|
||||
options
|
||||
options,
|
||||
resolutionNote
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -509,7 +524,9 @@ export class PriorityLoadBalancer implements LoadBalancer {
|
|||
}
|
||||
}
|
||||
this.updatesPaused = false;
|
||||
this.latestResolutionNote = resolutionNote;
|
||||
this.choosePriority();
|
||||
return true;
|
||||
}
|
||||
exitIdle(): void {
|
||||
if (this.currentPriority !== null) {
|
||||
|
|
|
@ -31,6 +31,7 @@ import UnavailablePicker = experimental.UnavailablePicker;
|
|||
import subchannelAddressToString = experimental.subchannelAddressToString;
|
||||
import registerLoadBalancerType = experimental.registerLoadBalancerType;
|
||||
import EndpointMap = experimental.EndpointMap;
|
||||
import StatusOr = experimental.StatusOr;
|
||||
import { loadXxhashApi, xxhashApi } from './xxhash';
|
||||
import { EXPERIMENTAL_RING_HASH } from './environment';
|
||||
import { loadProtosWithOptionsSync } from '@grpc/proto-loader/build/src/util';
|
||||
|
@ -401,26 +402,44 @@ class RingHashLoadBalancer implements LoadBalancer {
|
|||
}
|
||||
|
||||
updateAddressList(
|
||||
endpointList: Endpoint[],
|
||||
endpointList: StatusOr<Endpoint[]>,
|
||||
lbConfig: TypedLoadBalancingConfig,
|
||||
options: ChannelOptions
|
||||
): void {
|
||||
options: ChannelOptions,
|
||||
resolutionNote: string
|
||||
): boolean {
|
||||
if (!(lbConfig instanceof RingHashLoadBalancingConfig)) {
|
||||
trace('Discarding address update with unrecognized config ' + JSON.stringify(lbConfig.toJsonObject(), undefined, 2));
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if (!endpointList.ok) {
|
||||
if (this.ring.length === 0) {
|
||||
this.updateState(connectivityState.TRANSIENT_FAILURE, new UnavailablePicker(endpointList.error), endpointList.error.details);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (endpointList.value.length === 0) {
|
||||
for (const ringEntry of this.ring) {
|
||||
ringEntry.leafBalancer.destroy();
|
||||
}
|
||||
this.ring = [];
|
||||
this.leafMap.clear();
|
||||
this.leafWeightMap.clear();
|
||||
const errorMessage = `No addresses resolved. Resolution note: ${resolutionNote}`;
|
||||
this.updateState(connectivityState.TRANSIENT_FAILURE, new UnavailablePicker({code: status.UNAVAILABLE, details: errorMessage}), errorMessage);
|
||||
return false;
|
||||
}
|
||||
trace('Received update with config ' + JSON.stringify(lbConfig.toJsonObject(), undefined, 2));
|
||||
this.updatesPaused = true;
|
||||
this.leafWeightMap.clear();
|
||||
const dedupedEndpointList: Endpoint[] = [];
|
||||
for (const endpoint of endpointList) {
|
||||
for (const endpoint of endpointList.value) {
|
||||
const leafBalancer = this.leafMap.get(endpoint);
|
||||
if (leafBalancer) {
|
||||
leafBalancer.updateEndpoint(endpoint, options);
|
||||
} else {
|
||||
this.leafMap.set(
|
||||
endpoint,
|
||||
new LeafLoadBalancer(endpoint, this.childChannelControlHelper, options)
|
||||
new LeafLoadBalancer(endpoint, this.childChannelControlHelper, options, resolutionNote)
|
||||
);
|
||||
}
|
||||
const weight = this.leafWeightMap.get(endpoint);
|
||||
|
@ -429,7 +448,7 @@ class RingHashLoadBalancer implements LoadBalancer {
|
|||
}
|
||||
this.leafWeightMap.set(endpoint, (weight ?? 0) + (isLocalityEndpoint(endpoint) ? endpoint.endpointWeight : 1));
|
||||
}
|
||||
const removedLeaves = this.leafMap.deleteMissing(endpointList);
|
||||
const removedLeaves = this.leafMap.deleteMissing(endpointList.value);
|
||||
for (const leaf of removedLeaves) {
|
||||
leaf.destroy();
|
||||
}
|
||||
|
@ -440,6 +459,7 @@ class RingHashLoadBalancer implements LoadBalancer {
|
|||
this.calculateAndUpdateState();
|
||||
this.maybeProactivelyConnect();
|
||||
});
|
||||
return true;
|
||||
}
|
||||
exitIdle(): void {
|
||||
/* This operation does not make sense here. We don't want to make the whole
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
*
|
||||
*/
|
||||
|
||||
import { connectivityState as ConnectivityState, status as Status, Metadata, logVerbosity, experimental, LoadBalancingConfig, ChannelOptions } from "@grpc/grpc-js";
|
||||
import { connectivityState as ConnectivityState, status as Status, Metadata, logVerbosity, experimental, LoadBalancingConfig, ChannelOptions, connectivityState, status } from "@grpc/grpc-js";
|
||||
import { isLocalityEndpoint, LocalityEndpoint } from "./load-balancer-priority";
|
||||
import TypedLoadBalancingConfig = experimental.TypedLoadBalancingConfig;
|
||||
import LoadBalancer = experimental.LoadBalancer;
|
||||
|
@ -30,6 +30,8 @@ import UnavailablePicker = experimental.UnavailablePicker;
|
|||
import Endpoint = experimental.Endpoint;
|
||||
import endpointToString = experimental.endpointToString;
|
||||
import selectLbConfigFromList = experimental.selectLbConfigFromList;
|
||||
import StatusOr = experimental.StatusOr;
|
||||
import statusOrFromValue = experimental.statusOrFromValue;
|
||||
|
||||
const TRACER_NAME = 'weighted_target';
|
||||
|
||||
|
@ -154,7 +156,7 @@ class WeightedTargetPicker implements Picker {
|
|||
}
|
||||
|
||||
interface WeightedChild {
|
||||
updateAddressList(endpointList: Endpoint[], lbConfig: WeightedTarget, attributes: { [key: string]: unknown; }): void;
|
||||
updateAddressList(endpointList: StatusOr<Endpoint[]>, lbConfig: WeightedTarget, options: ChannelOptions, resolutionNote: string): void;
|
||||
exitIdle(): void;
|
||||
resetBackoff(): void;
|
||||
destroy(): void;
|
||||
|
@ -193,9 +195,9 @@ export class WeightedTargetLoadBalancer implements LoadBalancer {
|
|||
this.parent.maybeUpdateState();
|
||||
}
|
||||
|
||||
updateAddressList(endpointList: Endpoint[], lbConfig: WeightedTarget, options: ChannelOptions): void {
|
||||
updateAddressList(endpointList: StatusOr<Endpoint[]>, lbConfig: WeightedTarget, options: ChannelOptions, resolutionNote: string): void {
|
||||
this.weight = lbConfig.weight;
|
||||
this.childBalancer.updateAddressList(endpointList, lbConfig.child_policy, options);
|
||||
this.childBalancer.updateAddressList(endpointList, lbConfig.child_policy, options, resolutionNote);
|
||||
}
|
||||
exitIdle(): void {
|
||||
this.childBalancer.exitIdle();
|
||||
|
@ -325,26 +327,41 @@ export class WeightedTargetLoadBalancer implements LoadBalancer {
|
|||
this.channelControlHelper.updateState(connectivityState, picker, errorMessage);
|
||||
}
|
||||
|
||||
updateAddressList(addressList: Endpoint[], lbConfig: TypedLoadBalancingConfig, options: ChannelOptions): void {
|
||||
updateAddressList(addressList: StatusOr<Endpoint[]>, lbConfig: TypedLoadBalancingConfig, options: ChannelOptions, resolutionNote: string): boolean {
|
||||
if (!(lbConfig instanceof WeightedTargetLoadBalancingConfig)) {
|
||||
// Reject a config of the wrong type
|
||||
trace('Discarding address list update with unrecognized config ' + JSON.stringify(lbConfig.toJsonObject(), undefined, 2));
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if (!addressList.ok) {
|
||||
if (this.targets.size === 0) {
|
||||
this.channelControlHelper.updateState(connectivityState.TRANSIENT_FAILURE, new UnavailablePicker(addressList.error), addressList.error.details);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (addressList.value.length === 0) {
|
||||
for (const target of this.targets.values()) {
|
||||
target.destroy();
|
||||
}
|
||||
this.targets.clear();
|
||||
this.targetList = [];
|
||||
const errorMessage = `No addresses resolved. Resolution note: ${resolutionNote}`;
|
||||
this.channelControlHelper.updateState(connectivityState.TRANSIENT_FAILURE, new UnavailablePicker({code: status.UNAVAILABLE, details: errorMessage}), errorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* For each address, the first element of its localityPath array determines
|
||||
* which child it belongs to. So we bucket those addresses by that first
|
||||
* element, and pass along the rest of the localityPath for that child
|
||||
* to use. */
|
||||
const childEndpointMap = new Map<string, LocalityEndpoint[]>();
|
||||
for (const address of addressList) {
|
||||
for (const address of addressList.value) {
|
||||
if (!isLocalityEndpoint(address)) {
|
||||
// Reject address that cannot be associated with targets
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if (address.localityPath.length < 1) {
|
||||
// Reject address that cannot be associated with targets
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
const childName = address.localityPath[0];
|
||||
const childAddress: LocalityEndpoint = {
|
||||
|
@ -371,7 +388,7 @@ export class WeightedTargetLoadBalancer implements LoadBalancer {
|
|||
}
|
||||
const targetEndpoints = childEndpointMap.get(targetName) ?? [];
|
||||
trace('Assigning target ' + targetName + ' address list ' + targetEndpoints.map(endpoint => '(' + endpointToString(endpoint) + ' path=' + endpoint.localityPath + ')'));
|
||||
target.updateAddressList(targetEndpoints, targetConfig, options);
|
||||
target.updateAddressList(statusOrFromValue(targetEndpoints), targetConfig, options, resolutionNote);
|
||||
}
|
||||
|
||||
// Deactivate targets that are not in the new config
|
||||
|
@ -384,6 +401,7 @@ export class WeightedTargetLoadBalancer implements LoadBalancer {
|
|||
this.updatesPaused = false;
|
||||
|
||||
this.updateState();
|
||||
return true;
|
||||
}
|
||||
exitIdle(): void {
|
||||
for (const targetName of this.targetList) {
|
||||
|
|
|
@ -37,6 +37,7 @@ import selectLbConfigFromList = experimental.selectLbConfigFromList;
|
|||
import SubchannelInterface = experimental.SubchannelInterface;
|
||||
import BaseSubchannelWrapper = experimental.BaseSubchannelWrapper;
|
||||
import UnavailablePicker = experimental.UnavailablePicker;
|
||||
import StatusOr = experimental.StatusOr;
|
||||
import { Locality__Output } from "./generated/envoy/config/core/v3/Locality";
|
||||
import { ClusterConfig, XdsConfig } from "./xds-dependency-manager";
|
||||
import { CdsUpdate } from "./xds-resource-type/cluster-resource-type";
|
||||
|
@ -206,7 +207,7 @@ function getCallCounterMapKey(cluster: string, edsServiceName?: string): string
|
|||
|
||||
class XdsClusterImplBalancer implements LoadBalancer {
|
||||
private childBalancer: ChildLoadBalancerHandler;
|
||||
private lastestEndpointList: Endpoint[] | null = null;
|
||||
private lastestEndpointList: StatusOr<Endpoint[]> | null = null;
|
||||
private latestConfig: XdsClusterImplLoadBalancingConfig | null = null;
|
||||
private clusterDropStats: XdsClusterDropStats | null = null;
|
||||
private xdsClient: XdsClient | null = null;
|
||||
|
@ -215,12 +216,12 @@ class XdsClusterImplBalancer implements LoadBalancer {
|
|||
constructor(private readonly channelControlHelper: ChannelControlHelper) {
|
||||
this.childBalancer = new ChildLoadBalancerHandler(createChildChannelControlHelper(channelControlHelper, {
|
||||
createSubchannel: (subchannelAddress, subchannelArgs) => {
|
||||
if (!this.xdsClient || !this.latestConfig || !this.lastestEndpointList || !this.latestClusterConfig) {
|
||||
throw new Error('xds_cluster_impl: invalid state: createSubchannel called with xdsClient or latestConfig not populated');
|
||||
if (!this.xdsClient || !this.latestConfig || !this.lastestEndpointList || !this.lastestEndpointList.ok || !this.latestClusterConfig) {
|
||||
throw new Error('xds_cluster_impl: invalid state: createSubchannel called with xdsClient or latestConfig not populated or with resolver error');
|
||||
}
|
||||
const wrapperChild = channelControlHelper.createSubchannel(subchannelAddress, subchannelArgs);
|
||||
let locality: Locality__Output | null = null;
|
||||
for (const endpoint of this.lastestEndpointList) {
|
||||
for (const endpoint of this.lastestEndpointList.value) {
|
||||
if (endpointHasAddress(endpoint, subchannelAddress)) {
|
||||
locality = (endpoint as LocalityEndpoint).locality;
|
||||
}
|
||||
|
@ -251,28 +252,28 @@ class XdsClusterImplBalancer implements LoadBalancer {
|
|||
}
|
||||
}));
|
||||
}
|
||||
updateAddressList(endpointList: Endpoint[], lbConfig: TypedLoadBalancingConfig, options: ChannelOptions): void {
|
||||
updateAddressList(endpointList: StatusOr<Endpoint[]>, lbConfig: TypedLoadBalancingConfig, options: ChannelOptions, resolutionNote: string): boolean {
|
||||
if (!(lbConfig instanceof XdsClusterImplLoadBalancingConfig)) {
|
||||
trace('Discarding address list update with unrecognized config ' + JSON.stringify(lbConfig.toJsonObject(), undefined, 2));
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
trace('Received update with config: ' + JSON.stringify(lbConfig.toJsonObject(), undefined, 2));
|
||||
const xdsConfig = options[XDS_CONFIG_KEY] as XdsConfig;
|
||||
const maybeClusterConfig = xdsConfig.clusters.get(lbConfig.getCluster());
|
||||
if (!maybeClusterConfig) {
|
||||
trace('Received update with no config for cluster ' + lbConfig.getCluster());
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if (!maybeClusterConfig.success) {
|
||||
if (!maybeClusterConfig.ok) {
|
||||
this.latestClusterConfig = null;
|
||||
this.childBalancer.destroy();
|
||||
this.channelControlHelper.updateState(connectivityState.TRANSIENT_FAILURE, new UnavailablePicker(maybeClusterConfig.error), maybeClusterConfig.error.details);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
const clusterConfig = maybeClusterConfig.value;
|
||||
if (clusterConfig.children.type === 'aggregate') {
|
||||
trace('Received update for aggregate cluster ' + lbConfig.getCluster());
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if (!clusterConfig.children.endpoints) {
|
||||
this.childBalancer.destroy();
|
||||
|
@ -291,7 +292,8 @@ class XdsClusterImplBalancer implements LoadBalancer {
|
|||
);
|
||||
}
|
||||
|
||||
this.childBalancer.updateAddressList(endpointList, lbConfig.getChildPolicy(), options);
|
||||
this.childBalancer.updateAddressList(endpointList, lbConfig.getChildPolicy(), options, resolutionNote);
|
||||
return true;
|
||||
}
|
||||
exitIdle(): void {
|
||||
this.childBalancer.exitIdle();
|
||||
|
|
|
@ -30,6 +30,7 @@ import ChildLoadBalancerHandler = experimental.ChildLoadBalancerHandler;
|
|||
import ChannelControlHelper = experimental.ChannelControlHelper;
|
||||
import selectLbConfigFromList = experimental.selectLbConfigFromList;
|
||||
import registerLoadBalancerType = experimental.registerLoadBalancerType;
|
||||
import StatusOr = experimental.StatusOr;
|
||||
|
||||
const TRACER_NAME = 'xds_cluster_manager';
|
||||
|
||||
|
@ -111,7 +112,7 @@ class XdsClusterManagerPicker implements Picker {
|
|||
}
|
||||
|
||||
interface XdsClusterManagerChild {
|
||||
updateAddressList(endpointList: Endpoint[], childConfig: TypedLoadBalancingConfig, attributes: { [key: string]: unknown; }): void;
|
||||
updateAddressList(endpointList: StatusOr<Endpoint[]>, childConfig: TypedLoadBalancingConfig, options: ChannelOptions, resolutionNote: string): void;
|
||||
exitIdle(): void;
|
||||
resetBackoff(): void;
|
||||
destroy(): void;
|
||||
|
@ -145,8 +146,8 @@ class XdsClusterManager implements LoadBalancer {
|
|||
}
|
||||
this.parent.maybeUpdateState();
|
||||
}
|
||||
updateAddressList(endpointList: Endpoint[], childConfig: TypedLoadBalancingConfig, options: ChannelOptions): void {
|
||||
this.childBalancer.updateAddressList(endpointList, childConfig, options);
|
||||
updateAddressList(endpointList: StatusOr<Endpoint[]>, childConfig: TypedLoadBalancingConfig, options: ChannelOptions, resolutionNote: string): void {
|
||||
this.childBalancer.updateAddressList(endpointList, childConfig, options, resolutionNote);
|
||||
}
|
||||
exitIdle(): void {
|
||||
this.childBalancer.exitIdle();
|
||||
|
@ -213,11 +214,11 @@ class XdsClusterManager implements LoadBalancer {
|
|||
this.channelControlHelper.updateState(connectivityState, new XdsClusterManagerPicker(pickerMap), errorMessage);
|
||||
}
|
||||
|
||||
updateAddressList(endpointList: Endpoint[], lbConfig: TypedLoadBalancingConfig, options: ChannelOptions): void {
|
||||
updateAddressList(endpointList: StatusOr<Endpoint[]>, lbConfig: TypedLoadBalancingConfig, options: ChannelOptions, resolutionNote: string): boolean {
|
||||
if (!(lbConfig instanceof XdsClusterManagerLoadBalancingConfig)) {
|
||||
// Reject a config of the wrong type
|
||||
trace('Discarding address list update with unrecognized config ' + JSON.stringify(lbConfig.toJsonObject(), undefined, 2));
|
||||
return;
|
||||
return false;;
|
||||
}
|
||||
trace('Received update with config: ' + JSON.stringify(lbConfig.toJsonObject(), undefined, 2));
|
||||
const configChildren = lbConfig.getChildren();
|
||||
|
@ -240,10 +241,11 @@ class XdsClusterManager implements LoadBalancer {
|
|||
child = new this.XdsClusterManagerChildImpl(this, name);
|
||||
this.children.set(name, child);
|
||||
}
|
||||
child.updateAddressList(endpointList, childConfig, options);
|
||||
child.updateAddressList(endpointList, childConfig, options, resolutionNote);
|
||||
}
|
||||
this.updatesPaused = false;
|
||||
this.updateState();
|
||||
return true;
|
||||
}
|
||||
exitIdle(): void {
|
||||
for (const child of this.children.values()) {
|
||||
|
|
|
@ -29,6 +29,7 @@ import ChildLoadBalancerHandler = experimental.ChildLoadBalancerHandler;
|
|||
import Endpoint = experimental.Endpoint;
|
||||
import parseLoadBalancingConfig = experimental.parseLoadBalancingConfig;
|
||||
import registerLoadBalancerType = experimental.registerLoadBalancerType;
|
||||
import StatusOr = experimental.StatusOr;
|
||||
import { Any__Output } from "./generated/google/protobuf/Any";
|
||||
import { WrrLocality__Output } from "./generated/envoy/extensions/load_balancing_policies/wrr_locality/v3/WrrLocality";
|
||||
import { TypedExtensionConfig__Output } from "./generated/envoy/config/core/v3/TypedExtensionConfig";
|
||||
|
@ -76,15 +77,19 @@ class XdsWrrLocalityLoadBalancer implements LoadBalancer {
|
|||
constructor(private readonly channelControlHelper: ChannelControlHelper) {
|
||||
this.childBalancer = new ChildLoadBalancerHandler(channelControlHelper);
|
||||
}
|
||||
updateAddressList(endpointList: Endpoint[], lbConfig: TypedLoadBalancingConfig, options: ChannelOptions): void {
|
||||
updateAddressList(endpointList: StatusOr<Endpoint[]>, lbConfig: TypedLoadBalancingConfig, options: ChannelOptions, resolutionNote: string): boolean {
|
||||
if (!(lbConfig instanceof XdsWrrLocalityLoadBalancingConfig)) {
|
||||
trace('Discarding address list update with unrecognized config ' + JSON.stringify(lbConfig, undefined, 2));
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if (!endpointList.ok) {
|
||||
this.childBalancer.updateAddressList(endpointList, parseLoadBalancingConfig({weighted_target: { targets: [] }}), options, resolutionNote);
|
||||
return true;
|
||||
}
|
||||
const targets: {[localityName: string]: WeightedTargetRaw} = {};
|
||||
for (const address of endpointList) {
|
||||
for (const address of endpointList.value) {
|
||||
if (!isLocalityEndpoint(address)) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
const localityName = localityToName(address.locality);
|
||||
if (!(localityName in targets)) {
|
||||
|
@ -99,7 +104,8 @@ class XdsWrrLocalityLoadBalancer implements LoadBalancer {
|
|||
targets: targets
|
||||
}
|
||||
};
|
||||
this.childBalancer.updateAddressList(endpointList, parseLoadBalancingConfig(childConfig), options);
|
||||
this.childBalancer.updateAddressList(endpointList, parseLoadBalancingConfig(childConfig), options, resolutionNote);
|
||||
return true;
|
||||
}
|
||||
exitIdle(): void {
|
||||
this.childBalancer.exitIdle();
|
||||
|
|
|
@ -0,0 +1,399 @@
|
|||
/*
|
||||
* Copyright 2025 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 { Metadata } from "@grpc/grpc-js";
|
||||
import { Matcher, ValueMatcher } from "./matcher";
|
||||
import { CidrRange, cidrRangeMessageToCidrRange, inCidrRange } from "./cidr";
|
||||
import { PeerCertificate } from "tls";
|
||||
import { RBAC__Output } from "./generated/envoy/config/rbac/v3/RBAC";
|
||||
import { Policy__Output } from "./generated/envoy/config/rbac/v3/Policy";
|
||||
import { Permission__Output } from "./generated/envoy/config/rbac/v3/Permission";
|
||||
import { Principal__Output } from "./generated/envoy/config/rbac/v3/Principal";
|
||||
import { getPredicateForHeaderMatcher, getPredicateForStringMatcher } from "./route";
|
||||
|
||||
export interface RbacRule<InfoType> {
|
||||
apply(info: InfoType): boolean;
|
||||
toString(): string;
|
||||
}
|
||||
|
||||
export class AndRules<InfoType> implements RbacRule<InfoType> {
|
||||
constructor(private childRules: RbacRule<InfoType>[]) {}
|
||||
|
||||
apply(info: InfoType) {
|
||||
return this.childRules.every(rule => rule.apply(info));
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return `And(${this.childRules.map(rule => rule.toString())})`;
|
||||
}
|
||||
}
|
||||
|
||||
export class OrRules<InfoType> implements RbacRule<InfoType> {
|
||||
constructor(private childRules: RbacRule<InfoType>[]) {}
|
||||
|
||||
apply(info: InfoType) {
|
||||
return this.childRules.some(rule => rule.apply(info));
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return `Or(${this.childRules.map(rule => rule.toString())})`;
|
||||
}
|
||||
}
|
||||
|
||||
export class NotRule<InfoType> implements RbacRule<InfoType> {
|
||||
constructor(private childRule: RbacRule<InfoType>) {}
|
||||
|
||||
apply(info: InfoType) {
|
||||
return !this.childRule.apply(info);
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return `Not(${this.childRule.toString()})`;
|
||||
}
|
||||
}
|
||||
|
||||
export class AnyRule<InfoType> implements RbacRule<InfoType> {
|
||||
constructor() {}
|
||||
|
||||
apply(info: InfoType) {
|
||||
return true;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return `Any()`;
|
||||
}
|
||||
}
|
||||
|
||||
export class NoneRule<InfoType> implements RbacRule<InfoType> {
|
||||
constructor() {}
|
||||
|
||||
apply(info: InfoType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return `None()`;
|
||||
}
|
||||
}
|
||||
|
||||
export interface PermissionInfo {
|
||||
headers: Metadata;
|
||||
urlPath: string;
|
||||
destinationIp: string;
|
||||
destinationPort: number;
|
||||
}
|
||||
|
||||
export type PermissionRule = RbacRule<PermissionInfo>;
|
||||
|
||||
export class HeaderPermission implements PermissionRule {
|
||||
constructor(private matcher: Matcher) {}
|
||||
|
||||
apply(info: PermissionInfo) {
|
||||
return this.matcher.apply(info.urlPath, info.headers);
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return `Header(${this.matcher.toString()})`;
|
||||
}
|
||||
}
|
||||
|
||||
export class UrlPathPermission implements PermissionRule {
|
||||
constructor(private matcher: ValueMatcher) {}
|
||||
|
||||
apply(info: PermissionInfo): boolean {
|
||||
return this.matcher.apply(info.urlPath);
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return `UrlPath(${this.matcher.toString()})`;
|
||||
}
|
||||
}
|
||||
|
||||
export class DestinationIpPermission implements PermissionRule {
|
||||
constructor(private cidrRange: CidrRange) {}
|
||||
|
||||
apply(info: PermissionInfo): boolean {
|
||||
return inCidrRange(this.cidrRange, info.destinationIp);
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return `DestinationIp(${this.cidrRange.addressPrefix}/${this.cidrRange.prefixLen})`;
|
||||
}
|
||||
}
|
||||
|
||||
export class DestinationPortPermission implements PermissionRule {
|
||||
constructor(private port: number) {}
|
||||
|
||||
apply(info: PermissionInfo): boolean {
|
||||
return info.destinationPort === this.port;
|
||||
}
|
||||
toString(): string {
|
||||
return `DestinationPort(${this.port})`;
|
||||
}
|
||||
}
|
||||
|
||||
export class MetadataPermission implements PermissionRule {
|
||||
constructor() {}
|
||||
|
||||
apply(info: PermissionInfo): boolean {
|
||||
return false;
|
||||
}
|
||||
toString(): string {
|
||||
return `Metadata()`;
|
||||
}
|
||||
}
|
||||
|
||||
export class RequestedServerNamePermission implements PermissionRule {
|
||||
constructor(private matcher: ValueMatcher) {}
|
||||
|
||||
apply(info: PermissionInfo): boolean {
|
||||
return this.matcher.apply('');
|
||||
}
|
||||
toString(): string {
|
||||
return `RequestedServerName(${this.matcher.toString()})`;
|
||||
}
|
||||
}
|
||||
|
||||
export type BasicPeerCertificate = Pick<PeerCertificate, 'subjectaltname' | 'subject'>;
|
||||
|
||||
export interface PrincipalInfo {
|
||||
tls: boolean;
|
||||
peerCertificate: BasicPeerCertificate | null;
|
||||
sourceIp: string;
|
||||
headers: Metadata;
|
||||
urlPath: string;
|
||||
}
|
||||
|
||||
export type PrincipalRule = RbacRule<PrincipalInfo>;
|
||||
|
||||
interface SanEntry {
|
||||
type: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
function splitSanEntry(entry: string): SanEntry | null {
|
||||
const colonIndex = entry.indexOf(':');
|
||||
if (colonIndex < 0) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
type: entry.substring(0, colonIndex),
|
||||
value: entry.substring(colonIndex + 1)
|
||||
}
|
||||
}
|
||||
|
||||
export class AuthenticatedPrincipal implements PrincipalRule {
|
||||
constructor(private nameMatcher: ValueMatcher | null) {}
|
||||
|
||||
apply(info: PrincipalInfo): boolean {
|
||||
if (this.nameMatcher === null) {
|
||||
return info.tls;
|
||||
}
|
||||
if (!info.peerCertificate) {
|
||||
return this.nameMatcher.apply('');
|
||||
}
|
||||
if (info.peerCertificate.subjectaltname) {
|
||||
const sanEntries = info.peerCertificate.subjectaltname.split(', ').map(splitSanEntry).filter(x => x !== null);
|
||||
if (sanEntries.some(entry => entry.type === 'URI')) {
|
||||
for (const entry of sanEntries) {
|
||||
if (entry.type === 'URI') {
|
||||
if (this.nameMatcher.apply(entry.value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (sanEntries.some(entry => entry.type === 'DNS')) {
|
||||
for (const entry of sanEntries) {
|
||||
if (entry.type === 'DNS') {
|
||||
if (this.nameMatcher.apply(entry.value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.nameMatcher.apply(info.peerCertificate.subject.CN);
|
||||
}
|
||||
toString(): string {
|
||||
return `Authenticated(principal=${this.nameMatcher?.toString() ?? null})`;
|
||||
}
|
||||
}
|
||||
|
||||
export class SourceIpPrincipal implements PrincipalRule {
|
||||
constructor(private cidrRange: CidrRange) {}
|
||||
|
||||
apply(info: PrincipalInfo): boolean {
|
||||
return inCidrRange(this.cidrRange, info.sourceIp);
|
||||
}
|
||||
toString(): string {
|
||||
return `SourceIp(${this.cidrRange.addressPrefix}/${this.cidrRange.prefixLen})`;
|
||||
}
|
||||
}
|
||||
|
||||
export class HeaderPrincipal implements PrincipalRule {
|
||||
constructor(private matcher: Matcher) {}
|
||||
|
||||
apply(info: PrincipalInfo) {
|
||||
return this.matcher.apply(info.urlPath, info.headers);
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return `Header(${this.matcher.toString()})`;
|
||||
}
|
||||
}
|
||||
|
||||
export class UrlPathPrincipal implements PrincipalRule {
|
||||
constructor(private matcher: ValueMatcher) {}
|
||||
|
||||
apply(info: PrincipalInfo): boolean {
|
||||
return this.matcher.apply(info.urlPath);
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return `UrlPath(${this.matcher.toString()})`;
|
||||
}
|
||||
}
|
||||
|
||||
export class MetadataPrincipal implements PrincipalRule {
|
||||
constructor() {}
|
||||
|
||||
apply(info: PrincipalInfo): boolean {
|
||||
return false;
|
||||
}
|
||||
toString(): string {
|
||||
return `Metadata()`;
|
||||
}
|
||||
}
|
||||
|
||||
export interface UnifiedInfo extends PermissionInfo, PrincipalInfo {}
|
||||
|
||||
export class RbacPolicy {
|
||||
private permission: PermissionRule;
|
||||
private principal: PrincipalRule;
|
||||
|
||||
constructor(permissions: PermissionRule[], principals: PrincipalRule[]) {
|
||||
this.permission = new OrRules(permissions);
|
||||
this.principal = new OrRules(principals);
|
||||
}
|
||||
|
||||
matches(info: UnifiedInfo) {
|
||||
return this.principal.apply(info) && this.permission.apply(info);
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `principal=${this.principal.toString()} permission=${this.permission.toString()}`;
|
||||
}
|
||||
}
|
||||
|
||||
export class RbacPolicyGroup {
|
||||
constructor(private policies: Map<string, RbacPolicy>, private allow: boolean) {}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param info
|
||||
* @returns True if the call should be accepted, false if it should be rejected
|
||||
*/
|
||||
apply(info: UnifiedInfo): boolean {
|
||||
for (const policy of this.policies.values()) {
|
||||
if (policy.matches(info)) {
|
||||
return this.allow;
|
||||
}
|
||||
}
|
||||
return !this.allow;
|
||||
}
|
||||
|
||||
toString() {
|
||||
const policyStrings: string[] = [];
|
||||
for (const [name, policy] of this.policies) {
|
||||
policyStrings.push(`${name}: ${policy.toString()}`);
|
||||
}
|
||||
return `RBAC
|
||||
action=${this.allow ? 'ALLOW' : 'DENY'}
|
||||
policies:
|
||||
${policyStrings.join('\n')}`;
|
||||
}
|
||||
}
|
||||
|
||||
export function parsePermission(permission: Permission__Output): PermissionRule {
|
||||
switch (permission.rule) {
|
||||
case 'and_rules':
|
||||
return new AndRules(permission.and_rules!.rules.map(parsePermission));
|
||||
case 'or_rules':
|
||||
return new OrRules(permission.or_rules!.rules.map(parsePermission));
|
||||
case 'not_rule':
|
||||
return new NotRule(parsePermission(permission.not_rule!));
|
||||
case 'any':
|
||||
return new AnyRule();
|
||||
case 'destination_ip':
|
||||
return new DestinationIpPermission(cidrRangeMessageToCidrRange(permission.destination_ip!));
|
||||
case 'destination_port':
|
||||
return new DestinationPortPermission(permission.destination_port!);
|
||||
case 'header':
|
||||
return new HeaderPermission(getPredicateForHeaderMatcher(permission.header!));
|
||||
case 'metadata':
|
||||
return new MetadataPermission();
|
||||
case 'requested_server_name':
|
||||
return new RequestedServerNamePermission(getPredicateForStringMatcher(permission.requested_server_name!));
|
||||
case 'url_path':
|
||||
return new UrlPathPermission(getPredicateForStringMatcher(permission.url_path!.path!));
|
||||
default:
|
||||
return new NoneRule();
|
||||
}
|
||||
}
|
||||
|
||||
export function parsePrincipal(principal: Principal__Output): PrincipalRule {
|
||||
switch (principal.identifier) {
|
||||
case 'and_ids':
|
||||
return new AndRules(principal.and_ids!.ids.map(parsePrincipal));
|
||||
case 'or_ids':
|
||||
return new OrRules(principal.or_ids!.ids.map(parsePrincipal));
|
||||
case 'not_id':
|
||||
return new NotRule(parsePrincipal(principal.not_id!));
|
||||
case 'any':
|
||||
return new AnyRule();
|
||||
case 'authenticated':
|
||||
return new AuthenticatedPrincipal(principal.authenticated?.principal_name ? getPredicateForStringMatcher(principal.authenticated.principal_name) : null);
|
||||
case 'direct_remote_ip':
|
||||
return new SourceIpPrincipal(cidrRangeMessageToCidrRange(principal.direct_remote_ip!));
|
||||
case 'remote_ip':
|
||||
return new SourceIpPrincipal(cidrRangeMessageToCidrRange(principal.remote_ip!));
|
||||
case 'source_ip':
|
||||
return new SourceIpPrincipal(cidrRangeMessageToCidrRange(principal.source_ip!));
|
||||
case 'header':
|
||||
return new HeaderPrincipal(getPredicateForHeaderMatcher(principal.header!));
|
||||
case 'metadata':
|
||||
return new MetadataPrincipal();
|
||||
case 'url_path':
|
||||
return new UrlPathPrincipal(getPredicateForStringMatcher(principal.url_path!.path!));
|
||||
default:
|
||||
return new NoneRule();
|
||||
}
|
||||
}
|
||||
|
||||
export function parsePolicy(policy: Policy__Output): RbacPolicy {
|
||||
return new RbacPolicy(policy.permissions.map(parsePermission), policy.principals.map(parsePrincipal));
|
||||
}
|
||||
|
||||
export function parseConfig(rbac: RBAC__Output): RbacPolicyGroup {
|
||||
if (rbac.action === 'LOG') {
|
||||
throw new Error('Invalid RBAC action LOG');
|
||||
}
|
||||
const policyMap = new Map<string, RbacPolicy>();
|
||||
for (const [name, policyConfig] of Object.entries(rbac.policies)) {
|
||||
policyMap.set(name, parsePolicy(policyConfig));
|
||||
}
|
||||
return new RbacPolicyGroup(policyMap, rbac.action === 'ALLOW');
|
||||
}
|
|
@ -31,7 +31,7 @@ import { HashPolicy, RouteAction, SingleClusterRouteAction, WeightedCluster, Wei
|
|||
import { decodeSingleResource, HTTP_CONNECTION_MANGER_TYPE_URL } from './resources';
|
||||
import Duration = experimental.Duration;
|
||||
import { Duration__Output } from './generated/google/protobuf/Duration';
|
||||
import { createHttpFilter, HttpFilterConfig, parseOverrideFilterConfig, parseTopLevelFilterConfig } from './http-filter';
|
||||
import { createClientHttpFilter, HttpFilterConfig, parseOverrideFilterConfig, parseTopLevelFilterConfig } from './http-filter';
|
||||
import { EXPERIMENTAL_FAULT_INJECTION, EXPERIMENTAL_FEDERATION, EXPERIMENTAL_RETRY, EXPERIMENTAL_RING_HASH } from './environment';
|
||||
import Filter = experimental.Filter;
|
||||
import FilterFactory = experimental.FilterFactory;
|
||||
|
@ -41,6 +41,9 @@ import { loadXxhashApi } from './xxhash';
|
|||
import { formatTemplateString } from './xds-bootstrap';
|
||||
import { getPredicateForMatcher } from './route';
|
||||
import { XdsConfig, XdsConfigWatcher, XdsDependencyManager } from './xds-dependency-manager';
|
||||
import statusOrFromValue = experimental.statusOrFromValue;
|
||||
import statusOrFromError = experimental.statusOrFromError;
|
||||
import CHANNEL_ARGS_CONFIG_SELECTOR_KEY = experimental.CHANNEL_ARGS_CONFIG_SELECTOR_KEY;
|
||||
|
||||
const TRACER_NAME = 'xds_resolver';
|
||||
|
||||
|
@ -132,18 +135,12 @@ class XdsResolver implements Resolver {
|
|||
this.xdsClient = getSingletonXdsClient();
|
||||
}
|
||||
this.xdsConfigWatcher = {
|
||||
onUpdate: xdsConfig => {
|
||||
this.handleXdsConfig(xdsConfig);
|
||||
},
|
||||
onError: (context, status) => {
|
||||
trace('Resolution error for target ' + uriToString(this.target) + ' due to xDS client transient error retrieving ' + context + ': ' + status.details);
|
||||
this.reportResolutionError(`Error retrieving resource ${context}: ${status.details}`);
|
||||
},
|
||||
onResourceDoesNotExist: context => {
|
||||
trace('Resolution error for target ' + uriToString(this.target) + ': ' + context + ' does not exist');
|
||||
/* Return an empty endpoint list and service config, to explicitly
|
||||
* invalidate any previously returned service config */
|
||||
this.listener.onSuccessfulResolution([], null, null, null, {});
|
||||
onUpdate: maybeXdsConfig => {
|
||||
if (maybeXdsConfig.ok) {
|
||||
this.handleXdsConfig(maybeXdsConfig.value);
|
||||
} else {
|
||||
this.listener(statusOrFromValue([]), {}, null, `Resolution error for target ${uriToString(this.target)}: ${maybeXdsConfig.error.details}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -174,7 +171,7 @@ class XdsResolver implements Resolver {
|
|||
if (EXPERIMENTAL_FAULT_INJECTION) {
|
||||
for (const filter of httpConnectionManager.http_filters) {
|
||||
// typed_config must be set here, or validation would have failed
|
||||
const filterConfig = parseTopLevelFilterConfig(filter.typed_config!);
|
||||
const filterConfig = parseTopLevelFilterConfig(filter.typed_config!, true);
|
||||
if (filterConfig) {
|
||||
ldsHttpFilterConfigs.push({name: filter.name, config: filterConfig});
|
||||
}
|
||||
|
@ -276,17 +273,17 @@ class XdsResolver implements Resolver {
|
|||
if (EXPERIMENTAL_FAULT_INJECTION) {
|
||||
for (const filterConfig of ldsHttpFilterConfigs) {
|
||||
if (routeHttpFilterOverrides.has(filterConfig.name)) {
|
||||
const filter = createHttpFilter(filterConfig.config, routeHttpFilterOverrides.get(filterConfig.name)!);
|
||||
const filter = createClientHttpFilter(filterConfig.config, routeHttpFilterOverrides.get(filterConfig.name)!);
|
||||
if (filter) {
|
||||
extraFilterFactories.push(filter);
|
||||
}
|
||||
} else if (virtualHostHttpFilterOverrides.has(filterConfig.name)) {
|
||||
const filter = createHttpFilter(filterConfig.config, virtualHostHttpFilterOverrides.get(filterConfig.name)!);
|
||||
const filter = createClientHttpFilter(filterConfig.config, virtualHostHttpFilterOverrides.get(filterConfig.name)!);
|
||||
if (filter) {
|
||||
extraFilterFactories.push(filter);
|
||||
}
|
||||
} else {
|
||||
const filter = createHttpFilter(filterConfig.config);
|
||||
const filter = createClientHttpFilter(filterConfig.config);
|
||||
if (filter) {
|
||||
extraFilterFactories.push(filter);
|
||||
}
|
||||
|
@ -311,22 +308,22 @@ class XdsResolver implements Resolver {
|
|||
}
|
||||
for (const filterConfig of ldsHttpFilterConfigs) {
|
||||
if (clusterHttpFilterOverrides.has(filterConfig.name)) {
|
||||
const filter = createHttpFilter(filterConfig.config, clusterHttpFilterOverrides.get(filterConfig.name)!);
|
||||
const filter = createClientHttpFilter(filterConfig.config, clusterHttpFilterOverrides.get(filterConfig.name)!);
|
||||
if (filter) {
|
||||
extraFilterFactories.push(filter);
|
||||
}
|
||||
} else if (routeHttpFilterOverrides.has(filterConfig.name)) {
|
||||
const filter = createHttpFilter(filterConfig.config, routeHttpFilterOverrides.get(filterConfig.name)!);
|
||||
const filter = createClientHttpFilter(filterConfig.config, routeHttpFilterOverrides.get(filterConfig.name)!);
|
||||
if (filter) {
|
||||
extraFilterFactories.push(filter);
|
||||
}
|
||||
} else if (virtualHostHttpFilterOverrides.has(filterConfig.name)) {
|
||||
const filter = createHttpFilter(filterConfig.config, virtualHostHttpFilterOverrides.get(filterConfig.name)!);
|
||||
const filter = createClientHttpFilter(filterConfig.config, virtualHostHttpFilterOverrides.get(filterConfig.name)!);
|
||||
if (filter) {
|
||||
extraFilterFactories.push(filter);
|
||||
}
|
||||
} else {
|
||||
const filter = createHttpFilter(filterConfig.config);
|
||||
const filter = createClientHttpFilter(filterConfig.config);
|
||||
if (filter) {
|
||||
extraFilterFactories.push(filter);
|
||||
}
|
||||
|
@ -407,20 +404,20 @@ class XdsResolver implements Resolver {
|
|||
methodConfig: [],
|
||||
loadBalancingConfig: [lbPolicyConfig]
|
||||
}
|
||||
this.listener.onSuccessfulResolution([], serviceConfig, null, configSelector, {
|
||||
this.listener(statusOrFromValue([]), {
|
||||
[XDS_CLIENT_KEY]: this.xdsClient,
|
||||
[XDS_CONFIG_KEY]: xdsConfig
|
||||
});
|
||||
[XDS_CONFIG_KEY]: xdsConfig,
|
||||
[CHANNEL_ARGS_CONFIG_SELECTOR_KEY]: configSelector
|
||||
}, statusOrFromValue(serviceConfig), '');
|
||||
}
|
||||
|
||||
private reportResolutionError(reason: string) {
|
||||
this.listener.onError({
|
||||
this.listener(statusOrFromError({
|
||||
code: status.UNAVAILABLE,
|
||||
details: `xDS name resolution failed for target ${uriToString(
|
||||
this.target
|
||||
)}: ${reason}`,
|
||||
metadata: new Metadata(),
|
||||
});
|
||||
)}: ${reason}`
|
||||
}), {}, null, '');
|
||||
}
|
||||
|
||||
private startResolution(): void {
|
||||
|
|
|
@ -18,8 +18,26 @@ import { RouteMatch__Output } from './generated/envoy/config/route/v3/RouteMatch
|
|||
import { HeaderMatcher__Output } from './generated/envoy/config/route/v3/HeaderMatcher';
|
||||
import { ContainsValueMatcher, ExactValueMatcher, FullMatcher, HeaderMatcher, Matcher, PathExactValueMatcher, PathPrefixValueMatcher, PathSafeRegexValueMatcher, PrefixValueMatcher, PresentValueMatcher, RangeValueMatcher, RejectValueMatcher, SafeRegexValueMatcher, SuffixValueMatcher, ValueMatcher } from './matcher';
|
||||
import { envoyFractionToFraction, Fraction } from "./fraction";
|
||||
import { StringMatcher__Output } from './generated/envoy/type/matcher/v3/StringMatcher';
|
||||
|
||||
function getPredicateForHeaderMatcher(headerMatch: HeaderMatcher__Output): Matcher {
|
||||
export function getPredicateForStringMatcher(stringMatch: StringMatcher__Output): ValueMatcher {
|
||||
switch (stringMatch.match_pattern) {
|
||||
case 'exact':
|
||||
return new ExactValueMatcher(stringMatch.exact!, stringMatch.ignore_case);
|
||||
case 'safe_regex':
|
||||
return new SafeRegexValueMatcher(stringMatch.safe_regex!.regex);
|
||||
case 'prefix':
|
||||
return new PrefixValueMatcher(stringMatch.prefix!, stringMatch.ignore_case);
|
||||
case 'suffix':
|
||||
return new SuffixValueMatcher(stringMatch.suffix!, stringMatch.ignore_case);
|
||||
case 'contains':
|
||||
return new ContainsValueMatcher(stringMatch.contains!, stringMatch.ignore_case);
|
||||
default:
|
||||
return new RejectValueMatcher();
|
||||
}
|
||||
}
|
||||
|
||||
export function getPredicateForHeaderMatcher(headerMatch: HeaderMatcher__Output): Matcher {
|
||||
let valueChecker: ValueMatcher;
|
||||
switch (headerMatch.header_match_specifier) {
|
||||
case 'exact_match':
|
||||
|
@ -43,26 +61,7 @@ function getPredicateForHeaderMatcher(headerMatch: HeaderMatcher__Output): Match
|
|||
valueChecker = new SuffixValueMatcher(headerMatch.suffix_match!, false);
|
||||
break;
|
||||
case 'string_match':
|
||||
const stringMatch = headerMatch.string_match!;
|
||||
switch (stringMatch.match_pattern) {
|
||||
case 'exact':
|
||||
valueChecker = new ExactValueMatcher(stringMatch.exact!, stringMatch.ignore_case);
|
||||
break;
|
||||
case 'safe_regex':
|
||||
valueChecker = new SafeRegexValueMatcher(stringMatch.safe_regex!.regex);
|
||||
break;
|
||||
case 'prefix':
|
||||
valueChecker = new PrefixValueMatcher(stringMatch.prefix!, stringMatch.ignore_case);
|
||||
break;
|
||||
case 'suffix':
|
||||
valueChecker = new SuffixValueMatcher(stringMatch.suffix!, stringMatch.ignore_case);
|
||||
break;
|
||||
case 'contains':
|
||||
valueChecker = new ContainsValueMatcher(stringMatch.contains!, stringMatch.ignore_case);
|
||||
break;
|
||||
default:
|
||||
valueChecker = new RejectValueMatcher();
|
||||
}
|
||||
valueChecker = getPredicateForStringMatcher(headerMatch.string_match!);
|
||||
break;
|
||||
default:
|
||||
valueChecker = new RejectValueMatcher();
|
||||
|
|
|
@ -37,6 +37,7 @@ import { findVirtualHostForDomain } from "./xds-dependency-manager";
|
|||
import { LogVerbosity } from "@grpc/grpc-js/build/src/constants";
|
||||
import { XdsServerCredentials } from "./xds-credentials";
|
||||
import { CertificateValidationContext__Output } from "./generated/envoy/extensions/transport_sockets/tls/v3/CertificateValidationContext";
|
||||
import { createServerHttpFilter, HttpFilterConfig, parseOverrideFilterConfig, parseTopLevelFilterConfig } from "./http-filter";
|
||||
|
||||
const TRACER_NAME = 'xds_server';
|
||||
|
||||
|
@ -64,6 +65,7 @@ interface NormalizedFilterChainMatch {
|
|||
}
|
||||
|
||||
interface RouteEntry {
|
||||
id: string;
|
||||
matcher: Matcher;
|
||||
isNonForwardingAction: boolean;
|
||||
}
|
||||
|
@ -94,6 +96,10 @@ class FilterChainEntry {
|
|||
private virtualHosts: VirtualHostEntry[] | null = null;
|
||||
private connectionInjector: ConnectionInjector;
|
||||
private hasRouteConfigErrors = false;
|
||||
/**
|
||||
* filter name -> route ID -> config
|
||||
*/
|
||||
private overrideConfigMaps = new Map<string, Map<string, HttpFilterConfig>>();
|
||||
constructor(private configParameters: ConfigParameters, filterChain: FilterChain__Output, credentials: ServerCredentials, onRouteConfigPopulated: () => void) {
|
||||
this.matchers = normalizeFilterChainMatch(filterChain.filter_chain_match);
|
||||
const httpConnectionManager = decodeSingleResource(HTTP_CONNECTION_MANGER_TYPE_URL, filterChain.filters[0].typed_config!.value);
|
||||
|
@ -145,6 +151,7 @@ class FilterChainEntry {
|
|||
for (const route of virtualHost.routes) {
|
||||
if (route.matcher.apply(methodDescriptor.path, metadata)) {
|
||||
if (route.isNonForwardingAction) {
|
||||
metadata.set('grpc-route', route.id);
|
||||
next(metadata);
|
||||
} else {
|
||||
call.sendStatus(routeErrorStatus);
|
||||
|
@ -158,6 +165,18 @@ class FilterChainEntry {
|
|||
}
|
||||
});
|
||||
}
|
||||
const httpFilterInterceptors: ServerInterceptor[] = [];
|
||||
for (const filter of httpConnectionManager.http_filters) {
|
||||
const filterConfig = parseTopLevelFilterConfig(filter.typed_config!, false);
|
||||
if (filterConfig) {
|
||||
const filterOverrideConfigMap = new Map<string, HttpFilterConfig>();
|
||||
this.overrideConfigMaps.set(filterConfig.typeUrl, filterOverrideConfigMap);
|
||||
const filterInterceptor = createServerHttpFilter(filterConfig, filterOverrideConfigMap);
|
||||
if (filterInterceptor) {
|
||||
httpFilterInterceptors.push(filterInterceptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (credentials instanceof XdsServerCredentials) {
|
||||
if (filterChain.transport_socket) {
|
||||
trace('Using secure credentials');
|
||||
|
@ -193,20 +212,24 @@ class FilterChainEntry {
|
|||
credentials = credentials.getFallbackCredentials();
|
||||
}
|
||||
}
|
||||
const interceptingCredentials = createServerCredentialsWithInterceptors(credentials, [interceptor]);
|
||||
const interceptingCredentials = createServerCredentialsWithInterceptors(credentials, [interceptor, ...httpFilterInterceptors]);
|
||||
this.connectionInjector = configParameters.createConnectionInjector(interceptingCredentials);
|
||||
}
|
||||
|
||||
private handleRouteConfigurationResource(routeConfig: RouteConfiguration__Output) {
|
||||
let hasRouteConfigErrors = false;
|
||||
this.virtualHosts = [];
|
||||
for (const virtualHost of routeConfig.virtual_hosts) {
|
||||
for (const overrideMap of this.overrideConfigMaps.values()) {
|
||||
overrideMap.clear();
|
||||
}
|
||||
for (const [virtualHostIndex, virtualHost] of routeConfig.virtual_hosts.entries()) {
|
||||
const virtualHostEntry: VirtualHostEntry = {
|
||||
domains: virtualHost.domains,
|
||||
routes: []
|
||||
};
|
||||
for (const route of virtualHost.routes) {
|
||||
for (const [routeIndex, route] of virtualHost.routes.entries()) {
|
||||
const routeEntry: RouteEntry = {
|
||||
id: `virtualhost=${virtualHostIndex} route=${routeIndex}`,
|
||||
matcher: getPredicateForMatcher(route.match!),
|
||||
isNonForwardingAction: route.action === 'non_forwarding_action'
|
||||
};
|
||||
|
@ -215,6 +238,12 @@ class FilterChainEntry {
|
|||
this.logConfigurationError('For domains matching [' + virtualHostEntry.domains + '] requests will be rejected for routes matching ' + routeEntry.matcher.toString());
|
||||
}
|
||||
virtualHostEntry.routes.push(routeEntry);
|
||||
for (const [filterName, overrideConfig] of Object.entries(route.typed_per_filter_config)) {
|
||||
const parsedConfig = parseOverrideFilterConfig(overrideConfig);
|
||||
if (parsedConfig) {
|
||||
this.overrideConfigMaps.get(filterName)?.set(routeEntry.id, parsedConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.virtualHosts.push(virtualHostEntry);
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import { DropCategory } from "./load-balancer-xds-cluster-impl";
|
|||
import Endpoint = experimental.Endpoint;
|
||||
import Resolver = experimental.Resolver;
|
||||
import createResolver = experimental.createResolver;
|
||||
import StatusOr = experimental.StatusOr;
|
||||
import { decodeSingleResource, HTTP_CONNECTION_MANGER_TYPE_URL } from "./resources";
|
||||
import { RouteConfigurationResourceType } from "./xds-resource-type/route-config-resource-type";
|
||||
import { ListenerResourceType } from "./xds-resource-type/listener-resource-type";
|
||||
|
@ -75,14 +76,6 @@ export interface ClusterConfig {
|
|||
children: EndpointConfig | AggregateConfig;
|
||||
}
|
||||
|
||||
export type StatusOr<T> = {
|
||||
success: true;
|
||||
value: T
|
||||
} | {
|
||||
success: false;
|
||||
error: StatusObject;
|
||||
}
|
||||
|
||||
export interface ClusterResult {
|
||||
clusterConfig?: ClusterConfig;
|
||||
status?: StatusObject;
|
||||
|
@ -96,9 +89,7 @@ export interface XdsConfig {
|
|||
}
|
||||
|
||||
export interface XdsConfigWatcher {
|
||||
onUpdate(xdsConfig: XdsConfig): void;
|
||||
onError(context: string, status: StatusObject): void;
|
||||
onResourceDoesNotExist(context: string): void;
|
||||
onUpdate(xdsConfig: StatusOr<XdsConfig>): void;
|
||||
}
|
||||
|
||||
interface AggregateClusterInfo {
|
||||
|
@ -159,7 +150,7 @@ function isClusterTreeFullyUpdated(tree: ClusterGraph, roots: string[]): Cluster
|
|||
reason: 'Cluster entry ' + next + ' not updated'
|
||||
};
|
||||
}
|
||||
if (tree[next].latestUpdate.success) {
|
||||
if (tree[next].latestUpdate.ok) {
|
||||
if (tree[next].latestUpdate.value.type !== 'AGGREGATE') {
|
||||
if (!(tree[next].latestUpdate.value.latestUpdate)) {
|
||||
return {
|
||||
|
@ -332,6 +323,9 @@ function getEdsResource(edsUpdate: ClusterLoadAssignment__Output): EndpointResou
|
|||
}
|
||||
|
||||
function getDnsResource(endpoints: Endpoint[]): EndpointResource {
|
||||
const endpoint: Endpoint = {
|
||||
addresses: endpoints.map(endpoint => endpoint.addresses).flat()
|
||||
}
|
||||
return {
|
||||
priorities: [{
|
||||
localities: [{
|
||||
|
@ -341,7 +335,7 @@ function getDnsResource(endpoints: Endpoint[]): EndpointResource {
|
|||
sub_zone: ''
|
||||
},
|
||||
weight: 1,
|
||||
endpoints: endpoints.map(endpoint => ({endpoint: endpoint, weight: 1}))
|
||||
endpoints: [{endpoint: endpoint, weight: 1}]
|
||||
}]
|
||||
}],
|
||||
dropCategories: []
|
||||
|
@ -401,7 +395,13 @@ export class XdsDependencyManager {
|
|||
* not already provided a ServiceConfig for the upper layer to use */
|
||||
if (!this.latestListener) {
|
||||
this.trace('Resolution error due to xDS client transient error ' + error.details);
|
||||
this.watcher.onError(`Listener ${listenerResourceName}`, error);
|
||||
this.watcher.onUpdate({
|
||||
ok: false,
|
||||
error: {
|
||||
...error,
|
||||
details: `Listener ${listenerResourceName}: ${error.details}`
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
onResourceDoesNotExist: () => {
|
||||
|
@ -415,11 +415,24 @@ export class XdsDependencyManager {
|
|||
},
|
||||
onError: (error: StatusObject) => {
|
||||
if (!this.latestRouteConfiguration) {
|
||||
this.watcher.onError(`RouteConfiguration ${this.latestRouteConfigName}`, error);
|
||||
this.watcher.onUpdate({
|
||||
ok: false,
|
||||
error: {
|
||||
...error,
|
||||
details: `RouteConfiguration ${this.latestRouteConfigName}: ${error.details}`
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
onResourceDoesNotExist: () => {
|
||||
this.watcher.onResourceDoesNotExist(`RouteConfiguration ${this.latestRouteConfigName}`);
|
||||
this.watcher.onUpdate({
|
||||
ok: false,
|
||||
error: {
|
||||
code: status.UNAVAILABLE,
|
||||
details: `RouteConfiguration ${this.latestRouteConfigName} does not exist`,
|
||||
metadata: new Metadata()
|
||||
}
|
||||
});
|
||||
this.clusterRoots = [];
|
||||
this.pruneOrphanClusters();
|
||||
}
|
||||
|
@ -441,8 +454,14 @@ export class XdsDependencyManager {
|
|||
this.clusterRoots = [];
|
||||
this.pruneOrphanClusters();
|
||||
}
|
||||
this.watcher.onResourceDoesNotExist(`Listener ${this.listenerResourceName}`);
|
||||
|
||||
this.watcher.onUpdate({
|
||||
ok: false,
|
||||
error: {
|
||||
code: status.UNAVAILABLE,
|
||||
details: `Listener ${this.listenerResourceName} does not exist`,
|
||||
metadata: new Metadata()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private maybeSendUpdate() {
|
||||
|
@ -470,7 +489,7 @@ export class XdsDependencyManager {
|
|||
this.trace('Not sending update: Cluster entry ' + clusterName + ' not updated (not caught by isClusterTreeFullyUpdated)');
|
||||
return;
|
||||
}
|
||||
if (entry.latestUpdate.success) {
|
||||
if (entry.latestUpdate.ok) {
|
||||
let clusterChildren: EndpointConfig | AggregateConfig;
|
||||
if (entry.latestUpdate.value.type === 'AGGREGATE') {
|
||||
clusterChildren = {
|
||||
|
@ -485,7 +504,7 @@ export class XdsDependencyManager {
|
|||
};
|
||||
}
|
||||
update.clusters.set(clusterName, {
|
||||
success: true,
|
||||
ok: true,
|
||||
value: {
|
||||
cluster: entry.latestUpdate.value.cdsUpdate,
|
||||
children: clusterChildren
|
||||
|
@ -493,12 +512,12 @@ export class XdsDependencyManager {
|
|||
});
|
||||
} else {
|
||||
update.clusters.set(clusterName, {
|
||||
success: false,
|
||||
ok: false,
|
||||
error: entry.latestUpdate.error
|
||||
});
|
||||
}
|
||||
}
|
||||
this.watcher.onUpdate(update);
|
||||
this.watcher.onUpdate({ok: true, value: update});
|
||||
}
|
||||
|
||||
private addCluster(clusterName: string) {
|
||||
|
@ -510,7 +529,7 @@ export class XdsDependencyManager {
|
|||
onResourceChanged: (update: CdsUpdate) => {
|
||||
switch (update.type) {
|
||||
case 'AGGREGATE':
|
||||
if (entry.latestUpdate?.success) {
|
||||
if (entry.latestUpdate?.ok) {
|
||||
switch (entry.latestUpdate.value.type) {
|
||||
case 'AGGREGATE':
|
||||
break;
|
||||
|
@ -525,7 +544,7 @@ export class XdsDependencyManager {
|
|||
}
|
||||
entry.children = update.aggregateChildren;
|
||||
entry.latestUpdate = {
|
||||
success: true,
|
||||
ok: true,
|
||||
value: {
|
||||
type: 'AGGREGATE',
|
||||
cdsUpdate: update
|
||||
|
@ -539,7 +558,7 @@ export class XdsDependencyManager {
|
|||
break;
|
||||
case 'EDS':
|
||||
const edsServiceName = update.edsServiceName ?? clusterName;
|
||||
if (entry.latestUpdate?.success) {
|
||||
if (entry.latestUpdate?.ok) {
|
||||
switch (entry.latestUpdate.value.type) {
|
||||
case 'AGGREGATE':
|
||||
entry.children = [];
|
||||
|
@ -566,14 +585,14 @@ export class XdsDependencyManager {
|
|||
}
|
||||
const edsWatcher = new Watcher<ClusterLoadAssignment__Output>({
|
||||
onResourceChanged: (endpoint: ClusterLoadAssignment__Output) => {
|
||||
if (entry.latestUpdate?.success && entry.latestUpdate.value.type === 'EDS') {
|
||||
if (entry.latestUpdate?.ok && entry.latestUpdate.value.type === 'EDS') {
|
||||
entry.latestUpdate.value.latestUpdate = getEdsResource(endpoint);
|
||||
entry.latestUpdate.value.resolutionNote = undefined;
|
||||
this.maybeSendUpdate();
|
||||
}
|
||||
},
|
||||
onError: error => {
|
||||
if (entry.latestUpdate?.success && entry.latestUpdate.value.type === 'EDS') {
|
||||
if (entry.latestUpdate?.ok && entry.latestUpdate.value.type === 'EDS') {
|
||||
if (!entry.latestUpdate.value.latestUpdate) {
|
||||
entry.latestUpdate.value.resolutionNote = `Control plane error: ${error.details}`;
|
||||
this.maybeSendUpdate();
|
||||
|
@ -581,7 +600,7 @@ export class XdsDependencyManager {
|
|||
}
|
||||
},
|
||||
onResourceDoesNotExist: () => {
|
||||
if (entry.latestUpdate?.success && entry.latestUpdate.value.type === 'EDS') {
|
||||
if (entry.latestUpdate?.ok && entry.latestUpdate.value.type === 'EDS') {
|
||||
entry.latestUpdate.value.resolutionNote = 'Resource does not exist';
|
||||
entry.latestUpdate.value.latestUpdate = undefined;
|
||||
this.maybeSendUpdate();
|
||||
|
@ -589,7 +608,7 @@ export class XdsDependencyManager {
|
|||
}
|
||||
});
|
||||
entry.latestUpdate = {
|
||||
success: true,
|
||||
ok: true,
|
||||
value: {
|
||||
type: 'EDS',
|
||||
cdsUpdate: update,
|
||||
|
@ -602,7 +621,7 @@ export class XdsDependencyManager {
|
|||
this.maybeSendUpdate();
|
||||
break;
|
||||
case 'LOGICAL_DNS': {
|
||||
if (entry.latestUpdate?.success) {
|
||||
if (entry.latestUpdate?.ok) {
|
||||
switch (entry.latestUpdate.value.type) {
|
||||
case 'AGGREGATE':
|
||||
entry.children = [];
|
||||
|
@ -621,24 +640,24 @@ export class XdsDependencyManager {
|
|||
}
|
||||
}
|
||||
this.trace('Creating DNS resolver for hostname ' + update.dnsHostname!);
|
||||
const resolver = createResolver({scheme: 'dns', path: update.dnsHostname!}, {
|
||||
onSuccessfulResolution: endpointList => {
|
||||
if (entry.latestUpdate?.success && entry.latestUpdate.value.type === 'LOGICAL_DNS') {
|
||||
entry.latestUpdate.value.latestUpdate = getDnsResource(endpointList);
|
||||
const resolver = createResolver({scheme: 'dns', path: update.dnsHostname!}, endpointList => {
|
||||
if (endpointList.ok) {
|
||||
if (entry.latestUpdate?.ok && entry.latestUpdate.value.type === 'LOGICAL_DNS') {
|
||||
entry.latestUpdate.value.latestUpdate = getDnsResource(endpointList.value);
|
||||
this.maybeSendUpdate();
|
||||
}
|
||||
},
|
||||
onError: error => {
|
||||
if (entry.latestUpdate?.success && entry.latestUpdate.value.type === 'LOGICAL_DNS') {
|
||||
} else {
|
||||
if (entry.latestUpdate?.ok && entry.latestUpdate.value.type === 'LOGICAL_DNS') {
|
||||
if (!entry.latestUpdate.value.latestUpdate) {
|
||||
entry.latestUpdate.value.resolutionNote = `DNS resolution error: ${error.details}`;
|
||||
entry.latestUpdate.value.resolutionNote = `DNS resolution error: ${endpointList.error.details}`;
|
||||
this.maybeSendUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}, {'grpc.service_config_disable_resolution': 1});
|
||||
entry.latestUpdate = {
|
||||
success: true,
|
||||
ok: true,
|
||||
value: {
|
||||
type: 'LOGICAL_DNS',
|
||||
cdsUpdate: update,
|
||||
|
@ -653,16 +672,16 @@ export class XdsDependencyManager {
|
|||
}
|
||||
},
|
||||
onError: error => {
|
||||
if (!entry.latestUpdate?.success) {
|
||||
if (!entry.latestUpdate?.ok) {
|
||||
entry.latestUpdate = {
|
||||
success: false,
|
||||
ok: false,
|
||||
error: error
|
||||
};
|
||||
this.maybeSendUpdate();
|
||||
}
|
||||
},
|
||||
onResourceDoesNotExist: () => {
|
||||
if (entry.latestUpdate?.success) {
|
||||
if (entry.latestUpdate?.ok) {
|
||||
switch (entry.latestUpdate.value.type) {
|
||||
case 'EDS':
|
||||
this.trace('EDS.cancelWatch(' + entry.latestUpdate.value.edsServiceName + '): CDS resource does not exist');
|
||||
|
@ -676,7 +695,7 @@ export class XdsDependencyManager {
|
|||
}
|
||||
}
|
||||
entry.latestUpdate = {
|
||||
success: false,
|
||||
ok: false,
|
||||
error: {
|
||||
code: status.UNAVAILABLE,
|
||||
details: `Cluster resource ${clusterName} does not exist`,
|
||||
|
@ -718,7 +737,7 @@ export class XdsDependencyManager {
|
|||
return;
|
||||
}
|
||||
const entry = this.clusterForest[clusterName];
|
||||
if (entry.latestUpdate?.success) {
|
||||
if (entry.latestUpdate?.ok) {
|
||||
switch (entry.latestUpdate.value.type) {
|
||||
case 'EDS':
|
||||
this.trace('EDS.cancelWatch(' + entry.latestUpdate.value.edsServiceName + '): Cluster ' + clusterName + ' removed');
|
||||
|
@ -770,10 +789,13 @@ export class XdsDependencyManager {
|
|||
if (!virtualHost) {
|
||||
this.clusterRoots = [];
|
||||
this.pruneOrphanClusters();
|
||||
this.watcher.onError(`RouteConfiguration ${routeConfig.name}`, {
|
||||
code: status.UNAVAILABLE,
|
||||
details: `No matching route found for ${this.dataPlaneAuthority}`,
|
||||
metadata: new Metadata()
|
||||
this.watcher.onUpdate({
|
||||
ok: false,
|
||||
error: {
|
||||
code: status.UNAVAILABLE,
|
||||
details: `RouteConfiguration ${routeConfig.name}: No matching route found for ${this.dataPlaneAuthority}`,
|
||||
metadata: new Metadata()
|
||||
}
|
||||
});
|
||||
// Report error
|
||||
return;
|
||||
|
@ -806,7 +828,7 @@ export class XdsDependencyManager {
|
|||
|
||||
updateResolution() {
|
||||
for (const clusterEntry of Object.values(this.clusterForest)) {
|
||||
if (clusterEntry.latestUpdate?.success && clusterEntry.latestUpdate.value.type === 'LOGICAL_DNS') {
|
||||
if (clusterEntry.latestUpdate?.ok && clusterEntry.latestUpdate.value.type === 'LOGICAL_DNS') {
|
||||
clusterEntry.latestUpdate.value.resolver.updateResolution();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -87,9 +87,10 @@ function normalizeFilterChainMatch(filterChainMatch: FilterChainMatch__Output):
|
|||
|
||||
/**
|
||||
* @param httpConnectionManager
|
||||
* @param
|
||||
* @returns A list of validation errors, if there are any. An empty list indicates success
|
||||
*/
|
||||
function validateHttpConnectionManager(httpConnectionManager: HttpConnectionManager__Output): string[] {
|
||||
function validateHttpConnectionManager(httpConnectionManager: HttpConnectionManager__Output, client: boolean): string[] {
|
||||
const errors: string[] = [];
|
||||
if (EXPERIMENTAL_FAULT_INJECTION) {
|
||||
const filterNames = new Set<string>();
|
||||
|
@ -98,7 +99,7 @@ function validateHttpConnectionManager(httpConnectionManager: HttpConnectionMana
|
|||
errors.push(`duplicate HTTP filter name: ${httpFilter.name}`);
|
||||
}
|
||||
filterNames.add(httpFilter.name);
|
||||
if (!validateTopLevelFilter(httpFilter)) {
|
||||
if (!validateTopLevelFilter(httpFilter, client)) {
|
||||
errors.push(`${httpFilter.name} filter validation failed`);
|
||||
}
|
||||
/* Validate that the last filter, and only the last filter, is the
|
||||
|
@ -237,7 +238,7 @@ function validateFilterChain(context: XdsDecodeContext, filterChain: FilterChain
|
|||
if (filterChain.filters.length === 1) {
|
||||
if (filterChain.filters[0].typed_config?.type_url === HTTP_CONNECTION_MANGER_TYPE_URL) {
|
||||
const httpConnectionManager = decodeSingleResource(HTTP_CONNECTION_MANGER_TYPE_URL, filterChain.filters[0].typed_config.value);
|
||||
errors.push(...validateHttpConnectionManager(httpConnectionManager).map(error => `filters[0].typed_config: ${error}`));
|
||||
errors.push(...validateHttpConnectionManager(httpConnectionManager, false).map(error => `filters[0].typed_config: ${error}`));
|
||||
} else {
|
||||
errors.push(`Unexpected value of filters[0].typed_config.type_url: ${filterChain.filters[0].typed_config?.type_url}`);
|
||||
}
|
||||
|
@ -270,7 +271,7 @@ export class ListenerResourceType extends XdsResourceType {
|
|||
message.api_listener.api_listener.type_url === HTTP_CONNECTION_MANGER_TYPE_URL
|
||||
) {
|
||||
const httpConnectionManager = decodeSingleResource(HTTP_CONNECTION_MANGER_TYPE_URL, message.api_listener!.api_listener.value);
|
||||
errors.push(...validateHttpConnectionManager(httpConnectionManager).map(error => `api_listener.api_listener: ${error}`));
|
||||
errors.push(...validateHttpConnectionManager(httpConnectionManager, true).map(error => `api_listener.api_listener: ${error}`));
|
||||
} else {
|
||||
errors.push(`api_listener.api_listener.type_url != ${HTTP_CONNECTION_MANGER_TYPE_URL}`);
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ import { ClusterConfig } from "../src/generated/envoy/extensions/clusters/aggreg
|
|||
import { Any } from "../src/generated/google/protobuf/Any";
|
||||
import { ControlPlaneServer } from "./xds-server";
|
||||
import { UpstreamTlsContext } from "../src/generated/envoy/extensions/transport_sockets/tls/v3/UpstreamTlsContext";
|
||||
import { HttpFilter } from "../src/generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpFilter";
|
||||
|
||||
interface Endpoint {
|
||||
locality: Locality;
|
||||
|
@ -400,7 +401,7 @@ const DEFAULT_BASE_SERVER_ROUTE_CONFIG: RouteConfiguration = {
|
|||
export class FakeServerRoute {
|
||||
private listener: Listener;
|
||||
private routeConfiguration: RouteConfiguration;
|
||||
constructor(port: number, routeName: string, baseListener?: Listener | undefined, baseRouteConfiguration?: RouteConfiguration) {
|
||||
constructor(port: number, routeName: string, baseListener?: Listener | undefined, baseRouteConfiguration?: RouteConfiguration | undefined, httpFilters?: HttpFilter[]) {
|
||||
this.listener = baseListener ?? {...DEFAULT_BASE_SERVER_LISTENER};
|
||||
this.listener.name = `[::1]:${port}`;
|
||||
this.listener.address = {
|
||||
|
@ -414,11 +415,9 @@ export class FakeServerRoute {
|
|||
rds: {
|
||||
route_config_name: routeName,
|
||||
config_source: {ads: {}}
|
||||
}
|
||||
},
|
||||
http_filters: httpFilters ?? []
|
||||
};
|
||||
this.listener.api_listener = {
|
||||
api_listener: httpConnectionManager
|
||||
}
|
||||
const filterList = [{
|
||||
typed_config: httpConnectionManager
|
||||
}];
|
||||
|
|
|
@ -38,6 +38,7 @@ import PickResultType = experimental.PickResultType;
|
|||
import createChildChannelControlHelper = experimental.createChildChannelControlHelper;
|
||||
import parseLoadBalancingConfig = experimental.parseLoadBalancingConfig;
|
||||
import registerLoadBalancerType = experimental.registerLoadBalancerType;
|
||||
import StatusOr = experimental.StatusOr;
|
||||
import { PickFirst } from "../src/generated/envoy/extensions/load_balancing_policies/pick_first/v3/PickFirst";
|
||||
|
||||
const LB_POLICY_NAME = 'test.RpcBehaviorLoadBalancer';
|
||||
|
@ -95,12 +96,12 @@ class RpcBehaviorLoadBalancer implements LoadBalancer {
|
|||
});
|
||||
this.child = new ChildLoadBalancerHandler(childChannelControlHelper);
|
||||
}
|
||||
updateAddressList(endpointList: Endpoint[], lbConfig: TypedLoadBalancingConfig, options: ChannelOptions): void {
|
||||
updateAddressList(endpointList: StatusOr<Endpoint[]>, lbConfig: TypedLoadBalancingConfig, options: ChannelOptions, resolutionNote: string): boolean {
|
||||
if (!(lbConfig instanceof RpcBehaviorLoadBalancingConfig)) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
this.latestConfig = lbConfig;
|
||||
this.child.updateAddressList(endpointList, RPC_BEHAVIOR_CHILD_CONFIG, options);
|
||||
return this.child.updateAddressList(endpointList, RPC_BEHAVIOR_CHILD_CONFIG, options, resolutionNote);
|
||||
}
|
||||
exitIdle(): void {
|
||||
this.child.exitIdle();
|
||||
|
|
|
@ -0,0 +1,188 @@
|
|||
/*
|
||||
* Copyright 2025 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 assert from 'assert';
|
||||
import { createBackends } from "./backend";
|
||||
import { XdsTestClient } from "./client";
|
||||
import { FakeEdsCluster, FakeRouteGroup, FakeServerRoute } from "./framework";
|
||||
import { ControlPlaneServer } from "./xds-server";
|
||||
import { AnyExtension } from '@grpc/proto-loader';
|
||||
import { RBAC } from '../src/generated/envoy/extensions/filters/http/rbac/v3/RBAC';
|
||||
import { status } from '@grpc/grpc-js';
|
||||
|
||||
describe.only('RBAC HTTP filter', () => {
|
||||
let xdsServer: ControlPlaneServer;
|
||||
let client: XdsTestClient;
|
||||
beforeEach(done => {
|
||||
xdsServer = new ControlPlaneServer();
|
||||
xdsServer.startServer(error => {
|
||||
done(error);
|
||||
});
|
||||
});
|
||||
afterEach(() => {
|
||||
client?.close();
|
||||
xdsServer?.shutdownServer();
|
||||
});
|
||||
it('Should accept matching requests with ALLOW action', async () => {
|
||||
const [backend] = await createBackends(1);
|
||||
const rbacFilter: AnyExtension & RBAC = {
|
||||
'@type': 'type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC',
|
||||
rules: {
|
||||
action: 'ALLOW',
|
||||
policies: {
|
||||
local: {
|
||||
principals: [{any: true}],
|
||||
permissions: [{any: true}]
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const routerFilter: AnyExtension = {
|
||||
'@type': 'type.googleapis.com/envoy.extensions.filters.http.router.v3.Router'
|
||||
};
|
||||
const serverRoute = new FakeServerRoute(backend.getPort(), 'serverRoute', undefined, undefined, [{typed_config: rbacFilter, name: 'rbac'}, {typed_config: routerFilter, name: 'router'}]);
|
||||
xdsServer.setRdsResource(serverRoute.getRouteConfiguration());
|
||||
xdsServer.setLdsResource(serverRoute.getListener());
|
||||
xdsServer.addResponseListener((typeUrl, responseState) => {
|
||||
if (responseState.state === 'NACKED') {
|
||||
client?.stopCalls();
|
||||
assert.fail(`Client NACKED ${typeUrl} resource with message ${responseState.errorMessage}`);
|
||||
}
|
||||
});
|
||||
const cluster = new FakeEdsCluster('cluster1', 'endpoint1', [{backends: [backend], locality:{region: 'region1'}}]);
|
||||
const routeGroup = new FakeRouteGroup('listener1', 'route1', [{cluster: cluster}]);
|
||||
await routeGroup.startAllBackends(xdsServer);
|
||||
xdsServer.setEdsResource(cluster.getEndpointConfig());
|
||||
xdsServer.setCdsResource(cluster.getClusterConfig());
|
||||
xdsServer.setRdsResource(routeGroup.getRouteConfiguration());
|
||||
xdsServer.setLdsResource(routeGroup.getListener());
|
||||
client = XdsTestClient.createFromServer('listener1', xdsServer);
|
||||
const error = await client.sendOneCallAsync();
|
||||
assert.strictEqual(error, null);
|
||||
});
|
||||
it('Should reject matching requests with DENY action', async () => {
|
||||
const [backend] = await createBackends(1);
|
||||
const rbacFilter: AnyExtension & RBAC = {
|
||||
'@type': 'type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC',
|
||||
rules: {
|
||||
action: 'DENY',
|
||||
policies: {
|
||||
local: {
|
||||
principals: [{any: true}],
|
||||
permissions: [{any: true}]
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const routerFilter: AnyExtension = {
|
||||
'@type': 'type.googleapis.com/envoy.extensions.filters.http.router.v3.Router'
|
||||
};
|
||||
const serverRoute = new FakeServerRoute(backend.getPort(), 'serverRoute', undefined, undefined, [{typed_config: rbacFilter, name: 'rbac'}, {typed_config: routerFilter, name: 'router'}]);
|
||||
xdsServer.setRdsResource(serverRoute.getRouteConfiguration());
|
||||
xdsServer.setLdsResource(serverRoute.getListener());
|
||||
xdsServer.addResponseListener((typeUrl, responseState) => {
|
||||
if (responseState.state === 'NACKED') {
|
||||
client?.stopCalls();
|
||||
assert.fail(`Client NACKED ${typeUrl} resource with message ${responseState.errorMessage}`);
|
||||
}
|
||||
});
|
||||
const cluster = new FakeEdsCluster('cluster1', 'endpoint1', [{backends: [backend], locality:{region: 'region1'}}]);
|
||||
const routeGroup = new FakeRouteGroup('listener1', 'route1', [{cluster: cluster}]);
|
||||
await routeGroup.startAllBackends(xdsServer);
|
||||
xdsServer.setEdsResource(cluster.getEndpointConfig());
|
||||
xdsServer.setCdsResource(cluster.getClusterConfig());
|
||||
xdsServer.setRdsResource(routeGroup.getRouteConfiguration());
|
||||
xdsServer.setLdsResource(routeGroup.getListener());
|
||||
client = XdsTestClient.createFromServer('listener1', xdsServer);
|
||||
const error = await client.sendOneCallAsync();
|
||||
assert.strictEqual(error?.code, status.PERMISSION_DENIED);
|
||||
});
|
||||
it('Should reject non-matching requests with ALLOW action', async () => {
|
||||
const [backend] = await createBackends(1);
|
||||
const rbacFilter: AnyExtension & RBAC = {
|
||||
'@type': 'type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC',
|
||||
rules: {
|
||||
action: 'ALLOW',
|
||||
policies: {
|
||||
local: {
|
||||
principals: [{any: true}],
|
||||
permissions: [{not_rule: {any: true}}]
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const routerFilter: AnyExtension = {
|
||||
'@type': 'type.googleapis.com/envoy.extensions.filters.http.router.v3.Router'
|
||||
};
|
||||
const serverRoute = new FakeServerRoute(backend.getPort(), 'serverRoute', undefined, undefined, [{typed_config: rbacFilter, name: 'rbac'}, {typed_config: routerFilter, name: 'router'}]);
|
||||
xdsServer.setRdsResource(serverRoute.getRouteConfiguration());
|
||||
xdsServer.setLdsResource(serverRoute.getListener());
|
||||
xdsServer.addResponseListener((typeUrl, responseState) => {
|
||||
if (responseState.state === 'NACKED') {
|
||||
client?.stopCalls();
|
||||
assert.fail(`Client NACKED ${typeUrl} resource with message ${responseState.errorMessage}`);
|
||||
}
|
||||
});
|
||||
const cluster = new FakeEdsCluster('cluster1', 'endpoint1', [{backends: [backend], locality:{region: 'region1'}}]);
|
||||
const routeGroup = new FakeRouteGroup('listener1', 'route1', [{cluster: cluster}]);
|
||||
await routeGroup.startAllBackends(xdsServer);
|
||||
xdsServer.setEdsResource(cluster.getEndpointConfig());
|
||||
xdsServer.setCdsResource(cluster.getClusterConfig());
|
||||
xdsServer.setRdsResource(routeGroup.getRouteConfiguration());
|
||||
xdsServer.setLdsResource(routeGroup.getListener());
|
||||
client = XdsTestClient.createFromServer('listener1', xdsServer);
|
||||
const error = await client.sendOneCallAsync();
|
||||
assert.strictEqual(error?.code, status.PERMISSION_DENIED);
|
||||
});
|
||||
it('Should accept non-matching requests with DENY action', async () => {
|
||||
const [backend] = await createBackends(1);
|
||||
const rbacFilter: AnyExtension & RBAC = {
|
||||
'@type': 'type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC',
|
||||
rules: {
|
||||
action: 'DENY',
|
||||
policies: {
|
||||
local: {
|
||||
principals: [{any: true}],
|
||||
permissions: [{not_rule: {any: true}}]
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const routerFilter: AnyExtension = {
|
||||
'@type': 'type.googleapis.com/envoy.extensions.filters.http.router.v3.Router'
|
||||
};
|
||||
const serverRoute = new FakeServerRoute(backend.getPort(), 'serverRoute', undefined, undefined, [{typed_config: rbacFilter, name: 'rbac'}, {typed_config: routerFilter, name: 'router'}]);
|
||||
xdsServer.setRdsResource(serverRoute.getRouteConfiguration());
|
||||
xdsServer.setLdsResource(serverRoute.getListener());
|
||||
xdsServer.addResponseListener((typeUrl, responseState) => {
|
||||
if (responseState.state === 'NACKED') {
|
||||
client?.stopCalls();
|
||||
assert.fail(`Client NACKED ${typeUrl} resource with message ${responseState.errorMessage}`);
|
||||
}
|
||||
});
|
||||
const cluster = new FakeEdsCluster('cluster1', 'endpoint1', [{backends: [backend], locality:{region: 'region1'}}]);
|
||||
const routeGroup = new FakeRouteGroup('listener1', 'route1', [{cluster: cluster}]);
|
||||
await routeGroup.startAllBackends(xdsServer);
|
||||
xdsServer.setEdsResource(cluster.getEndpointConfig());
|
||||
xdsServer.setCdsResource(cluster.getClusterConfig());
|
||||
xdsServer.setRdsResource(routeGroup.getRouteConfiguration());
|
||||
xdsServer.setLdsResource(routeGroup.getListener());
|
||||
client = XdsTestClient.createFromServer('listener1', xdsServer);
|
||||
const error = await client.sendOneCallAsync();
|
||||
assert.strictEqual(error, null);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,376 @@
|
|||
/*
|
||||
* Copyright 2023 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 { Metadata } from '@grpc/grpc-js';
|
||||
import * as rbac from '../src/rbac';
|
||||
import * as assert from 'assert';
|
||||
import { ExactValueMatcher, HeaderMatcher } from '../src/matcher';
|
||||
|
||||
interface TestCase {
|
||||
rule: rbac.RbacRule<any>,
|
||||
input: any,
|
||||
expectedResult: boolean
|
||||
}
|
||||
|
||||
function createMetadata(key: string, value: string): Metadata {
|
||||
const metadata = new Metadata();
|
||||
metadata.set(key, value);
|
||||
return metadata;
|
||||
}
|
||||
|
||||
const testCases: TestCase[] = [
|
||||
{
|
||||
rule: new rbac.AnyRule(),
|
||||
input: {},
|
||||
expectedResult: true
|
||||
},
|
||||
{
|
||||
rule: new rbac.NoneRule(),
|
||||
input: {},
|
||||
expectedResult: false
|
||||
},
|
||||
{
|
||||
rule: new rbac.OrRules([new rbac.NoneRule(), new rbac.NoneRule(), new rbac.NoneRule()]),
|
||||
input: {},
|
||||
expectedResult: false
|
||||
},
|
||||
{
|
||||
rule: new rbac.OrRules([new rbac.NoneRule(), new rbac.NoneRule(), new rbac.AnyRule()]),
|
||||
input: {},
|
||||
expectedResult: true
|
||||
},
|
||||
{
|
||||
rule: new rbac.AndRules([new rbac.AnyRule(), new rbac.AnyRule(), new rbac.AnyRule()]),
|
||||
input: {},
|
||||
expectedResult: true
|
||||
},
|
||||
{
|
||||
rule: new rbac.AndRules([new rbac.AnyRule(), new rbac.AnyRule(), new rbac.NoneRule()]),
|
||||
input: {},
|
||||
expectedResult: false
|
||||
},
|
||||
{
|
||||
rule: new rbac.NotRule(new rbac.NoneRule()),
|
||||
input: {},
|
||||
expectedResult: true
|
||||
},
|
||||
{
|
||||
rule: new rbac.NotRule(new rbac.AnyRule()),
|
||||
input: {},
|
||||
expectedResult: false
|
||||
},
|
||||
{
|
||||
rule: new rbac.DestinationIpPermission({addressPrefix: '127.0.0.0', prefixLen: 8}),
|
||||
input: {
|
||||
headers: new Metadata(),
|
||||
urlPath: '/',
|
||||
destinationIp: '127.0.0.1',
|
||||
destinationPort: 443
|
||||
},
|
||||
expectedResult: true
|
||||
},
|
||||
{
|
||||
rule: new rbac.DestinationIpPermission({addressPrefix: '127.0.0.0', prefixLen: 8}),
|
||||
input: {
|
||||
headers: new Metadata(),
|
||||
urlPath: '/',
|
||||
destinationIp: '10.0.0.1',
|
||||
destinationPort: 443
|
||||
},
|
||||
expectedResult: false
|
||||
},
|
||||
{
|
||||
rule: new rbac.DestinationPortPermission(443),
|
||||
input: {
|
||||
headers: new Metadata(),
|
||||
urlPath: '/',
|
||||
destinationIp: '127.0.0.1',
|
||||
destinationPort: 443
|
||||
},
|
||||
expectedResult: true
|
||||
},
|
||||
{
|
||||
rule: new rbac.DestinationPortPermission(443),
|
||||
input: {
|
||||
headers: new Metadata(),
|
||||
urlPath: '/',
|
||||
destinationIp: '127.0.0.1',
|
||||
destinationPort: 80
|
||||
},
|
||||
expectedResult: false
|
||||
},
|
||||
{
|
||||
rule: new rbac.UrlPathPermission(new ExactValueMatcher('/', false)),
|
||||
input: {
|
||||
headers: new Metadata(),
|
||||
urlPath: '/',
|
||||
destinationIp: '127.0.0.1',
|
||||
destinationPort: 443
|
||||
},
|
||||
expectedResult: true
|
||||
},
|
||||
{
|
||||
rule: new rbac.UrlPathPermission(new ExactValueMatcher('/', false)),
|
||||
input: {
|
||||
headers: new Metadata(),
|
||||
urlPath: '/service/method',
|
||||
destinationIp: '127.0.0.1',
|
||||
destinationPort: 443
|
||||
},
|
||||
expectedResult: false
|
||||
},
|
||||
{
|
||||
rule: new rbac.HeaderPermission(new HeaderMatcher('test', new ExactValueMatcher('value', false), false)),
|
||||
input: {
|
||||
headers: createMetadata('test', 'value'),
|
||||
urlPath: '/',
|
||||
destinationIp: '127.0.0.1',
|
||||
destinationPort: 443
|
||||
},
|
||||
expectedResult: true
|
||||
},
|
||||
{
|
||||
rule: new rbac.HeaderPermission(new HeaderMatcher('test', new ExactValueMatcher('value', false), false)),
|
||||
input: {
|
||||
headers: createMetadata('test', 'incorrect'),
|
||||
urlPath: '/',
|
||||
destinationIp: '127.0.0.1',
|
||||
destinationPort: 443
|
||||
},
|
||||
expectedResult: false
|
||||
},
|
||||
{
|
||||
rule: new rbac.MetadataPermission(),
|
||||
input: {
|
||||
headers: new Metadata(),
|
||||
urlPath: '/',
|
||||
destinationIp: '127.0.0.1',
|
||||
destinationPort: 443
|
||||
},
|
||||
expectedResult: false
|
||||
},
|
||||
{
|
||||
rule: new rbac.RequestedServerNamePermission(new ExactValueMatcher('', false)),
|
||||
input: {
|
||||
headers: new Metadata(),
|
||||
urlPath: '/',
|
||||
destinationIp: '127.0.0.1',
|
||||
destinationPort: 443
|
||||
},
|
||||
expectedResult: true
|
||||
},
|
||||
{
|
||||
rule: new rbac.RequestedServerNamePermission(new ExactValueMatcher('test', false)),
|
||||
input: {
|
||||
headers: new Metadata(),
|
||||
urlPath: '/',
|
||||
destinationIp: '127.0.0.1',
|
||||
destinationPort: 443
|
||||
},
|
||||
expectedResult: false
|
||||
},
|
||||
{
|
||||
rule: new rbac.AuthenticatedPrincipal(null),
|
||||
input: {
|
||||
tls: true,
|
||||
peerCertificate: null,
|
||||
sourceIp: '127.0.0.1',
|
||||
headers: new Metadata(),
|
||||
urlPath: '/'
|
||||
},
|
||||
expectedResult: true
|
||||
},
|
||||
{
|
||||
rule: new rbac.AuthenticatedPrincipal(null),
|
||||
input: {
|
||||
tls: false,
|
||||
peerCertificate: null,
|
||||
sourceIp: '127.0.0.1',
|
||||
headers: new Metadata(),
|
||||
urlPath: '/'
|
||||
},
|
||||
expectedResult: false
|
||||
},
|
||||
{
|
||||
rule: new rbac.AuthenticatedPrincipal(new ExactValueMatcher('test', false)),
|
||||
input: {
|
||||
tls: true,
|
||||
peerCertificate: {
|
||||
subject: {
|
||||
C: '',
|
||||
ST: '',
|
||||
L: '',
|
||||
O: '',
|
||||
OU: '',
|
||||
CN: ''
|
||||
},
|
||||
subjectaltname: 'URI:test'
|
||||
},
|
||||
sourceIp: '127.0.0.1',
|
||||
headers: new Metadata(),
|
||||
urlPath: '/'
|
||||
},
|
||||
expectedResult: true
|
||||
},
|
||||
{
|
||||
rule: new rbac.AuthenticatedPrincipal(new ExactValueMatcher('test', false)),
|
||||
input: {
|
||||
tls: true,
|
||||
peerCertificate: {
|
||||
subject: {
|
||||
C: '',
|
||||
ST: '',
|
||||
L: '',
|
||||
O: '',
|
||||
OU: '',
|
||||
CN: ''
|
||||
},
|
||||
subjectaltname: 'DNS:test'
|
||||
},
|
||||
sourceIp: '127.0.0.1',
|
||||
headers: new Metadata(),
|
||||
urlPath: '/'
|
||||
},
|
||||
expectedResult: true
|
||||
},
|
||||
{
|
||||
rule: new rbac.AuthenticatedPrincipal(new ExactValueMatcher('test', false)),
|
||||
input: {
|
||||
tls: true,
|
||||
peerCertificate: {
|
||||
subject: {
|
||||
C: '',
|
||||
ST: '',
|
||||
L: '',
|
||||
O: '',
|
||||
OU: '',
|
||||
CN: ''
|
||||
},
|
||||
subjectaltname: 'URI:incorrect, DNS:test'
|
||||
},
|
||||
sourceIp: '127.0.0.1',
|
||||
headers: new Metadata(),
|
||||
urlPath: '/'
|
||||
},
|
||||
expectedResult: false
|
||||
},
|
||||
{
|
||||
rule: new rbac.AuthenticatedPrincipal(new ExactValueMatcher('test', false)),
|
||||
input: {
|
||||
tls: true,
|
||||
peerCertificate: {
|
||||
subject: {
|
||||
C: '',
|
||||
ST: '',
|
||||
L: '',
|
||||
O: '',
|
||||
OU: '',
|
||||
CN: 'test'
|
||||
},
|
||||
},
|
||||
sourceIp: '127.0.0.1',
|
||||
headers: new Metadata(),
|
||||
urlPath: '/'
|
||||
},
|
||||
expectedResult: true
|
||||
},
|
||||
{
|
||||
rule: new rbac.SourceIpPrincipal({addressPrefix: '127.0.0.0', prefixLen: 8}),
|
||||
input: {
|
||||
tls: true,
|
||||
peerCertificate: null,
|
||||
sourceIp: '127.0.0.1',
|
||||
headers: new Metadata(),
|
||||
urlPath: '/'
|
||||
},
|
||||
expectedResult: true
|
||||
},
|
||||
{
|
||||
rule: new rbac.SourceIpPrincipal({addressPrefix: '127.0.0.0', prefixLen: 8}),
|
||||
input: {
|
||||
tls: true,
|
||||
peerCertificate: null,
|
||||
sourceIp: '10.0.0.1',
|
||||
headers: new Metadata(),
|
||||
urlPath: '/'
|
||||
},
|
||||
expectedResult: false
|
||||
},
|
||||
{
|
||||
rule: new rbac.HeaderPrincipal(new HeaderMatcher('test', new ExactValueMatcher('value', false), false)),
|
||||
input: {
|
||||
tls: true,
|
||||
peerCertificate: null,
|
||||
sourceIp: '127.0.0.1',
|
||||
headers: createMetadata('test', 'value'),
|
||||
urlPath: '/'
|
||||
},
|
||||
expectedResult: true
|
||||
},
|
||||
{
|
||||
rule: new rbac.HeaderPrincipal(new HeaderMatcher('test', new ExactValueMatcher('value', false), false)),
|
||||
input: {
|
||||
tls: true,
|
||||
peerCertificate: null,
|
||||
sourceIp: '127.0.0.1',
|
||||
headers: createMetadata('test', 'incorrect'),
|
||||
urlPath: '/'
|
||||
},
|
||||
expectedResult: false
|
||||
},
|
||||
{
|
||||
rule: new rbac.UrlPathPrincipal(new ExactValueMatcher('/', false)),
|
||||
input: {
|
||||
tls: true,
|
||||
peerCertificate: null,
|
||||
sourceIp: '127.0.0.1',
|
||||
headers: new Metadata(),
|
||||
urlPath: '/'
|
||||
},
|
||||
expectedResult: true
|
||||
},
|
||||
{
|
||||
rule: new rbac.UrlPathPrincipal(new ExactValueMatcher('/', false)),
|
||||
input: {
|
||||
tls: true,
|
||||
peerCertificate: null,
|
||||
sourceIp: '127.0.0.1',
|
||||
headers: new Metadata(),
|
||||
urlPath: '/service/method'
|
||||
},
|
||||
expectedResult: false
|
||||
},
|
||||
{
|
||||
rule: new rbac.MetadataPrincipal(),
|
||||
input: {
|
||||
tls: true,
|
||||
peerCertificate: null,
|
||||
sourceIp: '127.0.0.1',
|
||||
headers: new Metadata(),
|
||||
urlPath: '/'
|
||||
},
|
||||
expectedResult: false
|
||||
},
|
||||
];
|
||||
|
||||
describe('RBAC engine', () => {
|
||||
for (const testCase of testCases) {
|
||||
it(`rule=${testCase.rule.toString()} input=${JSON.stringify(testCase.input)} result=${testCase.expectedResult}`, () => {
|
||||
assert.strictEqual(testCase.rule.apply(testCase.input), testCase.expectedResult);
|
||||
});
|
||||
}
|
||||
});
|
|
@ -54,7 +54,9 @@ const loadedProtos = loadPackageDefinition(loadSync(
|
|||
'envoy/extensions/load_balancing_policies/ring_hash/v3/ring_hash.proto',
|
||||
'envoy/extensions/load_balancing_policies/pick_first/v3/pick_first.proto',
|
||||
'envoy/extensions/transport_sockets/tls/v3/tls.proto',
|
||||
'xds/type/v3/typed_struct.proto'
|
||||
'xds/type/v3/typed_struct.proto',
|
||||
'envoy/extensions/filters/http/router/v3/router.proto',
|
||||
'envoy/extensions/filters/http/rbac/v3/rbac.proto'
|
||||
],
|
||||
{
|
||||
keepCase: true,
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright 2025 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 { PeerCertificate } from "tls";
|
||||
|
||||
export interface AuthContext {
|
||||
transportSecurityType?: string;
|
||||
sslPeerCertificate?: PeerCertificate;
|
||||
}
|
|
@ -15,6 +15,7 @@
|
|||
*
|
||||
*/
|
||||
|
||||
import { AuthContext } from './auth-context';
|
||||
import { CallCredentials } from './call-credentials';
|
||||
import { Status } from './constants';
|
||||
import { Deadline } from './deadline';
|
||||
|
@ -40,6 +41,35 @@ export type PartialStatusObject = Pick<StatusObject, 'code' | 'details'> & {
|
|||
metadata?: Metadata | null | undefined;
|
||||
};
|
||||
|
||||
export interface StatusOrOk<T> {
|
||||
ok: true;
|
||||
value: T;
|
||||
}
|
||||
|
||||
export interface StatusOrError {
|
||||
ok: false;
|
||||
error: StatusObject;
|
||||
}
|
||||
|
||||
export type StatusOr<T> = StatusOrOk<T> | StatusOrError;
|
||||
|
||||
export function statusOrFromValue<T>(value: T): StatusOr<T> {
|
||||
return {
|
||||
ok: true,
|
||||
value: value
|
||||
};
|
||||
}
|
||||
|
||||
export function statusOrFromError<T>(error: PartialStatusObject): StatusOr<T> {
|
||||
return {
|
||||
ok: false,
|
||||
error: {
|
||||
...error,
|
||||
metadata: error.metadata ?? new Metadata()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const enum WriteFlags {
|
||||
BufferHint = 1,
|
||||
NoCompress = 2,
|
||||
|
@ -170,6 +200,7 @@ export interface Call {
|
|||
halfClose(): void;
|
||||
getCallNumber(): number;
|
||||
setCredentials(credentials: CallCredentials): void;
|
||||
getAuthContext(): AuthContext | null;
|
||||
}
|
||||
|
||||
export interface DeadlineInfoProvider {
|
||||
|
|
|
@ -24,6 +24,7 @@ import { EmitterAugmentation1 } from './events';
|
|||
import { Metadata } from './metadata';
|
||||
import { ObjectReadable, ObjectWritable, WriteCallback } from './object-stream';
|
||||
import { InterceptingCallInterface } from './client-interceptors';
|
||||
import { AuthContext } from './auth-context';
|
||||
|
||||
/**
|
||||
* A type extending the built-in Error object with additional fields.
|
||||
|
@ -37,6 +38,7 @@ export type SurfaceCall = {
|
|||
call?: InterceptingCallInterface;
|
||||
cancel(): void;
|
||||
getPeer(): string;
|
||||
getAuthContext(): AuthContext | null;
|
||||
} & EmitterAugmentation1<'metadata', Metadata> &
|
||||
EmitterAugmentation1<'status', StatusObject> &
|
||||
EventEmitter;
|
||||
|
@ -100,6 +102,10 @@ export class ClientUnaryCallImpl
|
|||
getPeer(): string {
|
||||
return this.call?.getPeer() ?? 'unknown';
|
||||
}
|
||||
|
||||
getAuthContext(): AuthContext | null {
|
||||
return this.call?.getAuthContext() ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
export class ClientReadableStreamImpl<ResponseType>
|
||||
|
@ -119,6 +125,10 @@ export class ClientReadableStreamImpl<ResponseType>
|
|||
return this.call?.getPeer() ?? 'unknown';
|
||||
}
|
||||
|
||||
getAuthContext(): AuthContext | null {
|
||||
return this.call?.getAuthContext() ?? null;
|
||||
}
|
||||
|
||||
_read(_size: number): void {
|
||||
this.call?.startRead();
|
||||
}
|
||||
|
@ -141,6 +151,10 @@ export class ClientWritableStreamImpl<RequestType>
|
|||
return this.call?.getPeer() ?? 'unknown';
|
||||
}
|
||||
|
||||
getAuthContext(): AuthContext | null {
|
||||
return this.call?.getAuthContext() ?? null;
|
||||
}
|
||||
|
||||
_write(chunk: RequestType, encoding: string, cb: WriteCallback) {
|
||||
const context: MessageContext = {
|
||||
callback: cb,
|
||||
|
@ -178,6 +192,10 @@ export class ClientDuplexStreamImpl<RequestType, ResponseType>
|
|||
return this.call?.getPeer() ?? 'unknown';
|
||||
}
|
||||
|
||||
getAuthContext(): AuthContext | null {
|
||||
return this.call?.getAuthContext() ?? null;
|
||||
}
|
||||
|
||||
_read(_size: number): void {
|
||||
this.call?.startRead();
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ import { Channel } from './channel';
|
|||
import { CallOptions } from './client';
|
||||
import { ClientMethodDefinition } from './make-client';
|
||||
import { getErrorMessage } from './error';
|
||||
import { AuthContext } from './auth-context';
|
||||
|
||||
/**
|
||||
* Error class associated with passing both interceptors and interceptor
|
||||
|
@ -198,6 +199,7 @@ export interface InterceptingCallInterface {
|
|||
sendMessage(message: any): void;
|
||||
startRead(): void;
|
||||
halfClose(): void;
|
||||
getAuthContext(): AuthContext | null;
|
||||
}
|
||||
|
||||
export class InterceptingCall implements InterceptingCallInterface {
|
||||
|
@ -338,6 +340,9 @@ export class InterceptingCall implements InterceptingCallInterface {
|
|||
}
|
||||
});
|
||||
}
|
||||
getAuthContext(): AuthContext | null {
|
||||
return this.nextCall.getAuthContext();
|
||||
}
|
||||
}
|
||||
|
||||
function getCall(channel: Channel, path: string, options: CallOptions): Call {
|
||||
|
@ -427,6 +432,9 @@ class BaseInterceptingCall implements InterceptingCallInterface {
|
|||
halfClose(): void {
|
||||
this.call.halfClose();
|
||||
}
|
||||
getAuthContext(): AuthContext | null {
|
||||
return this.call.getAuthContext();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -5,6 +5,7 @@ export {
|
|||
registerResolver,
|
||||
ConfigSelector,
|
||||
createResolver,
|
||||
CHANNEL_ARGS_CONFIG_SELECTOR_KEY,
|
||||
} from './resolver';
|
||||
export { GrpcUri, uriToString, splitHostPort, HostPort } from './uri-parser';
|
||||
export { Duration, durationToMs, parseDuration } from './duration';
|
||||
|
@ -37,7 +38,12 @@ export {
|
|||
PickArgs,
|
||||
PickResultType,
|
||||
} from './picker';
|
||||
export { Call as CallStream } from './call-interface';
|
||||
export {
|
||||
Call as CallStream,
|
||||
StatusOr,
|
||||
statusOrFromValue,
|
||||
statusOrFromError
|
||||
} from './call-interface';
|
||||
export { Filter, BaseFilter, FilterFactory } from './filter';
|
||||
export { FilterStackFactory } from './filter-stack';
|
||||
export { registerAdminService } from './admin';
|
||||
|
|
|
@ -27,6 +27,7 @@ import { ConnectivityState } from './connectivity-state';
|
|||
import { Picker } from './picker';
|
||||
import type { ChannelRef, SubchannelRef } from './channelz';
|
||||
import { SubchannelInterface } from './subchannel-interface';
|
||||
import { StatusOr } from './call-interface';
|
||||
|
||||
const TYPE_NAME = 'child_load_balancer_helper';
|
||||
|
||||
|
@ -102,10 +103,11 @@ export class ChildLoadBalancerHandler {
|
|||
* @param attributes
|
||||
*/
|
||||
updateAddressList(
|
||||
endpointList: Endpoint[],
|
||||
endpointList: StatusOr<Endpoint[]>,
|
||||
lbConfig: TypedLoadBalancingConfig,
|
||||
options: ChannelOptions
|
||||
): void {
|
||||
options: ChannelOptions,
|
||||
resolutionNote: string
|
||||
): boolean {
|
||||
let childToUpdate: LoadBalancer;
|
||||
if (
|
||||
this.currentChild === null ||
|
||||
|
@ -133,7 +135,7 @@ export class ChildLoadBalancerHandler {
|
|||
}
|
||||
}
|
||||
this.latestConfig = lbConfig;
|
||||
childToUpdate.updateAddressList(endpointList, lbConfig, options);
|
||||
return childToUpdate.updateAddressList(endpointList, lbConfig, options, resolutionNote);
|
||||
}
|
||||
exitIdle(): void {
|
||||
if (this.currentChild) {
|
||||
|
|
|
@ -43,6 +43,7 @@ import {
|
|||
} from './subchannel-interface';
|
||||
import * as logging from './logging';
|
||||
import { LoadBalancingConfig } from './service-config';
|
||||
import { StatusOr } from './call-interface';
|
||||
|
||||
const TRACER_NAME = 'outlier_detection';
|
||||
|
||||
|
@ -757,28 +758,31 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer {
|
|||
}
|
||||
|
||||
updateAddressList(
|
||||
endpointList: Endpoint[],
|
||||
endpointList: StatusOr<Endpoint[]>,
|
||||
lbConfig: TypedLoadBalancingConfig,
|
||||
options: ChannelOptions
|
||||
): void {
|
||||
options: ChannelOptions,
|
||||
resolutionNote: string
|
||||
): boolean {
|
||||
if (!(lbConfig instanceof OutlierDetectionLoadBalancingConfig)) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
trace('Received update with config: ' + JSON.stringify(lbConfig.toJsonObject(), undefined, 2))
|
||||
for (const endpoint of endpointList) {
|
||||
if (!this.entryMap.has(endpoint)) {
|
||||
trace('Adding map entry for ' + endpointToString(endpoint));
|
||||
this.entryMap.set(endpoint, {
|
||||
counter: new CallCounter(),
|
||||
currentEjectionTimestamp: null,
|
||||
ejectionTimeMultiplier: 0,
|
||||
subchannelWrappers: [],
|
||||
});
|
||||
trace('Received update with config: ' + JSON.stringify(lbConfig.toJsonObject(), undefined, 2));
|
||||
if (endpointList.ok) {
|
||||
for (const endpoint of endpointList.value) {
|
||||
if (!this.entryMap.has(endpoint)) {
|
||||
trace('Adding map entry for ' + endpointToString(endpoint));
|
||||
this.entryMap.set(endpoint, {
|
||||
counter: new CallCounter(),
|
||||
currentEjectionTimestamp: null,
|
||||
ejectionTimeMultiplier: 0,
|
||||
subchannelWrappers: [],
|
||||
});
|
||||
}
|
||||
}
|
||||
this.entryMap.deleteMissing(endpointList.value);
|
||||
}
|
||||
this.entryMap.deleteMissing(endpointList);
|
||||
const childPolicy = lbConfig.getChildPolicy();
|
||||
this.childBalancer.updateAddressList(endpointList, childPolicy, options);
|
||||
this.childBalancer.updateAddressList(endpointList, childPolicy, options, resolutionNote);
|
||||
|
||||
if (
|
||||
lbConfig.getSuccessRateEjectionConfig() ||
|
||||
|
@ -808,6 +812,7 @@ export class OutlierDetectionLoadBalancer implements LoadBalancer {
|
|||
}
|
||||
|
||||
this.latestConfig = lbConfig;
|
||||
return true;
|
||||
}
|
||||
exitIdle(): void {
|
||||
this.childBalancer.exitIdle();
|
||||
|
|
|
@ -43,6 +43,7 @@ import {
|
|||
import { isTcpSubchannelAddress } from './subchannel-address';
|
||||
import { isIPv6 } from 'net';
|
||||
import { ChannelOptions } from './channel-options';
|
||||
import { StatusOr, statusOrFromValue } from './call-interface';
|
||||
|
||||
const TRACER_NAME = 'pick_first';
|
||||
|
||||
|
@ -236,6 +237,8 @@ export class PickFirstLoadBalancer implements LoadBalancer {
|
|||
|
||||
private latestOptions: ChannelOptions = {};
|
||||
|
||||
private latestResolutionNote: string = '';
|
||||
|
||||
/**
|
||||
* Load balancer that attempts to connect to each backend in the address list
|
||||
* in order, and picks the first one that connects, using it for every
|
||||
|
@ -277,7 +280,7 @@ export class PickFirstLoadBalancer implements LoadBalancer {
|
|||
);
|
||||
}
|
||||
} else if (this.latestAddressList?.length === 0) {
|
||||
const errorMessage = `No connection established. Last error: ${this.lastError}`;
|
||||
const errorMessage = `No connection established. Last error: ${this.lastError}. Resolution note: ${this.latestResolutionNote}`;
|
||||
this.updateState(
|
||||
ConnectivityState.TRANSIENT_FAILURE,
|
||||
new UnavailablePicker({
|
||||
|
@ -289,7 +292,7 @@ export class PickFirstLoadBalancer implements LoadBalancer {
|
|||
this.updateState(ConnectivityState.IDLE, new QueuePicker(this), null);
|
||||
} else {
|
||||
if (this.stickyTransientFailureMode) {
|
||||
const errorMessage = `No connection established. Last error: ${this.lastError}`;
|
||||
const errorMessage = `No connection established. Last error: ${this.lastError}. Resolution note: ${this.latestResolutionNote}`;
|
||||
this.updateState(
|
||||
ConnectivityState.TRANSIENT_FAILURE,
|
||||
new UnavailablePicker({
|
||||
|
@ -505,13 +508,25 @@ export class PickFirstLoadBalancer implements LoadBalancer {
|
|||
}
|
||||
|
||||
updateAddressList(
|
||||
endpointList: Endpoint[],
|
||||
maybeEndpointList: StatusOr<Endpoint[]>,
|
||||
lbConfig: TypedLoadBalancingConfig,
|
||||
options: ChannelOptions
|
||||
): void {
|
||||
options: ChannelOptions,
|
||||
resolutionNote: string
|
||||
): boolean {
|
||||
if (!(lbConfig instanceof PickFirstLoadBalancingConfig)) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if (!maybeEndpointList.ok) {
|
||||
if (this.children.length === 0 && this.currentPick === null) {
|
||||
this.channelControlHelper.updateState(
|
||||
ConnectivityState.TRANSIENT_FAILURE,
|
||||
new UnavailablePicker(maybeEndpointList.error),
|
||||
maybeEndpointList.error.details
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
let endpointList = maybeEndpointList.value;
|
||||
this.reportHealthStatus = options[REPORT_HEALTH_STATUS_OPTION_NAME];
|
||||
/* Previously, an update would be discarded if it was identical to the
|
||||
* previous update, to minimize churn. Now the DNS resolver is
|
||||
|
@ -523,13 +538,17 @@ export class PickFirstLoadBalancer implements LoadBalancer {
|
|||
...endpointList.map(endpoint => endpoint.addresses)
|
||||
);
|
||||
trace('updateAddressList([' + rawAddressList.map(address => subchannelAddressToString(address)) + '])');
|
||||
if (rawAddressList.length === 0) {
|
||||
this.lastError = 'No addresses resolved';
|
||||
}
|
||||
const addressList = interleaveAddressFamilies(rawAddressList);
|
||||
this.latestAddressList = addressList;
|
||||
this.latestOptions = options;
|
||||
this.connectToAddressList(addressList, options);
|
||||
this.latestResolutionNote = resolutionNote;
|
||||
if (rawAddressList.length > 0) {
|
||||
return true;
|
||||
} else {
|
||||
this.lastError = 'No addresses resolved';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
exitIdle() {
|
||||
|
@ -570,7 +589,8 @@ export class LeafLoadBalancer {
|
|||
constructor(
|
||||
private endpoint: Endpoint,
|
||||
channelControlHelper: ChannelControlHelper,
|
||||
private options: ChannelOptions
|
||||
private options: ChannelOptions,
|
||||
private resolutionNote: string
|
||||
) {
|
||||
const childChannelControlHelper = createChildChannelControlHelper(
|
||||
channelControlHelper,
|
||||
|
@ -590,9 +610,10 @@ export class LeafLoadBalancer {
|
|||
|
||||
startConnecting() {
|
||||
this.pickFirstBalancer.updateAddressList(
|
||||
[this.endpoint],
|
||||
statusOrFromValue([this.endpoint]),
|
||||
LEAF_CONFIG,
|
||||
{ ...this.options, [REPORT_HEALTH_STATUS_OPTION_NAME]: true }
|
||||
{ ...this.options, [REPORT_HEALTH_STATUS_OPTION_NAME]: true },
|
||||
this.resolutionNote
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -39,6 +39,7 @@ import {
|
|||
} from './subchannel-address';
|
||||
import { LeafLoadBalancer } from './load-balancer-pick-first';
|
||||
import { ChannelOptions } from './channel-options';
|
||||
import { StatusOr } from './call-interface';
|
||||
|
||||
const TRACER_NAME = 'round_robin';
|
||||
|
||||
|
@ -205,14 +206,38 @@ export class RoundRobinLoadBalancer implements LoadBalancer {
|
|||
for (const child of this.children) {
|
||||
child.destroy();
|
||||
}
|
||||
this.children = [];
|
||||
}
|
||||
|
||||
updateAddressList(
|
||||
endpointList: Endpoint[],
|
||||
maybeEndpointList: StatusOr<Endpoint[]>,
|
||||
lbConfig: TypedLoadBalancingConfig,
|
||||
options: ChannelOptions
|
||||
): void {
|
||||
options: ChannelOptions,
|
||||
resolutionNote: string
|
||||
): boolean {
|
||||
if (!(lbConfig instanceof RoundRobinLoadBalancingConfig)) {
|
||||
return false;
|
||||
}
|
||||
if (!maybeEndpointList.ok) {
|
||||
if (this.children.length === 0) {
|
||||
this.updateState(
|
||||
ConnectivityState.TRANSIENT_FAILURE,
|
||||
new UnavailablePicker(maybeEndpointList.error),
|
||||
maybeEndpointList.error.details
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
const endpointList = maybeEndpointList.value;
|
||||
this.resetSubchannelList();
|
||||
if (endpointList.length === 0) {
|
||||
const errorMessage = `No addresses resolved. Resolution note: ${resolutionNote}`;
|
||||
this.updateState(
|
||||
ConnectivityState.TRANSIENT_FAILURE,
|
||||
new UnavailablePicker({details: errorMessage}),
|
||||
errorMessage
|
||||
);
|
||||
}
|
||||
trace('Connect to endpoint list ' + endpointList.map(endpointToString));
|
||||
this.updatesPaused = true;
|
||||
this.children = endpointList.map(
|
||||
|
@ -220,7 +245,8 @@ export class RoundRobinLoadBalancer implements LoadBalancer {
|
|||
new LeafLoadBalancer(
|
||||
endpoint,
|
||||
this.childChannelControlHelper,
|
||||
options
|
||||
options,
|
||||
resolutionNote
|
||||
)
|
||||
);
|
||||
for (const child of this.children) {
|
||||
|
@ -228,6 +254,7 @@ export class RoundRobinLoadBalancer implements LoadBalancer {
|
|||
}
|
||||
this.updatesPaused = false;
|
||||
this.calculateAndUpdateState();
|
||||
return true;
|
||||
}
|
||||
|
||||
exitIdle(): void {
|
||||
|
|
|
@ -24,6 +24,7 @@ import { SubchannelInterface } from './subchannel-interface';
|
|||
import { LoadBalancingConfig } from './service-config';
|
||||
import { log } from './logging';
|
||||
import { LogVerbosity } from './constants';
|
||||
import { StatusOr } from './call-interface';
|
||||
|
||||
/**
|
||||
* A collection of functions associated with a channel that a load balancer
|
||||
|
@ -102,12 +103,16 @@ export interface LoadBalancer {
|
|||
* @param endpointList The new list of addresses to connect to
|
||||
* @param lbConfig The load balancing config object from the service config,
|
||||
* if one was provided
|
||||
* @param channelOptions Channel options from the channel, plus resolver
|
||||
* attributes
|
||||
* @param resolutionNote A not from the resolver to include in errors
|
||||
*/
|
||||
updateAddressList(
|
||||
endpointList: Endpoint[],
|
||||
endpointList: StatusOr<Endpoint[]>,
|
||||
lbConfig: TypedLoadBalancingConfig,
|
||||
channelOptions: ChannelOptions
|
||||
): void;
|
||||
channelOptions: ChannelOptions,
|
||||
resolutionNote: string
|
||||
): boolean;
|
||||
/**
|
||||
* If the load balancer is currently in the IDLE state, start connecting.
|
||||
*/
|
||||
|
|
|
@ -35,6 +35,7 @@ import { splitHostPort } from './uri-parser';
|
|||
import * as logging from './logging';
|
||||
import { restrictControlPlaneStatusCode } from './control-plane-status';
|
||||
import * as http2 from 'http2';
|
||||
import { AuthContext } from './auth-context';
|
||||
|
||||
const TRACER_NAME = 'load_balancing_call';
|
||||
|
||||
|
@ -375,4 +376,12 @@ export class LoadBalancingCall implements Call, DeadlineInfoProvider {
|
|||
getCallNumber(): number {
|
||||
return this.callNumber;
|
||||
}
|
||||
|
||||
getAuthContext(): AuthContext | null {
|
||||
if (this.child) {
|
||||
return this.child.getAuthContext();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ import * as http2 from 'http2';
|
|||
import { log } from './logging';
|
||||
import { LogVerbosity } from './constants';
|
||||
import { getErrorMessage } from './error';
|
||||
const LEGAL_KEY_REGEX = /^[0-9a-z_.-]+$/;
|
||||
const LEGAL_KEY_REGEX = /^[:0-9a-z_.-]+$/;
|
||||
const LEGAL_NON_BINARY_VALUE_REGEX = /^[ -~]*$/;
|
||||
|
||||
export type MetadataValue = string | Buffer;
|
||||
|
@ -222,6 +222,9 @@ export class Metadata {
|
|||
const result: http2.OutgoingHttpHeaders = {};
|
||||
|
||||
for (const [key, values] of this.internalRepr) {
|
||||
if (key.startsWith(':')) {
|
||||
continue;
|
||||
}
|
||||
// We assume that the user's interaction with this object is limited to
|
||||
// through its public API (i.e. keys and values are already validated).
|
||||
result[key] = values.map(bufToString);
|
||||
|
|
|
@ -23,7 +23,7 @@ import {
|
|||
import { promises as dns } from 'dns';
|
||||
import { extractAndSelectServiceConfig, ServiceConfig } from './service-config';
|
||||
import { Status } from './constants';
|
||||
import { StatusObject } from './call-interface';
|
||||
import { StatusObject, StatusOr, statusOrFromError, statusOrFromValue } from './call-interface';
|
||||
import { Metadata } from './metadata';
|
||||
import * as logging from './logging';
|
||||
import { LogVerbosity } from './constants';
|
||||
|
@ -62,9 +62,8 @@ class DnsResolver implements Resolver {
|
|||
private readonly minTimeBetweenResolutionsMs: number;
|
||||
private pendingLookupPromise: Promise<TcpSubchannelAddress[]> | null = null;
|
||||
private pendingTxtPromise: Promise<string[][]> | null = null;
|
||||
private latestLookupResult: Endpoint[] | null = null;
|
||||
private latestServiceConfig: ServiceConfig | null = null;
|
||||
private latestServiceConfigError: StatusObject | null = null;
|
||||
private latestLookupResult: StatusOr<Endpoint[]> | null = null;
|
||||
private latestServiceConfigResult: StatusOr<ServiceConfig> | null = null;
|
||||
private percentage: number;
|
||||
private defaultResolutionError: StatusObject;
|
||||
private backoff: BackoffTimeout;
|
||||
|
@ -149,13 +148,12 @@ class DnsResolver implements Resolver {
|
|||
if (!this.returnedIpResult) {
|
||||
trace('Returning IP address for target ' + uriToString(this.target));
|
||||
setImmediate(() => {
|
||||
this.listener.onSuccessfulResolution(
|
||||
this.ipResult!,
|
||||
this.listener(
|
||||
statusOrFromValue(this.ipResult!),
|
||||
{},
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
{}
|
||||
);
|
||||
''
|
||||
)
|
||||
});
|
||||
this.returnedIpResult = true;
|
||||
}
|
||||
|
@ -167,11 +165,15 @@ class DnsResolver implements Resolver {
|
|||
if (this.dnsHostname === null) {
|
||||
trace('Failed to parse DNS address ' + uriToString(this.target));
|
||||
setImmediate(() => {
|
||||
this.listener.onError({
|
||||
code: Status.UNAVAILABLE,
|
||||
details: `Failed to parse DNS address ${uriToString(this.target)}`,
|
||||
metadata: new Metadata(),
|
||||
});
|
||||
this.listener(
|
||||
statusOrFromError({
|
||||
code: Status.UNAVAILABLE,
|
||||
details: `Failed to parse DNS address ${uriToString(this.target)}`
|
||||
}),
|
||||
{},
|
||||
null,
|
||||
''
|
||||
);
|
||||
});
|
||||
this.stopNextResolutionTimer();
|
||||
} else {
|
||||
|
@ -194,11 +196,9 @@ class DnsResolver implements Resolver {
|
|||
return;
|
||||
}
|
||||
this.pendingLookupPromise = null;
|
||||
this.backoff.reset();
|
||||
this.backoff.stop();
|
||||
this.latestLookupResult = addressList.map(address => ({
|
||||
this.latestLookupResult = statusOrFromValue(addressList.map(address => ({
|
||||
addresses: [address],
|
||||
}));
|
||||
})));
|
||||
const allAddressesString: string =
|
||||
'[' +
|
||||
addressList.map(addr => addr.host + ':' + addr.port).join(',') +
|
||||
|
@ -209,21 +209,17 @@ class DnsResolver implements Resolver {
|
|||
': ' +
|
||||
allAddressesString
|
||||
);
|
||||
if (this.latestLookupResult.length === 0) {
|
||||
this.listener.onError(this.defaultResolutionError);
|
||||
return;
|
||||
}
|
||||
/* If the TXT lookup has not yet finished, both of the last two
|
||||
* arguments will be null, which is the equivalent of getting an
|
||||
* empty TXT response. When the TXT lookup does finish, its handler
|
||||
* can update the service config by using the same address list */
|
||||
this.listener.onSuccessfulResolution(
|
||||
const healthStatus = this.listener(
|
||||
this.latestLookupResult,
|
||||
this.latestServiceConfig,
|
||||
this.latestServiceConfigError,
|
||||
null,
|
||||
{}
|
||||
{},
|
||||
this.latestServiceConfigResult,
|
||||
''
|
||||
);
|
||||
this.handleHealthStatus(healthStatus);
|
||||
},
|
||||
err => {
|
||||
if (this.pendingLookupPromise === null) {
|
||||
|
@ -237,7 +233,12 @@ class DnsResolver implements Resolver {
|
|||
);
|
||||
this.pendingLookupPromise = null;
|
||||
this.stopNextResolutionTimer();
|
||||
this.listener.onError(this.defaultResolutionError);
|
||||
this.listener(
|
||||
statusOrFromError(this.defaultResolutionError),
|
||||
{},
|
||||
this.latestServiceConfigResult,
|
||||
''
|
||||
)
|
||||
}
|
||||
);
|
||||
/* If there already is a still-pending TXT resolution, we can just use
|
||||
|
@ -253,31 +254,35 @@ class DnsResolver implements Resolver {
|
|||
return;
|
||||
}
|
||||
this.pendingTxtPromise = null;
|
||||
let serviceConfig: ServiceConfig | null;
|
||||
try {
|
||||
this.latestServiceConfig = extractAndSelectServiceConfig(
|
||||
serviceConfig = extractAndSelectServiceConfig(
|
||||
txtRecord,
|
||||
this.percentage
|
||||
);
|
||||
if (serviceConfig) {
|
||||
this.latestServiceConfigResult = statusOrFromValue(serviceConfig);
|
||||
} else {
|
||||
this.latestServiceConfigResult = null;
|
||||
}
|
||||
} catch (err) {
|
||||
this.latestServiceConfigError = {
|
||||
this.latestServiceConfigResult = statusOrFromError({
|
||||
code: Status.UNAVAILABLE,
|
||||
details: `Parsing service config failed with error ${
|
||||
(err as Error).message
|
||||
}`,
|
||||
metadata: new Metadata(),
|
||||
};
|
||||
}`
|
||||
});
|
||||
}
|
||||
if (this.latestLookupResult !== null) {
|
||||
/* We rely here on the assumption that calling this function with
|
||||
* identical parameters will be essentialy idempotent, and calling
|
||||
* it with the same address list and a different service config
|
||||
* should result in a fast and seamless switchover. */
|
||||
this.listener.onSuccessfulResolution(
|
||||
this.listener(
|
||||
this.latestLookupResult,
|
||||
this.latestServiceConfig,
|
||||
this.latestServiceConfigError,
|
||||
null,
|
||||
{}
|
||||
{},
|
||||
this.latestServiceConfigResult,
|
||||
''
|
||||
);
|
||||
}
|
||||
},
|
||||
|
@ -295,6 +300,21 @@ class DnsResolver implements Resolver {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The ResolverListener returns a boolean indicating whether the LB policy
|
||||
* accepted the resolution result. A false result on an otherwise successful
|
||||
* resolution should be treated as a resolution failure.
|
||||
* @param healthStatus
|
||||
*/
|
||||
private handleHealthStatus(healthStatus: boolean) {
|
||||
if (healthStatus) {
|
||||
this.backoff.stop();
|
||||
this.backoff.reset();
|
||||
} else {
|
||||
this.continueResolving = true;
|
||||
}
|
||||
}
|
||||
|
||||
private async lookup(hostname: string): Promise<TcpSubchannelAddress[]> {
|
||||
if (GRPC_NODE_USE_ALTERNATIVE_RESOLVER) {
|
||||
trace('Using alternative DNS resolver.');
|
||||
|
@ -400,8 +420,7 @@ class DnsResolver implements Resolver {
|
|||
this.pendingLookupPromise = null;
|
||||
this.pendingTxtPromise = null;
|
||||
this.latestLookupResult = null;
|
||||
this.latestServiceConfig = null;
|
||||
this.latestServiceConfigError = null;
|
||||
this.latestServiceConfigResult = null;
|
||||
this.returnedIpResult = false;
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
|
||||
import { isIPv4, isIPv6 } from 'net';
|
||||
import { StatusObject } from './call-interface';
|
||||
import { StatusObject, statusOrFromError, statusOrFromValue } from './call-interface';
|
||||
import { ChannelOptions } from './channel-options';
|
||||
import { LogVerbosity, Status } from './constants';
|
||||
import { Metadata } from './metadata';
|
||||
|
@ -92,14 +92,18 @@ class IpResolver implements Resolver {
|
|||
this.hasReturnedResult = true;
|
||||
process.nextTick(() => {
|
||||
if (this.error) {
|
||||
this.listener.onError(this.error);
|
||||
this.listener(
|
||||
statusOrFromError(this.error),
|
||||
{},
|
||||
null,
|
||||
''
|
||||
);
|
||||
} else {
|
||||
this.listener.onSuccessfulResolution(
|
||||
this.endpoints,
|
||||
this.listener(
|
||||
statusOrFromValue(this.endpoints),
|
||||
{},
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
{}
|
||||
''
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -18,6 +18,7 @@ import { Resolver, ResolverListener, registerResolver } from './resolver';
|
|||
import { Endpoint } from './subchannel-address';
|
||||
import { GrpcUri } from './uri-parser';
|
||||
import { ChannelOptions } from './channel-options';
|
||||
import { statusOrFromValue } from './call-interface';
|
||||
|
||||
class UdsResolver implements Resolver {
|
||||
private hasReturnedResult = false;
|
||||
|
@ -39,12 +40,11 @@ class UdsResolver implements Resolver {
|
|||
if (!this.hasReturnedResult) {
|
||||
this.hasReturnedResult = true;
|
||||
process.nextTick(
|
||||
this.listener.onSuccessfulResolution,
|
||||
this.endpoints,
|
||||
this.listener,
|
||||
statusOrFromValue(this.endpoints),
|
||||
{},
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
{}
|
||||
''
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
*/
|
||||
|
||||
import { MethodConfig, ServiceConfig } from './service-config';
|
||||
import { StatusObject } from './call-interface';
|
||||
import { StatusOr } from './call-interface';
|
||||
import { Endpoint } from './subchannel-address';
|
||||
import { GrpcUri, uriToString } from './uri-parser';
|
||||
import { ChannelOptions } from './channel-options';
|
||||
|
@ -24,6 +24,8 @@ import { Metadata } from './metadata';
|
|||
import { Status } from './constants';
|
||||
import { Filter, FilterFactory } from './filter';
|
||||
|
||||
export const CHANNEL_ARGS_CONFIG_SELECTOR_KEY = 'grpc.internal.config_selector';
|
||||
|
||||
export interface CallConfig {
|
||||
methodConfig: MethodConfig;
|
||||
onCommitted?: () => void;
|
||||
|
@ -41,34 +43,27 @@ export interface ConfigSelector {
|
|||
unref(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* A listener object passed to the resolver's constructor that provides name
|
||||
* resolution updates back to the resolver's owner.
|
||||
*/
|
||||
export interface ResolverListener {
|
||||
/**
|
||||
* Called whenever the resolver has new name resolution results to report
|
||||
* @param addressList The new list of backend addresses
|
||||
* @param serviceConfig The new service configuration corresponding to the
|
||||
* `addressList`. Will be `null` if no service configuration was
|
||||
* retrieved or if the service configuration was invalid
|
||||
* @param serviceConfigError If non-`null`, indicates that the retrieved
|
||||
* service configuration was invalid
|
||||
* Called whenever the resolver has new name resolution results or an error to
|
||||
* report.
|
||||
* @param endpointList The list of endpoints, or an error if resolution failed
|
||||
* @param attributes Arbitrary key/value pairs to pass along to load balancing
|
||||
* policies
|
||||
* @param serviceConfig The service service config for the endpoint list, or an
|
||||
* error if the retrieved service config is invalid, or null if there is no
|
||||
* service config
|
||||
* @param resolutionNote Provides additional context to RPC failure status
|
||||
* messages generated by the load balancing policy.
|
||||
* @returns Whether or not the load balancing policy accepted the result.
|
||||
*/
|
||||
onSuccessfulResolution(
|
||||
addressList: Endpoint[],
|
||||
serviceConfig: ServiceConfig | null,
|
||||
serviceConfigError: StatusObject | null,
|
||||
configSelector: ConfigSelector | null,
|
||||
attributes: { [key: string]: unknown }
|
||||
): void;
|
||||
/**
|
||||
* Called whenever a name resolution attempt fails.
|
||||
* @param error Describes how resolution failed
|
||||
*/
|
||||
onError(error: StatusObject): void;
|
||||
(
|
||||
endpointList: StatusOr<Endpoint[]>,
|
||||
attributes: { [key: string]: unknown },
|
||||
serviceConfig: StatusOr<ServiceConfig> | null,
|
||||
resolutionNote: string
|
||||
): boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* A resolver class that handles one or more of the name syntax schemes defined
|
||||
* in the [gRPC Name Resolution document](https://github.com/grpc/grpc/blob/master/doc/naming.md)
|
||||
|
|
|
@ -37,6 +37,7 @@ import { InternalChannel } from './internal-channel';
|
|||
import { Metadata } from './metadata';
|
||||
import * as logging from './logging';
|
||||
import { restrictControlPlaneStatusCode } from './control-plane-status';
|
||||
import { AuthContext } from './auth-context';
|
||||
|
||||
const TRACER_NAME = 'resolving_call';
|
||||
|
||||
|
@ -367,4 +368,12 @@ export class ResolvingCall implements Call {
|
|||
getCallNumber(): number {
|
||||
return this.callNumber;
|
||||
}
|
||||
|
||||
getAuthContext(): AuthContext | null {
|
||||
if (this.child) {
|
||||
return this.child.getAuthContext();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,12 +27,11 @@ import {
|
|||
validateServiceConfig,
|
||||
} from './service-config';
|
||||
import { ConnectivityState } from './connectivity-state';
|
||||
import { ConfigSelector, createResolver, Resolver } from './resolver';
|
||||
import { ServiceError } from './call';
|
||||
import { CHANNEL_ARGS_CONFIG_SELECTOR_KEY, ConfigSelector, createResolver, Resolver } from './resolver';
|
||||
import { Picker, UnavailablePicker, QueuePicker } from './picker';
|
||||
import { BackoffOptions, BackoffTimeout } from './backoff-timeout';
|
||||
import { Status } from './constants';
|
||||
import { StatusObject } from './call-interface';
|
||||
import { StatusObject, StatusOr } from './call-interface';
|
||||
import { Metadata } from './metadata';
|
||||
import * as logging from './logging';
|
||||
import { LogVerbosity } from './constants';
|
||||
|
@ -251,75 +250,7 @@ export class ResolvingLoadBalancer implements LoadBalancer {
|
|||
);
|
||||
this.innerResolver = createResolver(
|
||||
target,
|
||||
{
|
||||
onSuccessfulResolution: (
|
||||
endpointList: Endpoint[],
|
||||
serviceConfig: ServiceConfig | null,
|
||||
serviceConfigError: ServiceError | null,
|
||||
configSelector: ConfigSelector | null,
|
||||
attributes: { [key: string]: unknown }
|
||||
) => {
|
||||
this.backoffTimeout.stop();
|
||||
this.backoffTimeout.reset();
|
||||
let workingServiceConfig: ServiceConfig | null = null;
|
||||
/* This first group of conditionals implements the algorithm described
|
||||
* in https://github.com/grpc/proposal/blob/master/A21-service-config-error-handling.md
|
||||
* in the section called "Behavior on receiving a new gRPC Config".
|
||||
*/
|
||||
if (serviceConfig === null) {
|
||||
// Step 4 and 5
|
||||
if (serviceConfigError === null) {
|
||||
// Step 5
|
||||
this.previousServiceConfig = null;
|
||||
workingServiceConfig = this.defaultServiceConfig;
|
||||
} else {
|
||||
// Step 4
|
||||
if (this.previousServiceConfig === null) {
|
||||
// Step 4.ii
|
||||
this.handleResolutionFailure(serviceConfigError);
|
||||
} else {
|
||||
// Step 4.i
|
||||
workingServiceConfig = this.previousServiceConfig;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Step 3
|
||||
workingServiceConfig = serviceConfig;
|
||||
this.previousServiceConfig = serviceConfig;
|
||||
}
|
||||
const workingConfigList =
|
||||
workingServiceConfig?.loadBalancingConfig ?? [];
|
||||
const loadBalancingConfig = selectLbConfigFromList(
|
||||
workingConfigList,
|
||||
true
|
||||
);
|
||||
if (loadBalancingConfig === null) {
|
||||
// There were load balancing configs but none are supported. This counts as a resolution failure
|
||||
this.handleResolutionFailure({
|
||||
code: Status.UNAVAILABLE,
|
||||
details:
|
||||
'All load balancer options in service config are not compatible',
|
||||
metadata: new Metadata(),
|
||||
});
|
||||
configSelector?.unref();
|
||||
return;
|
||||
}
|
||||
this.childLoadBalancer.updateAddressList(
|
||||
endpointList,
|
||||
loadBalancingConfig,
|
||||
{...this.channelOptions, ...attributes}
|
||||
);
|
||||
const finalServiceConfig =
|
||||
workingServiceConfig ?? this.defaultServiceConfig;
|
||||
this.onSuccessfulResolution(
|
||||
finalServiceConfig,
|
||||
configSelector ?? getDefaultConfigSelector(finalServiceConfig)
|
||||
);
|
||||
},
|
||||
onError: (error: StatusObject) => {
|
||||
this.handleResolutionFailure(error);
|
||||
},
|
||||
},
|
||||
this.handleResolverResult.bind(this),
|
||||
channelOptions
|
||||
);
|
||||
const backoffOptions: BackoffOptions = {
|
||||
|
@ -337,6 +268,62 @@ export class ResolvingLoadBalancer implements LoadBalancer {
|
|||
this.backoffTimeout.unref();
|
||||
}
|
||||
|
||||
private handleResolverResult(
|
||||
endpointList: StatusOr<Endpoint[]>,
|
||||
attributes: { [key: string]: unknown },
|
||||
serviceConfig: StatusOr<ServiceConfig> | null,
|
||||
resolutionNote: string
|
||||
): boolean {
|
||||
this.backoffTimeout.stop();
|
||||
this.backoffTimeout.reset();
|
||||
let resultAccepted = true;
|
||||
let workingServiceConfig: ServiceConfig | null = null;
|
||||
if (serviceConfig === null) {
|
||||
workingServiceConfig = this.defaultServiceConfig;
|
||||
} else if (serviceConfig.ok) {
|
||||
workingServiceConfig = serviceConfig.value;
|
||||
} else {
|
||||
if (this.previousServiceConfig !== null) {
|
||||
workingServiceConfig = this.previousServiceConfig;
|
||||
} else {
|
||||
resultAccepted = false;
|
||||
this.handleResolutionFailure(serviceConfig.error);
|
||||
}
|
||||
}
|
||||
|
||||
if (workingServiceConfig !== null) {
|
||||
const workingConfigList =
|
||||
workingServiceConfig?.loadBalancingConfig ?? [];
|
||||
const loadBalancingConfig = selectLbConfigFromList(
|
||||
workingConfigList,
|
||||
true
|
||||
);
|
||||
if (loadBalancingConfig === null) {
|
||||
resultAccepted = false;
|
||||
this.handleResolutionFailure({
|
||||
code: Status.UNAVAILABLE,
|
||||
details:
|
||||
'All load balancer options in service config are not compatible',
|
||||
metadata: new Metadata(),
|
||||
});
|
||||
} else {
|
||||
resultAccepted = this.childLoadBalancer.updateAddressList(
|
||||
endpointList,
|
||||
loadBalancingConfig,
|
||||
{...this.channelOptions, ...attributes},
|
||||
resolutionNote
|
||||
);
|
||||
}
|
||||
}
|
||||
if (resultAccepted) {
|
||||
this.onSuccessfulResolution(
|
||||
workingServiceConfig!,
|
||||
attributes[CHANNEL_ARGS_CONFIG_SELECTOR_KEY] as ConfigSelector ?? getDefaultConfigSelector(workingServiceConfig!)
|
||||
);
|
||||
}
|
||||
return resultAccepted;
|
||||
}
|
||||
|
||||
private updateResolution() {
|
||||
this.innerResolver.updateResolution();
|
||||
if (this.currentState === ConnectivityState.IDLE) {
|
||||
|
@ -391,7 +378,7 @@ export class ResolvingLoadBalancer implements LoadBalancer {
|
|||
}
|
||||
|
||||
updateAddressList(
|
||||
endpointList: Endpoint[],
|
||||
endpointList: StatusOr<Endpoint[]>,
|
||||
lbConfig: TypedLoadBalancingConfig | null
|
||||
): never {
|
||||
throw new Error('updateAddressList not supported on ResolvingLoadBalancer');
|
||||
|
|
|
@ -35,6 +35,7 @@ import {
|
|||
StatusObjectWithProgress,
|
||||
} from './load-balancing-call';
|
||||
import { InternalChannel } from './internal-channel';
|
||||
import { AuthContext } from './auth-context';
|
||||
|
||||
const TRACER_NAME = 'retrying_call';
|
||||
|
||||
|
@ -135,7 +136,12 @@ interface UnderlyingCall {
|
|||
* sent
|
||||
* NO_RETRY: Retries are disabled. Exists to track the transition to COMMITTED
|
||||
*/
|
||||
type RetryingCallState = 'RETRY' | 'HEDGING' | 'TRANSPARENT_ONLY' | 'COMMITTED' | 'NO_RETRY';
|
||||
type RetryingCallState =
|
||||
| 'RETRY'
|
||||
| 'HEDGING'
|
||||
| 'TRANSPARENT_ONLY'
|
||||
| 'COMMITTED'
|
||||
| 'NO_RETRY';
|
||||
|
||||
/**
|
||||
* The different types of objects that can be stored in the write buffer, with
|
||||
|
@ -216,7 +222,9 @@ export class RetryingCall implements Call, DeadlineInfoProvider {
|
|||
private readonly bufferTracker: MessageBufferTracker,
|
||||
private readonly retryThrottler?: RetryThrottler
|
||||
) {
|
||||
const maxAttemptsLimit = channel.getOptions()['grpc-node.retry_max_attempts_limit'] ?? DEFAULT_MAX_ATTEMPTS_LIMIT;
|
||||
const maxAttemptsLimit =
|
||||
channel.getOptions()['grpc-node.retry_max_attempts_limit'] ??
|
||||
DEFAULT_MAX_ATTEMPTS_LIMIT;
|
||||
if (callConfig.methodConfig.retryPolicy) {
|
||||
this.state = 'RETRY';
|
||||
const retryPolicy = callConfig.methodConfig.retryPolicy;
|
||||
|
@ -229,7 +237,10 @@ export class RetryingCall implements Call, DeadlineInfoProvider {
|
|||
this.maxAttempts = Math.min(retryPolicy.maxAttempts, maxAttemptsLimit);
|
||||
} else if (callConfig.methodConfig.hedgingPolicy) {
|
||||
this.state = 'HEDGING';
|
||||
this.maxAttempts = Math.min(callConfig.methodConfig.hedgingPolicy.maxAttempts, maxAttemptsLimit);
|
||||
this.maxAttempts = Math.min(
|
||||
callConfig.methodConfig.hedgingPolicy.maxAttempts,
|
||||
maxAttemptsLimit
|
||||
);
|
||||
} else if (channel.getOptions()['grpc.enable_retries'] === 0) {
|
||||
this.state = 'NO_RETRY';
|
||||
this.maxAttempts = 1;
|
||||
|
@ -246,10 +257,17 @@ export class RetryingCall implements Call, DeadlineInfoProvider {
|
|||
const deadlineInfo: string[] = [];
|
||||
const latestCall = this.underlyingCalls[this.underlyingCalls.length - 1];
|
||||
if (this.underlyingCalls.length > 1) {
|
||||
deadlineInfo.push(`previous attempts: ${this.underlyingCalls.length - 1}`);
|
||||
deadlineInfo.push(
|
||||
`previous attempts: ${this.underlyingCalls.length - 1}`
|
||||
);
|
||||
}
|
||||
if (latestCall.startTime > this.startTime) {
|
||||
deadlineInfo.push(`time to current attempt start: ${formatDateDifference(this.startTime, latestCall.startTime)}`);
|
||||
deadlineInfo.push(
|
||||
`time to current attempt start: ${formatDateDifference(
|
||||
this.startTime,
|
||||
latestCall.startTime
|
||||
)}`
|
||||
);
|
||||
}
|
||||
deadlineInfo.push(...latestCall.call.getDeadlineInfo());
|
||||
return deadlineInfo;
|
||||
|
@ -411,12 +429,18 @@ export class RetryingCall implements Call, DeadlineInfoProvider {
|
|||
);
|
||||
}
|
||||
|
||||
private getNextRetryJitter() {
|
||||
/* Jitter of +-20% is applied: https://github.com/grpc/proposal/blob/master/A6-client-retries.md#exponential-backoff */
|
||||
return Math.random() * (1.2 - 0.8) + 0.8;
|
||||
}
|
||||
|
||||
private getNextRetryBackoffMs() {
|
||||
const retryPolicy = this.callConfig?.methodConfig.retryPolicy;
|
||||
if (!retryPolicy) {
|
||||
return 0;
|
||||
}
|
||||
const nextBackoffMs = Math.random() * this.nextRetryBackoffSec * 1000;
|
||||
const jitter = this.getNextRetryJitter();
|
||||
const nextBackoffMs = jitter * this.nextRetryBackoffSec * 1000;
|
||||
const maxBackoffSec = Number(
|
||||
retryPolicy.maxBackoff.substring(0, retryPolicy.maxBackoff.length - 1)
|
||||
);
|
||||
|
@ -668,7 +692,7 @@ export class RetryingCall implements Call, DeadlineInfoProvider {
|
|||
state: 'ACTIVE',
|
||||
call: child,
|
||||
nextMessageToSend: 0,
|
||||
startTime: new Date()
|
||||
startTime: new Date(),
|
||||
});
|
||||
const previousAttempts = this.attempts - 1;
|
||||
const initialMetadata = this.initialMetadata!.clone();
|
||||
|
@ -859,4 +883,13 @@ export class RetryingCall implements Call, DeadlineInfoProvider {
|
|||
getHost(): string {
|
||||
return this.host;
|
||||
}
|
||||
getAuthContext(): AuthContext | null {
|
||||
if (this.committedCallIndex !== null) {
|
||||
return this.underlyingCalls[
|
||||
this.committedCallIndex
|
||||
].call.getAuthContext();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import type { ObjectReadable, ObjectWritable } from './object-stream';
|
|||
import type { StatusObject, PartialStatusObject } from './call-interface';
|
||||
import type { Deadline } from './deadline';
|
||||
import type { ServerInterceptingCallInterface } from './server-interceptors';
|
||||
import { AuthContext } from './auth-context';
|
||||
|
||||
export type ServerStatusResponse = Partial<StatusObject>;
|
||||
|
||||
|
@ -38,6 +39,7 @@ export type ServerSurfaceCall = {
|
|||
getDeadline(): Deadline;
|
||||
getPath(): string;
|
||||
getHost(): string;
|
||||
getAuthContext(): AuthContext;
|
||||
} & EventEmitter;
|
||||
|
||||
export type ServerUnaryCall<RequestType, ResponseType> = ServerSurfaceCall & {
|
||||
|
@ -114,6 +116,10 @@ export class ServerUnaryCallImpl<RequestType, ResponseType>
|
|||
getHost(): string {
|
||||
return this.call.getHost();
|
||||
}
|
||||
|
||||
getAuthContext(): AuthContext {
|
||||
return this.call.getAuthContext();
|
||||
}
|
||||
}
|
||||
|
||||
export class ServerReadableStreamImpl<RequestType, ResponseType>
|
||||
|
@ -154,6 +160,10 @@ export class ServerReadableStreamImpl<RequestType, ResponseType>
|
|||
getHost(): string {
|
||||
return this.call.getHost();
|
||||
}
|
||||
|
||||
getAuthContext(): AuthContext {
|
||||
return this.call.getAuthContext();
|
||||
}
|
||||
}
|
||||
|
||||
export class ServerWritableStreamImpl<RequestType, ResponseType>
|
||||
|
@ -203,6 +213,10 @@ export class ServerWritableStreamImpl<RequestType, ResponseType>
|
|||
return this.call.getHost();
|
||||
}
|
||||
|
||||
getAuthContext(): AuthContext {
|
||||
return this.call.getAuthContext();
|
||||
}
|
||||
|
||||
_write(
|
||||
chunk: ResponseType,
|
||||
encoding: string,
|
||||
|
@ -276,6 +290,10 @@ export class ServerDuplexStreamImpl<RequestType, ResponseType>
|
|||
return this.call.getHost();
|
||||
}
|
||||
|
||||
getAuthContext(): AuthContext {
|
||||
return this.call.getAuthContext();
|
||||
}
|
||||
|
||||
_read(size: number) {
|
||||
this.call.startRead();
|
||||
}
|
||||
|
|
|
@ -33,6 +33,8 @@ import * as zlib from 'zlib';
|
|||
import { StreamDecoder } from './stream-decoder';
|
||||
import { CallEventTracker } from './transport';
|
||||
import * as logging from './logging';
|
||||
import { AuthContext } from './auth-context';
|
||||
import { TLSSocket } from 'tls';
|
||||
|
||||
const TRACER_NAME = 'server_call';
|
||||
|
||||
|
@ -299,6 +301,13 @@ const defaultResponder: FullResponder = {
|
|||
},
|
||||
};
|
||||
|
||||
export interface ConnectionInfo {
|
||||
localAddress?: string | undefined;
|
||||
localPort?: number | undefined;
|
||||
remoteAddress?: string | undefined;
|
||||
remotePort?: number | undefined;
|
||||
}
|
||||
|
||||
export interface ServerInterceptingCallInterface {
|
||||
/**
|
||||
* Register the listener to handle inbound events.
|
||||
|
@ -332,6 +341,14 @@ export interface ServerInterceptingCallInterface {
|
|||
* Return the host requested by the client in the ":authority" header.
|
||||
*/
|
||||
getHost(): string;
|
||||
/**
|
||||
* Return the auth context of the connection the call is associated with.
|
||||
*/
|
||||
getAuthContext(): AuthContext;
|
||||
/**
|
||||
* Return information about the connection used to make the call.
|
||||
*/
|
||||
getConnectionInfo(): ConnectionInfo;
|
||||
}
|
||||
|
||||
export class ServerInterceptingCall implements ServerInterceptingCallInterface {
|
||||
|
@ -440,6 +457,12 @@ export class ServerInterceptingCall implements ServerInterceptingCallInterface {
|
|||
getHost(): string {
|
||||
return this.nextCall.getHost();
|
||||
}
|
||||
getAuthContext(): AuthContext {
|
||||
return this.nextCall.getAuthContext();
|
||||
}
|
||||
getConnectionInfo(): ConnectionInfo {
|
||||
return this.nextCall.getConnectionInfo();
|
||||
}
|
||||
}
|
||||
|
||||
export interface ServerInterceptor {
|
||||
|
@ -510,6 +533,7 @@ export class BaseServerInterceptingCall
|
|||
private receivedHalfClose = false;
|
||||
private streamEnded = false;
|
||||
private host: string;
|
||||
private connectionInfo: ConnectionInfo;
|
||||
|
||||
constructor(
|
||||
private readonly stream: http2.ServerHttp2Stream,
|
||||
|
@ -597,6 +621,14 @@ export class BaseServerInterceptingCall
|
|||
metadata.remove(http2.constants.HTTP2_HEADER_TE);
|
||||
metadata.remove(http2.constants.HTTP2_HEADER_CONTENT_TYPE);
|
||||
this.metadata = metadata;
|
||||
|
||||
const socket = stream.session?.socket;
|
||||
this.connectionInfo = {
|
||||
localAddress: socket?.localAddress,
|
||||
localPort: socket?.localPort,
|
||||
remoteAddress: socket?.remoteAddress,
|
||||
remotePort: socket?.remotePort
|
||||
};
|
||||
}
|
||||
|
||||
private handleTimeoutHeader(timeoutHeader: string) {
|
||||
|
@ -971,6 +1003,20 @@ export class BaseServerInterceptingCall
|
|||
getHost(): string {
|
||||
return this.host;
|
||||
}
|
||||
getAuthContext(): AuthContext {
|
||||
if (this.stream.session?.socket instanceof TLSSocket) {
|
||||
const peerCertificate = this.stream.session.socket.getPeerCertificate();
|
||||
return {
|
||||
transportSecurityType: 'ssl',
|
||||
sslPeerCertificate: peerCertificate.raw ? peerCertificate : undefined
|
||||
}
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
getConnectionInfo(): ConnectionInfo {
|
||||
return this.connectionInfo;
|
||||
}
|
||||
}
|
||||
|
||||
export function getServerInterceptingCall(
|
||||
|
|
|
@ -782,27 +782,31 @@ export class Server {
|
|||
|
||||
private resolvePort(port: GrpcUri): Promise<SubchannelAddress[]> {
|
||||
return new Promise<SubchannelAddress[]>((resolve, reject) => {
|
||||
const resolverListener: ResolverListener = {
|
||||
onSuccessfulResolution: (
|
||||
endpointList,
|
||||
serviceConfig,
|
||||
serviceConfigError
|
||||
) => {
|
||||
// We only want one resolution result. Discard all future results
|
||||
resolverListener.onSuccessfulResolution = () => {};
|
||||
const addressList = ([] as SubchannelAddress[]).concat(
|
||||
...endpointList.map(endpoint => endpoint.addresses)
|
||||
);
|
||||
if (addressList.length === 0) {
|
||||
reject(new Error(`No addresses resolved for port ${port}`));
|
||||
return;
|
||||
}
|
||||
resolve(addressList);
|
||||
},
|
||||
onError: error => {
|
||||
reject(new Error(error.details));
|
||||
},
|
||||
};
|
||||
let seenResolution = false;
|
||||
const resolverListener: ResolverListener = (
|
||||
endpointList,
|
||||
attributes,
|
||||
serviceConfig,
|
||||
resolutionNote
|
||||
) => {
|
||||
if (seenResolution) {
|
||||
return true;
|
||||
}
|
||||
seenResolution = true;
|
||||
if (!endpointList.ok) {
|
||||
reject(new Error(endpointList.error.details));
|
||||
return true;
|
||||
}
|
||||
const addressList = ([] as SubchannelAddress[]).concat(
|
||||
...endpointList.value.map(endpoint => endpoint.addresses)
|
||||
);
|
||||
if (addressList.length === 0) {
|
||||
reject(new Error(`No addresses resolved for port ${port}`));
|
||||
return true;
|
||||
}
|
||||
resolve(addressList);
|
||||
return true;
|
||||
}
|
||||
const resolver = createResolver(port, resolverListener, this.options);
|
||||
resolver.updateResolution();
|
||||
});
|
||||
|
|
|
@ -30,6 +30,7 @@ import {
|
|||
WriteCallback,
|
||||
} from './call-interface';
|
||||
import { CallEventTracker, Transport } from './transport';
|
||||
import { AuthContext } from './auth-context';
|
||||
|
||||
const TRACER_NAME = 'subchannel_call';
|
||||
|
||||
|
@ -71,6 +72,7 @@ export interface SubchannelCall {
|
|||
halfClose(): void;
|
||||
getCallNumber(): number;
|
||||
getDeadlineInfo(): string[];
|
||||
getAuthContext(): AuthContext;
|
||||
}
|
||||
|
||||
export interface StatusObjectWithRstCode extends StatusObject {
|
||||
|
@ -556,6 +558,10 @@ export class Http2SubchannelCall implements SubchannelCall {
|
|||
return this.callId;
|
||||
}
|
||||
|
||||
getAuthContext(): AuthContext {
|
||||
return this.transport.getAuthContext();
|
||||
}
|
||||
|
||||
startRead() {
|
||||
/* If the stream has ended with an error, we should not emit any more
|
||||
* messages and we should communicate that the stream has ended */
|
||||
|
|
|
@ -51,6 +51,7 @@ import {
|
|||
import { Metadata } from './metadata';
|
||||
import { getNextCallNumber } from './call-number';
|
||||
import { Socket } from 'net';
|
||||
import { AuthContext } from './auth-context';
|
||||
|
||||
const TRACER_NAME = 'transport';
|
||||
const FLOW_CONTROL_TRACER_NAME = 'transport_flowctrl';
|
||||
|
@ -83,6 +84,7 @@ export interface Transport {
|
|||
getChannelzRef(): SocketRef;
|
||||
getPeerName(): string;
|
||||
getOptions(): ChannelOptions;
|
||||
getAuthContext(): AuthContext;
|
||||
createCall(
|
||||
metadata: Metadata,
|
||||
host: string,
|
||||
|
@ -129,6 +131,8 @@ class Http2Transport implements Transport {
|
|||
|
||||
private disconnectHandled = false;
|
||||
|
||||
private authContext: AuthContext;
|
||||
|
||||
// Channelz info
|
||||
private channelzRef: SocketRef;
|
||||
private readonly channelzEnabled: boolean = true;
|
||||
|
@ -254,6 +258,15 @@ class Http2Transport implements Transport {
|
|||
if (this.keepaliveWithoutCalls) {
|
||||
this.maybeStartKeepalivePingTimer();
|
||||
}
|
||||
|
||||
if (session.socket instanceof TLSSocket) {
|
||||
this.authContext = {
|
||||
transportSecurityType: 'ssl',
|
||||
sslPeerCertificate: session.socket.getPeerCertificate()
|
||||
};
|
||||
} else {
|
||||
this.authContext = {};
|
||||
}
|
||||
}
|
||||
|
||||
private getChannelzInfo(): SocketInfo {
|
||||
|
@ -622,6 +635,10 @@ class Http2Transport implements Transport {
|
|||
return this.options;
|
||||
}
|
||||
|
||||
getAuthContext(): AuthContext {
|
||||
return this.authContext;
|
||||
}
|
||||
|
||||
shutdown() {
|
||||
this.session.close();
|
||||
unregisterChannelzRef(this.channelzRef);
|
||||
|
|
|
@ -150,15 +150,15 @@ export class TestClient {
|
|||
this.client.waitForReady(deadline, callback);
|
||||
}
|
||||
|
||||
sendRequest(callback: (error?: grpc.ServiceError) => void) {
|
||||
this.client.echo({}, callback);
|
||||
sendRequest(callback: (error?: grpc.ServiceError) => void): grpc.ClientUnaryCall {
|
||||
return this.client.echo({}, callback);
|
||||
}
|
||||
|
||||
sendRequestWithMetadata(
|
||||
metadata: grpc.Metadata,
|
||||
callback: (error?: grpc.ServiceError) => void
|
||||
) {
|
||||
this.client.echo({}, metadata, callback);
|
||||
): grpc.ClientUnaryCall {
|
||||
return this.client.echo({}, metadata, callback);
|
||||
}
|
||||
|
||||
getChannelState() {
|
||||
|
|
|
@ -218,6 +218,16 @@ describe('ChannelCredentials usage', () => {
|
|||
}
|
||||
);
|
||||
});
|
||||
it('Should provide certificates in getAuthContext', done => {
|
||||
const call = client.echo({ value: 'test value', value2: 3 }, (error: ServiceError, response: any) => {
|
||||
assert.ifError(error);
|
||||
const authContext = call.getAuthContext();
|
||||
assert(authContext);
|
||||
assert.strictEqual(authContext.transportSecurityType, 'ssl');
|
||||
assert(authContext.sslPeerCertificate);
|
||||
done();
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
describe('Channel credentials mtls', () => {
|
||||
|
|
|
@ -31,6 +31,8 @@ import { Metadata } from '../src/metadata';
|
|||
import { Picker } from '../src/picker';
|
||||
import { Endpoint, subchannelAddressToString } from '../src/subchannel-address';
|
||||
import { MockSubchannel, TestClient, TestServer } from './common';
|
||||
import { statusOrFromError, statusOrFromValue } from '../src/call-interface';
|
||||
import { Status } from '../src/constants';
|
||||
|
||||
function updateStateCallBackForExpectedStateSequence(
|
||||
expectedStateSequence: ConnectivityState[],
|
||||
|
@ -125,9 +127,10 @@ describe('pick_first load balancing policy', () => {
|
|||
);
|
||||
const pickFirst = new PickFirstLoadBalancer(channelControlHelper);
|
||||
pickFirst.updateAddressList(
|
||||
[{ addresses: [{ host: 'localhost', port: 1 }] }],
|
||||
statusOrFromValue([{ addresses: [{ host: 'localhost', port: 1 }] }]),
|
||||
config,
|
||||
{}
|
||||
{},
|
||||
''
|
||||
);
|
||||
process.nextTick(() => {
|
||||
subchannels[0].transitionToState(ConnectivityState.READY);
|
||||
|
@ -145,12 +148,13 @@ describe('pick_first load balancing policy', () => {
|
|||
);
|
||||
const pickFirst = new PickFirstLoadBalancer(channelControlHelper);
|
||||
pickFirst.updateAddressList(
|
||||
[
|
||||
statusOrFromValue([
|
||||
{ addresses: [{ host: 'localhost', port: 1 }] },
|
||||
{ addresses: [{ host: 'localhost', port: 2 }] },
|
||||
],
|
||||
]),
|
||||
config,
|
||||
{}
|
||||
{},
|
||||
''
|
||||
);
|
||||
process.nextTick(() => {
|
||||
subchannels[1].transitionToState(ConnectivityState.READY);
|
||||
|
@ -168,16 +172,17 @@ describe('pick_first load balancing policy', () => {
|
|||
);
|
||||
const pickFirst = new PickFirstLoadBalancer(channelControlHelper);
|
||||
pickFirst.updateAddressList(
|
||||
[
|
||||
statusOrFromValue([
|
||||
{
|
||||
addresses: [
|
||||
{ host: 'localhost', port: 1 },
|
||||
{ host: 'localhost', port: 2 },
|
||||
],
|
||||
},
|
||||
],
|
||||
]),
|
||||
config,
|
||||
{}
|
||||
{},
|
||||
''
|
||||
);
|
||||
process.nextTick(() => {
|
||||
subchannels[1].transitionToState(ConnectivityState.READY);
|
||||
|
@ -203,9 +208,10 @@ describe('pick_first load balancing policy', () => {
|
|||
);
|
||||
const pickFirst = new PickFirstLoadBalancer(channelControlHelper);
|
||||
pickFirst.updateAddressList(
|
||||
[{ addresses: [{ host: 'localhost', port: 1 }] }],
|
||||
statusOrFromValue([{ addresses: [{ host: 'localhost', port: 1 }] }]),
|
||||
config,
|
||||
{}
|
||||
{},
|
||||
''
|
||||
);
|
||||
});
|
||||
it('Should stay CONNECTING if only some subchannels fail to connect', done => {
|
||||
|
@ -220,12 +226,13 @@ describe('pick_first load balancing policy', () => {
|
|||
);
|
||||
const pickFirst = new PickFirstLoadBalancer(channelControlHelper);
|
||||
pickFirst.updateAddressList(
|
||||
[
|
||||
statusOrFromValue([
|
||||
{ addresses: [{ host: 'localhost', port: 1 }] },
|
||||
{ addresses: [{ host: 'localhost', port: 2 }] },
|
||||
],
|
||||
]),
|
||||
config,
|
||||
{}
|
||||
{},
|
||||
''
|
||||
);
|
||||
process.nextTick(() => {
|
||||
subchannels[0].transitionToState(ConnectivityState.TRANSIENT_FAILURE);
|
||||
|
@ -243,12 +250,13 @@ describe('pick_first load balancing policy', () => {
|
|||
);
|
||||
const pickFirst = new PickFirstLoadBalancer(channelControlHelper);
|
||||
pickFirst.updateAddressList(
|
||||
[
|
||||
statusOrFromValue([
|
||||
{ addresses: [{ host: 'localhost', port: 1 }] },
|
||||
{ addresses: [{ host: 'localhost', port: 2 }] },
|
||||
],
|
||||
]),
|
||||
config,
|
||||
{}
|
||||
{},
|
||||
''
|
||||
);
|
||||
process.nextTick(() => {
|
||||
subchannels[0].transitionToState(ConnectivityState.TRANSIENT_FAILURE);
|
||||
|
@ -269,12 +277,13 @@ describe('pick_first load balancing policy', () => {
|
|||
);
|
||||
const pickFirst = new PickFirstLoadBalancer(channelControlHelper);
|
||||
pickFirst.updateAddressList(
|
||||
[
|
||||
statusOrFromValue([
|
||||
{ addresses: [{ host: 'localhost', port: 1 }] },
|
||||
{ addresses: [{ host: 'localhost', port: 2 }] },
|
||||
],
|
||||
]),
|
||||
config,
|
||||
{}
|
||||
{},
|
||||
''
|
||||
);
|
||||
process.nextTick(() => {
|
||||
subchannels[0].transitionToState(ConnectivityState.TRANSIENT_FAILURE);
|
||||
|
@ -309,12 +318,13 @@ describe('pick_first load balancing policy', () => {
|
|||
);
|
||||
const pickFirst = new PickFirstLoadBalancer(channelControlHelper);
|
||||
pickFirst.updateAddressList(
|
||||
[
|
||||
statusOrFromValue([
|
||||
{ addresses: [{ host: 'localhost', port: 1 }] },
|
||||
{ addresses: [{ host: 'localhost', port: 2 }] },
|
||||
],
|
||||
]),
|
||||
config,
|
||||
{}
|
||||
{},
|
||||
''
|
||||
);
|
||||
});
|
||||
it('Should enter READY if a subchannel connects after entering TRANSIENT_FAILURE mode', done => {
|
||||
|
@ -337,12 +347,13 @@ describe('pick_first load balancing policy', () => {
|
|||
);
|
||||
const pickFirst = new PickFirstLoadBalancer(channelControlHelper);
|
||||
pickFirst.updateAddressList(
|
||||
[
|
||||
statusOrFromValue([
|
||||
{ addresses: [{ host: 'localhost', port: 1 }] },
|
||||
{ addresses: [{ host: 'localhost', port: 2 }] },
|
||||
],
|
||||
]),
|
||||
config,
|
||||
{}
|
||||
{},
|
||||
''
|
||||
);
|
||||
process.nextTick(() => {
|
||||
subchannels[0].transitionToState(ConnectivityState.READY);
|
||||
|
@ -369,22 +380,24 @@ describe('pick_first load balancing policy', () => {
|
|||
);
|
||||
const pickFirst = new PickFirstLoadBalancer(channelControlHelper);
|
||||
pickFirst.updateAddressList(
|
||||
[
|
||||
statusOrFromValue([
|
||||
{ addresses: [{ host: 'localhost', port: 1 }] },
|
||||
{ addresses: [{ host: 'localhost', port: 2 }] },
|
||||
],
|
||||
]),
|
||||
config,
|
||||
{}
|
||||
{},
|
||||
''
|
||||
);
|
||||
process.nextTick(() => {
|
||||
currentStartState = ConnectivityState.CONNECTING;
|
||||
pickFirst.updateAddressList(
|
||||
[
|
||||
statusOrFromValue([
|
||||
{ addresses: [{ host: 'localhost', port: 1 }] },
|
||||
{ addresses: [{ host: 'localhost', port: 2 }] },
|
||||
],
|
||||
]),
|
||||
config,
|
||||
{}
|
||||
{},
|
||||
''
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -409,19 +422,21 @@ describe('pick_first load balancing policy', () => {
|
|||
);
|
||||
const pickFirst = new PickFirstLoadBalancer(channelControlHelper);
|
||||
pickFirst.updateAddressList(
|
||||
[
|
||||
statusOrFromValue([
|
||||
{ addresses: [{ host: 'localhost', port: 1 }] },
|
||||
{ addresses: [{ host: 'localhost', port: 2 }] },
|
||||
],
|
||||
]),
|
||||
config,
|
||||
{}
|
||||
{},
|
||||
''
|
||||
);
|
||||
process.nextTick(() => {
|
||||
currentStartState = ConnectivityState.READY;
|
||||
pickFirst.updateAddressList(
|
||||
[{ addresses: [{ host: 'localhost', port: 3 }] }],
|
||||
statusOrFromValue([{ addresses: [{ host: 'localhost', port: 3 }] }]),
|
||||
config,
|
||||
{}
|
||||
{},
|
||||
''
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -446,9 +461,10 @@ describe('pick_first load balancing policy', () => {
|
|||
);
|
||||
const pickFirst = new PickFirstLoadBalancer(channelControlHelper);
|
||||
pickFirst.updateAddressList(
|
||||
[{ addresses: [{ host: 'localhost', port: 1 }] }],
|
||||
statusOrFromValue([{ addresses: [{ host: 'localhost', port: 1 }] }]),
|
||||
config,
|
||||
{}
|
||||
{},
|
||||
''
|
||||
);
|
||||
process.nextTick(() => {
|
||||
subchannels[0].transitionToState(ConnectivityState.IDLE);
|
||||
|
@ -475,16 +491,18 @@ describe('pick_first load balancing policy', () => {
|
|||
);
|
||||
const pickFirst = new PickFirstLoadBalancer(channelControlHelper);
|
||||
pickFirst.updateAddressList(
|
||||
[{ addresses: [{ host: 'localhost', port: 1 }] }],
|
||||
statusOrFromValue([{ addresses: [{ host: 'localhost', port: 1 }] }]),
|
||||
config,
|
||||
{}
|
||||
{},
|
||||
''
|
||||
);
|
||||
process.nextTick(() => {
|
||||
currentStartState = ConnectivityState.IDLE;
|
||||
pickFirst.updateAddressList(
|
||||
[{ addresses: [{ host: 'localhost', port: 2 }] }],
|
||||
statusOrFromValue([{ addresses: [{ host: 'localhost', port: 2 }] }]),
|
||||
config,
|
||||
{}
|
||||
{},
|
||||
''
|
||||
);
|
||||
process.nextTick(() => {
|
||||
subchannels[0].transitionToState(ConnectivityState.IDLE);
|
||||
|
@ -512,16 +530,18 @@ describe('pick_first load balancing policy', () => {
|
|||
);
|
||||
const pickFirst = new PickFirstLoadBalancer(channelControlHelper);
|
||||
pickFirst.updateAddressList(
|
||||
[{ addresses: [{ host: 'localhost', port: 1 }] }],
|
||||
statusOrFromValue([{ addresses: [{ host: 'localhost', port: 1 }] }]),
|
||||
config,
|
||||
{}
|
||||
{},
|
||||
''
|
||||
);
|
||||
process.nextTick(() => {
|
||||
currentStartState = ConnectivityState.TRANSIENT_FAILURE;
|
||||
pickFirst.updateAddressList(
|
||||
[{ addresses: [{ host: 'localhost', port: 2 }] }],
|
||||
statusOrFromValue([{ addresses: [{ host: 'localhost', port: 2 }] }]),
|
||||
config,
|
||||
{}
|
||||
{},
|
||||
''
|
||||
);
|
||||
process.nextTick(() => {
|
||||
subchannels[0].transitionToState(ConnectivityState.IDLE);
|
||||
|
@ -549,15 +569,17 @@ describe('pick_first load balancing policy', () => {
|
|||
);
|
||||
const pickFirst = new PickFirstLoadBalancer(channelControlHelper);
|
||||
pickFirst.updateAddressList(
|
||||
[{ addresses: [{ host: 'localhost', port: 1 }] }],
|
||||
statusOrFromValue([{ addresses: [{ host: 'localhost', port: 1 }] }]),
|
||||
config,
|
||||
{}
|
||||
{},
|
||||
''
|
||||
);
|
||||
process.nextTick(() => {
|
||||
pickFirst.updateAddressList(
|
||||
[{ addresses: [{ host: 'localhost', port: 2 }] }],
|
||||
statusOrFromValue([{ addresses: [{ host: 'localhost', port: 2 }] }]),
|
||||
config,
|
||||
{}
|
||||
{},
|
||||
''
|
||||
);
|
||||
process.nextTick(() => {
|
||||
subchannels[0].transitionToState(ConnectivityState.IDLE);
|
||||
|
@ -597,25 +619,28 @@ describe('pick_first load balancing policy', () => {
|
|||
);
|
||||
const pickFirst = new PickFirstLoadBalancer(channelControlHelper);
|
||||
pickFirst.updateAddressList(
|
||||
[{ addresses: [{ host: 'localhost', port: 1 }] }],
|
||||
statusOrFromValue([{ addresses: [{ host: 'localhost', port: 1 }] }]),
|
||||
config,
|
||||
{}
|
||||
{},
|
||||
''
|
||||
);
|
||||
process.nextTick(() => {
|
||||
subchannels[0].transitionToState(ConnectivityState.TRANSIENT_FAILURE);
|
||||
process.nextTick(() => {
|
||||
pickFirst.updateAddressList(
|
||||
[{ addresses: [{ host: 'localhost', port: 2 }] }],
|
||||
statusOrFromValue([{ addresses: [{ host: 'localhost', port: 2 }] }]),
|
||||
config,
|
||||
{}
|
||||
{},
|
||||
''
|
||||
);
|
||||
process.nextTick(() => {
|
||||
subchannels[1].transitionToState(ConnectivityState.TRANSIENT_FAILURE);
|
||||
process.nextTick(() => {
|
||||
pickFirst.updateAddressList(
|
||||
[{ addresses: [{ host: 'localhost', port: 3 }] }],
|
||||
statusOrFromValue([{ addresses: [{ host: 'localhost', port: 3 }] }]),
|
||||
config,
|
||||
{}
|
||||
{},
|
||||
''
|
||||
);
|
||||
process.nextTick(() => {
|
||||
subchannels[2].transitionToState(
|
||||
|
@ -660,21 +685,24 @@ describe('pick_first load balancing policy', () => {
|
|||
);
|
||||
const pickFirst = new PickFirstLoadBalancer(channelControlHelper);
|
||||
pickFirst.updateAddressList(
|
||||
[{ addresses: [{ host: 'localhost', port: 1 }] }],
|
||||
statusOrFromValue([{ addresses: [{ host: 'localhost', port: 1 }] }]),
|
||||
config,
|
||||
{}
|
||||
{},
|
||||
''
|
||||
);
|
||||
process.nextTick(() => {
|
||||
pickFirst.updateAddressList(
|
||||
[{ addresses: [{ host: 'localhost', port: 2 }] }],
|
||||
statusOrFromValue([{ addresses: [{ host: 'localhost', port: 2 }] }]),
|
||||
config,
|
||||
{}
|
||||
{},
|
||||
''
|
||||
);
|
||||
process.nextTick(() => {
|
||||
pickFirst.updateAddressList(
|
||||
[{ addresses: [{ host: 'localhost', port: 2 }] }],
|
||||
statusOrFromValue([{ addresses: [{ host: 'localhost', port: 2 }] }]),
|
||||
config,
|
||||
{}
|
||||
{},
|
||||
''
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -704,9 +732,10 @@ describe('pick_first load balancing policy', () => {
|
|||
);
|
||||
const pickFirst = new PickFirstLoadBalancer(channelControlHelper);
|
||||
pickFirst.updateAddressList(
|
||||
[{ addresses: [{ host: 'localhost', port: 1 }] }],
|
||||
statusOrFromValue([{ addresses: [{ host: 'localhost', port: 1 }] }]),
|
||||
config,
|
||||
{}
|
||||
{},
|
||||
''
|
||||
);
|
||||
process.nextTick(() => {
|
||||
subchannels[0].transitionToState(ConnectivityState.IDLE);
|
||||
|
@ -726,7 +755,20 @@ describe('pick_first load balancing policy', () => {
|
|||
}
|
||||
);
|
||||
const pickFirst = new PickFirstLoadBalancer(channelControlHelper);
|
||||
pickFirst.updateAddressList([], config, {});
|
||||
pickFirst.updateAddressList(statusOrFromValue([]), config, {}, '');
|
||||
});
|
||||
it('Should report TRANSIENT_FAILURE with an endpoint list error', done => {
|
||||
const channelControlHelper = createChildChannelControlHelper(
|
||||
baseChannelControlHelper,
|
||||
{
|
||||
updateState: updateStateCallBackForExpectedStateSequence(
|
||||
[ConnectivityState.TRANSIENT_FAILURE],
|
||||
done
|
||||
),
|
||||
}
|
||||
);
|
||||
const pickFirst = new PickFirstLoadBalancer(channelControlHelper);
|
||||
pickFirst.updateAddressList(statusOrFromError({code: Status.UNAVAILABLE, details: 'Resolver error'}), config, {}, '');
|
||||
});
|
||||
describe('Address list randomization', () => {
|
||||
const shuffleConfig = new PickFirstLoadBalancingConfig(true);
|
||||
|
@ -760,20 +802,21 @@ describe('pick_first load balancing policy', () => {
|
|||
for (let i = 0; i < 10; i++) {
|
||||
endpoints.push({ addresses: [{ host: 'localhost', port: i + 1 }] });
|
||||
}
|
||||
const endpointList = statusOrFromValue(endpoints);
|
||||
const pickFirst = new PickFirstLoadBalancer(channelControlHelper);
|
||||
/* Pick from 10 subchannels 5 times, with address randomization enabled,
|
||||
* and verify that at least two different subchannels are picked. The
|
||||
* probability choosing the same address every time is 1/10,000, which
|
||||
* I am considering an acceptable flake rate */
|
||||
pickFirst.updateAddressList(endpoints, shuffleConfig, {});
|
||||
pickFirst.updateAddressList(endpointList, shuffleConfig, {}, '');
|
||||
process.nextTick(() => {
|
||||
pickFirst.updateAddressList(endpoints, shuffleConfig, {});
|
||||
pickFirst.updateAddressList(endpointList, shuffleConfig, {}, '');
|
||||
process.nextTick(() => {
|
||||
pickFirst.updateAddressList(endpoints, shuffleConfig, {});
|
||||
pickFirst.updateAddressList(endpointList, shuffleConfig, {}, '');
|
||||
process.nextTick(() => {
|
||||
pickFirst.updateAddressList(endpoints, shuffleConfig, {});
|
||||
pickFirst.updateAddressList(endpointList, shuffleConfig, {}, '');
|
||||
process.nextTick(() => {
|
||||
pickFirst.updateAddressList(endpoints, shuffleConfig, {});
|
||||
pickFirst.updateAddressList(endpointList, shuffleConfig, {}, '');
|
||||
process.nextTick(() => {
|
||||
assert(pickedSubchannels.size > 1);
|
||||
done();
|
||||
|
@ -816,16 +859,17 @@ describe('pick_first load balancing policy', () => {
|
|||
for (let i = 0; i < 10; i++) {
|
||||
endpoints.push({ addresses: [{ host: 'localhost', port: i + 1 }] });
|
||||
}
|
||||
const endpointList = statusOrFromValue(endpoints);
|
||||
const pickFirst = new PickFirstLoadBalancer(channelControlHelper);
|
||||
pickFirst.updateAddressList(endpoints, config, {});
|
||||
pickFirst.updateAddressList(endpointList, config, {}, '');
|
||||
process.nextTick(() => {
|
||||
pickFirst.updateAddressList(endpoints, config, {});
|
||||
pickFirst.updateAddressList(endpointList, config, {}, '');
|
||||
process.nextTick(() => {
|
||||
pickFirst.updateAddressList(endpoints, config, {});
|
||||
pickFirst.updateAddressList(endpointList, config, {}, '');
|
||||
process.nextTick(() => {
|
||||
pickFirst.updateAddressList(endpoints, config, {});
|
||||
pickFirst.updateAddressList(endpointList, config, {}, '');
|
||||
process.nextTick(() => {
|
||||
pickFirst.updateAddressList(endpoints, config, {});
|
||||
pickFirst.updateAddressList(endpointList, config, {}, '');
|
||||
process.nextTick(() => {
|
||||
assert(pickedSubchannels.size === 1);
|
||||
done();
|
||||
|
|
|
@ -23,7 +23,7 @@ import * as resolver_dns from '../src/resolver-dns';
|
|||
import * as resolver_uds from '../src/resolver-uds';
|
||||
import * as resolver_ip from '../src/resolver-ip';
|
||||
import { ServiceConfig } from '../src/service-config';
|
||||
import { StatusObject } from '../src/call-interface';
|
||||
import { StatusOr } from '../src/call-interface';
|
||||
import {
|
||||
Endpoint,
|
||||
SubchannelAddress,
|
||||
|
@ -63,25 +63,27 @@ describe('Name Resolver', () => {
|
|||
const target = resolverManager.mapUriDefaultScheme(
|
||||
parseUri('localhost:50051')!
|
||||
)!;
|
||||
const listener: resolverManager.ResolverListener = {
|
||||
onSuccessfulResolution: (
|
||||
endpointList: Endpoint[],
|
||||
serviceConfig: ServiceConfig | null,
|
||||
serviceConfigError: StatusObject | null
|
||||
) => {
|
||||
// Only handle the first resolution result
|
||||
listener.onSuccessfulResolution = () => {};
|
||||
assert(
|
||||
hasMatchingAddress(endpointList, { host: '127.0.0.1', port: 50051 })
|
||||
);
|
||||
assert(
|
||||
hasMatchingAddress(endpointList, { host: '::1', port: 50051 })
|
||||
);
|
||||
done();
|
||||
},
|
||||
onError: (error: StatusObject) => {
|
||||
done(new Error(`Failed with status ${error.details}`));
|
||||
},
|
||||
let resultSeen = false;
|
||||
const listener: resolverManager.ResolverListener = (
|
||||
maybeEndpointList: StatusOr<Endpoint[]>,
|
||||
attributes: { [key: string]: unknown},
|
||||
serviceConfig: StatusOr<ServiceConfig> | null,
|
||||
resolutionNote: string
|
||||
) => {
|
||||
if (resultSeen) {
|
||||
return true;
|
||||
}
|
||||
resultSeen = true;
|
||||
assert(maybeEndpointList.ok);
|
||||
const endpointList = maybeEndpointList.value;
|
||||
assert(
|
||||
hasMatchingAddress(endpointList, { host: '127.0.0.1', port: 50051 })
|
||||
);
|
||||
assert(
|
||||
hasMatchingAddress(endpointList, { host: '::1', port: 50051 })
|
||||
);
|
||||
done();
|
||||
return true;
|
||||
};
|
||||
const resolver = resolverManager.createResolver(target, listener, {});
|
||||
resolver.updateResolution();
|
||||
|
@ -93,65 +95,71 @@ describe('Name Resolver', () => {
|
|||
const target = resolverManager.mapUriDefaultScheme(
|
||||
parseUri('localhost')!
|
||||
)!;
|
||||
const listener: resolverManager.ResolverListener = {
|
||||
onSuccessfulResolution: (
|
||||
endpointList: Endpoint[],
|
||||
serviceConfig: ServiceConfig | null,
|
||||
serviceConfigError: StatusObject | null
|
||||
) => {
|
||||
// Only handle the first resolution result
|
||||
listener.onSuccessfulResolution = () => {};
|
||||
assert(
|
||||
hasMatchingAddress(endpointList, { host: '127.0.0.1', port: 443 })
|
||||
);
|
||||
assert(hasMatchingAddress(endpointList, { host: '::1', port: 443 }));
|
||||
done();
|
||||
},
|
||||
onError: (error: StatusObject) => {
|
||||
done(new Error(`Failed with status ${error.details}`));
|
||||
},
|
||||
let resultSeen = false;
|
||||
const listener: resolverManager.ResolverListener = (
|
||||
maybeEndpointList: StatusOr<Endpoint[]>,
|
||||
attributes: { [key: string]: unknown},
|
||||
serviceConfig: StatusOr<ServiceConfig> | null,
|
||||
resolutionNote: string
|
||||
) => {
|
||||
if (resultSeen) {
|
||||
return true;
|
||||
}
|
||||
resultSeen = true;
|
||||
assert(maybeEndpointList.ok);
|
||||
const endpointList = maybeEndpointList.value;
|
||||
assert(
|
||||
hasMatchingAddress(endpointList, { host: '127.0.0.1', port: 443 })
|
||||
);
|
||||
assert(hasMatchingAddress(endpointList, { host: '::1', port: 443 }));
|
||||
done();
|
||||
return true;
|
||||
};
|
||||
const resolver = resolverManager.createResolver(target, listener, {});
|
||||
resolver.updateResolution();
|
||||
});
|
||||
it('Should correctly represent an ipv4 address', done => {
|
||||
const target = resolverManager.mapUriDefaultScheme(parseUri('1.2.3.4')!)!;
|
||||
const listener: resolverManager.ResolverListener = {
|
||||
onSuccessfulResolution: (
|
||||
endpointList: Endpoint[],
|
||||
serviceConfig: ServiceConfig | null,
|
||||
serviceConfigError: StatusObject | null
|
||||
) => {
|
||||
// Only handle the first resolution result
|
||||
listener.onSuccessfulResolution = () => {};
|
||||
assert(
|
||||
hasMatchingAddress(endpointList, { host: '1.2.3.4', port: 443 })
|
||||
);
|
||||
done();
|
||||
},
|
||||
onError: (error: StatusObject) => {
|
||||
done(new Error(`Failed with status ${error.details}`));
|
||||
},
|
||||
let resultSeen = false;
|
||||
const listener: resolverManager.ResolverListener = (
|
||||
maybeEndpointList: StatusOr<Endpoint[]>,
|
||||
attributes: { [key: string]: unknown},
|
||||
serviceConfig: StatusOr<ServiceConfig> | null,
|
||||
resolutionNote: string
|
||||
) => {
|
||||
if (resultSeen) {
|
||||
return true;
|
||||
}
|
||||
resultSeen = true;
|
||||
assert(maybeEndpointList.ok);
|
||||
const endpointList = maybeEndpointList.value;
|
||||
assert(
|
||||
hasMatchingAddress(endpointList, { host: '1.2.3.4', port: 443 })
|
||||
);
|
||||
done();
|
||||
return true;
|
||||
};
|
||||
const resolver = resolverManager.createResolver(target, listener, {});
|
||||
resolver.updateResolution();
|
||||
});
|
||||
it('Should correctly represent an ipv6 address', done => {
|
||||
const target = resolverManager.mapUriDefaultScheme(parseUri('::1')!)!;
|
||||
const listener: resolverManager.ResolverListener = {
|
||||
onSuccessfulResolution: (
|
||||
endpointList: Endpoint[],
|
||||
serviceConfig: ServiceConfig | null,
|
||||
serviceConfigError: StatusObject | null
|
||||
) => {
|
||||
// Only handle the first resolution result
|
||||
listener.onSuccessfulResolution = () => {};
|
||||
assert(hasMatchingAddress(endpointList, { host: '::1', port: 443 }));
|
||||
done();
|
||||
},
|
||||
onError: (error: StatusObject) => {
|
||||
done(new Error(`Failed with status ${error.details}`));
|
||||
},
|
||||
let resultSeen = false;
|
||||
const listener: resolverManager.ResolverListener = (
|
||||
maybeEndpointList: StatusOr<Endpoint[]>,
|
||||
attributes: { [key: string]: unknown},
|
||||
serviceConfig: StatusOr<ServiceConfig> | null,
|
||||
resolutionNote: string
|
||||
) => {
|
||||
if (resultSeen) {
|
||||
return true;
|
||||
}
|
||||
resultSeen = true;
|
||||
assert(maybeEndpointList.ok);
|
||||
const endpointList = maybeEndpointList.value;
|
||||
assert(hasMatchingAddress(endpointList, { host: '::1', port: 443 }));
|
||||
done();
|
||||
return true;
|
||||
};
|
||||
const resolver = resolverManager.createResolver(target, listener, {});
|
||||
resolver.updateResolution();
|
||||
|
@ -160,22 +168,24 @@ describe('Name Resolver', () => {
|
|||
const target = resolverManager.mapUriDefaultScheme(
|
||||
parseUri('[::1]:50051')!
|
||||
)!;
|
||||
const listener: resolverManager.ResolverListener = {
|
||||
onSuccessfulResolution: (
|
||||
endpointList: Endpoint[],
|
||||
serviceConfig: ServiceConfig | null,
|
||||
serviceConfigError: StatusObject | null
|
||||
) => {
|
||||
// Only handle the first resolution result
|
||||
listener.onSuccessfulResolution = () => {};
|
||||
assert(
|
||||
hasMatchingAddress(endpointList, { host: '::1', port: 50051 })
|
||||
);
|
||||
done();
|
||||
},
|
||||
onError: (error: StatusObject) => {
|
||||
done(new Error(`Failed with status ${error.details}`));
|
||||
},
|
||||
let resultSeen = false;
|
||||
const listener: resolverManager.ResolverListener = (
|
||||
maybeEndpointList: StatusOr<Endpoint[]>,
|
||||
attributes: { [key: string]: unknown},
|
||||
serviceConfig: StatusOr<ServiceConfig> | null,
|
||||
resolutionNote: string
|
||||
) => {
|
||||
if (resultSeen) {
|
||||
return true;
|
||||
}
|
||||
resultSeen = true;
|
||||
assert(maybeEndpointList.ok);
|
||||
const endpointList = maybeEndpointList.value;
|
||||
assert(
|
||||
hasMatchingAddress(endpointList, { host: '::1', port: 50051 })
|
||||
);
|
||||
done();
|
||||
return true;
|
||||
};
|
||||
const resolver = resolverManager.createResolver(target, listener, {});
|
||||
resolver.updateResolution();
|
||||
|
@ -184,20 +194,22 @@ describe('Name Resolver', () => {
|
|||
const target = resolverManager.mapUriDefaultScheme(
|
||||
parseUri('example.com')!
|
||||
)!;
|
||||
const listener: resolverManager.ResolverListener = {
|
||||
onSuccessfulResolution: (
|
||||
endpointList: Endpoint[],
|
||||
serviceConfig: ServiceConfig | null,
|
||||
serviceConfigError: StatusObject | null
|
||||
) => {
|
||||
// Only handle the first resolution result
|
||||
listener.onSuccessfulResolution = () => {};
|
||||
assert(endpointList.length > 0);
|
||||
done();
|
||||
},
|
||||
onError: (error: StatusObject) => {
|
||||
done(new Error(`Failed with status ${error.details}`));
|
||||
},
|
||||
let resultSeen = false;
|
||||
const listener: resolverManager.ResolverListener = (
|
||||
maybeEndpointList: StatusOr<Endpoint[]>,
|
||||
attributes: { [key: string]: unknown},
|
||||
serviceConfig: StatusOr<ServiceConfig> | null,
|
||||
resolutionNote: string
|
||||
) => {
|
||||
if (resultSeen) {
|
||||
return true;
|
||||
}
|
||||
resultSeen = true;
|
||||
assert(maybeEndpointList.ok);
|
||||
const endpointList = maybeEndpointList.value;
|
||||
assert(endpointList.length > 0);
|
||||
done();
|
||||
return true;
|
||||
};
|
||||
const resolver = resolverManager.createResolver(target, listener, {});
|
||||
resolver.updateResolution();
|
||||
|
@ -208,23 +220,21 @@ describe('Name Resolver', () => {
|
|||
const target = resolverManager.mapUriDefaultScheme(
|
||||
parseUri('grpctest.kleinsch.com')!
|
||||
)!;
|
||||
const listener: resolverManager.ResolverListener = {
|
||||
onSuccessfulResolution: (
|
||||
endpointList: Endpoint[],
|
||||
serviceConfig: ServiceConfig | null,
|
||||
serviceConfigError: StatusObject | null
|
||||
) => {
|
||||
if (serviceConfig !== null) {
|
||||
assert(
|
||||
serviceConfig.loadBalancingPolicy === 'round_robin',
|
||||
'Should have found round robin LB policy'
|
||||
);
|
||||
done();
|
||||
}
|
||||
},
|
||||
onError: (error: StatusObject) => {
|
||||
done(new Error(`Failed with status ${error.details}`));
|
||||
},
|
||||
const listener: resolverManager.ResolverListener = (
|
||||
maybeEndpointList: StatusOr<Endpoint[]>,
|
||||
attributes: { [key: string]: unknown},
|
||||
serviceConfig: StatusOr<ServiceConfig> | null,
|
||||
resolutionNote: string
|
||||
) => {
|
||||
if (serviceConfig !== null) {
|
||||
assert(serviceConfig.ok);
|
||||
assert(
|
||||
serviceConfig.value.loadBalancingPolicy === 'round_robin',
|
||||
'Should have found round robin LB policy'
|
||||
);
|
||||
done();
|
||||
}
|
||||
return true;
|
||||
};
|
||||
const resolver = resolverManager.createResolver(target, listener, {});
|
||||
resolver.updateResolution();
|
||||
|
@ -234,21 +244,18 @@ describe('Name Resolver', () => {
|
|||
parseUri('grpctest.kleinsch.com')!
|
||||
)!;
|
||||
let count = 0;
|
||||
const listener: resolverManager.ResolverListener = {
|
||||
onSuccessfulResolution: (
|
||||
endpointList: Endpoint[],
|
||||
serviceConfig: ServiceConfig | null,
|
||||
serviceConfigError: StatusObject | null
|
||||
) => {
|
||||
assert(
|
||||
serviceConfig === null,
|
||||
'Should not have found service config'
|
||||
);
|
||||
count++;
|
||||
},
|
||||
onError: (error: StatusObject) => {
|
||||
done(new Error(`Failed with status ${error.details}`));
|
||||
},
|
||||
const listener: resolverManager.ResolverListener = (
|
||||
maybeEndpointList: StatusOr<Endpoint[]>,
|
||||
attributes: { [key: string]: unknown},
|
||||
serviceConfig: StatusOr<ServiceConfig> | null,
|
||||
resolutionNote: string
|
||||
) => {
|
||||
assert(
|
||||
serviceConfig === null,
|
||||
'Should not have found service config'
|
||||
);
|
||||
count++;
|
||||
return true;
|
||||
};
|
||||
const resolver = resolverManager.createResolver(target, listener, {
|
||||
'grpc.service_config_disable_resolution': 1,
|
||||
|
@ -271,25 +278,27 @@ describe('Name Resolver', () => {
|
|||
const target = resolverManager.mapUriDefaultScheme(
|
||||
parseUri('loopback4.unittest.grpc.io')!
|
||||
)!;
|
||||
const listener: resolverManager.ResolverListener = {
|
||||
onSuccessfulResolution: (
|
||||
endpointList: Endpoint[],
|
||||
serviceConfig: ServiceConfig | null,
|
||||
serviceConfigError: StatusObject | null
|
||||
) => {
|
||||
// Only handle the first resolution result
|
||||
listener.onSuccessfulResolution = () => {};
|
||||
assert(
|
||||
hasMatchingAddress(endpointList, { host: '127.0.0.1', port: 443 }),
|
||||
`None of [${endpointList.map(addr =>
|
||||
endpointToString(addr)
|
||||
)}] matched '127.0.0.1:443'`
|
||||
);
|
||||
done();
|
||||
},
|
||||
onError: (error: StatusObject) => {
|
||||
done(new Error(`Failed with status ${error.details}`));
|
||||
},
|
||||
let resultSeen = false;
|
||||
const listener: resolverManager.ResolverListener = (
|
||||
maybeEndpointList: StatusOr<Endpoint[]>,
|
||||
attributes: { [key: string]: unknown},
|
||||
serviceConfig: StatusOr<ServiceConfig> | null,
|
||||
resolutionNote: string
|
||||
) => {
|
||||
if (resultSeen) {
|
||||
return true;
|
||||
}
|
||||
resultSeen = true;
|
||||
assert(maybeEndpointList.ok);
|
||||
const endpointList = maybeEndpointList.value;
|
||||
assert(
|
||||
hasMatchingAddress(endpointList, { host: '127.0.0.1', port: 443 }),
|
||||
`None of [${endpointList.map(addr =>
|
||||
endpointToString(addr)
|
||||
)}] matched '127.0.0.1:443'`
|
||||
);
|
||||
done();
|
||||
return true;
|
||||
};
|
||||
const resolver = resolverManager.createResolver(target, listener, {});
|
||||
resolver.updateResolution();
|
||||
|
@ -300,20 +309,22 @@ describe('Name Resolver', () => {
|
|||
const target = resolverManager.mapUriDefaultScheme(
|
||||
parseUri('loopback6.unittest.grpc.io')!
|
||||
)!;
|
||||
const listener: resolverManager.ResolverListener = {
|
||||
onSuccessfulResolution: (
|
||||
endpointList: Endpoint[],
|
||||
serviceConfig: ServiceConfig | null,
|
||||
serviceConfigError: StatusObject | null
|
||||
) => {
|
||||
// Only handle the first resolution result
|
||||
listener.onSuccessfulResolution = () => {};
|
||||
assert(hasMatchingAddress(endpointList, { host: '::1', port: 443 }));
|
||||
done();
|
||||
},
|
||||
onError: (error: StatusObject) => {
|
||||
done(new Error(`Failed with status ${error.details}`));
|
||||
},
|
||||
let resultSeen = false;
|
||||
const listener: resolverManager.ResolverListener = (
|
||||
maybeEndpointList: StatusOr<Endpoint[]>,
|
||||
attributes: { [key: string]: unknown},
|
||||
serviceConfig: StatusOr<ServiceConfig> | null,
|
||||
resolutionNote: string
|
||||
) => {
|
||||
if (resultSeen) {
|
||||
return true;
|
||||
}
|
||||
resultSeen = true;
|
||||
assert(maybeEndpointList.ok);
|
||||
const endpointList = maybeEndpointList.value;
|
||||
assert(hasMatchingAddress(endpointList, { host: '::1', port: 443 }));
|
||||
done();
|
||||
return true;
|
||||
};
|
||||
const resolver = resolverManager.createResolver(target, listener, {});
|
||||
resolver.updateResolution();
|
||||
|
@ -325,27 +336,27 @@ describe('Name Resolver', () => {
|
|||
const target = resolverManager.mapUriDefaultScheme(
|
||||
parseUri('loopback46.unittest.grpc.io')!
|
||||
)!;
|
||||
const listener: resolverManager.ResolverListener = {
|
||||
onSuccessfulResolution: (
|
||||
endpointList: Endpoint[],
|
||||
serviceConfig: ServiceConfig | null,
|
||||
serviceConfigError: StatusObject | null
|
||||
) => {
|
||||
// Only handle the first resolution result
|
||||
listener.onSuccessfulResolution = () => {};
|
||||
assert(
|
||||
hasMatchingAddress(endpointList, { host: '127.0.0.1', port: 443 }),
|
||||
`None of [${endpointList.map(addr =>
|
||||
endpointToString(addr)
|
||||
)}] matched '127.0.0.1:443'`
|
||||
);
|
||||
/* TODO(murgatroid99): check for IPv6 result, once we can get that
|
||||
* consistently */
|
||||
done();
|
||||
},
|
||||
onError: (error: StatusObject) => {
|
||||
done(new Error(`Failed with status ${error.details}`));
|
||||
},
|
||||
let resultSeen = false;
|
||||
const listener: resolverManager.ResolverListener = (
|
||||
maybeEndpointList: StatusOr<Endpoint[]>,
|
||||
attributes: { [key: string]: unknown},
|
||||
serviceConfig: StatusOr<ServiceConfig> | null,
|
||||
resolutionNote: string
|
||||
) => {
|
||||
if (resultSeen) {
|
||||
return true;
|
||||
}
|
||||
resultSeen = true;
|
||||
assert(maybeEndpointList.ok);
|
||||
const endpointList = maybeEndpointList.value;
|
||||
assert(
|
||||
hasMatchingAddress(endpointList, { host: '127.0.0.1', port: 443 }),
|
||||
`None of [${endpointList.map(addr =>
|
||||
endpointToString(addr)
|
||||
)}] matched '127.0.0.1:443'`
|
||||
);
|
||||
done();
|
||||
return true;
|
||||
};
|
||||
const resolver = resolverManager.createResolver(target, listener, {});
|
||||
resolver.updateResolution();
|
||||
|
@ -356,20 +367,22 @@ describe('Name Resolver', () => {
|
|||
const target = resolverManager.mapUriDefaultScheme(
|
||||
parseUri('network-tools.com')!
|
||||
)!;
|
||||
const listener: resolverManager.ResolverListener = {
|
||||
onSuccessfulResolution: (
|
||||
endpointList: Endpoint[],
|
||||
serviceConfig: ServiceConfig | null,
|
||||
serviceConfigError: StatusObject | null
|
||||
) => {
|
||||
// Only handle the first resolution result
|
||||
listener.onSuccessfulResolution = () => {};
|
||||
assert(endpointList.length > 0);
|
||||
done();
|
||||
},
|
||||
onError: (error: StatusObject) => {
|
||||
done(new Error(`Failed with status ${error.details}`));
|
||||
},
|
||||
let resultSeen = false;
|
||||
const listener: resolverManager.ResolverListener = (
|
||||
maybeEndpointList: StatusOr<Endpoint[]>,
|
||||
attributes: { [key: string]: unknown},
|
||||
serviceConfig: StatusOr<ServiceConfig> | null,
|
||||
resolutionNote: string
|
||||
) => {
|
||||
if (resultSeen) {
|
||||
return true;
|
||||
}
|
||||
resultSeen = true;
|
||||
assert(maybeEndpointList.ok);
|
||||
const endpointList = maybeEndpointList.value;
|
||||
assert(endpointList.length > 0);
|
||||
done();
|
||||
return true;
|
||||
};
|
||||
const resolver = resolverManager.createResolver(target, listener, {});
|
||||
resolver.updateResolution();
|
||||
|
@ -386,23 +399,23 @@ describe('Name Resolver', () => {
|
|||
const target2 = resolverManager.mapUriDefaultScheme(
|
||||
parseUri('grpc-test4.sandbox.googleapis.com')!
|
||||
)!;
|
||||
const listener: resolverManager.ResolverListener = {
|
||||
onSuccessfulResolution: (
|
||||
endpointList: Endpoint[],
|
||||
serviceConfig: ServiceConfig | null,
|
||||
serviceConfigError: StatusObject | null
|
||||
) => {
|
||||
assert(endpointList.length > 0);
|
||||
completeCount += 1;
|
||||
if (completeCount === 2) {
|
||||
// Only handle the first resolution result
|
||||
listener.onSuccessfulResolution = () => {};
|
||||
done();
|
||||
}
|
||||
},
|
||||
onError: (error: StatusObject) => {
|
||||
done(new Error(`Failed with status ${error.details}`));
|
||||
},
|
||||
const listener: resolverManager.ResolverListener = (
|
||||
maybeEndpointList: StatusOr<Endpoint[]>,
|
||||
attributes: { [key: string]: unknown},
|
||||
serviceConfig: StatusOr<ServiceConfig> | null,
|
||||
resolutionNote: string
|
||||
) => {
|
||||
if (completeCount >= 2) {
|
||||
return true;
|
||||
}
|
||||
assert(maybeEndpointList.ok);
|
||||
const endpointList = maybeEndpointList.value;
|
||||
assert(endpointList.length > 0);
|
||||
completeCount += 1;
|
||||
if (completeCount === 2) {
|
||||
done();
|
||||
}
|
||||
return true;
|
||||
};
|
||||
const resolver1 = resolverManager.createResolver(target1, listener, {});
|
||||
resolver1.updateResolution();
|
||||
|
@ -419,26 +432,25 @@ describe('Name Resolver', () => {
|
|||
let resultCount = 0;
|
||||
const resolver = resolverManager.createResolver(
|
||||
target,
|
||||
{
|
||||
onSuccessfulResolution: (
|
||||
endpointList: Endpoint[],
|
||||
serviceConfig: ServiceConfig | null,
|
||||
serviceConfigError: StatusObject | null
|
||||
) => {
|
||||
assert(
|
||||
hasMatchingAddress(endpointList, { host: '127.0.0.1', port: 443 })
|
||||
);
|
||||
assert(
|
||||
hasMatchingAddress(endpointList, { host: '::1', port: 443 })
|
||||
);
|
||||
resultCount += 1;
|
||||
if (resultCount === 1) {
|
||||
process.nextTick(() => resolver.updateResolution());
|
||||
}
|
||||
},
|
||||
onError: (error: StatusObject) => {
|
||||
assert.ifError(error);
|
||||
},
|
||||
(
|
||||
maybeEndpointList: StatusOr<Endpoint[]>,
|
||||
attributes: { [key: string]: unknown},
|
||||
serviceConfig: StatusOr<ServiceConfig> | null,
|
||||
resolutionNote: string
|
||||
) => {
|
||||
assert(maybeEndpointList.ok);
|
||||
const endpointList = maybeEndpointList.value;
|
||||
assert(
|
||||
hasMatchingAddress(endpointList, { host: '127.0.0.1', port: 443 })
|
||||
);
|
||||
assert(
|
||||
hasMatchingAddress(endpointList, { host: '::1', port: 443 })
|
||||
);
|
||||
resultCount += 1;
|
||||
if (resultCount === 1) {
|
||||
process.nextTick(() => resolver.updateResolution());
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{ 'grpc.dns_min_time_between_resolutions_ms': 2000 }
|
||||
);
|
||||
|
@ -455,20 +467,18 @@ describe('Name Resolver', () => {
|
|||
let resultCount = 0;
|
||||
const resolver = resolverManager.createResolver(
|
||||
target,
|
||||
{
|
||||
onSuccessfulResolution: (
|
||||
endpointList: Endpoint[],
|
||||
serviceConfig: ServiceConfig | null,
|
||||
serviceConfigError: StatusObject | null
|
||||
) => {
|
||||
assert.fail('Resolution succeeded unexpectedly');
|
||||
},
|
||||
onError: (error: StatusObject) => {
|
||||
resultCount += 1;
|
||||
if (resultCount === 1) {
|
||||
process.nextTick(() => resolver.updateResolution());
|
||||
}
|
||||
},
|
||||
(
|
||||
maybeEndpointList: StatusOr<Endpoint[]>,
|
||||
attributes: { [key: string]: unknown},
|
||||
serviceConfig: StatusOr<ServiceConfig> | null,
|
||||
resolutionNote: string
|
||||
) => {
|
||||
assert(!maybeEndpointList.ok);
|
||||
resultCount += 1;
|
||||
if (resultCount === 1) {
|
||||
process.nextTick(() => resolver.updateResolution());
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
@ -484,20 +494,22 @@ describe('Name Resolver', () => {
|
|||
const target = resolverManager.mapUriDefaultScheme(
|
||||
parseUri('unix:socket')!
|
||||
)!;
|
||||
const listener: resolverManager.ResolverListener = {
|
||||
onSuccessfulResolution: (
|
||||
endpointList: Endpoint[],
|
||||
serviceConfig: ServiceConfig | null,
|
||||
serviceConfigError: StatusObject | null
|
||||
) => {
|
||||
// Only handle the first resolution result
|
||||
listener.onSuccessfulResolution = () => {};
|
||||
assert(hasMatchingAddress(endpointList, { path: 'socket' }));
|
||||
done();
|
||||
},
|
||||
onError: (error: StatusObject) => {
|
||||
done(new Error(`Failed with status ${error.details}`));
|
||||
},
|
||||
let resultSeen = false;
|
||||
const listener: resolverManager.ResolverListener = (
|
||||
maybeEndpointList: StatusOr<Endpoint[]>,
|
||||
attributes: { [key: string]: unknown},
|
||||
serviceConfig: StatusOr<ServiceConfig> | null,
|
||||
resolutionNote: string
|
||||
) => {
|
||||
if (resultSeen) {
|
||||
return true;
|
||||
}
|
||||
resultSeen = true;
|
||||
assert(maybeEndpointList.ok);
|
||||
const endpointList = maybeEndpointList.value;
|
||||
assert(hasMatchingAddress(endpointList, { path: 'socket' }));
|
||||
done();
|
||||
return true;
|
||||
};
|
||||
const resolver = resolverManager.createResolver(target, listener, {});
|
||||
resolver.updateResolution();
|
||||
|
@ -506,20 +518,22 @@ describe('Name Resolver', () => {
|
|||
const target = resolverManager.mapUriDefaultScheme(
|
||||
parseUri('unix:///tmp/socket')!
|
||||
)!;
|
||||
const listener: resolverManager.ResolverListener = {
|
||||
onSuccessfulResolution: (
|
||||
endpointList: Endpoint[],
|
||||
serviceConfig: ServiceConfig | null,
|
||||
serviceConfigError: StatusObject | null
|
||||
) => {
|
||||
// Only handle the first resolution result
|
||||
listener.onSuccessfulResolution = () => {};
|
||||
assert(hasMatchingAddress(endpointList, { path: '/tmp/socket' }));
|
||||
done();
|
||||
},
|
||||
onError: (error: StatusObject) => {
|
||||
done(new Error(`Failed with status ${error.details}`));
|
||||
},
|
||||
let resultSeen = false;
|
||||
const listener: resolverManager.ResolverListener = (
|
||||
maybeEndpointList: StatusOr<Endpoint[]>,
|
||||
attributes: { [key: string]: unknown},
|
||||
serviceConfig: StatusOr<ServiceConfig> | null,
|
||||
resolutionNote: string
|
||||
) => {
|
||||
if (resultSeen) {
|
||||
return true;
|
||||
}
|
||||
resultSeen = true;
|
||||
assert(maybeEndpointList.ok);
|
||||
const endpointList = maybeEndpointList.value;
|
||||
assert(hasMatchingAddress(endpointList, { path: '/tmp/socket' }));
|
||||
done();
|
||||
return true;
|
||||
};
|
||||
const resolver = resolverManager.createResolver(target, listener, {});
|
||||
resolver.updateResolution();
|
||||
|
@ -530,22 +544,24 @@ describe('Name Resolver', () => {
|
|||
const target = resolverManager.mapUriDefaultScheme(
|
||||
parseUri('ipv4:127.0.0.1')!
|
||||
)!;
|
||||
const listener: resolverManager.ResolverListener = {
|
||||
onSuccessfulResolution: (
|
||||
endpointList: Endpoint[],
|
||||
serviceConfig: ServiceConfig | null,
|
||||
serviceConfigError: StatusObject | null
|
||||
) => {
|
||||
// Only handle the first resolution result
|
||||
listener.onSuccessfulResolution = () => {};
|
||||
assert(
|
||||
hasMatchingAddress(endpointList, { host: '127.0.0.1', port: 443 })
|
||||
);
|
||||
done();
|
||||
},
|
||||
onError: (error: StatusObject) => {
|
||||
done(new Error(`Failed with status ${error.details}`));
|
||||
},
|
||||
let resultSeen = false;
|
||||
const listener: resolverManager.ResolverListener = (
|
||||
maybeEndpointList: StatusOr<Endpoint[]>,
|
||||
attributes: { [key: string]: unknown},
|
||||
serviceConfig: StatusOr<ServiceConfig> | null,
|
||||
resolutionNote: string
|
||||
) => {
|
||||
if (resultSeen) {
|
||||
return true;
|
||||
}
|
||||
resultSeen = true;
|
||||
assert(maybeEndpointList.ok);
|
||||
const endpointList = maybeEndpointList.value;
|
||||
assert(
|
||||
hasMatchingAddress(endpointList, { host: '127.0.0.1', port: 443 })
|
||||
);
|
||||
done();
|
||||
return true;
|
||||
};
|
||||
const resolver = resolverManager.createResolver(target, listener, {});
|
||||
resolver.updateResolution();
|
||||
|
@ -554,22 +570,24 @@ describe('Name Resolver', () => {
|
|||
const target = resolverManager.mapUriDefaultScheme(
|
||||
parseUri('ipv4:127.0.0.1:50051')!
|
||||
)!;
|
||||
const listener: resolverManager.ResolverListener = {
|
||||
onSuccessfulResolution: (
|
||||
endpointList: Endpoint[],
|
||||
serviceConfig: ServiceConfig | null,
|
||||
serviceConfigError: StatusObject | null
|
||||
) => {
|
||||
// Only handle the first resolution result
|
||||
listener.onSuccessfulResolution = () => {};
|
||||
assert(
|
||||
hasMatchingAddress(endpointList, { host: '127.0.0.1', port: 50051 })
|
||||
);
|
||||
done();
|
||||
},
|
||||
onError: (error: StatusObject) => {
|
||||
done(new Error(`Failed with status ${error.details}`));
|
||||
},
|
||||
let resultSeen = false;
|
||||
const listener: resolverManager.ResolverListener = (
|
||||
maybeEndpointList: StatusOr<Endpoint[]>,
|
||||
attributes: { [key: string]: unknown},
|
||||
serviceConfig: StatusOr<ServiceConfig> | null,
|
||||
resolutionNote: string
|
||||
) => {
|
||||
if (resultSeen) {
|
||||
return true;
|
||||
}
|
||||
resultSeen = true;
|
||||
assert(maybeEndpointList.ok);
|
||||
const endpointList = maybeEndpointList.value;
|
||||
assert(
|
||||
hasMatchingAddress(endpointList, { host: '127.0.0.1', port: 50051 })
|
||||
);
|
||||
done();
|
||||
return true;
|
||||
};
|
||||
const resolver = resolverManager.createResolver(target, listener, {});
|
||||
resolver.updateResolution();
|
||||
|
@ -578,25 +596,27 @@ describe('Name Resolver', () => {
|
|||
const target = resolverManager.mapUriDefaultScheme(
|
||||
parseUri('ipv4:127.0.0.1:50051,127.0.0.1:50052')!
|
||||
)!;
|
||||
const listener: resolverManager.ResolverListener = {
|
||||
onSuccessfulResolution: (
|
||||
endpointList: Endpoint[],
|
||||
serviceConfig: ServiceConfig | null,
|
||||
serviceConfigError: StatusObject | null
|
||||
) => {
|
||||
// Only handle the first resolution result
|
||||
listener.onSuccessfulResolution = () => {};
|
||||
assert(
|
||||
hasMatchingAddress(endpointList, { host: '127.0.0.1', port: 50051 })
|
||||
);
|
||||
assert(
|
||||
hasMatchingAddress(endpointList, { host: '127.0.0.1', port: 50052 })
|
||||
);
|
||||
done();
|
||||
},
|
||||
onError: (error: StatusObject) => {
|
||||
done(new Error(`Failed with status ${error.details}`));
|
||||
},
|
||||
let resultSeen = false;
|
||||
const listener: resolverManager.ResolverListener = (
|
||||
maybeEndpointList: StatusOr<Endpoint[]>,
|
||||
attributes: { [key: string]: unknown},
|
||||
serviceConfig: StatusOr<ServiceConfig> | null,
|
||||
resolutionNote: string
|
||||
) => {
|
||||
if (resultSeen) {
|
||||
return true;
|
||||
}
|
||||
resultSeen = true;
|
||||
assert(maybeEndpointList.ok);
|
||||
const endpointList = maybeEndpointList.value;
|
||||
assert(
|
||||
hasMatchingAddress(endpointList, { host: '127.0.0.1', port: 50051 })
|
||||
);
|
||||
assert(
|
||||
hasMatchingAddress(endpointList, { host: '127.0.0.1', port: 50052 })
|
||||
);
|
||||
done();
|
||||
return true;
|
||||
};
|
||||
const resolver = resolverManager.createResolver(target, listener, {});
|
||||
resolver.updateResolution();
|
||||
|
@ -605,20 +625,22 @@ describe('Name Resolver', () => {
|
|||
const target = resolverManager.mapUriDefaultScheme(
|
||||
parseUri('ipv6:::1')!
|
||||
)!;
|
||||
const listener: resolverManager.ResolverListener = {
|
||||
onSuccessfulResolution: (
|
||||
endpointList: Endpoint[],
|
||||
serviceConfig: ServiceConfig | null,
|
||||
serviceConfigError: StatusObject | null
|
||||
) => {
|
||||
// Only handle the first resolution result
|
||||
listener.onSuccessfulResolution = () => {};
|
||||
assert(hasMatchingAddress(endpointList, { host: '::1', port: 443 }));
|
||||
done();
|
||||
},
|
||||
onError: (error: StatusObject) => {
|
||||
done(new Error(`Failed with status ${error.details}`));
|
||||
},
|
||||
let resultSeen = false;
|
||||
const listener: resolverManager.ResolverListener = (
|
||||
maybeEndpointList: StatusOr<Endpoint[]>,
|
||||
attributes: { [key: string]: unknown},
|
||||
serviceConfig: StatusOr<ServiceConfig> | null,
|
||||
resolutionNote: string
|
||||
) => {
|
||||
if (resultSeen) {
|
||||
return true;
|
||||
}
|
||||
resultSeen = true;
|
||||
assert(maybeEndpointList.ok);
|
||||
const endpointList = maybeEndpointList.value;
|
||||
assert(hasMatchingAddress(endpointList, { host: '::1', port: 443 }));
|
||||
done();
|
||||
return true;
|
||||
};
|
||||
const resolver = resolverManager.createResolver(target, listener, {});
|
||||
resolver.updateResolution();
|
||||
|
@ -627,22 +649,24 @@ describe('Name Resolver', () => {
|
|||
const target = resolverManager.mapUriDefaultScheme(
|
||||
parseUri('ipv6:[::1]:50051')!
|
||||
)!;
|
||||
const listener: resolverManager.ResolverListener = {
|
||||
onSuccessfulResolution: (
|
||||
endpointList: Endpoint[],
|
||||
serviceConfig: ServiceConfig | null,
|
||||
serviceConfigError: StatusObject | null
|
||||
) => {
|
||||
// Only handle the first resolution result
|
||||
listener.onSuccessfulResolution = () => {};
|
||||
assert(
|
||||
hasMatchingAddress(endpointList, { host: '::1', port: 50051 })
|
||||
);
|
||||
done();
|
||||
},
|
||||
onError: (error: StatusObject) => {
|
||||
done(new Error(`Failed with status ${error.details}`));
|
||||
},
|
||||
let resultSeen = false;
|
||||
const listener: resolverManager.ResolverListener = (
|
||||
maybeEndpointList: StatusOr<Endpoint[]>,
|
||||
attributes: { [key: string]: unknown},
|
||||
serviceConfig: StatusOr<ServiceConfig> | null,
|
||||
resolutionNote: string
|
||||
) => {
|
||||
if (resultSeen) {
|
||||
return true;
|
||||
}
|
||||
resultSeen = true;
|
||||
assert(maybeEndpointList.ok);
|
||||
const endpointList = maybeEndpointList.value;
|
||||
assert(
|
||||
hasMatchingAddress(endpointList, { host: '::1', port: 50051 })
|
||||
);
|
||||
done();
|
||||
return true;
|
||||
};
|
||||
const resolver = resolverManager.createResolver(target, listener, {});
|
||||
resolver.updateResolution();
|
||||
|
@ -651,25 +675,27 @@ describe('Name Resolver', () => {
|
|||
const target = resolverManager.mapUriDefaultScheme(
|
||||
parseUri('ipv6:[::1]:50051,[::1]:50052')!
|
||||
)!;
|
||||
const listener: resolverManager.ResolverListener = {
|
||||
onSuccessfulResolution: (
|
||||
endpointList: Endpoint[],
|
||||
serviceConfig: ServiceConfig | null,
|
||||
serviceConfigError: StatusObject | null
|
||||
) => {
|
||||
// Only handle the first resolution result
|
||||
listener.onSuccessfulResolution = () => {};
|
||||
assert(
|
||||
hasMatchingAddress(endpointList, { host: '::1', port: 50051 })
|
||||
);
|
||||
assert(
|
||||
hasMatchingAddress(endpointList, { host: '::1', port: 50052 })
|
||||
);
|
||||
done();
|
||||
},
|
||||
onError: (error: StatusObject) => {
|
||||
done(new Error(`Failed with status ${error.details}`));
|
||||
},
|
||||
let resultSeen = false;
|
||||
const listener: resolverManager.ResolverListener = (
|
||||
maybeEndpointList: StatusOr<Endpoint[]>,
|
||||
attributes: { [key: string]: unknown},
|
||||
serviceConfig: StatusOr<ServiceConfig> | null,
|
||||
resolutionNote: string
|
||||
) => {
|
||||
if (resultSeen) {
|
||||
return true;
|
||||
}
|
||||
resultSeen = true;
|
||||
assert(maybeEndpointList.ok);
|
||||
const endpointList = maybeEndpointList.value;
|
||||
assert(
|
||||
hasMatchingAddress(endpointList, { host: '::1', port: 50051 })
|
||||
);
|
||||
assert(
|
||||
hasMatchingAddress(endpointList, { host: '::1', port: 50052 })
|
||||
);
|
||||
done();
|
||||
return true;
|
||||
};
|
||||
const resolver = resolverManager.createResolver(target, listener, {});
|
||||
resolver.updateResolution();
|
||||
|
|
|
@ -127,6 +127,22 @@ const testHeaderInjectionInterceptor: grpc.ServerInterceptor = (
|
|||
});
|
||||
};
|
||||
|
||||
const callExaminationInterceptor: grpc.ServerInterceptor = (
|
||||
methodDescriptor,
|
||||
call
|
||||
) => {
|
||||
const connectionInfo = call.getConnectionInfo();
|
||||
return new grpc.ServerInterceptingCall(call, {
|
||||
sendMetadata: (metadata, next) => {
|
||||
metadata.add('local-address', `${connectionInfo.localAddress}`);
|
||||
metadata.add('local-port', `${connectionInfo.localPort}`);
|
||||
metadata.add('remote-address', `${connectionInfo.remoteAddress}`);
|
||||
metadata.add('remote-port', `${connectionInfo.remotePort}`);
|
||||
next(metadata);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
describe('Server interceptors', () => {
|
||||
describe('Auth-type interceptor', () => {
|
||||
let server: grpc.Server;
|
||||
|
@ -336,4 +352,45 @@ describe('Server interceptors', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
describe('Call properties', () => {
|
||||
let server: grpc.Server;
|
||||
let client: TestClient;
|
||||
let portNum: number;
|
||||
/* Tests that an interceptor can entirely prevent the handler from being
|
||||
* invoked, based on the contents of the metadata. */
|
||||
before(done => {
|
||||
server = new grpc.Server({ interceptors: [callExaminationInterceptor] });
|
||||
server.addService(echoService.service, {
|
||||
echo: (
|
||||
call: grpc.ServerUnaryCall<any, any>,
|
||||
callback: grpc.sendUnaryData<any>
|
||||
) => {
|
||||
callback(null, call.request);
|
||||
},
|
||||
});
|
||||
server.bindAsync(
|
||||
'[::1]:0',
|
||||
grpc.ServerCredentials.createInsecure(),
|
||||
(error, port) => {
|
||||
assert.ifError(error);
|
||||
client = new TestClient(`localhost:${port}`, false);
|
||||
portNum = port;
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
after(done => {
|
||||
client.close();
|
||||
server.tryShutdown(done);
|
||||
});
|
||||
it('Should get valid connection information', done => {
|
||||
const call = client.sendRequest(done);
|
||||
call.on('metadata', metadata => {
|
||||
assert.strictEqual(metadata.get('local-address')[0], '::1');
|
||||
assert.strictEqual(metadata.get('remote-address')[0], '::1');
|
||||
assert.strictEqual(metadata.get('local-port')[0], `${portNum}`);
|
||||
assert.notStrictEqual(metadata.get('remote-port')[0], 'undefined');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -61,44 +61,53 @@ The `proto-loader-gen-types` script distributed with this package can be used to
|
|||
proto-loader-gen-types.js [options] filenames...
|
||||
|
||||
Options:
|
||||
--help Show help [boolean]
|
||||
--version Show version number [boolean]
|
||||
--keepCase Preserve the case of field names
|
||||
--help Show help [boolean]
|
||||
--version Show version number [boolean]
|
||||
--keepCase Preserve the case of field names
|
||||
[boolean] [default: false]
|
||||
--longs The type that should be used to output 64 bit integer
|
||||
values. Can be String, Number[string] [default: "Long"]
|
||||
--enums The type that should be used to output enum fields. Can
|
||||
be String [string] [default: "number"]
|
||||
--bytes The type that should be used to output bytes fields.
|
||||
Can be String, Array [string] [default: "Buffer"]
|
||||
--defaults Output default values for omitted fields
|
||||
--longs The type that should be used to output 64 bit
|
||||
integer values. Can be String, Number
|
||||
[string] [default: "Long"]
|
||||
--enums The type that should be used to output enum fields.
|
||||
Can be String [string] [default: "number"]
|
||||
--bytes The type that should be used to output bytes
|
||||
fields. Can be String, Array
|
||||
[string] [default: "Buffer"]
|
||||
--defaults Output default values for omitted fields
|
||||
[boolean] [default: false]
|
||||
--arrays Output default values for omitted repeated fields even
|
||||
if --defaults is not set [boolean] [default: false]
|
||||
--objects Output default values for omitted message fields even
|
||||
if --defaults is not set [boolean] [default: false]
|
||||
--oneofs Output virtual oneof fields set to the present field's
|
||||
name [boolean] [default: false]
|
||||
--json Represent Infinity and NaN as strings in float fields.
|
||||
Also decode google.protobuf.Any automatically
|
||||
--arrays Output default values for omitted repeated fields
|
||||
even if --defaults is not set
|
||||
[boolean] [default: false]
|
||||
--includeComments Generate doc comments from comments in the original
|
||||
files [boolean] [default: false]
|
||||
-I, --includeDirs Directories to search for included files [array]
|
||||
-O, --outDir Directory in which to output files [string] [required]
|
||||
--grpcLib The gRPC implementation library that these types will
|
||||
be used with. If not provided, some types will not be
|
||||
generated [string]
|
||||
--inputTemplate Template for mapping input or "permissive" type names
|
||||
[string] [default: "%s"]
|
||||
--outputTemplate Template for mapping output or "restricted" type names
|
||||
[string] [default: "%s__Output"]
|
||||
--inputBranded Output property for branded type for "permissive"
|
||||
types with fullName of the Message as its value
|
||||
--objects Output default values for omitted message fields
|
||||
even if --defaults is not set
|
||||
[boolean] [default: false]
|
||||
--outputBranded Output property for branded type for "restricted"
|
||||
types with fullName of the Message as its value
|
||||
--oneofs Output virtual oneof fields set to the present
|
||||
field's name [boolean] [default: false]
|
||||
--json Represent Infinity and NaN as strings in float
|
||||
fields. Also decode google.protobuf.Any
|
||||
automatically [boolean] [default: false]
|
||||
--includeComments Generate doc comments from comments in the original
|
||||
files [boolean] [default: false]
|
||||
-I, --includeDirs Directories to search for included files [array]
|
||||
-O, --outDir Directory in which to output files
|
||||
[string] [required]
|
||||
--grpcLib The gRPC implementation library that these types
|
||||
will be used with. If not provided, some types will
|
||||
not be generated [string]
|
||||
--inputTemplate Template for mapping input or "permissive" type
|
||||
names [string] [default: "%s"]
|
||||
--outputTemplate Template for mapping output or "restricted" type
|
||||
names [string] [default: "%s__Output"]
|
||||
--inputBranded Output property for branded type for "permissive"
|
||||
types with fullName of the Message as its value
|
||||
[boolean] [default: false]
|
||||
--outputBranded Output property for branded type for "restricted"
|
||||
types with fullName of the Message as its value
|
||||
[boolean] [default: false]
|
||||
--targetFileExtension File extension for generated files.
|
||||
[string] [default: ".ts"]
|
||||
--importFileExtension File extension for import specifiers in generated
|
||||
code. [string] [default: ""]
|
||||
```
|
||||
|
||||
### Example Usage
|
||||
|
|
|
@ -47,6 +47,8 @@ type GeneratorOptions = Protobuf.IParseOptions & Protobuf.IConversionOptions & {
|
|||
outputTemplate: string;
|
||||
inputBranded: boolean;
|
||||
outputBranded: boolean;
|
||||
targetFileExtension: string;
|
||||
importFileExtension: string;
|
||||
}
|
||||
|
||||
class TextFormatter {
|
||||
|
@ -105,8 +107,8 @@ function getImportPath(to: Protobuf.Type | Protobuf.Enum | Protobuf.Service): st
|
|||
return stripLeadingPeriod(to.fullName).replace(/\./g, '/');
|
||||
}
|
||||
|
||||
function getPath(to: Protobuf.Type | Protobuf.Enum | Protobuf.Service) {
|
||||
return stripLeadingPeriod(to.fullName).replace(/\./g, '/') + '.ts';
|
||||
function getPath(to: Protobuf.Type | Protobuf.Enum | Protobuf.Service, options: GeneratorOptions) {
|
||||
return stripLeadingPeriod(to.fullName).replace(/\./g, '/') + options.targetFileExtension;
|
||||
}
|
||||
|
||||
function getPathToRoot(from: Protobuf.NamespaceBase) {
|
||||
|
@ -153,7 +155,7 @@ function getImportLine(dependency: Protobuf.Type | Protobuf.Enum | Protobuf.Serv
|
|||
throw new Error('Invalid object passed to getImportLine');
|
||||
}
|
||||
}
|
||||
return `import type { ${importedTypes} } from '${filePath}';`
|
||||
return `import type { ${importedTypes} } from '${filePath}${options.importFileExtension}';`
|
||||
}
|
||||
|
||||
function getChildMessagesAndEnums(namespace: Protobuf.NamespaceBase): (Protobuf.Type | Protobuf.Enum)[] {
|
||||
|
@ -787,21 +789,21 @@ function generateFilesForNamespace(namespace: Protobuf.NamespaceBase, options: G
|
|||
if (nested instanceof Protobuf.Type) {
|
||||
generateMessageInterfaces(fileFormatter, nested, options);
|
||||
if (options.verbose) {
|
||||
console.log(`Writing ${options.outDir}/${getPath(nested)} from file ${nested.filename}`);
|
||||
console.log(`Writing ${options.outDir}/${getPath(nested, options)} from file ${nested.filename}`);
|
||||
}
|
||||
filePromises.push(writeFile(`${options.outDir}/${getPath(nested)}`, fileFormatter.getFullText()));
|
||||
filePromises.push(writeFile(`${options.outDir}/${getPath(nested, options)}`, fileFormatter.getFullText()));
|
||||
} else if (nested instanceof Protobuf.Enum) {
|
||||
generateEnumInterface(fileFormatter, nested, options);
|
||||
if (options.verbose) {
|
||||
console.log(`Writing ${options.outDir}/${getPath(nested)} from file ${nested.filename}`);
|
||||
console.log(`Writing ${options.outDir}/${getPath(nested, options)} from file ${nested.filename}`);
|
||||
}
|
||||
filePromises.push(writeFile(`${options.outDir}/${getPath(nested)}`, fileFormatter.getFullText()));
|
||||
filePromises.push(writeFile(`${options.outDir}/${getPath(nested, options)}`, fileFormatter.getFullText()));
|
||||
} else if (nested instanceof Protobuf.Service) {
|
||||
generateServiceInterfaces(fileFormatter, nested, options);
|
||||
if (options.verbose) {
|
||||
console.log(`Writing ${options.outDir}/${getPath(nested)} from file ${nested.filename}`);
|
||||
console.log(`Writing ${options.outDir}/${getPath(nested, options)} from file ${nested.filename}`);
|
||||
}
|
||||
filePromises.push(writeFile(`${options.outDir}/${getPath(nested)}`, fileFormatter.getFullText()));
|
||||
filePromises.push(writeFile(`${options.outDir}/${getPath(nested, options)}`, fileFormatter.getFullText()));
|
||||
} else if (isNamespaceBase(nested)) {
|
||||
filePromises.push(...generateFilesForNamespace(nested, options));
|
||||
}
|
||||
|
@ -830,7 +832,7 @@ async function writeAllFiles(protoFiles: string[], options: GeneratorOptions) {
|
|||
await fs.promises.mkdir(options.outDir, {recursive: true});
|
||||
const basenameMap = new Map<string, string[]>();
|
||||
for (const filename of protoFiles) {
|
||||
const basename = path.basename(filename).replace(/\.proto$/, '.ts');
|
||||
const basename = path.basename(filename).replace(/\.proto$/, options.targetFileExtension);
|
||||
if (basenameMap.has(basename)) {
|
||||
basenameMap.get(basename)!.push(filename);
|
||||
} else {
|
||||
|
@ -877,6 +879,8 @@ async function runScript() {
|
|||
.option('outputTemplate', { string: true, default: `${templateStr}__Output` })
|
||||
.option('inputBranded', boolDefaultFalseOption)
|
||||
.option('outputBranded', boolDefaultFalseOption)
|
||||
.option('targetFileExtension', { string: true, default: '.ts' })
|
||||
.option('importFileExtension', { string: true, default: '' })
|
||||
.coerce('longs', value => {
|
||||
switch (value) {
|
||||
case 'String': return String;
|
||||
|
@ -916,6 +920,8 @@ async function runScript() {
|
|||
outputTemplate: 'Template for mapping output or "restricted" type names',
|
||||
inputBranded: 'Output property for branded type for "permissive" types with fullName of the Message as its value',
|
||||
outputBranded: 'Output property for branded type for "restricted" types with fullName of the Message as its value',
|
||||
targetFileExtension: 'File extension for generated files.',
|
||||
importFileExtension: 'File extension for import specifiers in generated code.'
|
||||
}).demandOption(['outDir'])
|
||||
.demand(1)
|
||||
.usage('$0 [options] filenames...')
|
||||
|
|
|
@ -42,11 +42,15 @@ export interface ProtoGrpcType {
|
|||
Any: MessageTypeDefinition
|
||||
DescriptorProto: MessageTypeDefinition
|
||||
Duration: MessageTypeDefinition
|
||||
Edition: EnumTypeDefinition
|
||||
Empty: MessageTypeDefinition
|
||||
EnumDescriptorProto: MessageTypeDefinition
|
||||
EnumOptions: MessageTypeDefinition
|
||||
EnumValueDescriptorProto: MessageTypeDefinition
|
||||
EnumValueOptions: MessageTypeDefinition
|
||||
ExtensionRangeOptions: MessageTypeDefinition
|
||||
FeatureSet: MessageTypeDefinition
|
||||
FeatureSetDefaults: MessageTypeDefinition
|
||||
FieldDescriptorProto: MessageTypeDefinition
|
||||
FieldOptions: MessageTypeDefinition
|
||||
FileDescriptorProto: MessageTypeDefinition
|
||||
|
@ -61,6 +65,7 @@ export interface ProtoGrpcType {
|
|||
ServiceDescriptorProto: MessageTypeDefinition
|
||||
ServiceOptions: MessageTypeDefinition
|
||||
SourceCodeInfo: MessageTypeDefinition
|
||||
SymbolVisibility: EnumTypeDefinition
|
||||
Timestamp: MessageTypeDefinition
|
||||
UninterpretedOption: MessageTypeDefinition
|
||||
}
|
||||
|
|
|
@ -5,15 +5,19 @@ import type { IDescriptorProto as I_google_protobuf_DescriptorProto, ODescriptor
|
|||
import type { IEnumDescriptorProto as I_google_protobuf_EnumDescriptorProto, OEnumDescriptorProto as O_google_protobuf_EnumDescriptorProto } from '../../google/protobuf/EnumDescriptorProto';
|
||||
import type { IMessageOptions as I_google_protobuf_MessageOptions, OMessageOptions as O_google_protobuf_MessageOptions } from '../../google/protobuf/MessageOptions';
|
||||
import type { IOneofDescriptorProto as I_google_protobuf_OneofDescriptorProto, OOneofDescriptorProto as O_google_protobuf_OneofDescriptorProto } from '../../google/protobuf/OneofDescriptorProto';
|
||||
import type { ISymbolVisibility as I_google_protobuf_SymbolVisibility, OSymbolVisibility as O_google_protobuf_SymbolVisibility } from '../../google/protobuf/SymbolVisibility';
|
||||
import type { IExtensionRangeOptions as I_google_protobuf_ExtensionRangeOptions, OExtensionRangeOptions as O_google_protobuf_ExtensionRangeOptions } from '../../google/protobuf/ExtensionRangeOptions';
|
||||
|
||||
export interface I_google_protobuf_DescriptorProto_ExtensionRange {
|
||||
'start'?: (number);
|
||||
'end'?: (number);
|
||||
'options'?: (I_google_protobuf_ExtensionRangeOptions | null);
|
||||
}
|
||||
|
||||
export interface O_google_protobuf_DescriptorProto_ExtensionRange {
|
||||
'start': (number);
|
||||
'end': (number);
|
||||
'options': (O_google_protobuf_ExtensionRangeOptions | null);
|
||||
}
|
||||
|
||||
export interface I_google_protobuf_DescriptorProto_ReservedRange {
|
||||
|
@ -37,6 +41,7 @@ export interface IDescriptorProto {
|
|||
'oneofDecl'?: (I_google_protobuf_OneofDescriptorProto)[];
|
||||
'reservedRange'?: (I_google_protobuf_DescriptorProto_ReservedRange)[];
|
||||
'reservedName'?: (string)[];
|
||||
'visibility'?: (I_google_protobuf_SymbolVisibility);
|
||||
}
|
||||
|
||||
export interface ODescriptorProto {
|
||||
|
@ -50,4 +55,5 @@ export interface ODescriptorProto {
|
|||
'oneofDecl': (O_google_protobuf_OneofDescriptorProto)[];
|
||||
'reservedRange': (O_google_protobuf_DescriptorProto_ReservedRange)[];
|
||||
'reservedName': (string)[];
|
||||
'visibility': (O_google_protobuf_SymbolVisibility);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
// Original file: null
|
||||
|
||||
export const Edition = {
|
||||
EDITION_UNKNOWN: 'EDITION_UNKNOWN',
|
||||
EDITION_LEGACY: 'EDITION_LEGACY',
|
||||
EDITION_PROTO2: 'EDITION_PROTO2',
|
||||
EDITION_PROTO3: 'EDITION_PROTO3',
|
||||
EDITION_2023: 'EDITION_2023',
|
||||
EDITION_2024: 'EDITION_2024',
|
||||
EDITION_1_TEST_ONLY: 'EDITION_1_TEST_ONLY',
|
||||
EDITION_2_TEST_ONLY: 'EDITION_2_TEST_ONLY',
|
||||
EDITION_99997_TEST_ONLY: 'EDITION_99997_TEST_ONLY',
|
||||
EDITION_99998_TEST_ONLY: 'EDITION_99998_TEST_ONLY',
|
||||
EDITION_99999_TEST_ONLY: 'EDITION_99999_TEST_ONLY',
|
||||
EDITION_MAX: 'EDITION_MAX',
|
||||
} as const;
|
||||
|
||||
export type IEdition =
|
||||
| 'EDITION_UNKNOWN'
|
||||
| 0
|
||||
| 'EDITION_LEGACY'
|
||||
| 900
|
||||
| 'EDITION_PROTO2'
|
||||
| 998
|
||||
| 'EDITION_PROTO3'
|
||||
| 999
|
||||
| 'EDITION_2023'
|
||||
| 1000
|
||||
| 'EDITION_2024'
|
||||
| 1001
|
||||
| 'EDITION_1_TEST_ONLY'
|
||||
| 1
|
||||
| 'EDITION_2_TEST_ONLY'
|
||||
| 2
|
||||
| 'EDITION_99997_TEST_ONLY'
|
||||
| 99997
|
||||
| 'EDITION_99998_TEST_ONLY'
|
||||
| 99998
|
||||
| 'EDITION_99999_TEST_ONLY'
|
||||
| 99999
|
||||
| 'EDITION_MAX'
|
||||
| 2147483647
|
||||
|
||||
export type OEdition = typeof Edition[keyof typeof Edition]
|
|
@ -2,15 +2,32 @@
|
|||
|
||||
import type { IEnumValueDescriptorProto as I_google_protobuf_EnumValueDescriptorProto, OEnumValueDescriptorProto as O_google_protobuf_EnumValueDescriptorProto } from '../../google/protobuf/EnumValueDescriptorProto';
|
||||
import type { IEnumOptions as I_google_protobuf_EnumOptions, OEnumOptions as O_google_protobuf_EnumOptions } from '../../google/protobuf/EnumOptions';
|
||||
import type { ISymbolVisibility as I_google_protobuf_SymbolVisibility, OSymbolVisibility as O_google_protobuf_SymbolVisibility } from '../../google/protobuf/SymbolVisibility';
|
||||
|
||||
export interface I_google_protobuf_EnumDescriptorProto_EnumReservedRange {
|
||||
'start'?: (number);
|
||||
'end'?: (number);
|
||||
}
|
||||
|
||||
export interface O_google_protobuf_EnumDescriptorProto_EnumReservedRange {
|
||||
'start': (number);
|
||||
'end': (number);
|
||||
}
|
||||
|
||||
export interface IEnumDescriptorProto {
|
||||
'name'?: (string);
|
||||
'value'?: (I_google_protobuf_EnumValueDescriptorProto)[];
|
||||
'options'?: (I_google_protobuf_EnumOptions | null);
|
||||
'reservedRange'?: (I_google_protobuf_EnumDescriptorProto_EnumReservedRange)[];
|
||||
'reservedName'?: (string)[];
|
||||
'visibility'?: (I_google_protobuf_SymbolVisibility);
|
||||
}
|
||||
|
||||
export interface OEnumDescriptorProto {
|
||||
'name': (string);
|
||||
'value': (O_google_protobuf_EnumValueDescriptorProto)[];
|
||||
'options': (O_google_protobuf_EnumOptions | null);
|
||||
'reservedRange': (O_google_protobuf_EnumDescriptorProto_EnumReservedRange)[];
|
||||
'reservedName': (string)[];
|
||||
'visibility': (O_google_protobuf_SymbolVisibility);
|
||||
}
|
||||
|
|
|
@ -1,15 +1,26 @@
|
|||
// Original file: null
|
||||
|
||||
import type { IFeatureSet as I_google_protobuf_FeatureSet, OFeatureSet as O_google_protobuf_FeatureSet } from '../../google/protobuf/FeatureSet';
|
||||
import type { IUninterpretedOption as I_google_protobuf_UninterpretedOption, OUninterpretedOption as O_google_protobuf_UninterpretedOption } from '../../google/protobuf/UninterpretedOption';
|
||||
|
||||
export interface IEnumOptions {
|
||||
'allowAlias'?: (boolean);
|
||||
'deprecated'?: (boolean);
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
'deprecatedLegacyJsonFieldConflicts'?: (boolean);
|
||||
'features'?: (I_google_protobuf_FeatureSet | null);
|
||||
'uninterpretedOption'?: (I_google_protobuf_UninterpretedOption)[];
|
||||
}
|
||||
|
||||
export interface OEnumOptions {
|
||||
'allowAlias': (boolean);
|
||||
'deprecated': (boolean);
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
'deprecatedLegacyJsonFieldConflicts': (boolean);
|
||||
'features': (O_google_protobuf_FeatureSet | null);
|
||||
'uninterpretedOption': (O_google_protobuf_UninterpretedOption)[];
|
||||
}
|
||||
|
|
|
@ -1,13 +1,21 @@
|
|||
// Original file: null
|
||||
|
||||
import type { IFeatureSet as I_google_protobuf_FeatureSet, OFeatureSet as O_google_protobuf_FeatureSet } from '../../google/protobuf/FeatureSet';
|
||||
import type { I_google_protobuf_FieldOptions_FeatureSupport, O_google_protobuf_FieldOptions_FeatureSupport } from '../../google/protobuf/FieldOptions';
|
||||
import type { IUninterpretedOption as I_google_protobuf_UninterpretedOption, OUninterpretedOption as O_google_protobuf_UninterpretedOption } from '../../google/protobuf/UninterpretedOption';
|
||||
|
||||
export interface IEnumValueOptions {
|
||||
'deprecated'?: (boolean);
|
||||
'features'?: (I_google_protobuf_FeatureSet | null);
|
||||
'debugRedact'?: (boolean);
|
||||
'featureSupport'?: (I_google_protobuf_FieldOptions_FeatureSupport | null);
|
||||
'uninterpretedOption'?: (I_google_protobuf_UninterpretedOption)[];
|
||||
}
|
||||
|
||||
export interface OEnumValueOptions {
|
||||
'deprecated': (boolean);
|
||||
'features': (O_google_protobuf_FeatureSet | null);
|
||||
'debugRedact': (boolean);
|
||||
'featureSupport': (O_google_protobuf_FieldOptions_FeatureSupport | null);
|
||||
'uninterpretedOption': (O_google_protobuf_UninterpretedOption)[];
|
||||
}
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
// Original file: null
|
||||
|
||||
import type { IFeatureSet as I_google_protobuf_FeatureSet, OFeatureSet as O_google_protobuf_FeatureSet } from '../../google/protobuf/FeatureSet';
|
||||
import type { IUninterpretedOption as I_google_protobuf_UninterpretedOption, OUninterpretedOption as O_google_protobuf_UninterpretedOption } from '../../google/protobuf/UninterpretedOption';
|
||||
|
||||
export interface I_google_protobuf_ExtensionRangeOptions_Declaration {
|
||||
'number'?: (number);
|
||||
'fullName'?: (string);
|
||||
'type'?: (string);
|
||||
'reserved'?: (boolean);
|
||||
'repeated'?: (boolean);
|
||||
}
|
||||
|
||||
export interface O_google_protobuf_ExtensionRangeOptions_Declaration {
|
||||
'number': (number);
|
||||
'fullName': (string);
|
||||
'type': (string);
|
||||
'reserved': (boolean);
|
||||
'repeated': (boolean);
|
||||
}
|
||||
|
||||
// Original file: null
|
||||
|
||||
export const _google_protobuf_ExtensionRangeOptions_VerificationState = {
|
||||
DECLARATION: 'DECLARATION',
|
||||
UNVERIFIED: 'UNVERIFIED',
|
||||
} as const;
|
||||
|
||||
export type I_google_protobuf_ExtensionRangeOptions_VerificationState =
|
||||
| 'DECLARATION'
|
||||
| 0
|
||||
| 'UNVERIFIED'
|
||||
| 1
|
||||
|
||||
export type O_google_protobuf_ExtensionRangeOptions_VerificationState = typeof _google_protobuf_ExtensionRangeOptions_VerificationState[keyof typeof _google_protobuf_ExtensionRangeOptions_VerificationState]
|
||||
|
||||
export interface IExtensionRangeOptions {
|
||||
'declaration'?: (I_google_protobuf_ExtensionRangeOptions_Declaration)[];
|
||||
'verification'?: (I_google_protobuf_ExtensionRangeOptions_VerificationState);
|
||||
'features'?: (I_google_protobuf_FeatureSet | null);
|
||||
'uninterpretedOption'?: (I_google_protobuf_UninterpretedOption)[];
|
||||
}
|
||||
|
||||
export interface OExtensionRangeOptions {
|
||||
'declaration': (O_google_protobuf_ExtensionRangeOptions_Declaration)[];
|
||||
'verification': (O_google_protobuf_ExtensionRangeOptions_VerificationState);
|
||||
'features': (O_google_protobuf_FeatureSet | null);
|
||||
'uninterpretedOption': (O_google_protobuf_UninterpretedOption)[];
|
||||
}
|
|
@ -0,0 +1,183 @@
|
|||
// Original file: null
|
||||
|
||||
|
||||
// Original file: null
|
||||
|
||||
export const _google_protobuf_FeatureSet_VisibilityFeature_DefaultSymbolVisibility = {
|
||||
DEFAULT_SYMBOL_VISIBILITY_UNKNOWN: 'DEFAULT_SYMBOL_VISIBILITY_UNKNOWN',
|
||||
EXPORT_ALL: 'EXPORT_ALL',
|
||||
EXPORT_TOP_LEVEL: 'EXPORT_TOP_LEVEL',
|
||||
LOCAL_ALL: 'LOCAL_ALL',
|
||||
STRICT: 'STRICT',
|
||||
} as const;
|
||||
|
||||
export type I_google_protobuf_FeatureSet_VisibilityFeature_DefaultSymbolVisibility =
|
||||
| 'DEFAULT_SYMBOL_VISIBILITY_UNKNOWN'
|
||||
| 0
|
||||
| 'EXPORT_ALL'
|
||||
| 1
|
||||
| 'EXPORT_TOP_LEVEL'
|
||||
| 2
|
||||
| 'LOCAL_ALL'
|
||||
| 3
|
||||
| 'STRICT'
|
||||
| 4
|
||||
|
||||
export type O_google_protobuf_FeatureSet_VisibilityFeature_DefaultSymbolVisibility = typeof _google_protobuf_FeatureSet_VisibilityFeature_DefaultSymbolVisibility[keyof typeof _google_protobuf_FeatureSet_VisibilityFeature_DefaultSymbolVisibility]
|
||||
|
||||
// Original file: null
|
||||
|
||||
export const _google_protobuf_FeatureSet_EnforceNamingStyle = {
|
||||
ENFORCE_NAMING_STYLE_UNKNOWN: 'ENFORCE_NAMING_STYLE_UNKNOWN',
|
||||
STYLE2024: 'STYLE2024',
|
||||
STYLE_LEGACY: 'STYLE_LEGACY',
|
||||
} as const;
|
||||
|
||||
export type I_google_protobuf_FeatureSet_EnforceNamingStyle =
|
||||
| 'ENFORCE_NAMING_STYLE_UNKNOWN'
|
||||
| 0
|
||||
| 'STYLE2024'
|
||||
| 1
|
||||
| 'STYLE_LEGACY'
|
||||
| 2
|
||||
|
||||
export type O_google_protobuf_FeatureSet_EnforceNamingStyle = typeof _google_protobuf_FeatureSet_EnforceNamingStyle[keyof typeof _google_protobuf_FeatureSet_EnforceNamingStyle]
|
||||
|
||||
// Original file: null
|
||||
|
||||
export const _google_protobuf_FeatureSet_EnumType = {
|
||||
ENUM_TYPE_UNKNOWN: 'ENUM_TYPE_UNKNOWN',
|
||||
OPEN: 'OPEN',
|
||||
CLOSED: 'CLOSED',
|
||||
} as const;
|
||||
|
||||
export type I_google_protobuf_FeatureSet_EnumType =
|
||||
| 'ENUM_TYPE_UNKNOWN'
|
||||
| 0
|
||||
| 'OPEN'
|
||||
| 1
|
||||
| 'CLOSED'
|
||||
| 2
|
||||
|
||||
export type O_google_protobuf_FeatureSet_EnumType = typeof _google_protobuf_FeatureSet_EnumType[keyof typeof _google_protobuf_FeatureSet_EnumType]
|
||||
|
||||
// Original file: null
|
||||
|
||||
export const _google_protobuf_FeatureSet_FieldPresence = {
|
||||
FIELD_PRESENCE_UNKNOWN: 'FIELD_PRESENCE_UNKNOWN',
|
||||
EXPLICIT: 'EXPLICIT',
|
||||
IMPLICIT: 'IMPLICIT',
|
||||
LEGACY_REQUIRED: 'LEGACY_REQUIRED',
|
||||
} as const;
|
||||
|
||||
export type I_google_protobuf_FeatureSet_FieldPresence =
|
||||
| 'FIELD_PRESENCE_UNKNOWN'
|
||||
| 0
|
||||
| 'EXPLICIT'
|
||||
| 1
|
||||
| 'IMPLICIT'
|
||||
| 2
|
||||
| 'LEGACY_REQUIRED'
|
||||
| 3
|
||||
|
||||
export type O_google_protobuf_FeatureSet_FieldPresence = typeof _google_protobuf_FeatureSet_FieldPresence[keyof typeof _google_protobuf_FeatureSet_FieldPresence]
|
||||
|
||||
// Original file: null
|
||||
|
||||
export const _google_protobuf_FeatureSet_JsonFormat = {
|
||||
JSON_FORMAT_UNKNOWN: 'JSON_FORMAT_UNKNOWN',
|
||||
ALLOW: 'ALLOW',
|
||||
LEGACY_BEST_EFFORT: 'LEGACY_BEST_EFFORT',
|
||||
} as const;
|
||||
|
||||
export type I_google_protobuf_FeatureSet_JsonFormat =
|
||||
| 'JSON_FORMAT_UNKNOWN'
|
||||
| 0
|
||||
| 'ALLOW'
|
||||
| 1
|
||||
| 'LEGACY_BEST_EFFORT'
|
||||
| 2
|
||||
|
||||
export type O_google_protobuf_FeatureSet_JsonFormat = typeof _google_protobuf_FeatureSet_JsonFormat[keyof typeof _google_protobuf_FeatureSet_JsonFormat]
|
||||
|
||||
// Original file: null
|
||||
|
||||
export const _google_protobuf_FeatureSet_MessageEncoding = {
|
||||
MESSAGE_ENCODING_UNKNOWN: 'MESSAGE_ENCODING_UNKNOWN',
|
||||
LENGTH_PREFIXED: 'LENGTH_PREFIXED',
|
||||
DELIMITED: 'DELIMITED',
|
||||
} as const;
|
||||
|
||||
export type I_google_protobuf_FeatureSet_MessageEncoding =
|
||||
| 'MESSAGE_ENCODING_UNKNOWN'
|
||||
| 0
|
||||
| 'LENGTH_PREFIXED'
|
||||
| 1
|
||||
| 'DELIMITED'
|
||||
| 2
|
||||
|
||||
export type O_google_protobuf_FeatureSet_MessageEncoding = typeof _google_protobuf_FeatureSet_MessageEncoding[keyof typeof _google_protobuf_FeatureSet_MessageEncoding]
|
||||
|
||||
// Original file: null
|
||||
|
||||
export const _google_protobuf_FeatureSet_RepeatedFieldEncoding = {
|
||||
REPEATED_FIELD_ENCODING_UNKNOWN: 'REPEATED_FIELD_ENCODING_UNKNOWN',
|
||||
PACKED: 'PACKED',
|
||||
EXPANDED: 'EXPANDED',
|
||||
} as const;
|
||||
|
||||
export type I_google_protobuf_FeatureSet_RepeatedFieldEncoding =
|
||||
| 'REPEATED_FIELD_ENCODING_UNKNOWN'
|
||||
| 0
|
||||
| 'PACKED'
|
||||
| 1
|
||||
| 'EXPANDED'
|
||||
| 2
|
||||
|
||||
export type O_google_protobuf_FeatureSet_RepeatedFieldEncoding = typeof _google_protobuf_FeatureSet_RepeatedFieldEncoding[keyof typeof _google_protobuf_FeatureSet_RepeatedFieldEncoding]
|
||||
|
||||
// Original file: null
|
||||
|
||||
export const _google_protobuf_FeatureSet_Utf8Validation = {
|
||||
UTF8_VALIDATION_UNKNOWN: 'UTF8_VALIDATION_UNKNOWN',
|
||||
VERIFY: 'VERIFY',
|
||||
NONE: 'NONE',
|
||||
} as const;
|
||||
|
||||
export type I_google_protobuf_FeatureSet_Utf8Validation =
|
||||
| 'UTF8_VALIDATION_UNKNOWN'
|
||||
| 0
|
||||
| 'VERIFY'
|
||||
| 2
|
||||
| 'NONE'
|
||||
| 3
|
||||
|
||||
export type O_google_protobuf_FeatureSet_Utf8Validation = typeof _google_protobuf_FeatureSet_Utf8Validation[keyof typeof _google_protobuf_FeatureSet_Utf8Validation]
|
||||
|
||||
export interface I_google_protobuf_FeatureSet_VisibilityFeature {
|
||||
}
|
||||
|
||||
export interface O_google_protobuf_FeatureSet_VisibilityFeature {
|
||||
}
|
||||
|
||||
export interface IFeatureSet {
|
||||
'fieldPresence'?: (I_google_protobuf_FeatureSet_FieldPresence);
|
||||
'enumType'?: (I_google_protobuf_FeatureSet_EnumType);
|
||||
'repeatedFieldEncoding'?: (I_google_protobuf_FeatureSet_RepeatedFieldEncoding);
|
||||
'utf8Validation'?: (I_google_protobuf_FeatureSet_Utf8Validation);
|
||||
'messageEncoding'?: (I_google_protobuf_FeatureSet_MessageEncoding);
|
||||
'jsonFormat'?: (I_google_protobuf_FeatureSet_JsonFormat);
|
||||
'enforceNamingStyle'?: (I_google_protobuf_FeatureSet_EnforceNamingStyle);
|
||||
'defaultSymbolVisibility'?: (I_google_protobuf_FeatureSet_VisibilityFeature_DefaultSymbolVisibility);
|
||||
}
|
||||
|
||||
export interface OFeatureSet {
|
||||
'fieldPresence': (O_google_protobuf_FeatureSet_FieldPresence);
|
||||
'enumType': (O_google_protobuf_FeatureSet_EnumType);
|
||||
'repeatedFieldEncoding': (O_google_protobuf_FeatureSet_RepeatedFieldEncoding);
|
||||
'utf8Validation': (O_google_protobuf_FeatureSet_Utf8Validation);
|
||||
'messageEncoding': (O_google_protobuf_FeatureSet_MessageEncoding);
|
||||
'jsonFormat': (O_google_protobuf_FeatureSet_JsonFormat);
|
||||
'enforceNamingStyle': (O_google_protobuf_FeatureSet_EnforceNamingStyle);
|
||||
'defaultSymbolVisibility': (O_google_protobuf_FeatureSet_VisibilityFeature_DefaultSymbolVisibility);
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
// Original file: null
|
||||
|
||||
import type { IEdition as I_google_protobuf_Edition, OEdition as O_google_protobuf_Edition } from '../../google/protobuf/Edition';
|
||||
import type { IFeatureSet as I_google_protobuf_FeatureSet, OFeatureSet as O_google_protobuf_FeatureSet } from '../../google/protobuf/FeatureSet';
|
||||
|
||||
export interface I_google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault {
|
||||
'edition'?: (I_google_protobuf_Edition);
|
||||
'overridableFeatures'?: (I_google_protobuf_FeatureSet | null);
|
||||
'fixedFeatures'?: (I_google_protobuf_FeatureSet | null);
|
||||
}
|
||||
|
||||
export interface O_google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault {
|
||||
'edition': (O_google_protobuf_Edition);
|
||||
'overridableFeatures': (O_google_protobuf_FeatureSet | null);
|
||||
'fixedFeatures': (O_google_protobuf_FeatureSet | null);
|
||||
}
|
||||
|
||||
export interface IFeatureSetDefaults {
|
||||
'defaults'?: (I_google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault)[];
|
||||
'minimumEdition'?: (I_google_protobuf_Edition);
|
||||
'maximumEdition'?: (I_google_protobuf_Edition);
|
||||
}
|
||||
|
||||
export interface OFeatureSetDefaults {
|
||||
'defaults': (O_google_protobuf_FeatureSetDefaults_FeatureSetEditionDefault)[];
|
||||
'minimumEdition': (O_google_protobuf_Edition);
|
||||
'maximumEdition': (O_google_protobuf_Edition);
|
||||
}
|
|
@ -6,17 +6,17 @@ import type { IFieldOptions as I_google_protobuf_FieldOptions, OFieldOptions as
|
|||
|
||||
export const _google_protobuf_FieldDescriptorProto_Label = {
|
||||
LABEL_OPTIONAL: 'LABEL_OPTIONAL',
|
||||
LABEL_REQUIRED: 'LABEL_REQUIRED',
|
||||
LABEL_REPEATED: 'LABEL_REPEATED',
|
||||
LABEL_REQUIRED: 'LABEL_REQUIRED',
|
||||
} as const;
|
||||
|
||||
export type I_google_protobuf_FieldDescriptorProto_Label =
|
||||
| 'LABEL_OPTIONAL'
|
||||
| 1
|
||||
| 'LABEL_REQUIRED'
|
||||
| 2
|
||||
| 'LABEL_REPEATED'
|
||||
| 3
|
||||
| 'LABEL_REQUIRED'
|
||||
| 2
|
||||
|
||||
export type O_google_protobuf_FieldDescriptorProto_Label = typeof _google_protobuf_FieldDescriptorProto_Label[keyof typeof _google_protobuf_FieldDescriptorProto_Label]
|
||||
|
||||
|
@ -94,6 +94,7 @@ export interface IFieldDescriptorProto {
|
|||
'options'?: (I_google_protobuf_FieldOptions | null);
|
||||
'oneofIndex'?: (number);
|
||||
'jsonName'?: (string);
|
||||
'proto3Optional'?: (boolean);
|
||||
}
|
||||
|
||||
export interface OFieldDescriptorProto {
|
||||
|
@ -107,4 +108,5 @@ export interface OFieldDescriptorProto {
|
|||
'options': (O_google_protobuf_FieldOptions | null);
|
||||
'oneofIndex': (number);
|
||||
'jsonName': (string);
|
||||
'proto3Optional': (boolean);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
// Original file: null
|
||||
|
||||
import type { IFeatureSet as I_google_protobuf_FeatureSet, OFeatureSet as O_google_protobuf_FeatureSet } from '../../google/protobuf/FeatureSet';
|
||||
import type { IUninterpretedOption as I_google_protobuf_UninterpretedOption, OUninterpretedOption as O_google_protobuf_UninterpretedOption } from '../../google/protobuf/UninterpretedOption';
|
||||
import type { IFieldBehavior as I_google_api_FieldBehavior, OFieldBehavior as O_google_api_FieldBehavior } from '../../google/api/FieldBehavior';
|
||||
import type { IEdition as I_google_protobuf_Edition, OEdition as O_google_protobuf_Edition } from '../../google/protobuf/Edition';
|
||||
|
||||
// Original file: null
|
||||
|
||||
|
@ -21,6 +23,30 @@ export type I_google_protobuf_FieldOptions_CType =
|
|||
|
||||
export type O_google_protobuf_FieldOptions_CType = typeof _google_protobuf_FieldOptions_CType[keyof typeof _google_protobuf_FieldOptions_CType]
|
||||
|
||||
export interface I_google_protobuf_FieldOptions_EditionDefault {
|
||||
'edition'?: (I_google_protobuf_Edition);
|
||||
'value'?: (string);
|
||||
}
|
||||
|
||||
export interface O_google_protobuf_FieldOptions_EditionDefault {
|
||||
'edition': (O_google_protobuf_Edition);
|
||||
'value': (string);
|
||||
}
|
||||
|
||||
export interface I_google_protobuf_FieldOptions_FeatureSupport {
|
||||
'editionIntroduced'?: (I_google_protobuf_Edition);
|
||||
'editionDeprecated'?: (I_google_protobuf_Edition);
|
||||
'deprecationWarning'?: (string);
|
||||
'editionRemoved'?: (I_google_protobuf_Edition);
|
||||
}
|
||||
|
||||
export interface O_google_protobuf_FieldOptions_FeatureSupport {
|
||||
'editionIntroduced': (O_google_protobuf_Edition);
|
||||
'editionDeprecated': (O_google_protobuf_Edition);
|
||||
'deprecationWarning': (string);
|
||||
'editionRemoved': (O_google_protobuf_Edition);
|
||||
}
|
||||
|
||||
// Original file: null
|
||||
|
||||
export const _google_protobuf_FieldOptions_JSType = {
|
||||
|
@ -39,13 +65,80 @@ export type I_google_protobuf_FieldOptions_JSType =
|
|||
|
||||
export type O_google_protobuf_FieldOptions_JSType = typeof _google_protobuf_FieldOptions_JSType[keyof typeof _google_protobuf_FieldOptions_JSType]
|
||||
|
||||
// Original file: null
|
||||
|
||||
export const _google_protobuf_FieldOptions_OptionRetention = {
|
||||
RETENTION_UNKNOWN: 'RETENTION_UNKNOWN',
|
||||
RETENTION_RUNTIME: 'RETENTION_RUNTIME',
|
||||
RETENTION_SOURCE: 'RETENTION_SOURCE',
|
||||
} as const;
|
||||
|
||||
export type I_google_protobuf_FieldOptions_OptionRetention =
|
||||
| 'RETENTION_UNKNOWN'
|
||||
| 0
|
||||
| 'RETENTION_RUNTIME'
|
||||
| 1
|
||||
| 'RETENTION_SOURCE'
|
||||
| 2
|
||||
|
||||
export type O_google_protobuf_FieldOptions_OptionRetention = typeof _google_protobuf_FieldOptions_OptionRetention[keyof typeof _google_protobuf_FieldOptions_OptionRetention]
|
||||
|
||||
// Original file: null
|
||||
|
||||
export const _google_protobuf_FieldOptions_OptionTargetType = {
|
||||
TARGET_TYPE_UNKNOWN: 'TARGET_TYPE_UNKNOWN',
|
||||
TARGET_TYPE_FILE: 'TARGET_TYPE_FILE',
|
||||
TARGET_TYPE_EXTENSION_RANGE: 'TARGET_TYPE_EXTENSION_RANGE',
|
||||
TARGET_TYPE_MESSAGE: 'TARGET_TYPE_MESSAGE',
|
||||
TARGET_TYPE_FIELD: 'TARGET_TYPE_FIELD',
|
||||
TARGET_TYPE_ONEOF: 'TARGET_TYPE_ONEOF',
|
||||
TARGET_TYPE_ENUM: 'TARGET_TYPE_ENUM',
|
||||
TARGET_TYPE_ENUM_ENTRY: 'TARGET_TYPE_ENUM_ENTRY',
|
||||
TARGET_TYPE_SERVICE: 'TARGET_TYPE_SERVICE',
|
||||
TARGET_TYPE_METHOD: 'TARGET_TYPE_METHOD',
|
||||
} as const;
|
||||
|
||||
export type I_google_protobuf_FieldOptions_OptionTargetType =
|
||||
| 'TARGET_TYPE_UNKNOWN'
|
||||
| 0
|
||||
| 'TARGET_TYPE_FILE'
|
||||
| 1
|
||||
| 'TARGET_TYPE_EXTENSION_RANGE'
|
||||
| 2
|
||||
| 'TARGET_TYPE_MESSAGE'
|
||||
| 3
|
||||
| 'TARGET_TYPE_FIELD'
|
||||
| 4
|
||||
| 'TARGET_TYPE_ONEOF'
|
||||
| 5
|
||||
| 'TARGET_TYPE_ENUM'
|
||||
| 6
|
||||
| 'TARGET_TYPE_ENUM_ENTRY'
|
||||
| 7
|
||||
| 'TARGET_TYPE_SERVICE'
|
||||
| 8
|
||||
| 'TARGET_TYPE_METHOD'
|
||||
| 9
|
||||
|
||||
export type O_google_protobuf_FieldOptions_OptionTargetType = typeof _google_protobuf_FieldOptions_OptionTargetType[keyof typeof _google_protobuf_FieldOptions_OptionTargetType]
|
||||
|
||||
export interface IFieldOptions {
|
||||
'ctype'?: (I_google_protobuf_FieldOptions_CType);
|
||||
'packed'?: (boolean);
|
||||
'deprecated'?: (boolean);
|
||||
'lazy'?: (boolean);
|
||||
'jstype'?: (I_google_protobuf_FieldOptions_JSType);
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
'weak'?: (boolean);
|
||||
'unverifiedLazy'?: (boolean);
|
||||
'debugRedact'?: (boolean);
|
||||
'retention'?: (I_google_protobuf_FieldOptions_OptionRetention);
|
||||
'targets'?: (I_google_protobuf_FieldOptions_OptionTargetType)[];
|
||||
'editionDefaults'?: (I_google_protobuf_FieldOptions_EditionDefault)[];
|
||||
'features'?: (I_google_protobuf_FeatureSet | null);
|
||||
'featureSupport'?: (I_google_protobuf_FieldOptions_FeatureSupport | null);
|
||||
'uninterpretedOption'?: (I_google_protobuf_UninterpretedOption)[];
|
||||
'.google.api.field_behavior'?: (I_google_api_FieldBehavior)[];
|
||||
}
|
||||
|
@ -56,7 +149,17 @@ export interface OFieldOptions {
|
|||
'deprecated': (boolean);
|
||||
'lazy': (boolean);
|
||||
'jstype': (O_google_protobuf_FieldOptions_JSType);
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
'weak': (boolean);
|
||||
'unverifiedLazy': (boolean);
|
||||
'debugRedact': (boolean);
|
||||
'retention': (O_google_protobuf_FieldOptions_OptionRetention);
|
||||
'targets': (O_google_protobuf_FieldOptions_OptionTargetType)[];
|
||||
'editionDefaults': (O_google_protobuf_FieldOptions_EditionDefault)[];
|
||||
'features': (O_google_protobuf_FeatureSet | null);
|
||||
'featureSupport': (O_google_protobuf_FieldOptions_FeatureSupport | null);
|
||||
'uninterpretedOption': (O_google_protobuf_UninterpretedOption)[];
|
||||
'.google.api.field_behavior': (O_google_api_FieldBehavior)[];
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import type { IServiceDescriptorProto as I_google_protobuf_ServiceDescriptorProt
|
|||
import type { IFieldDescriptorProto as I_google_protobuf_FieldDescriptorProto, OFieldDescriptorProto as O_google_protobuf_FieldDescriptorProto } from '../../google/protobuf/FieldDescriptorProto';
|
||||
import type { IFileOptions as I_google_protobuf_FileOptions, OFileOptions as O_google_protobuf_FileOptions } from '../../google/protobuf/FileOptions';
|
||||
import type { ISourceCodeInfo as I_google_protobuf_SourceCodeInfo, OSourceCodeInfo as O_google_protobuf_SourceCodeInfo } from '../../google/protobuf/SourceCodeInfo';
|
||||
import type { IEdition as I_google_protobuf_Edition, OEdition as O_google_protobuf_Edition } from '../../google/protobuf/Edition';
|
||||
|
||||
export interface IFileDescriptorProto {
|
||||
'name'?: (string);
|
||||
|
@ -20,6 +21,8 @@ export interface IFileDescriptorProto {
|
|||
'publicDependency'?: (number)[];
|
||||
'weakDependency'?: (number)[];
|
||||
'syntax'?: (string);
|
||||
'edition'?: (I_google_protobuf_Edition);
|
||||
'optionDependency'?: (string)[];
|
||||
}
|
||||
|
||||
export interface OFileDescriptorProto {
|
||||
|
@ -35,4 +38,6 @@ export interface OFileDescriptorProto {
|
|||
'publicDependency': (number)[];
|
||||
'weakDependency': (number)[];
|
||||
'syntax': (string);
|
||||
'edition': (O_google_protobuf_Edition);
|
||||
'optionDependency': (string)[];
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// Original file: null
|
||||
|
||||
import type { IFeatureSet as I_google_protobuf_FeatureSet, OFeatureSet as O_google_protobuf_FeatureSet } from '../../google/protobuf/FeatureSet';
|
||||
import type { IUninterpretedOption as I_google_protobuf_UninterpretedOption, OUninterpretedOption as O_google_protobuf_UninterpretedOption } from '../../google/protobuf/UninterpretedOption';
|
||||
|
||||
// Original file: null
|
||||
|
@ -38,6 +39,12 @@ export interface IFileOptions {
|
|||
'ccEnableArenas'?: (boolean);
|
||||
'objcClassPrefix'?: (string);
|
||||
'csharpNamespace'?: (string);
|
||||
'swiftPrefix'?: (string);
|
||||
'phpClassPrefix'?: (string);
|
||||
'phpNamespace'?: (string);
|
||||
'phpMetadataNamespace'?: (string);
|
||||
'rubyPackage'?: (string);
|
||||
'features'?: (I_google_protobuf_FeatureSet | null);
|
||||
'uninterpretedOption'?: (I_google_protobuf_UninterpretedOption)[];
|
||||
}
|
||||
|
||||
|
@ -59,5 +66,11 @@ export interface OFileOptions {
|
|||
'ccEnableArenas': (boolean);
|
||||
'objcClassPrefix': (string);
|
||||
'csharpNamespace': (string);
|
||||
'swiftPrefix': (string);
|
||||
'phpClassPrefix': (string);
|
||||
'phpNamespace': (string);
|
||||
'phpMetadataNamespace': (string);
|
||||
'rubyPackage': (string);
|
||||
'features': (O_google_protobuf_FeatureSet | null);
|
||||
'uninterpretedOption': (O_google_protobuf_UninterpretedOption)[];
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ export interface I_google_protobuf_GeneratedCodeInfo_Annotation {
|
|||
'sourceFile'?: (string);
|
||||
'begin'?: (number);
|
||||
'end'?: (number);
|
||||
'semantic'?: (I_google_protobuf_GeneratedCodeInfo_Annotation_Semantic);
|
||||
}
|
||||
|
||||
export interface O_google_protobuf_GeneratedCodeInfo_Annotation {
|
||||
|
@ -13,8 +14,27 @@ export interface O_google_protobuf_GeneratedCodeInfo_Annotation {
|
|||
'sourceFile': (string);
|
||||
'begin': (number);
|
||||
'end': (number);
|
||||
'semantic': (O_google_protobuf_GeneratedCodeInfo_Annotation_Semantic);
|
||||
}
|
||||
|
||||
// Original file: null
|
||||
|
||||
export const _google_protobuf_GeneratedCodeInfo_Annotation_Semantic = {
|
||||
NONE: 'NONE',
|
||||
SET: 'SET',
|
||||
ALIAS: 'ALIAS',
|
||||
} as const;
|
||||
|
||||
export type I_google_protobuf_GeneratedCodeInfo_Annotation_Semantic =
|
||||
| 'NONE'
|
||||
| 0
|
||||
| 'SET'
|
||||
| 1
|
||||
| 'ALIAS'
|
||||
| 2
|
||||
|
||||
export type O_google_protobuf_GeneratedCodeInfo_Annotation_Semantic = typeof _google_protobuf_GeneratedCodeInfo_Annotation_Semantic[keyof typeof _google_protobuf_GeneratedCodeInfo_Annotation_Semantic]
|
||||
|
||||
export interface IGeneratedCodeInfo {
|
||||
'annotation'?: (I_google_protobuf_GeneratedCodeInfo_Annotation)[];
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue