From ee612d79c3996f433e53517229780b371839cad8 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Sat, 9 Apr 2022 17:26:39 -0700 Subject: [PATCH] rust: Remove build dependency on `protoc` (#104) The protobuf project does not publish `protoc` binaries for 32-bit ARM hosts, which complicates our compilation tooling for ARM. This problem can be avoided completely by committing the generated interface bindings so that crates that depend on `linkerd2-proxy-api` don't need any build dependencies. This change removes the `client`, `server`, and `transport` features. Client and server bindings are now always generated for gRPC services. The `transport` feature is removed, since it cannot be optional if the generated code is stored in the repo. We don't want to require tonic's transport feature because it pulls in many dependencies that we don't really need. Signed-off-by: Oliver Gould --- .gitattributes | 2 +- .github/workflows/ci.yml | 14 +- CHANGES.md | 7 + Cargo.toml | 29 +- Makefile | 7 +- build.rs | 49 -- src/gen.rs | 23 - src/gen/io.linkerd.proxy.destination.rs | 603 ++++++++++++++++++++++++ src/gen/io.linkerd.proxy.http_types.rs | 64 +++ src/gen/io.linkerd.proxy.identity.rs | 244 ++++++++++ src/gen/io.linkerd.proxy.inbound.rs | 468 ++++++++++++++++++ src/gen/io.linkerd.proxy.net.rs | 38 ++ src/gen/io.linkerd.proxy.tap.rs | 494 +++++++++++++++++++ src/{gen => }/http_types.rs | 2 +- src/lib.rs | 24 +- src/{gen => }/net.rs | 2 +- src/{gen => }/tap.rs | 4 +- tests/bootstrap.rs | 51 ++ 18 files changed, 2016 insertions(+), 109 deletions(-) delete mode 100644 build.rs delete mode 100644 src/gen.rs create mode 100644 src/gen/io.linkerd.proxy.destination.rs create mode 100644 src/gen/io.linkerd.proxy.http_types.rs create mode 100644 src/gen/io.linkerd.proxy.identity.rs create mode 100644 src/gen/io.linkerd.proxy.inbound.rs create mode 100644 src/gen/io.linkerd.proxy.net.rs create mode 100644 src/gen/io.linkerd.proxy.tap.rs rename src/{gen => }/http_types.rs (98%) rename src/{gen => }/net.rs (99%) rename src/{gen => }/tap.rs (98%) create mode 100644 tests/bootstrap.rs diff --git a/.gitattributes b/.gitattributes index 3ab4b8d99..d22aab762 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,2 @@ -Gopkg.lock linguist-generated=false go/**/*.pb.go linguist-generated=true +src/gen/*.rs linguist-generated=true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 13e807ed0..e8eafb92f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,9 +10,10 @@ jobs: container: image: docker://rust:1.59.0-buster steps: + - run: rustup component add clippy - uses: actions/checkout@v3 - - run: rustup component add rustfmt # Needed for the rustfmt feature. - - run: make rs + - run: make fetch + - run: make clippy rust_audit: name: Rust audit @@ -31,15 +32,6 @@ jobs: with: command: check ${{ matrix.checks }} - rust_lint: - name: Rust lint - timeout-minutes: 5 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - run: rustup component add clippy rustfmt - - run: make clippy - go_build: name: Go build timeout-minutes: 5 diff --git a/CHANGES.md b/CHANGES.md index f151bf3eb..e607335f8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,10 @@ +# `linkerd2-proxy-api` changes + +## v0.5.0 + +* Rust: Remove the build-time dependency on `protoc` +* Rust: Remove the `client`, `server`, and `transport` features + ## v0.4.0 * Go: Update `google.golang.org/protobuf` to v1.28 diff --git a/Cargo.toml b/Cargo.toml index ea414b281..51b39e281 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "linkerd2-proxy-api" -version = "0.4.0" +version = "0.5.0" authors = ["Linkerd Developers "] license = "Apache-2.0" edition = "2021" @@ -14,22 +14,15 @@ rust-version = "1.59" [features] default = [] -# Generate code that is compatible with Tonic's `transport` module. -transport = ["tonic-build/transport", "tonic/transport"] - # Enable generation of arbitrary protos with quickcheck. arbitrary = ["quickcheck"] -# Determines whether clients or servers are built -client = ["transport"] -server = ["transport"] - -destination = ["http_types", "net", "prost-types"] +destination = ["http_types", "net", "prost-types", "tonic/codegen"] http_types = ["http", "thiserror"] -identity = ["prost-types"] -inbound = ["net", "prost-types"] +identity = ["prost-types", "tonic/codegen"] +inbound = ["net", "prost-types", "tonic/codegen"] net = ["ipnet", "thiserror"] -tap = ["h2", "http_types", "net", "prost-types"] +tap = ["h2", "http_types", "net", "prost-types", "tonic/codegen"] [dependencies] h2 = { version = "0.3", optional = true } @@ -39,7 +32,13 @@ prost = "0.10" prost-types = { version = "0.10", optional = true } quickcheck = { version = "1", default-features = false, optional = true } thiserror = { version = "1", optional = true } -tonic = { version = "0.7", default-features = false, features = ["prost", "codegen"] } -[build-dependencies] -tonic-build = { version = "0.7", default-features = false, features = ["prost"] } +[dependencies.tonic] +version = "0.7" +default-features = false +features = ["prost"] + +[dev-dependencies.tonic-build] +version = "0.7" +default-features = false +features = ["prost"] diff --git a/Makefile b/Makefile index f196e4d75..e3a5b96a8 100644 --- a/Makefile +++ b/Makefile @@ -48,14 +48,13 @@ fetch: Cargo.toml .PHONY: rs rs: fetch $(PROTOC) + cargo test --test=bootstrap cargo check --all-features --frozen $(RELEASE) .PHONY: clippy -clippy: fetch $(PROTOC) +clippy: rs for api in destination http_types identity inbound net tap ; do \ - for kind in arbitrary client server ; do \ - $(CARGO) clippy --frozen $(RELEASE) --features=$$api,$$kind --all-targets ; \ - done ; \ + $(CARGO) clippy --frozen $(RELEASE) --features=$$api,arbitrary --all-targets ; \ done .PHONY: go diff --git a/build.rs b/build.rs deleted file mode 100644 index 73315d45e..000000000 --- a/build.rs +++ /dev/null @@ -1,49 +0,0 @@ -use std::error::Error; - -const DIR: &str = "proto"; -const PROTOS: [&str; 6] = [ - "destination", - "http_types", - "identity", - "inbound", - "net", - "tap", -]; - -fn main() -> Result<(), Box> { - let files = PROTOS - .iter() - .filter_map(|p| feature_proto(DIR, *p)) - .collect::>(); - if files.is_empty() { - return Err("no interfaces enabled".into()); - } - - let build_client = std::env::var_os("CARGO_FEATURE_CLIENT").is_some(); - let build_server = std::env::var_os("CARGO_FEATURE_SERVER").is_some(); - let build_arbitrary = std::env::var_os("CARGO_FEATURE_ARBITRARY").is_some(); - if !build_client && !build_server && !build_arbitrary { - return Err( - "either the `client`, `server`, or `arbitrary` features must be enabled".into(), - ); - } - tonic_build::configure() - .build_client(build_client) - .build_server(build_server) - .compile(&*files, &[DIR.to_string()])?; - - // recompile protobufs only if any of the proto files changes. - for file in files.iter() { - println!("cargo:rerun-if-changed={}", file); - } - Ok(()) -} - -fn feature_proto(dir: &str, name: &str) -> Option { - let env = format!("CARGO_FEATURE_{}", name.to_uppercase()); - if std::env::var(env).is_ok() { - Some(format!("{}/{}.proto", dir, name.to_lowercase())) - } else { - None - } -} diff --git a/src/gen.rs b/src/gen.rs deleted file mode 100644 index 7170c4fe0..000000000 --- a/src/gen.rs +++ /dev/null @@ -1,23 +0,0 @@ -#[cfg(feature = "net")] -pub mod net; - -#[cfg(feature = "http_types")] -pub mod http_types; - -#[cfg(feature = "destination")] -pub mod destination { - tonic::include_proto!("io.linkerd.proxy.destination"); -} - -#[cfg(feature = "identity")] -pub mod identity { - tonic::include_proto!("io.linkerd.proxy.identity"); -} - -#[cfg(feature = "inbound")] -pub mod inbound { - tonic::include_proto!("io.linkerd.proxy.inbound"); -} - -#[cfg(feature = "tap")] -pub mod tap; diff --git a/src/gen/io.linkerd.proxy.destination.rs b/src/gen/io.linkerd.proxy.destination.rs new file mode 100644 index 000000000..c7349267c --- /dev/null +++ b/src/gen/io.linkerd.proxy.destination.rs @@ -0,0 +1,603 @@ +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetDestination { + #[prost(string, tag="1")] + pub scheme: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub path: ::prost::alloc::string::String, + /// An opaque value that is set at injection-time and sent with destintion + /// lookups. + /// + /// If, for instance, the token encodes a namespace or some locality + /// information, the service may alter its results to take this locality into + /// account. + #[prost(string, tag="3")] + pub context_token: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Update { + #[prost(oneof="update::Update", tags="1, 2, 3")] + pub update: ::core::option::Option, +} +/// Nested message and enum types in `Update`. +pub mod update { + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Update { + /// A new set of endpoints are available for the service. The set might be + /// empty. + #[prost(message, tag="1")] + Add(super::WeightedAddrSet), + /// Some endpoints have been removed from the service. + #[prost(message, tag="2")] + Remove(super::AddrSet), + /// `no_endpoints{exists: false}` indicates that the service does not exist + /// and the client MAY try an alternate service discovery method (e.g. DNS). + /// + /// `no_endpoints(exists: true)` indicates that the service does exist and + /// the client MUST NOT fall back to an alternate service discovery method. + #[prost(message, tag="3")] + NoEndpoints(super::NoEndpoints), + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct AddrSet { + #[prost(message, repeated, tag="1")] + pub addrs: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct WeightedAddrSet { + #[prost(message, repeated, tag="1")] + pub addrs: ::prost::alloc::vec::Vec, + #[prost(map="string, string", tag="2")] + pub metric_labels: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct WeightedAddr { + #[prost(message, optional, tag="1")] + pub addr: ::core::option::Option, + #[prost(uint32, tag="3")] + pub weight: u32, + #[prost(map="string, string", tag="4")] + pub metric_labels: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + #[prost(message, optional, tag="5")] + pub tls_identity: ::core::option::Option, + #[prost(message, optional, tag="6")] + pub protocol_hint: ::core::option::Option, + #[prost(message, optional, tag="7")] + pub authority_override: ::core::option::Option, +} +/// Which strategy should be used for verifying TLS. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TlsIdentity { + #[prost(oneof="tls_identity::Strategy", tags="1")] + pub strategy: ::core::option::Option, +} +/// Nested message and enum types in `TlsIdentity`. +pub mod tls_identity { + /// Verify the certificate based on the Kubernetes pod identity. + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct DnsLikeIdentity { + /// A DNS-like name that encodes workload coordinates. + /// + /// For example: + /// {name}.{namespace}.{type}.identity.{control-namespace}.{trust-domain...} + #[prost(string, tag="1")] + pub name: ::prost::alloc::string::String, + } + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Strategy { + #[prost(message, tag="1")] + DnsLikeIdentity(DnsLikeIdentity), + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct AuthorityOverride { + #[prost(string, tag="1")] + pub authority_override: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct NoEndpoints { + #[prost(bool, tag="1")] + pub exists: bool, +} +/// A hint of what protocol the service knows. The default value is +/// for the `hint` field to be not be set, essentially meaning "unknown". +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ProtocolHint { + /// When set, indicates that the target supports receiving opaque traffic + /// wrapped with the Linkerd connection header on the specified port. + #[prost(message, optional, tag="2")] + pub opaque_transport: ::core::option::Option, + #[prost(oneof="protocol_hint::Protocol", tags="1")] + pub protocol: ::core::option::Option, +} +/// Nested message and enum types in `ProtocolHint`. +pub mod protocol_hint { + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct H2 { + } + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct OpaqueTransport { + /// The target proxy's inbound port. + #[prost(uint32, tag="1")] + pub inbound_port: u32, + } + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Protocol { + /// Hints that the service understands HTTP2 and the proxy's internal + /// http2-upgrade mechanism. + #[prost(message, tag="1")] + H2(H2), + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DestinationProfile { + /// The fully-qualified service name, if one exists. + /// + /// When resolving (especially by IP), this field provides the fully-qualified + /// name of the resolved service, if one exists. This field does NOT include + /// any port information. E.g. a lookup for 10.2.3.4:8080 might have a name + /// like `foo.bar.svc.cluster.local`. + /// + /// Implementations MAY provide names for non-service IP-lookups (e.g., pod or + /// node dns names), but this is not required. + /// + /// If the lookup does not refer to a known named entity, this field MUST be + /// left empty. + #[prost(string, tag="5")] + pub fully_qualified_name: ::prost::alloc::string::String, + /// Indicates that connections on this service address should be handled as + /// opaque TCP streams. HTTP routes returned on for such services will be + /// ignored. + #[prost(bool, tag="4")] + pub opaque_protocol: bool, + /// A list of routes, each with a RequestMatch. If a request matches + /// more than one route, the first match wins. + #[prost(message, repeated, tag="1")] + pub routes: ::prost::alloc::vec::Vec, + /// The retry budget controls how much additional load the proxy can generate + /// as retries. Failured requests on retryable routes will not be retried if + /// there is no available budget. + #[prost(message, optional, tag="2")] + pub retry_budget: ::core::option::Option, + /// If this list is non-empty, requests to this destination should instead be + /// split between the destinations in this list. Each destination should + /// receive a portion of the requests proportional to its weight. If this + /// list is empty, requests should be sent to this destination as normal. + #[prost(message, repeated, tag="3")] + pub dst_overrides: ::prost::alloc::vec::Vec, + /// If this field is set, it indicates that the target is a known endpoint (and + /// not a service address). The values of `fully_qualified_name` and + /// `dst_overrides` will be ignored for the purposes of service discovery-- + /// traffic split and load balancing will be skipped and the single endpoint + /// are used. + /// + /// No endpoint should be set If the target is unknown. + #[prost(message, optional, tag="6")] + pub endpoint: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Route { + /// This route contains requests which match this condition. + #[prost(message, optional, tag="1")] + pub condition: ::core::option::Option, + /// A list of response classes for this route. If a response matches + /// more than one ResponseClass, the first match wins. If a response does not + /// match any ResponseClasses, it is considered to be a successful response. + #[prost(message, repeated, tag="2")] + pub response_classes: ::prost::alloc::vec::Vec, + /// Metric labels to attach to requests and responses that match this route. + #[prost(map="string, string", tag="3")] + pub metrics_labels: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + /// If a route is retryable, any failed requests on that route may be retried + /// by the proxy. + #[prost(bool, tag="4")] + pub is_retryable: bool, + /// After this time has elapsed since receiving the initial request, any + /// outstanding request will be cancelled, a timeout error response will be + /// returned, and no more retries will be attempted. + #[prost(message, optional, tag="5")] + pub timeout: ::core::option::Option<::prost_types::Duration>, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RetryBudget { + /// The ratio of additional traffic that may be added by retries. A + /// retry_ratio of 0.1 means that 1 retry may be attempted for every 10 regular + /// requests. A retry_ratio of 1.0 means that 1 retry may be attempted for + /// every 1 regular request (in other words, total request load may be doubled + /// as a result of retries). + #[prost(float, tag="1")] + pub retry_ratio: f32, + /// The proxy may always attempt this number of retries per second, even if it + /// would violate the retry_ratio. This is to allow retries to happen even + /// when the request rate is very low. + #[prost(uint32, tag="2")] + pub min_retries_per_second: u32, + /// This duration indicates for how long requests should be considered for the + /// purposes of enforcing the retry_ratio. A higher value considers a larger + /// window and therefore allows burstier retries. + #[prost(message, optional, tag="3")] + pub ttl: ::core::option::Option<::prost_types::Duration>, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ResponseClass { + /// This class contains responses which match this condition. + #[prost(message, optional, tag="1")] + pub condition: ::core::option::Option, + /// If responses in this class should be considered failures. This defaults + /// to false (success). + #[prost(bool, tag="2")] + pub is_failure: bool, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RequestMatch { + #[prost(oneof="request_match::Match", tags="1, 2, 3, 4, 5")] + pub r#match: ::core::option::Option, +} +/// Nested message and enum types in `RequestMatch`. +pub mod request_match { + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Seq { + #[prost(message, repeated, tag="1")] + pub matches: ::prost::alloc::vec::Vec, + } + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Match { + #[prost(message, tag="1")] + All(Seq), + #[prost(message, tag="2")] + Any(Seq), + #[prost(message, tag="3")] + Not(::prost::alloc::boxed::Box), + #[prost(message, tag="4")] + Path(super::PathMatch), + /// TODO: match on arbitrary header + #[prost(message, tag="5")] + Method(super::super::http_types::HttpMethod), + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PathMatch { + /// Match if the request path matches this regex. + #[prost(string, tag="1")] + pub regex: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ResponseMatch { + #[prost(oneof="response_match::Match", tags="1, 2, 3, 4")] + pub r#match: ::core::option::Option, +} +/// Nested message and enum types in `ResponseMatch`. +pub mod response_match { + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Seq { + #[prost(message, repeated, tag="1")] + pub matches: ::prost::alloc::vec::Vec, + } + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Match { + #[prost(message, tag="1")] + All(Seq), + #[prost(message, tag="2")] + Any(Seq), + #[prost(message, tag="3")] + Not(::prost::alloc::boxed::Box), + /// TODO: match on arbitrary header or trailer + #[prost(message, tag="4")] + Status(super::HttpStatusRange), + } +} +/// If either a minimum or maximum is not specified, the range is considered to +/// be over a discrete value. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct HttpStatusRange { + /// Minimum matching http status code (inclusive), if specified. + #[prost(uint32, tag="1")] + pub min: u32, + /// Maximum matching http status code (inclusive), if specified. + #[prost(uint32, tag="2")] + pub max: u32, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct WeightedDst { + /// This authority will be used as the `path` in a call to the Destination.Get + /// rpc. + #[prost(string, tag="1")] + pub authority: ::prost::alloc::string::String, + /// The proportion of requests to send to this destination. This value is + /// relative to other weights in the same dst_overrides list. + #[prost(uint32, tag="2")] + pub weight: u32, +} +/// Generated client implementations. +pub mod destination_client { + #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] + use tonic::codegen::*; + #[derive(Debug, Clone)] + pub struct DestinationClient { + inner: tonic::client::Grpc, + } + impl DestinationClient + where + T: tonic::client::GrpcService, + T::Error: Into, + T::ResponseBody: Default + Body + Send + 'static, + ::Error: Into + Send, + { + pub fn new(inner: T) -> Self { + let inner = tonic::client::Grpc::new(inner); + Self { inner } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> DestinationClient> + where + F: tonic::service::Interceptor, + T: tonic::codegen::Service< + http::Request, + Response = http::Response< + >::ResponseBody, + >, + >, + , + >>::Error: Into + Send + Sync, + { + DestinationClient::new(InterceptedService::new(inner, interceptor)) + } + /// Compress requests with `gzip`. + /// + /// This requires the server to support it otherwise it might respond with an + /// error. + #[must_use] + pub fn send_gzip(mut self) -> Self { + self.inner = self.inner.send_gzip(); + self + } + /// Enable decompressing responses with `gzip`. + #[must_use] + pub fn accept_gzip(mut self) -> Self { + self.inner = self.inner.accept_gzip(); + self + } + /// Given a destination, return all addresses in that destination as a long- + /// running stream of updates. + pub async fn get( + &mut self, + request: impl tonic::IntoRequest, + ) -> Result< + tonic::Response>, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/io.linkerd.proxy.destination.Destination/Get", + ); + self.inner.server_streaming(request.into_request(), path, codec).await + } + /// Given a destination, return that destination's profile and send an update + /// whenever it changes. + pub async fn get_profile( + &mut self, + request: impl tonic::IntoRequest, + ) -> Result< + tonic::Response>, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/io.linkerd.proxy.destination.Destination/GetProfile", + ); + self.inner.server_streaming(request.into_request(), path, codec).await + } + } +} +/// Generated server implementations. +pub mod destination_server { + #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] + use tonic::codegen::*; + ///Generated trait containing gRPC methods that should be implemented for use with DestinationServer. + #[async_trait] + pub trait Destination: Send + Sync + 'static { + ///Server streaming response type for the Get method. + type GetStream: futures_core::Stream> + + Send + + 'static; + /// Given a destination, return all addresses in that destination as a long- + /// running stream of updates. + async fn get( + &self, + request: tonic::Request, + ) -> Result, tonic::Status>; + ///Server streaming response type for the GetProfile method. + type GetProfileStream: futures_core::Stream< + Item = Result, + > + + Send + + 'static; + /// Given a destination, return that destination's profile and send an update + /// whenever it changes. + async fn get_profile( + &self, + request: tonic::Request, + ) -> Result, tonic::Status>; + } + #[derive(Debug)] + pub struct DestinationServer { + inner: _Inner, + accept_compression_encodings: (), + send_compression_encodings: (), + } + struct _Inner(Arc); + impl DestinationServer { + pub fn new(inner: T) -> Self { + Self::from_arc(Arc::new(inner)) + } + pub fn from_arc(inner: Arc) -> Self { + let inner = _Inner(inner); + Self { + inner, + accept_compression_encodings: Default::default(), + send_compression_encodings: Default::default(), + } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> InterceptedService + where + F: tonic::service::Interceptor, + { + InterceptedService::new(Self::new(inner), interceptor) + } + } + impl tonic::codegen::Service> for DestinationServer + where + T: Destination, + B: Body + Send + 'static, + B::Error: Into + Send + 'static, + { + type Response = http::Response; + type Error = std::convert::Infallible; + type Future = BoxFuture; + fn poll_ready( + &mut self, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + fn call(&mut self, req: http::Request) -> Self::Future { + let inner = self.inner.clone(); + match req.uri().path() { + "/io.linkerd.proxy.destination.Destination/Get" => { + #[allow(non_camel_case_types)] + struct GetSvc(pub Arc); + impl< + T: Destination, + > tonic::server::ServerStreamingService + for GetSvc { + type Response = super::Update; + type ResponseStream = T::GetStream; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = self.0.clone(); + let fut = async move { (*inner).get(request).await }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = GetSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ); + let res = grpc.server_streaming(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/io.linkerd.proxy.destination.Destination/GetProfile" => { + #[allow(non_camel_case_types)] + struct GetProfileSvc(pub Arc); + impl< + T: Destination, + > tonic::server::ServerStreamingService + for GetProfileSvc { + type Response = super::DestinationProfile; + type ResponseStream = T::GetProfileStream; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = self.0.clone(); + let fut = async move { (*inner).get_profile(request).await }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = GetProfileSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ); + let res = grpc.server_streaming(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + _ => { + Box::pin(async move { + Ok( + http::Response::builder() + .status(200) + .header("grpc-status", "12") + .header("content-type", "application/grpc") + .body(empty_body()) + .unwrap(), + ) + }) + } + } + } + } + impl Clone for DestinationServer { + fn clone(&self) -> Self { + let inner = self.inner.clone(); + Self { + inner, + accept_compression_encodings: self.accept_compression_encodings, + send_compression_encodings: self.send_compression_encodings, + } + } + } + impl Clone for _Inner { + fn clone(&self) -> Self { + Self(self.0.clone()) + } + } + impl std::fmt::Debug for _Inner { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.0) + } + } +} diff --git a/src/gen/io.linkerd.proxy.http_types.rs b/src/gen/io.linkerd.proxy.http_types.rs new file mode 100644 index 000000000..41a516d9c --- /dev/null +++ b/src/gen/io.linkerd.proxy.http_types.rs @@ -0,0 +1,64 @@ +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct HttpMethod { + #[prost(oneof="http_method::Type", tags="1, 2")] + pub r#type: ::core::option::Option, +} +/// Nested message and enum types in `HttpMethod`. +pub mod http_method { + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] + #[repr(i32)] + pub enum Registered { + Get = 0, + Post = 1, + Put = 2, + Delete = 3, + Patch = 4, + Options = 5, + Connect = 6, + Head = 7, + Trace = 8, + } + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Type { + #[prost(enumeration="Registered", tag="1")] + Registered(i32), + #[prost(string, tag="2")] + Unregistered(::prost::alloc::string::String), + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Scheme { + #[prost(oneof="scheme::Type", tags="1, 2")] + pub r#type: ::core::option::Option, +} +/// Nested message and enum types in `Scheme`. +pub mod scheme { + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] + #[repr(i32)] + pub enum Registered { + Http = 0, + Https = 1, + } + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Type { + #[prost(enumeration="Registered", tag="1")] + Registered(i32), + #[prost(string, tag="2")] + Unregistered(::prost::alloc::string::String), + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Headers { + #[prost(message, repeated, tag="1")] + pub headers: ::prost::alloc::vec::Vec, +} +/// Nested message and enum types in `Headers`. +pub mod headers { + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Header { + #[prost(string, tag="1")] + pub name: ::prost::alloc::string::String, + #[prost(bytes="vec", tag="2")] + pub value: ::prost::alloc::vec::Vec, + } +} diff --git a/src/gen/io.linkerd.proxy.identity.rs b/src/gen/io.linkerd.proxy.identity.rs new file mode 100644 index 000000000..0196bebc1 --- /dev/null +++ b/src/gen/io.linkerd.proxy.identity.rs @@ -0,0 +1,244 @@ +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct CertifyRequest { + #[prost(string, tag="1")] + pub identity: ::prost::alloc::string::String, + /// Proof of the requester's identity. + /// + /// In Kubernetes, for instance, this is the contents of a service account + /// token. + #[prost(bytes="vec", tag="2")] + pub token: ::prost::alloc::vec::Vec, + /// A PEM-encoded x509 Certificate Signing Request. + #[prost(bytes="vec", tag="3")] + pub certificate_signing_request: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct CertifyResponse { + /// A PEM-encoded x509 Certificate. + #[prost(bytes="vec", tag="1")] + pub leaf_certificate: ::prost::alloc::vec::Vec, + /// A list of PEM-encoded x509 Certificates that establish the trust chain + /// between the leaf_certificate and the well-known trust anchors. + #[prost(bytes="vec", repeated, tag="2")] + pub intermediate_certificates: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec>, + #[prost(message, optional, tag="3")] + pub valid_until: ::core::option::Option<::prost_types::Timestamp>, +} +/// Generated client implementations. +pub mod identity_client { + #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] + use tonic::codegen::*; + #[derive(Debug, Clone)] + pub struct IdentityClient { + inner: tonic::client::Grpc, + } + impl IdentityClient + where + T: tonic::client::GrpcService, + T::Error: Into, + T::ResponseBody: Default + Body + Send + 'static, + ::Error: Into + Send, + { + pub fn new(inner: T) -> Self { + let inner = tonic::client::Grpc::new(inner); + Self { inner } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> IdentityClient> + where + F: tonic::service::Interceptor, + T: tonic::codegen::Service< + http::Request, + Response = http::Response< + >::ResponseBody, + >, + >, + , + >>::Error: Into + Send + Sync, + { + IdentityClient::new(InterceptedService::new(inner, interceptor)) + } + /// Compress requests with `gzip`. + /// + /// This requires the server to support it otherwise it might respond with an + /// error. + #[must_use] + pub fn send_gzip(mut self) -> Self { + self.inner = self.inner.send_gzip(); + self + } + /// Enable decompressing responses with `gzip`. + #[must_use] + pub fn accept_gzip(mut self) -> Self { + self.inner = self.inner.accept_gzip(); + self + } + /// Requests that a time-bounded certificate be signed. + /// + /// The requester must provide a token that verifies the client's identity and + /// a Certificate Signing Request that adheres to the service naming rules. + /// + /// Errors are returned when the provided request is invalid or when + /// authentication cannot be performed. + pub async fn certify( + &mut self, + request: impl tonic::IntoRequest, + ) -> Result, tonic::Status> { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/io.linkerd.proxy.identity.Identity/Certify", + ); + self.inner.unary(request.into_request(), path, codec).await + } + } +} +/// Generated server implementations. +pub mod identity_server { + #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] + use tonic::codegen::*; + ///Generated trait containing gRPC methods that should be implemented for use with IdentityServer. + #[async_trait] + pub trait Identity: Send + Sync + 'static { + /// Requests that a time-bounded certificate be signed. + /// + /// The requester must provide a token that verifies the client's identity and + /// a Certificate Signing Request that adheres to the service naming rules. + /// + /// Errors are returned when the provided request is invalid or when + /// authentication cannot be performed. + async fn certify( + &self, + request: tonic::Request, + ) -> Result, tonic::Status>; + } + #[derive(Debug)] + pub struct IdentityServer { + inner: _Inner, + accept_compression_encodings: (), + send_compression_encodings: (), + } + struct _Inner(Arc); + impl IdentityServer { + pub fn new(inner: T) -> Self { + Self::from_arc(Arc::new(inner)) + } + pub fn from_arc(inner: Arc) -> Self { + let inner = _Inner(inner); + Self { + inner, + accept_compression_encodings: Default::default(), + send_compression_encodings: Default::default(), + } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> InterceptedService + where + F: tonic::service::Interceptor, + { + InterceptedService::new(Self::new(inner), interceptor) + } + } + impl tonic::codegen::Service> for IdentityServer + where + T: Identity, + B: Body + Send + 'static, + B::Error: Into + Send + 'static, + { + type Response = http::Response; + type Error = std::convert::Infallible; + type Future = BoxFuture; + fn poll_ready( + &mut self, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + fn call(&mut self, req: http::Request) -> Self::Future { + let inner = self.inner.clone(); + match req.uri().path() { + "/io.linkerd.proxy.identity.Identity/Certify" => { + #[allow(non_camel_case_types)] + struct CertifySvc(pub Arc); + impl tonic::server::UnaryService + for CertifySvc { + type Response = super::CertifyResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = self.0.clone(); + let fut = async move { (*inner).certify(request).await }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = CertifySvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + _ => { + Box::pin(async move { + Ok( + http::Response::builder() + .status(200) + .header("grpc-status", "12") + .header("content-type", "application/grpc") + .body(empty_body()) + .unwrap(), + ) + }) + } + } + } + } + impl Clone for IdentityServer { + fn clone(&self) -> Self { + let inner = self.inner.clone(); + Self { + inner, + accept_compression_encodings: self.accept_compression_encodings, + send_compression_encodings: self.send_compression_encodings, + } + } + } + impl Clone for _Inner { + fn clone(&self) -> Self { + Self(self.0.clone()) + } + } + impl std::fmt::Debug for _Inner { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.0) + } + } +} diff --git a/src/gen/io.linkerd.proxy.inbound.rs b/src/gen/io.linkerd.proxy.inbound.rs new file mode 100644 index 000000000..693ab765f --- /dev/null +++ b/src/gen/io.linkerd.proxy.inbound.rs @@ -0,0 +1,468 @@ +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PortSpec { + /// Identifies a proxy workload (e.g., pod name). + #[prost(string, tag="1")] + pub workload: ::prost::alloc::string::String, + /// An inbound port on _workload_. + #[prost(uint32, tag="2")] + pub port: u32, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Server { + /// If set, indicates how the proxy should proxy connections on the specified + /// port. + #[prost(message, optional, tag="1")] + pub protocol: ::core::option::Option, + /// Indicates the IP addresses on which the proxy may receive connections. + /// Connections targetting other IP addresses will be dropped. + #[prost(message, repeated, tag="2")] + pub server_ips: ::prost::alloc::vec::Vec, + /// Configures a proxy to allow connections from the specified clients. + /// + /// If unset, no connections are permitted. + #[prost(message, repeated, tag="3")] + pub authorizations: ::prost::alloc::vec::Vec, + /// Descriptive labels to be added to metrics, etc. + /// + /// A control plane SHOULD return the same keys in all policies. That is, we do + /// NOT want to return arbitrary pod labels in this field. + #[prost(map="string, string", tag="4")] + pub labels: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ProxyProtocol { + #[prost(oneof="proxy_protocol::Kind", tags="1, 2, 3, 4, 5, 6")] + pub kind: ::core::option::Option, +} +/// Nested message and enum types in `ProxyProtocol`. +pub mod proxy_protocol { + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Detect { + #[prost(message, optional, tag="1")] + pub timeout: ::core::option::Option<::prost_types::Duration>, + } + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Http1 { + /// Disables the setting of informational headers on this server. + #[prost(bool, tag="1")] + pub disable_informational_headers: bool, + } + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Http2 { + /// Disables the setting of informational headers on this server. + #[prost(bool, tag="1")] + pub disable_informational_headers: bool, + } + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Grpc { + /// Disables the setting of informational headers on this server. + #[prost(bool, tag="1")] + pub disable_informational_headers: bool, + } + /// TODO: opaque TLS settings (versions, algorithms, SNI) + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Opaque { + } + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Tls { + } + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Kind { + #[prost(message, tag="1")] + Detect(Detect), + #[prost(message, tag="2")] + Opaque(Opaque), + #[prost(message, tag="3")] + Tls(Tls), + #[prost(message, tag="4")] + Http1(Http1), + #[prost(message, tag="5")] + Http2(Http2), + #[prost(message, tag="6")] + Grpc(Grpc), + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Authz { + /// Limits this authorization to client addresses in the provided networks. + /// + /// Must have at least one network, otherwise the authorization must be + /// ignored. An authorization matches all clients by including an explicit + /// match on, i.e., `[0.0.0.0/0, 0::/0]``. + #[prost(message, repeated, tag="1")] + pub networks: ::prost::alloc::vec::Vec, + /// Must be set. + #[prost(message, optional, tag="2")] + pub authentication: ::core::option::Option, + /// Descriptive labels to be added to metrics, etc. + /// + /// A control plane SHOULD return the same keys in all authorizations. That is, + /// we do NOT want to return arbitrary pod labels in this field. + #[prost(map="string, string", tag="3")] + pub labels: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, +} +/// Describes a network of authorized clients. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Network { + #[prost(message, optional, tag="1")] + pub net: ::core::option::Option, + #[prost(message, repeated, tag="2")] + pub except: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Authn { + #[prost(oneof="authn::Permit", tags="1, 2")] + pub permit: ::core::option::Option, +} +/// Nested message and enum types in `Authn`. +pub mod authn { + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct PermitUnauthenticated { + } + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct PermitMeshTls { + #[prost(oneof="permit_mesh_tls::Clients", tags="1, 2")] + pub clients: ::core::option::Option, + } + /// Nested message and enum types in `PermitMeshTLS`. + pub mod permit_mesh_tls { + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct PermitClientIdentities { + /// A list of literal identities. + #[prost(message, repeated, tag="1")] + pub identities: ::prost::alloc::vec::Vec, + /// A list of identity suffixes. + /// + /// If this contains an empty suffix, all identities are matched. + #[prost(message, repeated, tag="2")] + pub suffixes: ::prost::alloc::vec::Vec, + } + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Clients { + /// Indicates that client identities are not required. + #[prost(message, tag="1")] + Unauthenticated(super::PermitUnauthenticated), + /// Indicates that mutually-authenticated connections are permitted from + /// clients with matching identities. + #[prost(message, tag="2")] + Identities(PermitClientIdentities), + } + } + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Permit { + #[prost(message, tag="1")] + Unauthenticated(PermitUnauthenticated), + /// If set, requires that the connection is transported over mesh TLS. + #[prost(message, tag="2")] + MeshTls(PermitMeshTls), + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Identity { + #[prost(string, tag="1")] + pub name: ::prost::alloc::string::String, +} +/// Encodes a DNS-like name suffix as sequence of parts. +/// +/// An empty list is equivalent to `.` (matching all names); the list `["foo", +/// "bar"]` is equivalent to "foo.bar." (matching `*.foo.bar`), etc. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct IdentitySuffix { + #[prost(string, repeated, tag="1")] + pub parts: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, +} +/// Generated client implementations. +pub mod inbound_server_policies_client { + #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] + use tonic::codegen::*; + //// An API exposed to the linkerd2-proxy to configure the inbound proxy with per-port configuration + //// + //// Proxies are expected to watch policies for each known port. As policies change, proxies update + //// their behavior for newly accepted connections. + //// + //// The unary `GetPort` endpoint is exposed as a convenience for clients to query policies for + //// diagnostic purposes. + #[derive(Debug, Clone)] + pub struct InboundServerPoliciesClient { + inner: tonic::client::Grpc, + } + impl InboundServerPoliciesClient + where + T: tonic::client::GrpcService, + T::Error: Into, + T::ResponseBody: Default + Body + Send + 'static, + ::Error: Into + Send, + { + pub fn new(inner: T) -> Self { + let inner = tonic::client::Grpc::new(inner); + Self { inner } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> InboundServerPoliciesClient> + where + F: tonic::service::Interceptor, + T: tonic::codegen::Service< + http::Request, + Response = http::Response< + >::ResponseBody, + >, + >, + , + >>::Error: Into + Send + Sync, + { + InboundServerPoliciesClient::new(InterceptedService::new(inner, interceptor)) + } + /// Compress requests with `gzip`. + /// + /// This requires the server to support it otherwise it might respond with an + /// error. + #[must_use] + pub fn send_gzip(mut self) -> Self { + self.inner = self.inner.send_gzip(); + self + } + /// Enable decompressing responses with `gzip`. + #[must_use] + pub fn accept_gzip(mut self) -> Self { + self.inner = self.inner.accept_gzip(); + self + } + //// Gets the inbound server policy for a given workload port. + pub async fn get_port( + &mut self, + request: impl tonic::IntoRequest, + ) -> Result, tonic::Status> { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/io.linkerd.proxy.inbound.InboundServerPolicies/GetPort", + ); + self.inner.unary(request.into_request(), path, codec).await + } + //// Watches the inbound server policy for a given workload port. + pub async fn watch_port( + &mut self, + request: impl tonic::IntoRequest, + ) -> Result< + tonic::Response>, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/io.linkerd.proxy.inbound.InboundServerPolicies/WatchPort", + ); + self.inner.server_streaming(request.into_request(), path, codec).await + } + } +} +/// Generated server implementations. +pub mod inbound_server_policies_server { + #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] + use tonic::codegen::*; + ///Generated trait containing gRPC methods that should be implemented for use with InboundServerPoliciesServer. + #[async_trait] + pub trait InboundServerPolicies: Send + Sync + 'static { + //// Gets the inbound server policy for a given workload port. + async fn get_port( + &self, + request: tonic::Request, + ) -> Result, tonic::Status>; + ///Server streaming response type for the WatchPort method. + type WatchPortStream: futures_core::Stream< + Item = Result, + > + + Send + + 'static; + //// Watches the inbound server policy for a given workload port. + async fn watch_port( + &self, + request: tonic::Request, + ) -> Result, tonic::Status>; + } + //// An API exposed to the linkerd2-proxy to configure the inbound proxy with per-port configuration + //// + //// Proxies are expected to watch policies for each known port. As policies change, proxies update + //// their behavior for newly accepted connections. + //// + //// The unary `GetPort` endpoint is exposed as a convenience for clients to query policies for + //// diagnostic purposes. + #[derive(Debug)] + pub struct InboundServerPoliciesServer { + inner: _Inner, + accept_compression_encodings: (), + send_compression_encodings: (), + } + struct _Inner(Arc); + impl InboundServerPoliciesServer { + pub fn new(inner: T) -> Self { + Self::from_arc(Arc::new(inner)) + } + pub fn from_arc(inner: Arc) -> Self { + let inner = _Inner(inner); + Self { + inner, + accept_compression_encodings: Default::default(), + send_compression_encodings: Default::default(), + } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> InterceptedService + where + F: tonic::service::Interceptor, + { + InterceptedService::new(Self::new(inner), interceptor) + } + } + impl tonic::codegen::Service> + for InboundServerPoliciesServer + where + T: InboundServerPolicies, + B: Body + Send + 'static, + B::Error: Into + Send + 'static, + { + type Response = http::Response; + type Error = std::convert::Infallible; + type Future = BoxFuture; + fn poll_ready( + &mut self, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + fn call(&mut self, req: http::Request) -> Self::Future { + let inner = self.inner.clone(); + match req.uri().path() { + "/io.linkerd.proxy.inbound.InboundServerPolicies/GetPort" => { + #[allow(non_camel_case_types)] + struct GetPortSvc(pub Arc); + impl< + T: InboundServerPolicies, + > tonic::server::UnaryService for GetPortSvc { + type Response = super::Server; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = self.0.clone(); + let fut = async move { (*inner).get_port(request).await }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = GetPortSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/io.linkerd.proxy.inbound.InboundServerPolicies/WatchPort" => { + #[allow(non_camel_case_types)] + struct WatchPortSvc(pub Arc); + impl< + T: InboundServerPolicies, + > tonic::server::ServerStreamingService + for WatchPortSvc { + type Response = super::Server; + type ResponseStream = T::WatchPortStream; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = self.0.clone(); + let fut = async move { (*inner).watch_port(request).await }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = WatchPortSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ); + let res = grpc.server_streaming(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + _ => { + Box::pin(async move { + Ok( + http::Response::builder() + .status(200) + .header("grpc-status", "12") + .header("content-type", "application/grpc") + .body(empty_body()) + .unwrap(), + ) + }) + } + } + } + } + impl Clone for InboundServerPoliciesServer { + fn clone(&self) -> Self { + let inner = self.inner.clone(); + Self { + inner, + accept_compression_encodings: self.accept_compression_encodings, + send_compression_encodings: self.send_compression_encodings, + } + } + } + impl Clone for _Inner { + fn clone(&self) -> Self { + Self(self.0.clone()) + } + } + impl std::fmt::Debug for _Inner { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.0) + } + } +} diff --git a/src/gen/io.linkerd.proxy.net.rs b/src/gen/io.linkerd.proxy.net.rs new file mode 100644 index 000000000..6bb38962c --- /dev/null +++ b/src/gen/io.linkerd.proxy.net.rs @@ -0,0 +1,38 @@ +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct IpAddress { + #[prost(oneof="ip_address::Ip", tags="1, 2")] + pub ip: ::core::option::Option, +} +/// Nested message and enum types in `IPAddress`. +pub mod ip_address { + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Ip { + #[prost(fixed32, tag="1")] + Ipv4(u32), + #[prost(message, tag="2")] + Ipv6(super::IPv6), + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct IpNetwork { + #[prost(message, optional, tag="1")] + pub ip: ::core::option::Option, + #[prost(uint32, tag="2")] + pub prefix_len: u32, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct IPv6 { + /// hextets 1-4 + #[prost(fixed64, tag="1")] + pub first: u64, + /// hextets 5-8 + #[prost(fixed64, tag="2")] + pub last: u64, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TcpAddress { + #[prost(message, optional, tag="1")] + pub ip: ::core::option::Option, + #[prost(uint32, tag="2")] + pub port: u32, +} diff --git a/src/gen/io.linkerd.proxy.tap.rs b/src/gen/io.linkerd.proxy.tap.rs new file mode 100644 index 000000000..6a375a260 --- /dev/null +++ b/src/gen/io.linkerd.proxy.tap.rs @@ -0,0 +1,494 @@ +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ObserveRequest { + /// Limits the number of event keys that will be returned by this tap. + #[prost(uint32, tag="1")] + pub limit: u32, + /// Encodes request-matching logic. + #[prost(message, optional, tag="2")] + pub r#match: ::core::option::Option, + /// Conditionally extracts components from requests and responses to include + /// in tap events + #[prost(message, optional, tag="3")] + pub extract: ::core::option::Option, +} +/// Nested message and enum types in `ObserveRequest`. +pub mod observe_request { + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Match { + #[prost(oneof="r#match::Match", tags="1, 2, 3, 4, 5, 6, 7, 8")] + pub r#match: ::core::option::Option, + } + /// Nested message and enum types in `Match`. + pub mod r#match { + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Seq { + #[prost(message, repeated, tag="1")] + pub matches: ::prost::alloc::vec::Vec, + } + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Label { + #[prost(string, tag="1")] + pub key: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub value: ::prost::alloc::string::String, + } + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Tcp { + #[prost(oneof="tcp::Match", tags="1, 3")] + pub r#match: ::core::option::Option, + } + /// Nested message and enum types in `Tcp`. + pub mod tcp { + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Netmask { + #[prost(message, optional, tag="1")] + pub ip: ::core::option::Option, + #[prost(uint32, tag="2")] + pub mask: u32, + } + /// If either a minimum or maximum is not specified, the range is + /// considered to be over a discrete value. + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct PortRange { + /// Minimum matching port value (inclusive), if specified. + #[prost(uint32, tag="1")] + pub min: u32, + /// Maximum matching port value (inclusive), if specified. + #[prost(uint32, tag="2")] + pub max: u32, + } + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Match { + #[prost(message, tag="1")] + Netmask(Netmask), + #[prost(message, tag="3")] + Ports(PortRange), + } + } + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Http { + #[prost(oneof="http::Match", tags="1, 3, 2, 4")] + pub r#match: ::core::option::Option, + } + /// Nested message and enum types in `Http`. + pub mod http { + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct StringMatch { + #[prost(oneof="string_match::Match", tags="1, 2")] + pub r#match: ::core::option::Option, + } + /// Nested message and enum types in `StringMatch`. + pub mod string_match { + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Match { + #[prost(string, tag="1")] + Exact(::prost::alloc::string::String), + #[prost(string, tag="2")] + Prefix(::prost::alloc::string::String), + } + } + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Match { + #[prost(message, tag="1")] + Scheme(super::super::super::super::http_types::Scheme), + #[prost(message, tag="3")] + Method(super::super::super::super::http_types::HttpMethod), + #[prost(message, tag="2")] + Authority(StringMatch), + /// TODO Header header = 4; + #[prost(message, tag="4")] + Path(StringMatch), + } + } + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Match { + #[prost(message, tag="1")] + All(Seq), + #[prost(message, tag="2")] + Any(Seq), + #[prost(message, tag="3")] + Not(::prost::alloc::boxed::Box), + #[prost(message, tag="4")] + Source(Tcp), + #[prost(message, tag="5")] + Destination(Tcp), + #[prost(message, tag="6")] + Http(Http), + #[prost(message, tag="7")] + DestinationLabel(Label), + #[prost(message, tag="8")] + RouteLabel(Label), + } + } + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Extract { + #[prost(oneof="extract::Extract", tags="1")] + pub extract: ::core::option::Option, + } + /// Nested message and enum types in `Extract`. + pub mod extract { + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Http { + #[prost(oneof="http::Extract", tags="1")] + pub extract: ::core::option::Option, + } + /// Nested message and enum types in `Http`. + pub mod http { + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Headers { + } + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Extract { + #[prost(message, tag="1")] + Headers(Headers), + } + } + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Extract { + #[prost(message, tag="1")] + Http(Http), + } + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Eos { + #[prost(oneof="eos::End", tags="1, 2")] + pub end: ::core::option::Option, +} +/// Nested message and enum types in `Eos`. +pub mod eos { + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum End { + #[prost(uint32, tag="1")] + GrpcStatusCode(u32), + #[prost(uint32, tag="2")] + ResetErrorCode(u32), + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TapEvent { + #[prost(message, optional, tag="1")] + pub source: ::core::option::Option, + #[prost(message, optional, tag="5")] + pub source_meta: ::core::option::Option, + #[prost(message, optional, tag="7")] + pub route_meta: ::core::option::Option, + #[prost(message, optional, tag="2")] + pub destination: ::core::option::Option, + #[prost(message, optional, tag="4")] + pub destination_meta: ::core::option::Option, + #[prost(enumeration="tap_event::ProxyDirection", tag="6")] + pub proxy_direction: i32, + #[prost(oneof="tap_event::Event", tags="3")] + pub event: ::core::option::Option, +} +/// Nested message and enum types in `TapEvent`. +pub mod tap_event { + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct EndpointMeta { + #[prost(map="string, string", tag="1")] + pub labels: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + } + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct RouteMeta { + #[prost(map="string, string", tag="1")] + pub labels: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, + } + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Http { + #[prost(oneof="http::Event", tags="1, 2, 3")] + pub event: ::core::option::Option, + } + /// Nested message and enum types in `Http`. + pub mod http { + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct StreamId { + /// A randomized base (stable across a process's runtime) + #[prost(uint32, tag="1")] + pub base: u32, + /// A stream id unique within the lifetime of `base`. + #[prost(uint64, tag="2")] + pub stream: u64, + } + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct RequestInit { + #[prost(message, optional, tag="1")] + pub id: ::core::option::Option, + #[prost(message, optional, tag="2")] + pub method: ::core::option::Option, + #[prost(message, optional, tag="3")] + pub scheme: ::core::option::Option, + #[prost(string, tag="4")] + pub authority: ::prost::alloc::string::String, + #[prost(string, tag="5")] + pub path: ::prost::alloc::string::String, + #[prost(message, optional, tag="6")] + pub headers: ::core::option::Option, + } + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct ResponseInit { + #[prost(message, optional, tag="1")] + pub id: ::core::option::Option, + #[prost(message, optional, tag="2")] + pub since_request_init: ::core::option::Option<::prost_types::Duration>, + #[prost(uint32, tag="3")] + pub http_status: u32, + #[prost(message, optional, tag="4")] + pub headers: ::core::option::Option, + } + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct ResponseEnd { + #[prost(message, optional, tag="1")] + pub id: ::core::option::Option, + #[prost(message, optional, tag="2")] + pub since_request_init: ::core::option::Option<::prost_types::Duration>, + #[prost(message, optional, tag="3")] + pub since_response_init: ::core::option::Option<::prost_types::Duration>, + #[prost(uint64, tag="4")] + pub response_bytes: u64, + #[prost(message, optional, tag="5")] + pub eos: ::core::option::Option, + #[prost(message, optional, tag="6")] + pub trailers: ::core::option::Option, + } + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Event { + #[prost(message, tag="1")] + RequestInit(RequestInit), + #[prost(message, tag="2")] + ResponseInit(ResponseInit), + #[prost(message, tag="3")] + ResponseEnd(ResponseEnd), + } + } + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] + #[repr(i32)] + pub enum ProxyDirection { + Unknown = 0, + Inbound = 1, + Outbound = 2, + } + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Event { + #[prost(message, tag="3")] + Http(Http), + } +} +/// Generated client implementations. +pub mod tap_client { + #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] + use tonic::codegen::*; + /// A service exposed by proxy instances to setup + #[derive(Debug, Clone)] + pub struct TapClient { + inner: tonic::client::Grpc, + } + impl TapClient + where + T: tonic::client::GrpcService, + T::Error: Into, + T::ResponseBody: Default + Body + Send + 'static, + ::Error: Into + Send, + { + pub fn new(inner: T) -> Self { + let inner = tonic::client::Grpc::new(inner); + Self { inner } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> TapClient> + where + F: tonic::service::Interceptor, + T: tonic::codegen::Service< + http::Request, + Response = http::Response< + >::ResponseBody, + >, + >, + , + >>::Error: Into + Send + Sync, + { + TapClient::new(InterceptedService::new(inner, interceptor)) + } + /// Compress requests with `gzip`. + /// + /// This requires the server to support it otherwise it might respond with an + /// error. + #[must_use] + pub fn send_gzip(mut self) -> Self { + self.inner = self.inner.send_gzip(); + self + } + /// Enable decompressing responses with `gzip`. + #[must_use] + pub fn accept_gzip(mut self) -> Self { + self.inner = self.inner.accept_gzip(); + self + } + pub async fn observe( + &mut self, + request: impl tonic::IntoRequest, + ) -> Result< + tonic::Response>, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/io.linkerd.proxy.tap.Tap/Observe", + ); + self.inner.server_streaming(request.into_request(), path, codec).await + } + } +} +/// Generated server implementations. +pub mod tap_server { + #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] + use tonic::codegen::*; + ///Generated trait containing gRPC methods that should be implemented for use with TapServer. + #[async_trait] + pub trait Tap: Send + Sync + 'static { + ///Server streaming response type for the Observe method. + type ObserveStream: futures_core::Stream< + Item = Result, + > + + Send + + 'static; + async fn observe( + &self, + request: tonic::Request, + ) -> Result, tonic::Status>; + } + /// A service exposed by proxy instances to setup + #[derive(Debug)] + pub struct TapServer { + inner: _Inner, + accept_compression_encodings: (), + send_compression_encodings: (), + } + struct _Inner(Arc); + impl TapServer { + pub fn new(inner: T) -> Self { + Self::from_arc(Arc::new(inner)) + } + pub fn from_arc(inner: Arc) -> Self { + let inner = _Inner(inner); + Self { + inner, + accept_compression_encodings: Default::default(), + send_compression_encodings: Default::default(), + } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> InterceptedService + where + F: tonic::service::Interceptor, + { + InterceptedService::new(Self::new(inner), interceptor) + } + } + impl tonic::codegen::Service> for TapServer + where + T: Tap, + B: Body + Send + 'static, + B::Error: Into + Send + 'static, + { + type Response = http::Response; + type Error = std::convert::Infallible; + type Future = BoxFuture; + fn poll_ready( + &mut self, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + fn call(&mut self, req: http::Request) -> Self::Future { + let inner = self.inner.clone(); + match req.uri().path() { + "/io.linkerd.proxy.tap.Tap/Observe" => { + #[allow(non_camel_case_types)] + struct ObserveSvc(pub Arc); + impl< + T: Tap, + > tonic::server::ServerStreamingService + for ObserveSvc { + type Response = super::TapEvent; + type ResponseStream = T::ObserveStream; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = self.0.clone(); + let fut = async move { (*inner).observe(request).await }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = ObserveSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ); + let res = grpc.server_streaming(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + _ => { + Box::pin(async move { + Ok( + http::Response::builder() + .status(200) + .header("grpc-status", "12") + .header("content-type", "application/grpc") + .body(empty_body()) + .unwrap(), + ) + }) + } + } + } + } + impl Clone for TapServer { + fn clone(&self) -> Self { + let inner = self.inner.clone(); + Self { + inner, + accept_compression_encodings: self.accept_compression_encodings, + send_compression_encodings: self.send_compression_encodings, + } + } + } + impl Clone for _Inner { + fn clone(&self) -> Self { + Self(self.0.clone()) + } + } + impl std::fmt::Debug for _Inner { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.0) + } + } +} diff --git a/src/gen/http_types.rs b/src/http_types.rs similarity index 98% rename from src/gen/http_types.rs rename to src/http_types.rs index e0723506c..cdfdcc8a4 100644 --- a/src/gen/http_types.rs +++ b/src/http_types.rs @@ -4,7 +4,7 @@ use std::{ }; use thiserror::Error; -tonic::include_proto!("io.linkerd.proxy.http_types"); +include!("gen/io.linkerd.proxy.http_types.rs"); /// Indicates an HTTP Method could not be decoded. #[derive(Clone, Debug, Error)] diff --git a/src/lib.rs b/src/lib.rs index 8fb517fb5..394122e69 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,26 @@ #![deny(warnings, rust_2018_idioms)] #![forbid(unsafe_code)] -mod gen; +#[cfg(feature = "net")] +pub mod net; -pub use self::gen::*; +#[cfg(feature = "http_types")] +pub mod http_types; + +#[cfg(feature = "destination")] +pub mod destination { + include!("gen/io.linkerd.proxy.destination.rs"); +} + +#[cfg(feature = "identity")] +pub mod identity { + include!("gen/io.linkerd.proxy.identity.rs"); +} + +#[cfg(feature = "inbound")] +pub mod inbound { + include!("gen/io.linkerd.proxy.inbound.rs"); +} + +#[cfg(feature = "tap")] +pub mod tap; diff --git a/src/gen/net.rs b/src/net.rs similarity index 99% rename from src/gen/net.rs rename to src/net.rs index eb3a2f6d1..0ebb70c54 100644 --- a/src/gen/net.rs +++ b/src/net.rs @@ -1,7 +1,7 @@ use std::convert::{TryFrom, TryInto}; use thiserror::Error; -tonic::include_proto!("io.linkerd.proxy.net"); +include!("gen/io.linkerd.proxy.net.rs"); /// Indicates an IP address could not be decoded. #[derive(Clone, Debug, Error)] diff --git a/src/gen/tap.rs b/src/tap.rs similarity index 98% rename from src/gen/tap.rs rename to src/tap.rs index 924cf1d15..358493ebe 100644 --- a/src/gen/tap.rs +++ b/src/tap.rs @@ -1,4 +1,4 @@ -tonic::include_proto!("io.linkerd.proxy.tap"); +include!("gen/io.linkerd.proxy.tap.rs"); // === impl Eos === @@ -19,7 +19,7 @@ impl Eos { #[cfg(feature = "arbitrary")] mod arbitary { use super::*; - use crate::gen::{http_types::*, net::*}; + use crate::{http_types::*, net::*}; use quickcheck::*; impl Arbitrary for ObserveRequest { diff --git a/tests/bootstrap.rs b/tests/bootstrap.rs new file mode 100644 index 000000000..96f8dc53c --- /dev/null +++ b/tests/bootstrap.rs @@ -0,0 +1,51 @@ +//! A test that regenerates the Rust protobuf bindings. +//! +//! It can be run via: +//! +//! ```no_run +//! cargo test --test=bootstrap +//! ``` + +/// Generates protobuf bindings into src/gen and fails if the generated files do +/// not match those that are already checked into git +#[test] +fn bootstrap() { + let out_dir = std::path::PathBuf::from(std::env!("CARGO_MANIFEST_DIR")) + .join("src") + .join("gen"); + generate(&*out_dir); + if changed(&*out_dir) { + panic!("protobuf interfaces do not match generated sources"); + } +} + +/// Generates protobuf bindings into the given directory +fn generate(out_dir: &std::path::Path) { + let iface_files = &[ + "proto/destination.proto", + "proto/http_types.proto", + "proto/identity.proto", + "proto/inbound.proto", + "proto/net.proto", + "proto/tap.proto", + ]; + tonic_build::configure() + .build_client(true) + .build_server(true) + .out_dir(out_dir) + .compile(iface_files, &["proto"]) + .expect("failed to compile protobuf"); +} + +/// Returns true if the given path contains files that have changed since the +/// last Git commit +fn changed(path: &std::path::Path) -> bool { + let status = std::process::Command::new("git") + .arg("diff") + .arg("--exit-code") + .arg("--") + .arg(path) + .status() + .expect("failed to run git"); + !status.success() +}