async-grpc-xds 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data/fixtures/async/grpc/test_interface.rb +79 -0
- data/fixtures/async/grpc/test_message.rb +56 -0
- data/lib/async/grpc/xds/ads_stream.rb +70 -0
- data/lib/async/grpc/xds/client.rb +255 -0
- data/lib/async/grpc/xds/context.rb +201 -0
- data/lib/async/grpc/xds/control_plane.rb +143 -0
- data/lib/async/grpc/xds/discovery_client.rb +356 -0
- data/lib/async/grpc/xds/health_checker.rb +88 -0
- data/lib/async/grpc/xds/load_balancer.rb +196 -0
- data/lib/async/grpc/xds/resource_builder.rb +138 -0
- data/lib/async/grpc/xds/resource_cache.rb +55 -0
- data/lib/async/grpc/xds/resources.rb +270 -0
- data/lib/async/grpc/xds/server.rb +34 -0
- data/lib/async/grpc/xds/service.rb +117 -0
- data/lib/async/grpc/xds/version.rb +12 -0
- data/lib/async/grpc/xds.rb +42 -0
- data/lib/envoy/annotations/deprecation_pb.rb +19 -0
- data/lib/envoy/config/cluster/v3/circuit_breaker_pb.rb +31 -0
- data/lib/envoy/config/cluster/v3/cluster_pb.rb +80 -0
- data/lib/envoy/config/cluster/v3/filter_pb.rb +28 -0
- data/lib/envoy/config/cluster/v3/outlier_detection_pb.rb +29 -0
- data/lib/envoy/config/core/v3/address_pb.rb +38 -0
- data/lib/envoy/config/core/v3/backoff_pb.rb +27 -0
- data/lib/envoy/config/core/v3/base_pb.rb +68 -0
- data/lib/envoy/config/core/v3/cel_pb.rb +24 -0
- data/lib/envoy/config/core/v3/config_source_pb.rb +42 -0
- data/lib/envoy/config/core/v3/event_service_config_pb.rb +27 -0
- data/lib/envoy/config/core/v3/extension_pb.rb +26 -0
- data/lib/envoy/config/core/v3/grpc_method_list_pb.rb +27 -0
- data/lib/envoy/config/core/v3/grpc_service_pb.rb +45 -0
- data/lib/envoy/config/core/v3/health_check_pb.rb +47 -0
- data/lib/envoy/config/core/v3/http_service_pb.rb +27 -0
- data/lib/envoy/config/core/v3/http_uri_pb.rb +27 -0
- data/lib/envoy/config/core/v3/protocol_pb.rb +51 -0
- data/lib/envoy/config/core/v3/proxy_protocol_pb.rb +31 -0
- data/lib/envoy/config/core/v3/resolver_pb.rb +27 -0
- data/lib/envoy/config/core/v3/socket_cmsg_headers_pb.rb +25 -0
- data/lib/envoy/config/core/v3/socket_option_pb.rb +31 -0
- data/lib/envoy/config/core/v3/substitution_format_string_pb.rb +30 -0
- data/lib/envoy/config/core/v3/udp_socket_config_pb.rb +26 -0
- data/lib/envoy/config/endpoint/v3/endpoint_components_pb.rb +40 -0
- data/lib/envoy/config/endpoint/v3/endpoint_pb.rb +32 -0
- data/lib/envoy/config/endpoint/v3/load_report_pb.rb +36 -0
- data/lib/envoy/service/discovery/v3/ads_pb.rb +26 -0
- data/lib/envoy/service/discovery/v3/aggregated_discovery_service.rb +64 -0
- data/lib/envoy/service/discovery/v3/discovery_pb.rb +42 -0
- data/lib/envoy/type/matcher/v3/address_pb.rb +25 -0
- data/lib/envoy/type/matcher/v3/filter_state_pb.rb +27 -0
- data/lib/envoy/type/matcher/v3/http_inputs_pb.rb +29 -0
- data/lib/envoy/type/matcher/v3/metadata_pb.rb +28 -0
- data/lib/envoy/type/matcher/v3/node_pb.rb +27 -0
- data/lib/envoy/type/matcher/v3/number_pb.rb +27 -0
- data/lib/envoy/type/matcher/v3/path_pb.rb +27 -0
- data/lib/envoy/type/matcher/v3/regex_pb.rb +30 -0
- data/lib/envoy/type/matcher/v3/status_code_input_pb.rb +25 -0
- data/lib/envoy/type/matcher/v3/string_pb.rb +29 -0
- data/lib/envoy/type/matcher/v3/struct_pb.rb +28 -0
- data/lib/envoy/type/matcher/v3/value_pb.rb +31 -0
- data/lib/envoy/type/metadata/v3/metadata_pb.rb +32 -0
- data/lib/envoy/type/v3/hash_policy_pb.rb +26 -0
- data/lib/envoy/type/v3/http_pb.rb +22 -0
- data/lib/envoy/type/v3/http_status_pb.rb +25 -0
- data/lib/envoy/type/v3/percent_pb.rb +26 -0
- data/lib/envoy/type/v3/range_pb.rb +25 -0
- data/lib/envoy/type/v3/ratelimit_strategy_pb.rb +28 -0
- data/lib/envoy/type/v3/ratelimit_unit_pb.rb +22 -0
- data/lib/envoy/type/v3/semantic_version_pb.rb +23 -0
- data/lib/envoy/type/v3/token_bucket_pb.rb +26 -0
- data/lib/envoy.rb +83 -0
- data/lib/google/protobuf/any_pb.rb +18 -0
- data/lib/google/protobuf/duration_pb.rb +18 -0
- data/lib/google/protobuf/empty_pb.rb +18 -0
- data/lib/google/protobuf/struct_pb.rb +21 -0
- data/lib/google/protobuf/timestamp_pb.rb +18 -0
- data/lib/google/protobuf/wrappers_pb.rb +26 -0
- data/lib/google/rpc/status_pb.rb +20 -0
- data/lib/udpa/annotations/migrate_pb.rb +22 -0
- data/lib/udpa/annotations/security_pb.rb +23 -0
- data/lib/udpa/annotations/sensitive_pb.rb +19 -0
- data/lib/udpa/annotations/status_pb.rb +21 -0
- data/lib/udpa/annotations/versioning_pb.rb +20 -0
- data/lib/validate/validate_pb.rb +43 -0
- data/lib/xds/annotations/v3/status_pb.rb +26 -0
- data/lib/xds/core/v3/authority_pb.rb +23 -0
- data/lib/xds/core/v3/cidr_pb.rb +24 -0
- data/lib/xds/core/v3/collection_entry_pb.rb +26 -0
- data/lib/xds/core/v3/context_params_pb.rb +22 -0
- data/lib/xds/core/v3/extension_pb.rb +23 -0
- data/lib/xds/core/v3/resource_locator_pb.rb +26 -0
- data/lib/xds/core/v3/resource_name_pb.rb +24 -0
- data/lib/xds/core/v3/resource_pb.rb +24 -0
- data/lib/xds/type/matcher/v3/domain_pb.rb +27 -0
- data/lib/xds/type/matcher/v3/http_inputs_pb.rb +22 -0
- data/lib/xds/type/matcher/v3/ip_pb.rb +28 -0
- data/lib/xds/type/matcher/v3/matcher_pb.rb +34 -0
- data/lib/xds/type/matcher/v3/range_pb.rb +31 -0
- data/lib/xds/type/matcher/v3/regex_pb.rb +25 -0
- data/lib/xds/type/matcher/v3/string_pb.rb +27 -0
- data/license.md +21 -0
- data/plan.md +156 -0
- data/proto/envoy/annotations/deprecation.proto +34 -0
- data/proto/envoy/annotations/resource.proto +19 -0
- data/proto/envoy/config/README.md +3 -0
- data/proto/envoy/config/cluster/v3/BUILD +18 -0
- data/proto/envoy/config/cluster/v3/circuit_breaker.proto +121 -0
- data/proto/envoy/config/cluster/v3/cluster.proto +1407 -0
- data/proto/envoy/config/cluster/v3/filter.proto +40 -0
- data/proto/envoy/config/cluster/v3/outlier_detection.proto +180 -0
- data/proto/envoy/config/core/v3/BUILD +16 -0
- data/proto/envoy/config/core/v3/address.proto +214 -0
- data/proto/envoy/config/core/v3/backoff.proto +37 -0
- data/proto/envoy/config/core/v3/base.proto +662 -0
- data/proto/envoy/config/core/v3/cel.proto +63 -0
- data/proto/envoy/config/core/v3/config_source.proto +283 -0
- data/proto/envoy/config/core/v3/event_service_config.proto +29 -0
- data/proto/envoy/config/core/v3/extension.proto +32 -0
- data/proto/envoy/config/core/v3/grpc_method_list.proto +33 -0
- data/proto/envoy/config/core/v3/grpc_service.proto +355 -0
- data/proto/envoy/config/core/v3/health_check.proto +443 -0
- data/proto/envoy/config/core/v3/http_service.proto +35 -0
- data/proto/envoy/config/core/v3/http_uri.proto +58 -0
- data/proto/envoy/config/core/v3/protocol.proto +807 -0
- data/proto/envoy/config/core/v3/proxy_protocol.proto +114 -0
- data/proto/envoy/config/core/v3/resolver.proto +36 -0
- data/proto/envoy/config/core/v3/socket_cmsg_headers.proto +28 -0
- data/proto/envoy/config/core/v3/socket_option.proto +108 -0
- data/proto/envoy/config/core/v3/substitution_format_string.proto +136 -0
- data/proto/envoy/config/core/v3/udp_socket_config.proto +32 -0
- data/proto/envoy/config/endpoint/v3/BUILD +16 -0
- data/proto/envoy/config/endpoint/v3/endpoint.proto +137 -0
- data/proto/envoy/config/endpoint/v3/endpoint_components.proto +229 -0
- data/proto/envoy/config/endpoint/v3/load_report.proto +220 -0
- data/proto/envoy/config/listener/v3/BUILD +18 -0
- data/proto/envoy/config/listener/v3/api_listener.proto +34 -0
- data/proto/envoy/config/listener/v3/listener.proto +455 -0
- data/proto/envoy/config/listener/v3/listener_components.proto +353 -0
- data/proto/envoy/config/listener/v3/quic_config.proto +108 -0
- data/proto/envoy/config/listener/v3/udp_listener_config.proto +52 -0
- data/proto/envoy/config/route/v3/BUILD +19 -0
- data/proto/envoy/config/route/v3/route.proto +172 -0
- data/proto/envoy/config/route/v3/route_components.proto +2918 -0
- data/proto/envoy/config/route/v3/scoped_route.proto +133 -0
- data/proto/envoy/extensions/transport_sockets/tls/v3/BUILD +14 -0
- data/proto/envoy/extensions/transport_sockets/tls/v3/cert.proto +12 -0
- data/proto/envoy/extensions/transport_sockets/tls/v3/common.proto +597 -0
- data/proto/envoy/extensions/transport_sockets/tls/v3/secret.proto +61 -0
- data/proto/envoy/extensions/transport_sockets/tls/v3/tls.proto +366 -0
- data/proto/envoy/extensions/transport_sockets/tls/v3/tls_spiffe_validator_config.proto +67 -0
- data/proto/envoy/service/README.md +3 -0
- data/proto/envoy/service/discovery/v3/BUILD +13 -0
- data/proto/envoy/service/discovery/v3/ads.proto +44 -0
- data/proto/envoy/service/discovery/v3/discovery.proto +443 -0
- data/proto/envoy/type/BUILD +9 -0
- data/proto/envoy/type/hash_policy.proto +28 -0
- data/proto/envoy/type/http.proto +24 -0
- data/proto/envoy/type/http_status.proto +140 -0
- data/proto/envoy/type/matcher/v3/address.proto +22 -0
- data/proto/envoy/type/matcher/v3/filter_state.proto +33 -0
- data/proto/envoy/type/matcher/v3/http_inputs.proto +71 -0
- data/proto/envoy/type/matcher/v3/metadata.proto +110 -0
- data/proto/envoy/type/matcher/v3/node.proto +29 -0
- data/proto/envoy/type/matcher/v3/number.proto +33 -0
- data/proto/envoy/type/matcher/v3/path.proto +31 -0
- data/proto/envoy/type/matcher/v3/regex.proto +97 -0
- data/proto/envoy/type/matcher/v3/status_code_input.proto +23 -0
- data/proto/envoy/type/matcher/v3/string.proto +94 -0
- data/proto/envoy/type/matcher/v3/struct.proto +91 -0
- data/proto/envoy/type/matcher/v3/value.proto +80 -0
- data/proto/envoy/type/metadata/v3/metadata.proto +117 -0
- data/proto/envoy/type/percent.proto +52 -0
- data/proto/envoy/type/range.proto +43 -0
- data/proto/envoy/type/semantic_version.proto +24 -0
- data/proto/envoy/type/token_bucket.proto +36 -0
- data/proto/envoy/type/v3/BUILD +12 -0
- data/proto/envoy/type/v3/hash_policy.proto +43 -0
- data/proto/envoy/type/v3/http.proto +24 -0
- data/proto/envoy/type/v3/http_status.proto +199 -0
- data/proto/envoy/type/v3/percent.proto +57 -0
- data/proto/envoy/type/v3/range.proto +50 -0
- data/proto/envoy/type/v3/ratelimit_strategy.proto +79 -0
- data/proto/envoy/type/v3/ratelimit_unit.proto +37 -0
- data/proto/envoy/type/v3/semantic_version.proto +27 -0
- data/proto/envoy/type/v3/token_bucket.proto +39 -0
- data/proto/google/protobuf/any.proto +162 -0
- data/proto/google/protobuf/duration.proto +115 -0
- data/proto/google/protobuf/empty.proto +51 -0
- data/proto/google/protobuf/struct.proto +95 -0
- data/proto/google/protobuf/timestamp.proto +145 -0
- data/proto/google/protobuf/wrappers.proto +157 -0
- data/proto/google/rpc/status.proto +47 -0
- data/proto/readme.md +70 -0
- data/proto/udpa/annotations/migrate.proto +49 -0
- data/proto/udpa/annotations/security.proto +31 -0
- data/proto/udpa/annotations/sensitive.proto +14 -0
- data/proto/udpa/annotations/status.proto +34 -0
- data/proto/udpa/annotations/versioning.proto +17 -0
- data/proto/validate/validate.proto +862 -0
- data/proto/xds/annotations/v3/migrate.proto +46 -0
- data/proto/xds/annotations/v3/security.proto +30 -0
- data/proto/xds/annotations/v3/sensitive.proto +16 -0
- data/proto/xds/annotations/v3/status.proto +59 -0
- data/proto/xds/annotations/v3/versioning.proto +20 -0
- data/proto/xds/core/v3/authority.proto +22 -0
- data/proto/xds/core/v3/cidr.proto +25 -0
- data/proto/xds/core/v3/collection_entry.proto +55 -0
- data/proto/xds/core/v3/context_params.proto +23 -0
- data/proto/xds/core/v3/extension.proto +26 -0
- data/proto/xds/core/v3/resource.proto +29 -0
- data/proto/xds/core/v3/resource_locator.proto +118 -0
- data/proto/xds/core/v3/resource_name.proto +42 -0
- data/proto/xds/type/matcher/v3/cel.proto +37 -0
- data/proto/xds/type/matcher/v3/domain.proto +46 -0
- data/proto/xds/type/matcher/v3/http_inputs.proto +23 -0
- data/proto/xds/type/matcher/v3/ip.proto +53 -0
- data/proto/xds/type/matcher/v3/matcher.proto +144 -0
- data/proto/xds/type/matcher/v3/range.proto +69 -0
- data/proto/xds/type/matcher/v3/regex.proto +46 -0
- data/proto/xds/type/matcher/v3/string.proto +71 -0
- data/proto/xds/type/v3/cel.proto +77 -0
- data/proto/xds/type/v3/range.proto +40 -0
- data/proto/xds/type/v3/typed_struct.proto +44 -0
- data/readme.md +37 -0
- data/releases.md +5 -0
- data/xds/Dockerfile.backend +24 -0
- data/xds/Dockerfile.control-plane +22 -0
- data/xds/backend_server.rb +68 -0
- data/xds/docker-compose.yaml +89 -0
- data/xds/go.mod +22 -0
- data/xds/go.sum +82 -0
- data/xds/readme.md +122 -0
- data/xds/test/async/grpc/xds/client.rb +294 -0
- data/xds/test/async/grpc/xds/control_plane.rb +94 -0
- data/xds/test_server.go +355 -0
- data/xds/update_protos.sh +123 -0
- data.tar.gz.sig +0 -0
- metadata +386 -0
- metadata.gz.sig +2 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2026, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
require "async/grpc/xds/control_plane"
|
|
7
|
+
require "async/grpc/xds/client"
|
|
8
|
+
require "async/grpc/xds/resource_builder"
|
|
9
|
+
require "async/grpc/xds/server"
|
|
10
|
+
require "async/http/endpoint"
|
|
11
|
+
require "sus/fixtures/async"
|
|
12
|
+
require "socket"
|
|
13
|
+
|
|
14
|
+
describe Async::GRPC::XDS::ControlPlane do
|
|
15
|
+
include Sus::Fixtures::Async::ReactorContext
|
|
16
|
+
|
|
17
|
+
let(:control_plane) {subject.new}
|
|
18
|
+
|
|
19
|
+
it "publishes cluster resources" do
|
|
20
|
+
control_plane.update_cluster("myservice")
|
|
21
|
+
|
|
22
|
+
response = control_plane.response(
|
|
23
|
+
Async::GRPC::XDS::ControlPlane::CLUSTER_TYPE,
|
|
24
|
+
["myservice"]
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
expect(response.version_info).to be == "1"
|
|
28
|
+
expect(response.resources.size).to be == 1
|
|
29
|
+
expect(response.resources.first.type_url).to be == Async::GRPC::XDS::ControlPlane::CLUSTER_TYPE
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "publishes endpoint resources" do
|
|
33
|
+
control_plane.update_endpoints(
|
|
34
|
+
"myservice",
|
|
35
|
+
[
|
|
36
|
+
{address: "127.0.0.1", port: 50051},
|
|
37
|
+
{address: "127.0.0.2", port: 50052, healthy: false}
|
|
38
|
+
]
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
response = control_plane.response(
|
|
42
|
+
Async::GRPC::XDS::ControlPlane::ENDPOINT_TYPE,
|
|
43
|
+
["myservice"]
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
expect(response.version_info).to be == "1"
|
|
47
|
+
expect(response.resources.size).to be == 1
|
|
48
|
+
expect(response.resources.first.type_url).to be == Async::GRPC::XDS::ControlPlane::ENDPOINT_TYPE
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it "increments resource versions" do
|
|
52
|
+
control_plane.update_endpoints("myservice", [{address: "127.0.0.1", port: 50051}])
|
|
53
|
+
control_plane.update_endpoints("myservice", [{address: "127.0.0.2", port: 50052}])
|
|
54
|
+
|
|
55
|
+
expect(control_plane.version(Async::GRPC::XDS::ControlPlane::ENDPOINT_TYPE)).to be == "2"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it "serves resources over ADS" do
|
|
59
|
+
control_plane.update_cluster("myservice")
|
|
60
|
+
control_plane.update_endpoints("myservice", [{address: "127.0.0.1", port: 50051}])
|
|
61
|
+
|
|
62
|
+
port = available_port
|
|
63
|
+
endpoint = Async::HTTP::Endpoint.parse(
|
|
64
|
+
"http://127.0.0.1:#{port}",
|
|
65
|
+
protocol: Async::HTTP::Protocol::HTTP2
|
|
66
|
+
)
|
|
67
|
+
server = Async::GRPC::XDS::Server.new(control_plane)
|
|
68
|
+
server_task = Async{server.run(endpoint)}
|
|
69
|
+
|
|
70
|
+
client = Async::GRPC::XDS::Client.new("myservice", bootstrap: {
|
|
71
|
+
xds_servers: [
|
|
72
|
+
{
|
|
73
|
+
server_uri: "127.0.0.1:#{port}",
|
|
74
|
+
channel_creds: [{type: "insecure"}]
|
|
75
|
+
}
|
|
76
|
+
],
|
|
77
|
+
node: {id: "test-#{Process.pid}", cluster: "test"}
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
endpoints = client.resolve_endpoints
|
|
81
|
+
|
|
82
|
+
expect(endpoints.map(&:authority)).to be == ["127.0.0.1:50051"]
|
|
83
|
+
ensure
|
|
84
|
+
client&.close
|
|
85
|
+
server_task&.stop
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def available_port
|
|
89
|
+
server = TCPServer.new("127.0.0.1", 0)
|
|
90
|
+
server.addr[1]
|
|
91
|
+
ensure
|
|
92
|
+
server&.close
|
|
93
|
+
end
|
|
94
|
+
end
|
data/xds/test_server.go
ADDED
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"encoding/json"
|
|
6
|
+
"flag"
|
|
7
|
+
"fmt"
|
|
8
|
+
"log"
|
|
9
|
+
"net"
|
|
10
|
+
"net/http"
|
|
11
|
+
"strings"
|
|
12
|
+
"sync"
|
|
13
|
+
"sync/atomic"
|
|
14
|
+
|
|
15
|
+
"google.golang.org/grpc"
|
|
16
|
+
"google.golang.org/grpc/credentials/insecure"
|
|
17
|
+
|
|
18
|
+
"github.com/envoyproxy/go-control-plane/pkg/cache/types"
|
|
19
|
+
"github.com/envoyproxy/go-control-plane/pkg/cache/v3"
|
|
20
|
+
"github.com/envoyproxy/go-control-plane/pkg/resource/v3"
|
|
21
|
+
serverv3 "github.com/envoyproxy/go-control-plane/pkg/server/v3"
|
|
22
|
+
|
|
23
|
+
clusterv3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
|
|
24
|
+
corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
|
25
|
+
endpointv3 "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
|
|
26
|
+
discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
var (
|
|
30
|
+
port = flag.Int("port", 18000, "xDS server port")
|
|
31
|
+
adminPort = flag.Int("admin-port", 18001, "Admin HTTP server port")
|
|
32
|
+
upstream = flag.String("upstream", "backend-1:50051,backend-2:50052,backend-3:50053", "Comma-separated list of upstream endpoints")
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
// Custom hash function that accepts any node ID for testing
|
|
36
|
+
type anyNodeHash struct{}
|
|
37
|
+
|
|
38
|
+
func (h *anyNodeHash) ID(node *corev3.Node) string {
|
|
39
|
+
return "any"
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
func main() {
|
|
43
|
+
flag.Parse()
|
|
44
|
+
|
|
45
|
+
ctx := context.Background()
|
|
46
|
+
version := uint64(1)
|
|
47
|
+
|
|
48
|
+
// Create snapshot with cluster and endpoints
|
|
49
|
+
snapshot, err := createSnapshot(*upstream, fmt.Sprintf("%d", version))
|
|
50
|
+
if err != nil {
|
|
51
|
+
log.Fatalf("Failed to create snapshot: %v", err)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// For testing, accept any node ID by using a custom hash that always returns the same key
|
|
55
|
+
// This allows any client to connect and get the same snapshot
|
|
56
|
+
snapshotCache := cache.NewSnapshotCache(false, &anyNodeHash{}, nil)
|
|
57
|
+
if err := snapshotCache.SetSnapshot(ctx, "any", snapshot); err != nil {
|
|
58
|
+
log.Fatalf("Failed to set snapshot: %v", err)
|
|
59
|
+
}
|
|
60
|
+
log.Printf("Set snapshot for any node ID")
|
|
61
|
+
|
|
62
|
+
// Create callbacks for logging
|
|
63
|
+
callbacks := serverv3.CallbackFuncs{
|
|
64
|
+
StreamOpenFunc: func(ctx context.Context, streamID int64, typeURL string) error {
|
|
65
|
+
log.Printf("Stream opened: streamID=%d, typeURL=%s", streamID, typeURL)
|
|
66
|
+
return nil
|
|
67
|
+
},
|
|
68
|
+
StreamRequestFunc: func(streamID int64, request *discovery.DiscoveryRequest) error {
|
|
69
|
+
log.Printf("Stream request: streamID=%d, typeURL=%s, resource_names=%v", streamID, request.TypeUrl, request.ResourceNames)
|
|
70
|
+
return nil
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Create xDS server with callbacks
|
|
75
|
+
srv := serverv3.NewServer(ctx, snapshotCache, callbacks)
|
|
76
|
+
|
|
77
|
+
// Start gRPC server
|
|
78
|
+
baseListener, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
|
|
79
|
+
if err != nil {
|
|
80
|
+
log.Fatalf("Failed to listen: %v", err)
|
|
81
|
+
}
|
|
82
|
+
lis := newTrackingListener(baseListener)
|
|
83
|
+
|
|
84
|
+
// Create gRPC server with insecure credentials (for testing without TLS)
|
|
85
|
+
grpcServer := grpc.NewServer(grpc.Creds(insecure.NewCredentials()))
|
|
86
|
+
discovery.RegisterAggregatedDiscoveryServiceServer(grpcServer, srv)
|
|
87
|
+
|
|
88
|
+
admin := &adminServer{
|
|
89
|
+
ctx: ctx,
|
|
90
|
+
cache: snapshotCache,
|
|
91
|
+
listener: lis,
|
|
92
|
+
version: &version,
|
|
93
|
+
currentConfig: *upstream,
|
|
94
|
+
}
|
|
95
|
+
go admin.serve(*adminPort)
|
|
96
|
+
|
|
97
|
+
log.Printf("xDS test server listening on :%d", *port)
|
|
98
|
+
log.Printf("xDS admin server listening on :%d", *adminPort)
|
|
99
|
+
log.Printf("Serving cluster 'myservice' with endpoints: %s", *upstream)
|
|
100
|
+
|
|
101
|
+
if err := grpcServer.Serve(lis); err != nil {
|
|
102
|
+
log.Fatalf("Failed to serve: %v", err)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
type adminServer struct {
|
|
107
|
+
ctx context.Context
|
|
108
|
+
cache cache.SnapshotCache
|
|
109
|
+
listener *trackingListener
|
|
110
|
+
version *uint64
|
|
111
|
+
mutex sync.Mutex
|
|
112
|
+
currentConfig string
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
type endpointsRequest struct {
|
|
116
|
+
Upstream string `json:"upstream"`
|
|
117
|
+
Endpoints []string `json:"endpoints"`
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
func (server *adminServer) serve(port int) {
|
|
121
|
+
mux := http.NewServeMux()
|
|
122
|
+
mux.HandleFunc("/endpoints", server.handleEndpoints)
|
|
123
|
+
mux.HandleFunc("/reset-streams", server.handleResetStreams)
|
|
124
|
+
mux.HandleFunc("/status", server.handleStatus)
|
|
125
|
+
|
|
126
|
+
if err := http.ListenAndServe(fmt.Sprintf(":%d", port), mux); err != nil {
|
|
127
|
+
log.Fatalf("Failed to serve admin API: %v", err)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
func (server *adminServer) handleEndpoints(response http.ResponseWriter, request *http.Request) {
|
|
132
|
+
if request.Method != http.MethodPost {
|
|
133
|
+
http.Error(response, "method not allowed", http.StatusMethodNotAllowed)
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
var body endpointsRequest
|
|
138
|
+
if err := json.NewDecoder(request.Body).Decode(&body); err != nil {
|
|
139
|
+
http.Error(response, err.Error(), http.StatusBadRequest)
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
upstreams := body.Upstream
|
|
144
|
+
if upstreams == "" {
|
|
145
|
+
upstreams = strings.Join(body.Endpoints, ",")
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
nextVersion := atomic.AddUint64(server.version, 1)
|
|
149
|
+
snapshot, err := createSnapshot(upstreams, fmt.Sprintf("%d", nextVersion))
|
|
150
|
+
if err != nil {
|
|
151
|
+
http.Error(response, err.Error(), http.StatusBadRequest)
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
if err := server.cache.SetSnapshot(server.ctx, "any", snapshot); err != nil {
|
|
155
|
+
http.Error(response, err.Error(), http.StatusInternalServerError)
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
server.mutex.Lock()
|
|
160
|
+
server.currentConfig = upstreams
|
|
161
|
+
server.mutex.Unlock()
|
|
162
|
+
|
|
163
|
+
log.Printf("Updated snapshot version %d with endpoints: %s", nextVersion, upstreams)
|
|
164
|
+
writeJSON(response, map[string]any{
|
|
165
|
+
"version": nextVersion,
|
|
166
|
+
"endpoints": parseEndpoints(upstreams),
|
|
167
|
+
})
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
func (server *adminServer) handleResetStreams(response http.ResponseWriter, request *http.Request) {
|
|
171
|
+
if request.Method != http.MethodPost {
|
|
172
|
+
http.Error(response, "method not allowed", http.StatusMethodNotAllowed)
|
|
173
|
+
return
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
closed := server.listener.closeConnections()
|
|
177
|
+
log.Printf("Reset %d active xDS connections", closed)
|
|
178
|
+
writeJSON(response, map[string]any{"closed": closed})
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
func (server *adminServer) handleStatus(response http.ResponseWriter, request *http.Request) {
|
|
182
|
+
server.mutex.Lock()
|
|
183
|
+
currentConfig := server.currentConfig
|
|
184
|
+
server.mutex.Unlock()
|
|
185
|
+
|
|
186
|
+
writeJSON(response, map[string]any{
|
|
187
|
+
"version": atomic.LoadUint64(server.version),
|
|
188
|
+
"endpoints": parseEndpoints(currentConfig),
|
|
189
|
+
})
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
func writeJSON(response http.ResponseWriter, value any) {
|
|
193
|
+
response.Header().Set("Content-Type", "application/json")
|
|
194
|
+
if err := json.NewEncoder(response).Encode(value); err != nil {
|
|
195
|
+
log.Printf("Failed to write JSON response: %v", err)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
type trackingListener struct {
|
|
200
|
+
net.Listener
|
|
201
|
+
mutex sync.Mutex
|
|
202
|
+
connections map[net.Conn]struct{}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
func newTrackingListener(listener net.Listener) *trackingListener {
|
|
206
|
+
return &trackingListener{
|
|
207
|
+
Listener: listener,
|
|
208
|
+
connections: make(map[net.Conn]struct{}),
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
func (listener *trackingListener) Accept() (net.Conn, error) {
|
|
213
|
+
connection, err := listener.Listener.Accept()
|
|
214
|
+
if err != nil {
|
|
215
|
+
return nil, err
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
tracked := &trackedConn{Conn: connection, listener: listener}
|
|
219
|
+
listener.mutex.Lock()
|
|
220
|
+
listener.connections[tracked] = struct{}{}
|
|
221
|
+
listener.mutex.Unlock()
|
|
222
|
+
|
|
223
|
+
return tracked, nil
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
func (listener *trackingListener) closeConnections() int {
|
|
227
|
+
listener.mutex.Lock()
|
|
228
|
+
connections := make([]net.Conn, 0, len(listener.connections))
|
|
229
|
+
for connection := range listener.connections {
|
|
230
|
+
connections = append(connections, connection)
|
|
231
|
+
}
|
|
232
|
+
listener.mutex.Unlock()
|
|
233
|
+
|
|
234
|
+
for _, connection := range connections {
|
|
235
|
+
_ = connection.Close()
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return len(connections)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
func (listener *trackingListener) remove(connection net.Conn) {
|
|
242
|
+
listener.mutex.Lock()
|
|
243
|
+
delete(listener.connections, connection)
|
|
244
|
+
listener.mutex.Unlock()
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
type trackedConn struct {
|
|
248
|
+
net.Conn
|
|
249
|
+
listener *trackingListener
|
|
250
|
+
once sync.Once
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
func (connection *trackedConn) Close() error {
|
|
254
|
+
err := connection.Conn.Close()
|
|
255
|
+
connection.once.Do(func() {
|
|
256
|
+
connection.listener.remove(connection)
|
|
257
|
+
})
|
|
258
|
+
return err
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
func createSnapshot(upstreams string, version string) (*cache.Snapshot, error) {
|
|
262
|
+
// Parse upstream endpoints
|
|
263
|
+
endpoints := parseEndpoints(upstreams)
|
|
264
|
+
|
|
265
|
+
// Create cluster
|
|
266
|
+
cluster := &clusterv3.Cluster{
|
|
267
|
+
Name: "myservice",
|
|
268
|
+
ClusterDiscoveryType: &clusterv3.Cluster_Type{Type: clusterv3.Cluster_EDS},
|
|
269
|
+
LbPolicy: clusterv3.Cluster_ROUND_ROBIN,
|
|
270
|
+
EdsClusterConfig: &clusterv3.Cluster_EdsClusterConfig{
|
|
271
|
+
ServiceName: "myservice",
|
|
272
|
+
EdsConfig: &corev3.ConfigSource{
|
|
273
|
+
ConfigSourceSpecifier: &corev3.ConfigSource_Ads{},
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Create endpoint assignment
|
|
279
|
+
lbEndpoints := make([]*endpointv3.LbEndpoint, 0, len(endpoints))
|
|
280
|
+
for _, ep := range endpoints {
|
|
281
|
+
lbEndpoints = append(lbEndpoints, &endpointv3.LbEndpoint{
|
|
282
|
+
HostIdentifier: &endpointv3.LbEndpoint_Endpoint{
|
|
283
|
+
Endpoint: &endpointv3.Endpoint{
|
|
284
|
+
Address: &corev3.Address{
|
|
285
|
+
Address: &corev3.Address_SocketAddress{
|
|
286
|
+
SocketAddress: &corev3.SocketAddress{
|
|
287
|
+
Protocol: corev3.SocketAddress_TCP,
|
|
288
|
+
Address: ep.Host,
|
|
289
|
+
PortSpecifier: &corev3.SocketAddress_PortValue{
|
|
290
|
+
PortValue: ep.Port,
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
HealthStatus: corev3.HealthStatus_HEALTHY,
|
|
298
|
+
})
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
endpointAssignment := &endpointv3.ClusterLoadAssignment{
|
|
302
|
+
ClusterName: "myservice",
|
|
303
|
+
Endpoints: []*endpointv3.LocalityLbEndpoints{
|
|
304
|
+
{
|
|
305
|
+
LbEndpoints: lbEndpoints,
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Create snapshot
|
|
311
|
+
// types.Resource is proto.Message, which Cluster and ClusterLoadAssignment implement
|
|
312
|
+
return cache.NewSnapshot(
|
|
313
|
+
version,
|
|
314
|
+
map[resource.Type][]types.Resource{
|
|
315
|
+
resource.ClusterType: {cluster},
|
|
316
|
+
resource.EndpointType: {endpointAssignment},
|
|
317
|
+
},
|
|
318
|
+
)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
type endpoint struct {
|
|
322
|
+
Host string `json:"host"`
|
|
323
|
+
Port uint32 `json:"port"`
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
func parseEndpoints(upstreams string) []endpoint {
|
|
327
|
+
var endpoints []endpoint
|
|
328
|
+
parts := splitComma(upstreams)
|
|
329
|
+
for _, part := range parts {
|
|
330
|
+
part = strings.TrimSpace(part)
|
|
331
|
+
if part == "" {
|
|
332
|
+
continue
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
host, port := parseHostPort(part)
|
|
336
|
+
endpoints = append(endpoints, endpoint{Host: host, Port: port})
|
|
337
|
+
}
|
|
338
|
+
return endpoints
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
func splitComma(s string) []string {
|
|
342
|
+
return strings.Split(s, ",")
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
func parseHostPort(addr string) (string, uint32) {
|
|
346
|
+
parts := strings.Split(addr, ":")
|
|
347
|
+
if len(parts) == 2 {
|
|
348
|
+
var port uint32
|
|
349
|
+
fmt.Sscanf(parts[1], "%d", &port)
|
|
350
|
+
if port > 0 {
|
|
351
|
+
return parts[0], port
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return addr, 50051
|
|
355
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Update Envoy protobuf definitions
|
|
3
|
+
# This script clones the envoy data-plane-api and copies only the needed .proto files
|
|
4
|
+
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
|
+
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
9
|
+
PROTO_DIR="$PROJECT_ROOT/proto"
|
|
10
|
+
TEMP_DIR="/tmp/envoy-api-$$"
|
|
11
|
+
|
|
12
|
+
echo "Cloning envoyproxy/data-plane-api..."
|
|
13
|
+
|
|
14
|
+
# Clone with sparse checkout to only get what we need
|
|
15
|
+
git clone --depth 1 --filter=blob:none --sparse https://github.com/envoyproxy/data-plane-api.git "$TEMP_DIR"
|
|
16
|
+
|
|
17
|
+
cd "$TEMP_DIR"
|
|
18
|
+
|
|
19
|
+
echo "Setting up sparse checkout..."
|
|
20
|
+
|
|
21
|
+
# First get envoy config files
|
|
22
|
+
git sparse-checkout set \
|
|
23
|
+
envoy/config/cluster/v3 \
|
|
24
|
+
envoy/config/endpoint/v3 \
|
|
25
|
+
envoy/config/listener/v3 \
|
|
26
|
+
envoy/config/route/v3 \
|
|
27
|
+
envoy/config/core/v3 \
|
|
28
|
+
envoy/extensions/transport_sockets/tls/v3
|
|
29
|
+
|
|
30
|
+
# Copy envoy config files
|
|
31
|
+
cp -r envoy "$PROTO_DIR/"
|
|
32
|
+
|
|
33
|
+
# Now get discovery service and google protobuf files
|
|
34
|
+
git sparse-checkout set \
|
|
35
|
+
envoy/service/discovery/v3 \
|
|
36
|
+
google/protobuf
|
|
37
|
+
|
|
38
|
+
# Copy discovery service
|
|
39
|
+
cp -r envoy/service "$PROTO_DIR/envoy/"
|
|
40
|
+
|
|
41
|
+
# Copy google protobuf (if exists in repo)
|
|
42
|
+
if [ -d "google" ]; then
|
|
43
|
+
cp -r google "$PROTO_DIR/"
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
# Get Google protobuf well-known types from protobuf repo
|
|
47
|
+
echo "Fetching Google protobuf well-known types..."
|
|
48
|
+
git clone --depth 1 https://github.com/protocolbuffers/protobuf.git /tmp/protobuf-$$
|
|
49
|
+
mkdir -p "$PROTO_DIR/google/protobuf"
|
|
50
|
+
cp /tmp/protobuf-$$/src/google/protobuf/{any,duration,timestamp,struct,empty,wrappers}.proto "$PROTO_DIR/google/protobuf/" 2>/dev/null || true
|
|
51
|
+
rm -rf /tmp/protobuf-$$
|
|
52
|
+
|
|
53
|
+
# Get google/rpc/status.proto from api-common-protos
|
|
54
|
+
echo "Fetching google/rpc/status.proto..."
|
|
55
|
+
git clone --depth 1 https://github.com/googleapis/api-common-protos.git /tmp/api-common-$$
|
|
56
|
+
mkdir -p "$PROTO_DIR/google/rpc"
|
|
57
|
+
cp /tmp/api-common-$$/google/rpc/status.proto "$PROTO_DIR/google/rpc/" 2>/dev/null || true
|
|
58
|
+
rm -rf /tmp/api-common-$$
|
|
59
|
+
|
|
60
|
+
# Get envoy/type/v3 and xds/core/v3 from data-plane-api
|
|
61
|
+
echo "Fetching envoy/type/v3 and xds/core/v3..."
|
|
62
|
+
git clone --depth 1 --filter=blob:none --sparse https://github.com/envoyproxy/data-plane-api.git /tmp/envoy-types-$$
|
|
63
|
+
cd /tmp/envoy-types-$$
|
|
64
|
+
git sparse-checkout set envoy/type/v3 envoy/type/matcher/v3 envoy/type/metadata/v3 xds/core/v3
|
|
65
|
+
mkdir -p "$PROTO_DIR/envoy/type/v3" "$PROTO_DIR/envoy/type/matcher/v3" "$PROTO_DIR/envoy/type/metadata/v3" "$PROTO_DIR/xds/core/v3"
|
|
66
|
+
cp -r envoy/type "$PROTO_DIR/envoy/" 2>/dev/null || true
|
|
67
|
+
cp -r xds/core "$PROTO_DIR/xds/" 2>/dev/null || true
|
|
68
|
+
rm -rf /tmp/envoy-types-$$
|
|
69
|
+
|
|
70
|
+
# Get xds/type/matcher/v3 and xds/core/v3 from cncf/xds repo
|
|
71
|
+
echo "Fetching xds/type/matcher/v3 and xds/core/v3..."
|
|
72
|
+
git clone --depth 1 https://github.com/cncf/xds.git /tmp/xds-types-$$
|
|
73
|
+
mkdir -p "$PROTO_DIR/xds/type/matcher/v3" "$PROTO_DIR/xds/core/v3"
|
|
74
|
+
find /tmp/xds-types-$$/xds/type/matcher/v3 -name "*.proto" ! -name "*cel*" -exec cp {} "$PROTO_DIR/xds/type/matcher/v3/" \;
|
|
75
|
+
find /tmp/xds-types-$$/xds/core/v3 -name "*.proto" -exec cp {} "$PROTO_DIR/xds/core/v3/" \;
|
|
76
|
+
rm -rf /tmp/xds-types-$$
|
|
77
|
+
|
|
78
|
+
# Get udpa annotations
|
|
79
|
+
echo "Fetching udpa annotations..."
|
|
80
|
+
git clone --depth 1 https://github.com/cncf/udpa.git /tmp/udpa-$$
|
|
81
|
+
mkdir -p "$PROTO_DIR/udpa/annotations"
|
|
82
|
+
cp /tmp/udpa-$$/udpa/annotations/*.proto "$PROTO_DIR/udpa/annotations/" 2>/dev/null || true
|
|
83
|
+
rm -rf /tmp/udpa-$$
|
|
84
|
+
|
|
85
|
+
# Get validate annotations
|
|
86
|
+
echo "Fetching validate annotations..."
|
|
87
|
+
git clone --depth 1 https://github.com/envoyproxy/protoc-gen-validate.git /tmp/validate-$$
|
|
88
|
+
mkdir -p "$PROTO_DIR/validate"
|
|
89
|
+
cp /tmp/validate-$$/validate/validate.proto "$PROTO_DIR/validate/" 2>/dev/null || true
|
|
90
|
+
rm -rf /tmp/validate-$$
|
|
91
|
+
|
|
92
|
+
# Get envoy annotations
|
|
93
|
+
echo "Fetching envoy annotations..."
|
|
94
|
+
git clone --depth 1 --filter=blob:none --sparse https://github.com/envoyproxy/envoy.git /tmp/envoy-annotations-$$
|
|
95
|
+
cd /tmp/envoy-annotations-$$
|
|
96
|
+
git sparse-checkout set api/envoy/annotations
|
|
97
|
+
mkdir -p "$PROTO_DIR/envoy/annotations"
|
|
98
|
+
cp api/envoy/annotations/*.proto "$PROTO_DIR/envoy/annotations/" 2>/dev/null || true
|
|
99
|
+
rm -rf /tmp/envoy-annotations-$$
|
|
100
|
+
|
|
101
|
+
echo "Copying .proto files to $PROTO_DIR..."
|
|
102
|
+
|
|
103
|
+
# Create directories
|
|
104
|
+
mkdir -p "$PROTO_DIR/envoy/service/discovery/v3"
|
|
105
|
+
mkdir -p "$PROTO_DIR/envoy/config/cluster/v3"
|
|
106
|
+
mkdir -p "$PROTO_DIR/envoy/config/endpoint/v3"
|
|
107
|
+
mkdir -p "$PROTO_DIR/envoy/config/listener/v3"
|
|
108
|
+
mkdir -p "$PROTO_DIR/envoy/config/route/v3"
|
|
109
|
+
mkdir -p "$PROTO_DIR/envoy/config/core/v3"
|
|
110
|
+
mkdir -p "$PROTO_DIR/envoy/extensions/transport_sockets/tls/v3"
|
|
111
|
+
mkdir -p "$PROTO_DIR/google/protobuf"
|
|
112
|
+
|
|
113
|
+
# Copy files
|
|
114
|
+
cp -r envoy "$PROTO_DIR/"
|
|
115
|
+
cp -r google "$PROTO_DIR/"
|
|
116
|
+
|
|
117
|
+
# Cleanup
|
|
118
|
+
rm -rf "$TEMP_DIR"
|
|
119
|
+
|
|
120
|
+
echo "Done! Proto files updated in $PROTO_DIR"
|
|
121
|
+
echo ""
|
|
122
|
+
echo "To generate Ruby code, run:"
|
|
123
|
+
echo " bundle exec bake async:grpc:xds:generate_protos"
|
data.tar.gz.sig
ADDED
|
Binary file
|