From 44362a252ffb6d179e324b66eb545c81d8b192ea Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Fri, 8 Apr 2022 20:47:46 +0000 Subject: [PATCH] Update to linkerd2-proxy-api v0.5 and tonic v0.7 With tonic v0.7, we now have the ability to provide a fixed PROTOC binary (rather than building/fetching a protoc implementation at build-time). This change updates the `linkerd-transport-header` and `opencensus-proto` crates to use statically-generated sources, eliminating the need for a `protoc` binary at build-time. Each crate includes a `bootstrap` test that fails if the generated sources differ from what is checked into git. These tests can also be used to regenerate sources when the protobuf (or tonic generation) changes. A local `install-protoc` action is added that fetches a protoc binary and configures the `PROTOC_NO_VENDOR` and `PROTOC` environment variables. This action is used by the check-all, check-each, and test workflows. Signed-off-by: Oliver Gould --- .devcontainer/Dockerfile | 13 + .devcontainer/devcontainer.json | 2 +- .gitattributes | 2 + .github/actions/install-protoc/action.yml | 33 ++ .github/workflows/check-all.yml | 1 + .github/workflows/check-each.yml | 5 +- .github/workflows/test.yml | 1 + Cargo.lock | 101 +--- deny.toml | 3 - linkerd/app/Cargo.toml | 2 +- linkerd/app/core/Cargo.toml | 2 +- linkerd/app/inbound/Cargo.toml | 8 +- linkerd/app/inbound/src/policy/discover.rs | 6 +- linkerd/app/inbound/src/policy/store.rs | 3 +- linkerd/app/integration/Cargo.toml | 25 +- linkerd/opencensus/Cargo.toml | 2 +- linkerd/opencensus/src/lib.rs | 8 +- linkerd/proxy/api-resolve/Cargo.toml | 9 +- linkerd/proxy/api-resolve/src/resolve.rs | 6 +- linkerd/proxy/identity-client/Cargo.toml | 7 +- linkerd/proxy/identity-client/src/certify.rs | 6 +- linkerd/proxy/tap/Cargo.toml | 11 +- linkerd/service-profiles/Cargo.toml | 11 +- linkerd/service-profiles/src/client.rs | 11 +- linkerd/tonic-watch/Cargo.toml | 2 +- linkerd/transport-header/Cargo.toml | 6 +- linkerd/transport-header/build.rs | 13 - .../src/gen/transport.l5d.io.rs | 34 ++ linkerd/transport-header/src/lib.rs | 2 +- linkerd/transport-header/tests/bootstrap.rs | 41 ++ opencensus-proto/Cargo.toml | 16 +- opencensus-proto/build.rs | 14 - .../gen/opencensus.proto.agent.common.v1.rs | 71 +++ .../gen/opencensus.proto.agent.trace.v1.rs | 155 ++++++ .../src/gen/opencensus.proto.resource.v1.rs | 10 + .../src/gen/opencensus.proto.trace.v1.rs | 519 ++++++++++++++++++ opencensus-proto/src/lib.rs | 14 +- opencensus-proto/tests/bootstrap.rs | 50 ++ 38 files changed, 1042 insertions(+), 183 deletions(-) create mode 100644 .github/actions/install-protoc/action.yml delete mode 100644 linkerd/transport-header/build.rs create mode 100644 linkerd/transport-header/src/gen/transport.l5d.io.rs create mode 100644 linkerd/transport-header/tests/bootstrap.rs delete mode 100644 opencensus-proto/build.rs create mode 100644 opencensus-proto/src/gen/opencensus.proto.agent.common.v1.rs create mode 100644 opencensus-proto/src/gen/opencensus.proto.agent.trace.v1.rs create mode 100644 opencensus-proto/src/gen/opencensus.proto.resource.v1.rs create mode 100644 opencensus-proto/src/gen/opencensus.proto.trace.v1.rs create mode 100644 opencensus-proto/tests/bootstrap.rs diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index c4b001b158..34af9f2573 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -24,6 +24,15 @@ RUN rustup toolchain add nightly FROM nightly as fuzz RUN cargo +nightly install cargo-fuzz +FROM docker.io/rust:${RUST_VERSION}-bullseye as protoc +ARG PROTOC_VERSION=v3.20.0 +WORKDIR /tmp +RUN arch="$(uname -m)" ; \ + version="$PROTOC_VERSION" ; \ + curl --proto '=https' --tlsv1.3 -vsSfLo protoc.zip "https://github.com/google/protobuf/releases/download/$version/protoc-${version#v}-linux-$arch.zip" && \ + unzip protoc.zip bin/protoc && \ + chmod 755 bin/protoc + # # Main image # @@ -72,5 +81,9 @@ COPY --from=yq /usr/local/bin/yq /usr/local/bin/yq COPY --from=nightly /usr/local/rustup/toolchains/nightly-x86_64-unknown-linux-gnu /usr/local/rustup/toolchains/nightly-x86_64-unknown-linux-gnu COPY --from=fuzz /usr/local/cargo/bin/cargo-fuzz /usr/local/cargo/bin/cargo-fuzz +COPY --from=protoc /tmp/bin/protoc /usr/local/bin/protoc +ENV PROTOC_NO_VENDOR=1 +ENV PROTOC=/usr/local/bin/protoc + ENTRYPOINT ["/usr/local/share/docker-init.sh"] CMD ["sleep", "infinity"] diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 98f8edf55a..c0101b47c3 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,6 +1,6 @@ { "name": "linkerd2-proxy", - "image": "ghcr.io/linkerd/dev-proxy:v8", + "image": "ghcr.io/linkerd/dev-proxy:v9", // "dockerFile": "./Dockerfile", "extensions": [ "matklad.rust-analyzer", diff --git a/.gitattributes b/.gitattributes index 2af3ba96b4..97978789da 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,3 @@ Cargo.lock linguist-generated=false +linkerd/transport-header/src/gen/* linguist-generated=true +opencensus-proto/src/gen/* linguist-generated=true diff --git a/.github/actions/install-protoc/action.yml b/.github/actions/install-protoc/action.yml new file mode 100644 index 0000000000..9444d669ca --- /dev/null +++ b/.github/actions/install-protoc/action.yml @@ -0,0 +1,33 @@ +name: install-protoc + +description: Install protoc and set the `PROTOC` environment variablec + +inputs: + version: + required: true + description: "Protoc version" + default: "v3.20.0" + +runs: + using: composite + steps: + - name: Install protoc + shell: bash + run: | + os=linux + if [ "$(uname -s)" = Darwin ]; then + os=osx + fi + arch="$(uname -m)" + version="${{ inputs.version }}" + tmp=$(mktemp -d -t protoc.XXX) + curl --fail --silent --show-error --location \ + --proto '=https' --tlsv1.3 \ + --output "$tmp/protoc.zip" \ + "https://github.com/google/protobuf/releases/download/$version/protoc-${version#v}-$os-$arch.zip" + unzip $tmp/protoc.zip bin/protoc -d /usr/local + chmod 755 /usr/local/bin/protoc + ( echo "PROTOC_NO_VENDOR=1" + echo "PROTOC=/usr/local/bin/protoc" + ) >> $GITHUB_ENV + diff --git a/.github/workflows/check-all.yml b/.github/workflows/check-all.yml index 02302450bc..d1c1a74a5a 100644 --- a/.github/workflows/check-all.yml +++ b/.github/workflows/check-all.yml @@ -32,6 +32,7 @@ jobs: curl --proto =https --tlsv1.3 -vsSfLo /usr/local/bin/cargo-action-fmt "https://github.com/olix0r/cargo-action-fmt/releases/download/release%2F${CARGO_ACTION_FMT_VERSION}/cargo-action-fmt-x86_64-unknown-linux-gnu" chmod 755 /usr/local/bin/cargo-action-fmt - uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846 + - uses: ./.github/actions/install-protoc - run: cargo fetch - run: | cargo check --frozen \ diff --git a/.github/workflows/check-each.yml b/.github/workflows/check-each.yml index d576d1c350..c5cf2a0a2a 100644 --- a/.github/workflows/check-each.yml +++ b/.github/workflows/check-each.yml @@ -59,10 +59,13 @@ jobs: - run: | curl --proto =https --tlsv1.3 -vsSfLo /usr/local/bin/cargo-action-fmt "https://github.com/olix0r/cargo-action-fmt/releases/download/release%2F${CARGO_ACTION_FMT_VERSION}/cargo-action-fmt-x86_64-unknown-linux-gnu" chmod 755 /usr/local/bin/cargo-action-fmt - - name: install meshtls-boring build deps + - name: Install meshtls-boring build deps if: matrix.crate == 'linkerd-meshtls-boring' run: apt update && apt install -y clang cmake - uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846 + - name: Install protoc + if: matrix.crate == 'linkerd-transport-header' || matrix.crate == 'opencensus-proto' + uses: ./.github/actions/install-protoc - run: cargo fetch - run: cargo check -p ${{ matrix.crate }} --frozen --all-targets --message-format=json | cargo-action-fmt diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 325ebd60d7..329bea3e64 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -62,6 +62,7 @@ jobs: image: docker://rust:1.59.0-buster steps: - uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846 + - uses: ./.github/actions/install-protoc - run: | cargo test --all --no-run \ --exclude=linkerd-app \ diff --git a/Cargo.lock b/Cargo.lock index 52b9a05e82..75a95ef1e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -256,7 +256,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21cdad81446a7f7dc43f6a77409efeb9733d2fa65553efef6018ef257c959b73" dependencies = [ - "heck 0.4.0", + "heck", "proc-macro2", "quote", "syn", @@ -476,15 +476,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "heck" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "heck" version = "0.4.0" @@ -594,18 +585,6 @@ dependencies = [ "tower", ] -[[package]] -name = "hyper-timeout" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" -dependencies = [ - "hyper", - "pin-project-lite", - "tokio", - "tokio-io-timeout", -] - [[package]] name = "idna" version = "0.2.3" @@ -1693,9 +1672,9 @@ dependencies = [ [[package]] name = "linkerd2-proxy-api" -version = "0.3.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6aaf91178c272abeaac52b2472351e73affa723bfdd0c15e147e2f975f2fbe5" +checksum = "12c93aba8dbdc8f465de51ef08c5e1938790ea0ae7390d66b3f4d2d36c297d0b" dependencies = [ "h2", "http", @@ -1705,7 +1684,6 @@ dependencies = [ "quickcheck", "thiserror", "tonic", - "tonic-build", ] [[package]] @@ -1953,6 +1931,16 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +[[package]] +name = "prettyplease" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b83ec2d0af5c5c556257ff52c9f98934e243b9fd39604bfb2a9b75ec2e97f18" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.37" @@ -1976,9 +1964,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" +checksum = "1bd5316aa8f5c82add416dfbc25116b84b748a21153f512917e8143640a71bbd" dependencies = [ "bytes", "prost-derive", @@ -1986,12 +1974,14 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" +checksum = "328f9f29b82409216decb172d81e936415d21245befa79cd34c3f29d87d1c50b" dependencies = [ "bytes", - "heck 0.3.3", + "cfg-if", + "cmake", + "heck", "itertools", "lazy_static", "log", @@ -2006,9 +1996,9 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" +checksum = "df35198f0777b75e9ff669737c6da5136b59dba33cf5a010a6d1cc4d56defc6f" dependencies = [ "anyhow", "itertools", @@ -2019,9 +2009,9 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" +checksum = "926681c118ae6e512a3ccefd4abbe5521a14f4cc1e207356d4d00c0b7f2006fd" dependencies = [ "bytes", "prost", @@ -2392,16 +2382,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-io-timeout" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" -dependencies = [ - "pin-project-lite", - "tokio", -] - [[package]] name = "tokio-macros" version = "1.7.0" @@ -2479,9 +2459,9 @@ dependencies = [ [[package]] name = "tonic" -version = "0.6.2" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff08f4649d10a70ffa3522ca559031285d8e421d727ac85c60825761818f5d0a" +checksum = "30fb54bf1e446f44d870d260d99957e7d11fb9d0a0f5bd1a662ad1411cc103f9" dependencies = [ "async-stream", "async-trait", @@ -2489,31 +2469,26 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "h2", "http", "http-body", - "hyper", - "hyper-timeout", "percent-encoding", "pin-project", "prost", "prost-derive", - "tokio", "tokio-stream", - "tokio-util 0.6.9", - "tower", + "tokio-util 0.7.1", "tower-layer", "tower-service", "tracing", - "tracing-futures", ] [[package]] name = "tonic-build" -version = "0.6.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9403f1bafde247186684b230dc6f38b5cd514584e8bec1dd32514be4745fa757" +checksum = "4d17087af5c80e5d5fc8ba9878e60258065a0a757e35efe7a05b7904bece1943" dependencies = [ + "prettyplease", "proc-macro2", "prost-build", "quote", @@ -2600,16 +2575,6 @@ dependencies = [ "valuable", ] -[[package]] -name = "tracing-futures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" -dependencies = [ - "pin-project", - "tracing", -] - [[package]] name = "tracing-log" version = "0.1.2" @@ -2719,12 +2684,6 @@ dependencies = [ "tinyvec", ] -[[package]] -name = "unicode-segmentation" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" - [[package]] name = "unicode-xid" version = "0.2.2" diff --git a/deny.toml b/deny.toml index 70f9aa9485..74a84d1634 100644 --- a/deny.toml +++ b/deny.toml @@ -48,9 +48,6 @@ deny = [ { name = "rustls", wrappers = ["tokio-rustls"] }, ] skip = [ - # Waiting on a prost-build release that includes - # https://github.com/tokio-rs/prost/pull/583 - { name = "heck", version = "0.3" }, # waiting on `h2` and `tower` releases that update h2 to v0.7 { name = "tokio-util", version = "0.6" }, ] diff --git a/linkerd/app/Cargo.toml b/linkerd/app/Cargo.toml index 0d6d0395e9..a140892825 100644 --- a/linkerd/app/Cargo.toml +++ b/linkerd/app/Cargo.toml @@ -27,6 +27,6 @@ regex = "1" thiserror = "1" tokio = { version = "1", features = ["rt"] } tokio-stream = { version = "0.1", features = ["time", "sync"] } -tonic = { version = "0.6", default-features = false, features = ["prost"] } +tonic = { version = "0.7", default-features = false, features = ["prost"] } tower = "0.4" tracing = "0.1" diff --git a/linkerd/app/core/Cargo.toml b/linkerd/app/core/Cargo.toml index 2a40d62958..3b31891745 100644 --- a/linkerd/app/core/Cargo.toml +++ b/linkerd/app/core/Cargo.toml @@ -62,7 +62,7 @@ serde_json = "1" thiserror = "1" tokio = { version = "1", features = ["macros", "sync", "parking_lot"] } tokio-stream = { version = "0.1", features = ["time"] } -tonic = { version = "0.6", default-features = false, features = ["prost"] } +tonic = { version = "0.7", default-features = false, features = ["prost"] } tracing = "0.1" parking_lot = "0.12" pin-project = "1" diff --git a/linkerd/app/inbound/Cargo.toml b/linkerd/app/inbound/Cargo.toml index bed0e8652d..1257a8aad5 100644 --- a/linkerd/app/inbound/Cargo.toml +++ b/linkerd/app/inbound/Cargo.toml @@ -17,11 +17,11 @@ linkerd-app-core = { path = "../core" } linkerd-http-access-log = { path = "../../http-access-log" } linkerd-server-policy = { path = "../../server-policy" } linkerd-tonic-watch = { path = "../../tonic-watch" } -linkerd2-proxy-api = { version = "0.3", features = ["client", "inbound"] } +linkerd2-proxy-api = { version = "0.5", features = ["inbound"] } parking_lot = "0.12" thiserror = "1" tokio = { version = "1", features = ["sync"] } -tonic = { version = "0.6", default-features = false } +tonic = { version = "0.7", default-features = false } tower = { version = "0.4", features = ["util"] } tracing = "0.1" @@ -36,7 +36,9 @@ hyper = { version = "0.14", features = ["http1", "http2"] } linkerd-app-test = { path = "../test" } linkerd-io = { path = "../../io", features = ["tokio-test"] } linkerd-meshtls = { path = "../../meshtls", features = ["rustls"] } -linkerd-meshtls-rustls = { path = "../../meshtls/rustls", features = ["test-util"] } +linkerd-meshtls-rustls = { path = "../../meshtls/rustls", features = [ + "test-util", +] } linkerd-tracing = { path = "../../tracing", features = ["ansi"] } tokio = { version = "1", features = ["full", "macros"] } tokio-test = "0.4" diff --git a/linkerd/app/inbound/src/policy/discover.rs b/linkerd/app/inbound/src/policy/discover.rs index 1296df50c8..fbd49736c1 100644 --- a/linkerd/app/inbound/src/policy/discover.rs +++ b/linkerd/app/inbound/src/policy/discover.rs @@ -28,7 +28,8 @@ pub(super) type Watch = StreamWatch>; impl Discover where S: tonic::client::GrpcService + Clone, - S::ResponseBody: http::HttpBody + Send + Sync + 'static, + S::ResponseBody: + http::HttpBody + Default + Send + 'static, { pub(super) fn new(workload: String, client: S) -> Self { Self { @@ -46,8 +47,9 @@ impl Service for Discover where S: tonic::client::GrpcService, S: Clone + Send + Sync + 'static, + S::ResponseBody: + http::HttpBody + Default + Send + 'static, S::Future: Send + 'static, - S::ResponseBody: http::HttpBody + Send + Sync + 'static, { type Response = tonic::Response>>; diff --git a/linkerd/app/inbound/src/policy/store.rs b/linkerd/app/inbound/src/policy/store.rs index e9eca591f0..93278d31fe 100644 --- a/linkerd/app/inbound/src/policy/store.rs +++ b/linkerd/app/inbound/src/policy/store.rs @@ -81,7 +81,8 @@ impl Store { S: tonic::client::GrpcService, S: Clone + Send + Sync + 'static, S::Future: Send, - S::ResponseBody: http::HttpBody + Send + Sync + 'static, + S::ResponseBody: + http::HttpBody + Default + Send + 'static, { let rxs = ports .into_iter() diff --git a/linkerd/app/integration/Cargo.toml b/linkerd/app/integration/Cargo.toml index 1ecae69467..e949bbb8ed 100644 --- a/linkerd/app/integration/Cargo.toml +++ b/linkerd/app/integration/Cargo.toml @@ -16,7 +16,6 @@ a dedicated crate to help the compiler cache dependencies properly. default = ["flakey-in-ci"] flakey-in-ci = ["flakey-in-coverage"] flakey-in-coverage = [] -rustfmt = ["linkerd2-proxy-api/rustfmt"] [dependencies] bytes = "1" @@ -24,11 +23,20 @@ futures = { version = "0.3", default-features = false, features = ["executor"] } h2 = "0.3" http = "0.2" http-body = "0.4" -hyper = { version = "0.14", features = ["http1", "http2", "stream", "client", "server"] } +hyper = { version = "0.14", features = [ + "http1", + "http2", + "stream", + "client", + "server", +] } linkerd-app = { path = "..", features = ["allow-loopback"] } linkerd-app-core = { path = "../core" } linkerd-metrics = { path = "../../metrics", features = ["test_util"] } -linkerd2-proxy-api = { version = "0.3", features = ["destination", "arbitrary"] } +linkerd2-proxy-api = { version = "0.5", features = [ + "destination", + "arbitrary", +] } linkerd-app-test = { path = "../test" } linkerd-tracing = { path = "../../tracing" } parking_lot = "0.12" @@ -39,12 +47,17 @@ tokio-stream = { version = "0.1", features = ["sync"] } tokio-rustls = "0.23" rustls-pemfile = "0.3" tower = { version = "0.4", default-features = false } -tonic = { version = "0.6", default-features = false } +tonic = { version = "0.7", default-features = false } tracing = "0.1" -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt", "std"] } +tracing-subscriber = { version = "0.3", default-features = false, features = [ + "fmt", + "std", +] } [dev-dependencies] -flate2 = { version = "1", default-features = false, features = ["rust_backend"] } +flate2 = { version = "1", default-features = false, features = [ + "rust_backend", +] } # No code from this crate is actually used; only necessary to enable the Rustls # implementation. linkerd-meshtls = { path = "../../meshtls", features = ["rustls"] } diff --git a/linkerd/opencensus/Cargo.toml b/linkerd/opencensus/Cargo.toml index 94fa71269e..1e744cdf04 100644 --- a/linkerd/opencensus/Cargo.toml +++ b/linkerd/opencensus/Cargo.toml @@ -13,7 +13,7 @@ http-body = "0.4" linkerd-error = { path = "../error" } linkerd-metrics = { path = "../metrics" } opencensus-proto = { path = "../../opencensus-proto" } -tonic = { version = "0.6", default-features = false, features = ["prost", "codegen"] } +tonic = { version = "0.7", default-features = false, features = ["prost", "codegen"] } tokio = { version = "1", features = ["macros", "sync", "time"] } tokio-stream = { version = "0.1", features = ["sync"] } tracing = "0.1" diff --git a/linkerd/opencensus/src/lib.rs b/linkerd/opencensus/src/lib.rs index 9d7b9833fb..e70ed1b7c7 100644 --- a/linkerd/opencensus/src/lib.rs +++ b/linkerd/opencensus/src/lib.rs @@ -27,8 +27,8 @@ pub async fn export_spans(client: T, node: Node, spans: S, metrics: Regist where T: GrpcService + Clone, T::Error: Into, - ::Error: Into + Send + Sync, - T::ResponseBody: Send + Sync + 'static, + T::ResponseBody: Default + HttpBody + Send + 'static, + ::Error: Into + Send, S: Stream + Unpin, { debug!("Span exporter running"); @@ -52,8 +52,8 @@ impl SpanExporter where T: GrpcService, T::Error: Into, - ::Error: Into + Send + Sync, - T::ResponseBody: Send + Sync + 'static, + T::ResponseBody: Default + HttpBody + Send + 'static, + ::Error: Into + Send, S: Stream + Unpin, { const MAX_BATCH_SIZE: usize = 1000; diff --git a/linkerd/proxy/api-resolve/Cargo.toml b/linkerd/proxy/api-resolve/Cargo.toml index abe721585c..3ec57bb98e 100644 --- a/linkerd/proxy/api-resolve/Cargo.toml +++ b/linkerd/proxy/api-resolve/Cargo.toml @@ -9,22 +9,19 @@ description = """ Implements the Resolve trait using the proxy's gRPC API """ -[features] -rustfmt = ["linkerd2-proxy-api/rustfmt"] - [dependencies] async-stream = "0.3" futures = { version = "0.3", default-features = false } linkerd-addr = { path = "../../addr" } linkerd-error = { path = "../../error" } -linkerd2-proxy-api = { version = "0.3", features = ["destination", "client"] } +linkerd2-proxy-api = { version = "0.5", features = ["destination"] } linkerd-proxy-core = { path = "../core" } linkerd-stack = { path = "../../stack" } linkerd-tls = { path = "../../tls" } http = "0.2" http-body = "0.4" pin-project = "1" -prost = "0.9" -tonic = { version = "0.6", default-features = false } +prost = "0.10" +tonic = { version = "0.7", default-features = false } tower = { version = "0.4", default-features = false } tracing = "0.1" diff --git a/linkerd/proxy/api-resolve/src/resolve.rs b/linkerd/proxy/api-resolve/src/resolve.rs index c48a76face..cf09f45b3a 100644 --- a/linkerd/proxy/api-resolve/src/resolve.rs +++ b/linkerd/proxy/api-resolve/src/resolve.rs @@ -28,8 +28,7 @@ impl Resolve where S: GrpcService + Clone + Send + 'static, S::Error: Into + Send, - S::ResponseBody: Send + Sync, - ::Data: Send, + S::ResponseBody: Default + Body + Send + 'static, ::Error: Into + Send, S::Future: Send, { @@ -52,8 +51,7 @@ where T: Param, S: GrpcService + Clone + Send + 'static, S::Error: Into + Send, - S::ResponseBody: Send + Sync, - ::Data: Send, + S::ResponseBody: Default + Body + Send + 'static, ::Error: Into + Send, S::Future: Send, { diff --git a/linkerd/proxy/identity-client/Cargo.toml b/linkerd/proxy/identity-client/Cargo.toml index 1d4a7812bd..650fbd07d9 100644 --- a/linkerd/proxy/identity-client/Cargo.toml +++ b/linkerd/proxy/identity-client/Cargo.toml @@ -6,12 +6,9 @@ license = "Apache-2.0" edition = "2021" publish = false -[features] -rustfmt = ["linkerd2-proxy-api/rustfmt"] - [dependencies] futures = { version = "0.3", default-features = false } -linkerd2-proxy-api = { version = "0.3", features = ["identity", "client"] } +linkerd2-proxy-api = { version = "0.5", features = ["identity"] } linkerd-error = { path = "../../error" } linkerd-identity = { path = "../../identity" } linkerd-metrics = { path = "../../metrics" } @@ -20,6 +17,6 @@ parking_lot = "0.12" pin-project = "1" thiserror = "1" tokio = { version = "1", features = ["time", "sync"] } -tonic = { version = "0.6", default-features = false } +tonic = { version = "0.7", default-features = false } tracing = "0.1" http-body = "0.4" diff --git a/linkerd/proxy/identity-client/src/certify.rs b/linkerd/proxy/identity-client/src/certify.rs index ad7cbe167a..2d9af0e10b 100644 --- a/linkerd/proxy/identity-client/src/certify.rs +++ b/linkerd/proxy/identity-client/src/certify.rs @@ -49,8 +49,7 @@ impl Certify { C: Credentials, N: NewService<(), Service = S>, S: GrpcService, - S::ResponseBody: Send + Sync + 'static, - ::Data: Send, + S::ResponseBody: Default + Body + Send + 'static, ::Error: Into + Send, { debug!("Identity daemon running"); @@ -91,8 +90,7 @@ async fn certify(token: &TokenSource, client: S, credentials: &mut C) -> R where C: Credentials, S: GrpcService, - S::ResponseBody: Send + Sync + 'static, - ::Data: Send, + S::ResponseBody: Default + Body + Send + 'static, ::Error: Into + Send, { let req = tonic::Request::new(api::CertifyRequest { diff --git a/linkerd/proxy/tap/Cargo.toml b/linkerd/proxy/tap/Cargo.toml index d83fb0ff9d..06f70ae1c0 100644 --- a/linkerd/proxy/tap/Cargo.toml +++ b/linkerd/proxy/tap/Cargo.toml @@ -6,15 +6,12 @@ license = "Apache-2.0" edition = "2021" publish = false -[features] -rustfmt = ["linkerd2-proxy-api/rustfmt"] - [dependencies] http = "0.2" hyper = { version = "0.14", features = ["http1", "http2"] } futures = { version = "0.3", default-features = false } ipnet = "2.4" -linkerd2-proxy-api = { version = "0.3", features = ["tap", "server"] } +linkerd2-proxy-api = { version = "0.5", features = ["tap"] } linkerd-conditional = { path = "../../conditional" } linkerd-error = { path = "../../error" } linkerd-meshtls = { path = "../../meshtls" } @@ -27,11 +24,11 @@ rand = { version = "0.8" } thiserror = "1" tokio = { version = "1", features = ["time"] } tower = { version = "0.4", default-features = false } -tonic = { version = "0.6", default-features = false } +tonic = { version = "0.7", default-features = false } tracing = "0.1" pin-project = "1" [dev-dependencies] -linkerd2-proxy-api = { version = "0.3", features = ["arbitrary"] } -prost-types = "0.9" +linkerd2-proxy-api = { version = "0.5", features = ["arbitrary"] } +prost-types = "0.10" quickcheck = { version = "1", default-features = false } diff --git a/linkerd/service-profiles/Cargo.toml b/linkerd/service-profiles/Cargo.toml index 9ae245d9cd..613fddf6e1 100644 --- a/linkerd/service-profiles/Cargo.toml +++ b/linkerd/service-profiles/Cargo.toml @@ -9,9 +9,6 @@ description = """ Implements client layers for Linkerd ServiceProfiles. """ -[features] -rustfmt = ["linkerd2-proxy-api/rustfmt"] - [dependencies] bytes = "1" futures = { version = "0.3", default-features = false } @@ -25,18 +22,18 @@ linkerd-http-box = { path = "../http-box" } linkerd-proxy-api-resolve = { path = "../proxy/api-resolve" } linkerd-stack = { path = "../stack" } linkerd-tonic-watch = { path = "../tonic-watch" } -linkerd2-proxy-api = { version = "0.3", features = ["destination", "client"] } +linkerd2-proxy-api = { version = "0.5", features = ["destination"] } rand = { version = "0.8", features = ["small_rng"] } regex = "1" tokio = { version = "1", features = ["macros", "rt", "sync", "time"] } tokio-stream = { version = "0.1", features = ["sync"] } -tonic = { version = "0.6", default-features = false } +tonic = { version = "0.7", default-features = false } tower = { version = "0.4", features = ["ready-cache", "retry", "util"] } thiserror = "1" tracing = "0.1" pin-project = "1" [dev-dependencies] -linkerd2-proxy-api = { version = "0.3", features = ["arbitrary"] } -prost-types = "0.9" +linkerd2-proxy-api = { version = "0.5", features = ["arbitrary"] } +prost-types = "0.10" quickcheck = { version = "1", default-features = false } diff --git a/linkerd/service-profiles/src/client.rs b/linkerd/service-profiles/src/client.rs index c22bb66b95..0712a641c3 100644 --- a/linkerd/service-profiles/src/client.rs +++ b/linkerd/service-profiles/src/client.rs @@ -31,7 +31,7 @@ impl Client where S: GrpcService + Clone + Send + 'static, S::ResponseBody: Send + Sync, - ::Data: Send, + S::ResponseBody: Default + Body + Send + 'static, ::Error: Into> + Send, S::Future: Send, @@ -49,8 +49,7 @@ impl Service for Client where T: Param, S: GrpcService + Clone + Send + 'static, - S::ResponseBody: Send + Sync, - ::Data: Send, + S::ResponseBody: Default + Body + Send + 'static, ::Error: Into> + Send, S::Future: Send, @@ -96,8 +95,7 @@ type InnerFuture = impl Inner where S: GrpcService + Clone + Send + 'static, - S::ResponseBody: Send + Sync, - ::Data: Send, + S::ResponseBody: Default + Body + Send + 'static, ::Error: Into> + Send, S::Future: Send, @@ -113,8 +111,7 @@ where impl Service for Inner where S: GrpcService + Clone + Send + 'static, - S::ResponseBody: Send + Sync, - ::Data: Send, + S::ResponseBody: Default + Body + Send + 'static, ::Error: Into> + Send, S::Future: Send, diff --git a/linkerd/tonic-watch/Cargo.toml b/linkerd/tonic-watch/Cargo.toml index 3f9bc39ba0..98ba4874d2 100644 --- a/linkerd/tonic-watch/Cargo.toml +++ b/linkerd/tonic-watch/Cargo.toml @@ -13,7 +13,7 @@ Provides a utility for creating robust watches from a service that returns a str futures = { version = "0.3", default-features = false } linkerd-error = { path = "../error" } linkerd-stack = { path = "../stack" } -tonic = { version = "0.6", default-features = false } +tonic = { version = "0.7", default-features = false } tokio = { version = "1", features = ["macros", "rt", "sync", "time"] } tracing = "0.1" diff --git a/linkerd/transport-header/Cargo.toml b/linkerd/transport-header/Cargo.toml index 0ab98e9335..44f99dadbe 100644 --- a/linkerd/transport-header/Cargo.toml +++ b/linkerd/transport-header/Cargo.toml @@ -14,17 +14,15 @@ linkerd-dns-name = { path = "../dns/name" } linkerd-error = { path = "../error" } linkerd-io = { path = "../io" } linkerd-stack = { path = "../stack" } -prost = "0.9" +prost = "0.10" tokio = { version = "1", features = ["time"] } tracing = "0.1" -[build-dependencies] -prost-build = { version = "0.9", default-features = false } - [target.'cfg(fuzzing)'.dependencies] arbitrary = { version = "1", features = ["derive"] } libfuzzer-sys = { version = "0.4", features = ["arbitrary-derive"] } [dev-dependencies] +prost-build = { version = "0.10", default-features = false } tokio = { version = "1", features = ["macros"] } tokio-test = "0.4" diff --git a/linkerd/transport-header/build.rs b/linkerd/transport-header/build.rs deleted file mode 100644 index 5c6d8e3297..0000000000 --- a/linkerd/transport-header/build.rs +++ /dev/null @@ -1,13 +0,0 @@ -fn main() -> Result<(), Box> { - let files = &["proto/header.proto"]; - let dirs = &["proto"]; - - prost_build::compile_protos(files, dirs)?; - - // recompile protobufs only if any of the proto files changes. - for file in files { - println!("cargo:rerun-if-changed={}", file); - } - - Ok(()) -} diff --git a/linkerd/transport-header/src/gen/transport.l5d.io.rs b/linkerd/transport-header/src/gen/transport.l5d.io.rs new file mode 100644 index 0000000000..ae3e4319ba --- /dev/null +++ b/linkerd/transport-header/src/gen/transport.l5d.io.rs @@ -0,0 +1,34 @@ +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Header { + /// The target port. + #[prost(int32, tag="1")] + pub port: i32, + /// An optional hostname. Intended for gateway forwarding. + #[prost(string, tag="2")] + pub name: ::prost::alloc::string::String, + /// The session protocol, if one is known. When no protocol is specified, the + /// connection is handled opaquely. + #[prost(message, optional, tag="3")] + pub session_protocol: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SessionProtocol { + #[prost(oneof="session_protocol::Kind", tags="1, 2")] + pub kind: ::core::option::Option, +} +/// Nested message and enum types in `SessionProtocol`. +pub mod session_protocol { + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Http1 { + } + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Http2 { + } + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Kind { + #[prost(message, tag="1")] + Http1(Http1), + #[prost(message, tag="2")] + Http2(Http2), + } +} diff --git a/linkerd/transport-header/src/lib.rs b/linkerd/transport-header/src/lib.rs index 4fd79077f1..76a72da59e 100644 --- a/linkerd/transport-header/src/lib.rs +++ b/linkerd/transport-header/src/lib.rs @@ -21,7 +21,7 @@ use std::str::FromStr; use tracing::trace; mod proto { - include!(concat!(env!("OUT_DIR"), "/transport.l5d.io.rs")); + include!("gen/transport.l5d.io.rs"); } #[derive(Clone, Debug, PartialEq, Hash)] diff --git a/linkerd/transport-header/tests/bootstrap.rs b/linkerd/transport-header/tests/bootstrap.rs new file mode 100644 index 0000000000..c0d34be6e5 --- /dev/null +++ b/linkerd/transport-header/tests/bootstrap.rs @@ -0,0 +1,41 @@ +//! A test that regenerates the Rust protobuf bindings. +//! +//! It can be run via: +//! +//! ```no_run +//! cargo test -p linkerd-transport-header --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) { + prost_build::Config::new() + .out_dir(out_dir.display().to_string()) + .compile_protos(&["proto/header.proto"], &["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() +} diff --git a/opencensus-proto/Cargo.toml b/opencensus-proto/Cargo.toml index 8826083cb7..8da7f4b2a2 100644 --- a/opencensus-proto/Cargo.toml +++ b/opencensus-proto/Cargo.toml @@ -13,12 +13,18 @@ Vendored from https://github.com/census-instrumentation/opencensus-proto/. [dependencies] bytes = "1" -tonic = { version = "0.6", default-features = false, features = ["prost", "codegen"] } -prost = "0.9" -prost-types = "0.9" +prost = "0.10" +prost-types = "0.10" -[build-dependencies] -tonic-build = { version = "0.6", features = ["prost"], default-features = false } +[dependencies.tonic] +version = "0.7" +default-features = false +features = ["prost", "codegen"] + +[dev-dependencies.tonic-build] +version = "0.7" +default-features = false +features = ["prost"] [lib] doctest = false diff --git a/opencensus-proto/build.rs b/opencensus-proto/build.rs deleted file mode 100644 index 0c8483be3e..0000000000 --- a/opencensus-proto/build.rs +++ /dev/null @@ -1,14 +0,0 @@ -fn main() { - let iface_files = &["opencensus/proto/agent/trace/v1/trace_service.proto"]; - let dirs = &["."]; - - tonic_build::configure() - .build_client(true) - .compile(iface_files, dirs) - .unwrap_or_else(|e| panic!("protobuf compilation failed: {}", e)); - - // recompile protobufs only if any of the proto files changes. - for file in iface_files { - println!("cargo:rerun-if-changed={}", file); - } -} diff --git a/opencensus-proto/src/gen/opencensus.proto.agent.common.v1.rs b/opencensus-proto/src/gen/opencensus.proto.agent.common.v1.rs new file mode 100644 index 0000000000..8a70a6ad78 --- /dev/null +++ b/opencensus-proto/src/gen/opencensus.proto.agent.common.v1.rs @@ -0,0 +1,71 @@ +/// Identifier metadata of the Node that produces the span or tracing data. +/// Note, this is not the metadata about the Node or service that is described by associated spans. +/// In the future we plan to extend the identifier proto definition to support +/// additional information (e.g cloud id, etc.) +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Node { + /// Identifier that uniquely identifies a process within a VM/container. + #[prost(message, optional, tag="1")] + pub identifier: ::core::option::Option, + /// Information on the OpenCensus Library that initiates the stream. + #[prost(message, optional, tag="2")] + pub library_info: ::core::option::Option, + /// Additional information on service. + #[prost(message, optional, tag="3")] + pub service_info: ::core::option::Option, + /// Additional attributes. + #[prost(map="string, string", tag="4")] + pub attributes: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, +} +/// Identifier that uniquely identifies a process within a VM/container. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ProcessIdentifier { + /// The host name. Usually refers to the machine/container name. + /// For example: os.Hostname() in Go, socket.gethostname() in Python. + #[prost(string, tag="1")] + pub host_name: ::prost::alloc::string::String, + /// Process id. + #[prost(uint32, tag="2")] + pub pid: u32, + /// Start time of this ProcessIdentifier. Represented in epoch time. + #[prost(message, optional, tag="3")] + pub start_timestamp: ::core::option::Option<::prost_types::Timestamp>, +} +/// Information on OpenCensus Library. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct LibraryInfo { + /// Language of OpenCensus Library. + #[prost(enumeration="library_info::Language", tag="1")] + pub language: i32, + /// Version of Agent exporter of Library. + #[prost(string, tag="2")] + pub exporter_version: ::prost::alloc::string::String, + /// Version of OpenCensus Library. + #[prost(string, tag="3")] + pub core_library_version: ::prost::alloc::string::String, +} +/// Nested message and enum types in `LibraryInfo`. +pub mod library_info { + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] + #[repr(i32)] + pub enum Language { + Unspecified = 0, + Cpp = 1, + CSharp = 2, + Erlang = 3, + GoLang = 4, + Java = 5, + NodeJs = 6, + Php = 7, + Python = 8, + Ruby = 9, + WebJs = 10, + } +} +/// Additional service information. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ServiceInfo { + /// Name of the service. + #[prost(string, tag="1")] + pub name: ::prost::alloc::string::String, +} diff --git a/opencensus-proto/src/gen/opencensus.proto.agent.trace.v1.rs b/opencensus-proto/src/gen/opencensus.proto.agent.trace.v1.rs new file mode 100644 index 0000000000..191ea114e6 --- /dev/null +++ b/opencensus-proto/src/gen/opencensus.proto.agent.trace.v1.rs @@ -0,0 +1,155 @@ +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct CurrentLibraryConfig { + /// This is required only in the first message on the stream or if the + /// previous sent CurrentLibraryConfig message has a different Node (e.g. + /// when the same RPC is used to configure multiple Applications). + #[prost(message, optional, tag="1")] + pub node: ::core::option::Option, + /// Current configuration. + #[prost(message, optional, tag="2")] + pub config: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct UpdatedLibraryConfig { + /// This field is ignored when the RPC is used to configure only one Application. + /// This is required only in the first message on the stream or if the + /// previous sent UpdatedLibraryConfig message has a different Node. + #[prost(message, optional, tag="1")] + pub node: ::core::option::Option, + /// Requested updated configuration. + #[prost(message, optional, tag="2")] + pub config: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ExportTraceServiceRequest { + /// This is required only in the first message on the stream or if the + /// previous sent ExportTraceServiceRequest message has a different Node (e.g. + /// when the same RPC is used to send Spans from multiple Applications). + #[prost(message, optional, tag="1")] + pub node: ::core::option::Option, + /// A list of Spans that belong to the last received Node. + #[prost(message, repeated, tag="2")] + pub spans: ::prost::alloc::vec::Vec, + /// The resource for the spans in this message that do not have an explicit + /// resource set. + /// If unset, the most recently set resource in the RPC stream applies. It is + /// valid to never be set within a stream, e.g. when no resource info is known. + #[prost(message, optional, tag="3")] + pub resource: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ExportTraceServiceResponse { +} +/// Generated client implementations. +pub mod trace_service_client { + #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] + use tonic::codegen::*; + /// Service that can be used to push spans and configs between one Application + /// instrumented with OpenCensus and an agent, or between an agent and a + /// central collector or config service (in this case spans and configs are + /// sent/received to/from multiple Applications). + #[derive(Debug, Clone)] + pub struct TraceServiceClient { + inner: tonic::client::Grpc, + } + impl TraceServiceClient + 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, + ) -> TraceServiceClient> + where + F: tonic::service::Interceptor, + T: tonic::codegen::Service< + http::Request, + Response = http::Response< + >::ResponseBody, + >, + >, + , + >>::Error: Into + Send + Sync, + { + TraceServiceClient::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 + } + /// After initialization, this RPC must be kept alive for the entire life of + /// the application. The agent pushes configs down to applications via a + /// stream. + pub async fn config( + &mut self, + request: impl tonic::IntoStreamingRequest< + Message = super::CurrentLibraryConfig, + >, + ) -> 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( + "/opencensus.proto.agent.trace.v1.TraceService/Config", + ); + self.inner.streaming(request.into_streaming_request(), path, codec).await + } + /// For performance reasons, it is recommended to keep this RPC + /// alive for the entire life of the application. + pub async fn export( + &mut self, + request: impl tonic::IntoStreamingRequest< + Message = super::ExportTraceServiceRequest, + >, + ) -> Result< + tonic::Response< + tonic::codec::Streaming, + >, + 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( + "/opencensus.proto.agent.trace.v1.TraceService/Export", + ); + self.inner.streaming(request.into_streaming_request(), path, codec).await + } + } +} diff --git a/opencensus-proto/src/gen/opencensus.proto.resource.v1.rs b/opencensus-proto/src/gen/opencensus.proto.resource.v1.rs new file mode 100644 index 0000000000..5745fbc8d4 --- /dev/null +++ b/opencensus-proto/src/gen/opencensus.proto.resource.v1.rs @@ -0,0 +1,10 @@ +/// Resource information. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Resource { + /// Type identifier for the resource. + #[prost(string, tag="1")] + pub r#type: ::prost::alloc::string::String, + /// Set of labels that describe the resource. + #[prost(map="string, string", tag="2")] + pub labels: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>, +} diff --git a/opencensus-proto/src/gen/opencensus.proto.trace.v1.rs b/opencensus-proto/src/gen/opencensus.proto.trace.v1.rs new file mode 100644 index 0000000000..5b83f5c9bd --- /dev/null +++ b/opencensus-proto/src/gen/opencensus.proto.trace.v1.rs @@ -0,0 +1,519 @@ +/// A span represents a single operation within a trace. Spans can be +/// nested to form a trace tree. Spans may also be linked to other spans +/// from the same or different trace. And form graphs. Often, a trace +/// contains a root span that describes the end-to-end latency, and one +/// or more subspans for its sub-operations. A trace can also contain +/// multiple root spans, or none at all. Spans do not need to be +/// contiguous - there may be gaps or overlaps between spans in a trace. +/// +/// The next id is 17. +/// TODO(bdrutu): Add an example. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Span { + /// A unique identifier for a trace. All spans from the same trace share + /// the same `trace_id`. The ID is a 16-byte array. An ID with all zeroes + /// is considered invalid. + /// + /// This field is semantically required. Receiver should generate new + /// random trace_id if empty or invalid trace_id was received. + /// + /// This field is required. + #[prost(bytes="vec", tag="1")] + pub trace_id: ::prost::alloc::vec::Vec, + /// A unique identifier for a span within a trace, assigned when the span + /// is created. The ID is an 8-byte array. An ID with all zeroes is considered + /// invalid. + /// + /// This field is semantically required. Receiver should generate new + /// random span_id if empty or invalid span_id was received. + /// + /// This field is required. + #[prost(bytes="vec", tag="2")] + pub span_id: ::prost::alloc::vec::Vec, + /// The Tracestate on the span. + #[prost(message, optional, tag="15")] + pub tracestate: ::core::option::Option, + /// The `span_id` of this span's parent span. If this is a root span, then this + /// field must be empty. The ID is an 8-byte array. + #[prost(bytes="vec", tag="3")] + pub parent_span_id: ::prost::alloc::vec::Vec, + /// A description of the span's operation. + /// + /// For example, the name can be a qualified method name or a file name + /// and a line number where the operation is called. A best practice is to use + /// the same display name at the same call point in an application. + /// This makes it easier to correlate spans in different traces. + /// + /// This field is semantically required to be set to non-empty string. + /// When null or empty string received - receiver may use string "name" + /// as a replacement. There might be smarted algorithms implemented by + /// receiver to fix the empty span name. + /// + /// This field is required. + #[prost(message, optional, tag="4")] + pub name: ::core::option::Option, + /// Distinguishes between spans generated in a particular context. For example, + /// two spans with the same name may be distinguished using `CLIENT` (caller) + /// and `SERVER` (callee) to identify queueing latency associated with the span. + #[prost(enumeration="span::SpanKind", tag="14")] + pub kind: i32, + /// The start time of the span. On the client side, this is the time kept by + /// the local machine where the span execution starts. On the server side, this + /// is the time when the server's application handler starts running. + /// + /// This field is semantically required. When not set on receive - + /// receiver should set it to the value of end_time field if it was + /// set. Or to the current time if neither was set. It is important to + /// keep end_time > start_time for consistency. + /// + /// This field is required. + #[prost(message, optional, tag="5")] + pub start_time: ::core::option::Option<::prost_types::Timestamp>, + /// The end time of the span. On the client side, this is the time kept by + /// the local machine where the span execution ends. On the server side, this + /// is the time when the server application handler stops running. + /// + /// This field is semantically required. When not set on receive - + /// receiver should set it to start_time value. It is important to + /// keep end_time > start_time for consistency. + /// + /// This field is required. + #[prost(message, optional, tag="6")] + pub end_time: ::core::option::Option<::prost_types::Timestamp>, + /// A set of attributes on the span. + #[prost(message, optional, tag="7")] + pub attributes: ::core::option::Option, + /// A stack trace captured at the start of the span. + #[prost(message, optional, tag="8")] + pub stack_trace: ::core::option::Option, + /// The included time events. + #[prost(message, optional, tag="9")] + pub time_events: ::core::option::Option, + /// The included links. + #[prost(message, optional, tag="10")] + pub links: ::core::option::Option, + /// An optional final status for this span. Semantically when Status + /// wasn't set it is means span ended without errors and assume + /// Status.Ok (code = 0). + #[prost(message, optional, tag="11")] + pub status: ::core::option::Option, + /// An optional resource that is associated with this span. If not set, this span + /// should be part of a batch that does include the resource information, unless resource + /// information is unknown. + #[prost(message, optional, tag="16")] + pub resource: ::core::option::Option, + /// A highly recommended but not required flag that identifies when a + /// trace crosses a process boundary. True when the parent_span belongs + /// to the same process as the current span. This flag is most commonly + /// used to indicate the need to adjust time as clocks in different + /// processes may not be synchronized. + #[prost(message, optional, tag="12")] + pub same_process_as_parent_span: ::core::option::Option, + /// An optional number of child spans that were generated while this span + /// was active. If set, allows an implementation to detect missing child spans. + #[prost(message, optional, tag="13")] + pub child_span_count: ::core::option::Option, +} +/// Nested message and enum types in `Span`. +pub mod span { + /// This field conveys information about request position in multiple distributed tracing graphs. + /// It is a list of Tracestate.Entry with a maximum of 32 members in the list. + /// + /// See the for more details about this field. + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Tracestate { + /// A list of entries that represent the Tracestate. + #[prost(message, repeated, tag="1")] + pub entries: ::prost::alloc::vec::Vec, + } + /// Nested message and enum types in `Tracestate`. + pub mod tracestate { + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Entry { + /// The key must begin with a lowercase letter, and can only contain + /// lowercase letters 'a'-'z', digits '0'-'9', underscores '_', dashes + /// '-', asterisks '*', and forward slashes '/'. + #[prost(string, tag="1")] + pub key: ::prost::alloc::string::String, + /// The value is opaque string up to 256 characters printable ASCII + /// RFC0020 characters (i.e., the range 0x20 to 0x7E) except ',' and '='. + /// Note that this also excludes tabs, newlines, carriage returns, etc. + #[prost(string, tag="2")] + pub value: ::prost::alloc::string::String, + } + } + /// A set of attributes, each with a key and a value. + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Attributes { + /// The set of attributes. The value can be a string, an integer, a double + /// or the Boolean values `true` or `false`. Note, global attributes like + /// server name can be set as tags using resource API. Examples of attributes: + /// + /// "/http/user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36" + /// "/http/server_latency": 300 + /// "abc.com/myattribute": true + /// "abc.com/score": 10.239 + #[prost(map="string, message", tag="1")] + pub attribute_map: ::std::collections::HashMap<::prost::alloc::string::String, super::AttributeValue>, + /// The number of attributes that were discarded. Attributes can be discarded + /// because their keys are too long or because there are too many attributes. + /// If this value is 0, then no attributes were dropped. + #[prost(int32, tag="2")] + pub dropped_attributes_count: i32, + } + /// A time-stamped annotation or message event in the Span. + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct TimeEvent { + /// The time the event occurred. + #[prost(message, optional, tag="1")] + pub time: ::core::option::Option<::prost_types::Timestamp>, + /// A `TimeEvent` can contain either an `Annotation` object or a + /// `MessageEvent` object, but not both. + #[prost(oneof="time_event::Value", tags="2, 3")] + pub value: ::core::option::Option, + } + /// Nested message and enum types in `TimeEvent`. + pub mod time_event { + /// A text annotation with a set of attributes. + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Annotation { + /// A user-supplied message describing the event. + #[prost(message, optional, tag="1")] + pub description: ::core::option::Option, + /// A set of attributes on the annotation. + #[prost(message, optional, tag="2")] + pub attributes: ::core::option::Option, + } + /// An event describing a message sent/received between Spans. + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct MessageEvent { + /// The type of MessageEvent. Indicates whether the message was sent or + /// received. + #[prost(enumeration="message_event::Type", tag="1")] + pub r#type: i32, + /// An identifier for the MessageEvent's message that can be used to match + /// SENT and RECEIVED MessageEvents. For example, this field could + /// represent a sequence ID for a streaming RPC. It is recommended to be + /// unique within a Span. + #[prost(uint64, tag="2")] + pub id: u64, + /// The number of uncompressed bytes sent or received. + #[prost(uint64, tag="3")] + pub uncompressed_size: u64, + /// The number of compressed bytes sent or received. If zero, assumed to + /// be the same size as uncompressed. + #[prost(uint64, tag="4")] + pub compressed_size: u64, + } + /// Nested message and enum types in `MessageEvent`. + pub mod message_event { + /// Indicates whether the message was sent or received. + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] + #[repr(i32)] + pub enum Type { + /// Unknown event type. + Unspecified = 0, + /// Indicates a sent message. + Sent = 1, + /// Indicates a received message. + Received = 2, + } + } + /// A `TimeEvent` can contain either an `Annotation` object or a + /// `MessageEvent` object, but not both. + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Value { + /// A text annotation with a set of attributes. + #[prost(message, tag="2")] + Annotation(Annotation), + /// An event describing a message sent/received between Spans. + #[prost(message, tag="3")] + MessageEvent(MessageEvent), + } + } + /// A collection of `TimeEvent`s. A `TimeEvent` is a time-stamped annotation + /// on the span, consisting of either user-supplied key-value pairs, or + /// details of a message sent/received between Spans. + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct TimeEvents { + /// A collection of `TimeEvent`s. + #[prost(message, repeated, tag="1")] + pub time_event: ::prost::alloc::vec::Vec, + /// The number of dropped annotations in all the included time events. + /// If the value is 0, then no annotations were dropped. + #[prost(int32, tag="2")] + pub dropped_annotations_count: i32, + /// The number of dropped message events in all the included time events. + /// If the value is 0, then no message events were dropped. + #[prost(int32, tag="3")] + pub dropped_message_events_count: i32, + } + /// A pointer from the current span to another span in the same trace or in a + /// different trace. For example, this can be used in batching operations, + /// where a single batch handler processes multiple requests from different + /// traces or when the handler receives a request from a different project. + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Link { + /// A unique identifier of a trace that this linked span is part of. The ID is a + /// 16-byte array. + #[prost(bytes="vec", tag="1")] + pub trace_id: ::prost::alloc::vec::Vec, + /// A unique identifier for the linked span. The ID is an 8-byte array. + #[prost(bytes="vec", tag="2")] + pub span_id: ::prost::alloc::vec::Vec, + /// The relationship of the current span relative to the linked span. + #[prost(enumeration="link::Type", tag="3")] + pub r#type: i32, + /// A set of attributes on the link. + #[prost(message, optional, tag="4")] + pub attributes: ::core::option::Option, + /// The Tracestate associated with the link. + #[prost(message, optional, tag="5")] + pub tracestate: ::core::option::Option, + } + /// Nested message and enum types in `Link`. + pub mod link { + /// The relationship of the current span relative to the linked span: child, + /// parent, or unspecified. + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] + #[repr(i32)] + pub enum Type { + /// The relationship of the two spans is unknown, or known but other + /// than parent-child. + Unspecified = 0, + /// The linked span is a child of the current span. + ChildLinkedSpan = 1, + /// The linked span is a parent of the current span. + ParentLinkedSpan = 2, + } + } + /// A collection of links, which are references from this span to a span + /// in the same or different trace. + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Links { + /// A collection of links. + #[prost(message, repeated, tag="1")] + pub link: ::prost::alloc::vec::Vec, + /// The number of dropped links after the maximum size was enforced. If + /// this value is 0, then no links were dropped. + #[prost(int32, tag="2")] + pub dropped_links_count: i32, + } + /// Type of span. Can be used to specify additional relationships between spans + /// in addition to a parent/child relationship. + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] + #[repr(i32)] + pub enum SpanKind { + /// Unspecified. + Unspecified = 0, + /// Indicates that the span covers server-side handling of an RPC or other + /// remote network request. + Server = 1, + /// Indicates that the span covers the client-side wrapper around an RPC or + /// other remote request. + Client = 2, + } +} +/// The `Status` type defines a logical error model that is suitable for different +/// programming environments, including REST APIs and RPC APIs. This proto's fields +/// are a subset of those of +/// \[google.rpc.Status\](), +/// which is used by \[gRPC\](). +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Status { + /// The status code. This is optional field. It is safe to assume 0 (OK) + /// when not set. + #[prost(int32, tag="1")] + pub code: i32, + /// A developer-facing error message, which should be in English. + #[prost(string, tag="2")] + pub message: ::prost::alloc::string::String, +} +/// The value of an Attribute. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct AttributeValue { + /// The type of the value. + #[prost(oneof="attribute_value::Value", tags="1, 2, 3, 4")] + pub value: ::core::option::Option, +} +/// Nested message and enum types in `AttributeValue`. +pub mod attribute_value { + /// The type of the value. + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Value { + /// A string up to 256 bytes long. + #[prost(message, tag="1")] + StringValue(super::TruncatableString), + /// A 64-bit signed integer. + #[prost(int64, tag="2")] + IntValue(i64), + /// A Boolean value represented by `true` or `false`. + #[prost(bool, tag="3")] + BoolValue(bool), + /// A double value. + #[prost(double, tag="4")] + DoubleValue(f64), + } +} +/// The call stack which originated this span. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct StackTrace { + /// Stack frames in this stack trace. + #[prost(message, optional, tag="1")] + pub stack_frames: ::core::option::Option, + /// The hash ID is used to conserve network bandwidth for duplicate + /// stack traces within a single trace. + /// + /// Often multiple spans will have identical stack traces. + /// The first occurrence of a stack trace should contain both + /// `stack_frames` and a value in `stack_trace_hash_id`. + /// + /// Subsequent spans within the same request can refer + /// to that stack trace by setting only `stack_trace_hash_id`. + /// + /// TODO: describe how to deal with the case where stack_trace_hash_id is + /// zero because it was not set. + #[prost(uint64, tag="2")] + pub stack_trace_hash_id: u64, +} +/// Nested message and enum types in `StackTrace`. +pub mod stack_trace { + /// A single stack frame in a stack trace. + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct StackFrame { + /// The fully-qualified name that uniquely identifies the function or + /// method that is active in this frame. + #[prost(message, optional, tag="1")] + pub function_name: ::core::option::Option, + /// An un-mangled function name, if `function_name` is + /// \[mangled\](). The name can + /// be fully qualified. + #[prost(message, optional, tag="2")] + pub original_function_name: ::core::option::Option, + /// The name of the source file where the function call appears. + #[prost(message, optional, tag="3")] + pub file_name: ::core::option::Option, + /// The line number in `file_name` where the function call appears. + #[prost(int64, tag="4")] + pub line_number: i64, + /// The column number where the function call appears, if available. + /// This is important in JavaScript because of its anonymous functions. + #[prost(int64, tag="5")] + pub column_number: i64, + /// The binary module from where the code was loaded. + #[prost(message, optional, tag="6")] + pub load_module: ::core::option::Option, + /// The version of the deployed source code. + #[prost(message, optional, tag="7")] + pub source_version: ::core::option::Option, + } + /// A collection of stack frames, which can be truncated. + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct StackFrames { + /// Stack frames in this call stack. + #[prost(message, repeated, tag="1")] + pub frame: ::prost::alloc::vec::Vec, + /// The number of stack frames that were dropped because there + /// were too many stack frames. + /// If this value is 0, then no stack frames were dropped. + #[prost(int32, tag="2")] + pub dropped_frames_count: i32, + } +} +/// A description of a binary module. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Module { + /// TODO: document the meaning of this field. + /// For example: main binary, kernel modules, and dynamic libraries + /// such as libc.so, sharedlib.so. + #[prost(message, optional, tag="1")] + pub module: ::core::option::Option, + /// A unique identifier for the module, usually a hash of its + /// contents. + #[prost(message, optional, tag="2")] + pub build_id: ::core::option::Option, +} +/// A string that might be shortened to a specified length. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TruncatableString { + /// The shortened string. For example, if the original string was 500 bytes long and + /// the limit of the string was 128 bytes, then this value contains the first 128 + /// bytes of the 500-byte string. Note that truncation always happens on a + /// character boundary, to ensure that a truncated string is still valid UTF-8. + /// Because it may contain multi-byte characters, the size of the truncated string + /// may be less than the truncation limit. + #[prost(string, tag="1")] + pub value: ::prost::alloc::string::String, + /// The number of bytes removed from the original string. If this + /// value is 0, then the string was not shortened. + #[prost(int32, tag="2")] + pub truncated_byte_count: i32, +} +/// Global configuration of the trace service. All fields must be specified, or +/// the default (zero) values will be used for each type. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TraceConfig { + /// The global default max number of attributes per span. + #[prost(int64, tag="4")] + pub max_number_of_attributes: i64, + /// The global default max number of annotation events per span. + #[prost(int64, tag="5")] + pub max_number_of_annotations: i64, + /// The global default max number of message events per span. + #[prost(int64, tag="6")] + pub max_number_of_message_events: i64, + /// The global default max number of link entries per span. + #[prost(int64, tag="7")] + pub max_number_of_links: i64, + /// The global default sampler used to make decisions on span sampling. + #[prost(oneof="trace_config::Sampler", tags="1, 2, 3")] + pub sampler: ::core::option::Option, +} +/// Nested message and enum types in `TraceConfig`. +pub mod trace_config { + /// The global default sampler used to make decisions on span sampling. + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Sampler { + #[prost(message, tag="1")] + ProbabilitySampler(super::ProbabilitySampler), + #[prost(message, tag="2")] + ConstantSampler(super::ConstantSampler), + #[prost(message, tag="3")] + RateLimitingSampler(super::RateLimitingSampler), + } +} +/// Sampler that tries to uniformly sample traces with a given probability. +/// The probability of sampling a trace is equal to that of the specified probability. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ProbabilitySampler { + /// The desired probability of sampling. Must be within [0.0, 1.0]. + #[prost(double, tag="1")] + pub sampling_probability: f64, +} +/// Sampler that always makes a constant decision on span sampling. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ConstantSampler { + #[prost(enumeration="constant_sampler::ConstantDecision", tag="1")] + pub decision: i32, +} +/// Nested message and enum types in `ConstantSampler`. +pub mod constant_sampler { + /// How spans should be sampled: + /// - Always off + /// - Always on + /// - Always follow the parent Span's decision (off if no parent). + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] + #[repr(i32)] + pub enum ConstantDecision { + AlwaysOff = 0, + AlwaysOn = 1, + AlwaysParent = 2, + } +} +/// Sampler that tries to sample with a rate per time window. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RateLimitingSampler { + /// Rate per second. + #[prost(int64, tag="1")] + pub qps: i64, +} diff --git a/opencensus-proto/src/lib.rs b/opencensus-proto/src/lib.rs index fdf613c0d6..555a523951 100644 --- a/opencensus-proto/src/lib.rs +++ b/opencensus-proto/src/lib.rs @@ -13,31 +13,25 @@ pub mod agent { pub mod trace { pub mod v1 { - include!(concat!( - env!("OUT_DIR"), - "/opencensus.proto.agent.trace.v1.rs" - )); + include!("gen/opencensus.proto.agent.trace.v1.rs"); } } pub mod common { pub mod v1 { - include!(concat!( - env!("OUT_DIR"), - "/opencensus.proto.agent.common.v1.rs" - )); + include!("gen/opencensus.proto.agent.common.v1.rs"); } } } pub mod trace { pub mod v1 { - include!(concat!(env!("OUT_DIR"), "/opencensus.proto.trace.v1.rs")); + include!("gen/opencensus.proto.trace.v1.rs"); } } pub mod resource { pub mod v1 { - include!(concat!(env!("OUT_DIR"), "/opencensus.proto.resource.v1.rs")); + include!("gen/opencensus.proto.resource.v1.rs"); } } diff --git a/opencensus-proto/tests/bootstrap.rs b/opencensus-proto/tests/bootstrap.rs new file mode 100644 index 0000000000..5079ec70c5 --- /dev/null +++ b/opencensus-proto/tests/bootstrap.rs @@ -0,0 +1,50 @@ +//! A test that regenerates the Rust protobuf bindings. +//! +//! It can be run via: +//! +//! ```no_run +//! cargo test -p opencensus-proto --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 = &[ + "opencensus/proto/agent/common/v1/common.proto", + "opencensus/proto/agent/trace/v1/trace_service.proto", + "opencensus/proto/resource/v1/resource.proto", + "opencensus/proto/trace/v1/trace_config.proto", + "opencensus/proto/trace/v1/trace.proto", + ]; + tonic_build::configure() + .build_client(true) + .build_server(false) + .out_dir(out_dir) + .compile(iface_files, &["."]) + .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() +}