feat(ops/yandex-cloud-rs): generated gRPC clients for Yandex Cloud
This uses tonic to generate the full set of gRPC clients for Yandex Cloud. Includes some utility functions like an authentication interceptor to make these actually work. Since the upstream protos are exported regularly I've decided that the versioning will simply be date-based. The point of this is journaldriver integration, of course, hence also the log-centric example code. Change-Id: I00a615dcba80030e7f9bcfd476b2cfdb298f130d Reviewed-on: https://cl.tvl.fyi/c/depot/+/8525 Tested-by: BuildkiteCI Reviewed-by: tazjin <tazjin@tvl.su>
This commit is contained in:
parent
f1ca5a3096
commit
ea1383682d
7 changed files with 1613 additions and 0 deletions
5
ops/yandex-cloud-rs/.gitignore
vendored
Normal file
5
ops/yandex-cloud-rs/.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
target/
|
||||
result/
|
||||
# Ignore everything under src (except for lib.rs)
|
||||
src/*
|
||||
!src/lib.rs
|
1394
ops/yandex-cloud-rs/Cargo.lock
generated
Normal file
1394
ops/yandex-cloud-rs/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
17
ops/yandex-cloud-rs/Cargo.toml
Normal file
17
ops/yandex-cloud-rs/Cargo.toml
Normal file
|
@ -0,0 +1,17 @@
|
|||
[package]
|
||||
name = "yandex-cloud"
|
||||
description = "Generated gRPC clients for the Yandex Cloud API"
|
||||
version = "2023.4.24"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
prost = "0.11"
|
||||
prost-types = "0.11"
|
||||
|
||||
[dependencies.tonic]
|
||||
version = "0.9"
|
||||
features = [ "tls", "tls-roots", "gzip" ]
|
||||
|
||||
[build-dependencies]
|
||||
tonic-build = "0.9"
|
||||
walkdir = "2.3.3"
|
43
ops/yandex-cloud-rs/build.rs
Normal file
43
ops/yandex-cloud-rs/build.rs
Normal file
|
@ -0,0 +1,43 @@
|
|||
use std::path::PathBuf;
|
||||
use walkdir::{DirEntry, WalkDir};
|
||||
|
||||
fn proto_files(proto_dir: &str) -> Vec<PathBuf> {
|
||||
let mut out = vec![];
|
||||
|
||||
fn is_proto(entry: &DirEntry) -> bool {
|
||||
entry.file_type().is_file()
|
||||
&& entry
|
||||
.path()
|
||||
.extension()
|
||||
.map(|e| e.to_string_lossy() == "proto")
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
for entry in WalkDir::new(format!("{}/yandex", proto_dir)).into_iter() {
|
||||
let entry = entry.expect("failed to list proto files");
|
||||
|
||||
if is_proto(&entry) {
|
||||
out.push(entry.into_path())
|
||||
}
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if let Some(proto_dir) = option_env!("YANDEX_CLOUD_PROTOS") {
|
||||
tonic_build::configure()
|
||||
.build_client(true)
|
||||
.build_server(false)
|
||||
.out_dir("src/")
|
||||
.include_file("includes.rs")
|
||||
.compile(
|
||||
&proto_files(proto_dir),
|
||||
&[
|
||||
format!("{}", proto_dir),
|
||||
format!("{}/third_party/googleapis", proto_dir),
|
||||
],
|
||||
)
|
||||
.expect("failed to generate gRPC clients for Yandex Cloud")
|
||||
}
|
||||
}
|
22
ops/yandex-cloud-rs/default.nix
Normal file
22
ops/yandex-cloud-rs/default.nix
Normal file
|
@ -0,0 +1,22 @@
|
|||
{ depot, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
protoSrc = pkgs.fetchFromGitHub {
|
||||
owner = "yandex-cloud";
|
||||
repo = "cloudapi";
|
||||
rev = "67589e7503bd8997b7aa27af86a81fb6b9eec655";
|
||||
sha256 = "0d8w98h58xzzcjq9s3y949gv5b4z4r05vgpz3n22asyfrfx6x6kf";
|
||||
};
|
||||
in
|
||||
pkgs.rustPlatform.buildRustPackage rec {
|
||||
name = "yandex-cloud-rs";
|
||||
src = depot.third_party.gitignoreSource ./.;
|
||||
cargoLock.lockFile = ./Cargo.lock;
|
||||
YANDEX_CLOUD_PROTOS = "${protoSrc}";
|
||||
nativeBuildInputs = [ pkgs.protobuf ];
|
||||
|
||||
# The generated doc comments contain lots of things that rustc
|
||||
# *thinks* are doctests, but are actually just garbage leading to
|
||||
# compiler errors.
|
||||
doCheck = false;
|
||||
}
|
37
ops/yandex-cloud-rs/examples/log-write.rs
Normal file
37
ops/yandex-cloud-rs/examples/log-write.rs
Normal file
|
@ -0,0 +1,37 @@
|
|||
//! This example uses the Yandex Cloud Logging API to write a log entry.
|
||||
|
||||
use prost_types::Timestamp;
|
||||
use tonic::transport::channel::Endpoint;
|
||||
use yandex_cloud::yandex::cloud::logging::v1::destination::Destination;
|
||||
use yandex_cloud::yandex::cloud::logging::v1::log_ingestion_service_client::LogIngestionServiceClient;
|
||||
use yandex_cloud::yandex::cloud::logging::v1::Destination as OuterDestination;
|
||||
use yandex_cloud::yandex::cloud::logging::v1::IncomingLogEntry;
|
||||
use yandex_cloud::yandex::cloud::logging::v1::WriteRequest;
|
||||
use yandex_cloud::AuthInterceptor;
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let channel = Endpoint::from_static("https://ingester.logging.yandexcloud.net")
|
||||
.connect()
|
||||
.await?;
|
||||
|
||||
let mut client = LogIngestionServiceClient::with_interceptor(
|
||||
channel,
|
||||
AuthInterceptor::new("YOUR_TOKEN_HERE"),
|
||||
);
|
||||
|
||||
let request = WriteRequest {
|
||||
destination: Some(OuterDestination {
|
||||
destination: Some(Destination::LogGroupId("YOUR_LOG_GROUP_ID".into())),
|
||||
}),
|
||||
entries: vec![IncomingLogEntry {
|
||||
timestamp: Some(Timestamp::date_time(2023, 04, 24, 23, 44, 30).unwrap()),
|
||||
message: "test log message".into(),
|
||||
..Default::default()
|
||||
}],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
client.write(request).await.unwrap();
|
||||
Ok(())
|
||||
}
|
95
ops/yandex-cloud-rs/src/lib.rs
Normal file
95
ops/yandex-cloud-rs/src/lib.rs
Normal file
|
@ -0,0 +1,95 @@
|
|||
//! This module provides low-level generated gRPC clients for the
|
||||
//! Yandex Cloud APIs.
|
||||
//!
|
||||
//! The clients are generated using the [tonic][] and [prost][]
|
||||
//! crates and have default configuration.
|
||||
//!
|
||||
//! Documentation present in the protos is retained into the generated
|
||||
//! Rust types, but for detailed API information you should visit the
|
||||
//! official Yandex Cloud Documentation pages:
|
||||
//!
|
||||
//! * [in English](https://cloud.yandex.com/en-ru/docs/overview/api)
|
||||
//! * [in Russian](https://cloud.yandex.ru/docs/overview/api)
|
||||
//!
|
||||
//! The proto sources are available on the [Yandex Cloud GitHub][protos].
|
||||
//!
|
||||
//! [tonic]: https://docs.rs/tonic/latest/tonic/
|
||||
//! [prost]: https://docs.rs/prost/latest/prost/
|
||||
//! [protos]: https://github.com/yandex-cloud/cloudapi
|
||||
//!
|
||||
//! The majority of user-facing structures can be found in the
|
||||
//! [`yandex::cloud`] module.
|
||||
//!
|
||||
//! ## Usage
|
||||
//!
|
||||
//! Typically to use these APIs, you need to provide an authentication
|
||||
//! credential and an endpoint to connect to. The full list of
|
||||
//! Yandex's endpoints is [available online][endpoints] and you should
|
||||
//! look up the service you plan to use and pick the correct endpoint
|
||||
//! from the list.
|
||||
//!
|
||||
//! Authentication is done via an HTTP header using an IAM token,
|
||||
//! which can be done in Tonic using [interceptors][]. The
|
||||
//! [`AuthInterceptor`] provided by this crate can be used for that
|
||||
//! purpose.
|
||||
//!
|
||||
//! [endpoints]: https://cloud.yandex.com/en/docs/api-design-guide/concepts/endpoints
|
||||
//! [interceptors]: https://docs.rs/tonic/latest/tonic/service/trait.Interceptor.html
|
||||
|
||||
use tonic::metadata::{Ascii, MetadataValue};
|
||||
use tonic::service::Interceptor;
|
||||
|
||||
/// Helper trait for types or closures that can provide authentication
|
||||
/// tokens for Yandex Cloud.
|
||||
pub trait TokenProvider {
|
||||
/// Fetch a currently valid authentication token for Yandex Cloud.
|
||||
fn get_token<'a>(&'a mut self) -> &'a str;
|
||||
}
|
||||
|
||||
impl TokenProvider for String {
|
||||
fn get_token<'a>(&'a mut self) -> &'a str {
|
||||
self.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl TokenProvider for &'static str {
|
||||
fn get_token(&mut self) -> &'static str {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
/// Interceptor for adding authentication headers to gRPC requests.
|
||||
/// This is constructed with a callable that returns authentication
|
||||
/// tokens.
|
||||
///
|
||||
/// This callable is responsible for ensuring that the returned tokens
|
||||
/// are valid at the given time, i.e. it should take care of
|
||||
/// refreshing and so on.
|
||||
pub struct AuthInterceptor<T: TokenProvider> {
|
||||
token_provider: T,
|
||||
}
|
||||
|
||||
impl<T: TokenProvider> AuthInterceptor<T> {
|
||||
pub fn new(token_provider: T) -> Self {
|
||||
Self { token_provider }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: TokenProvider> Interceptor for AuthInterceptor<T> {
|
||||
fn call(
|
||||
&mut self,
|
||||
mut request: tonic::Request<()>,
|
||||
) -> Result<tonic::Request<()>, tonic::Status> {
|
||||
let token: MetadataValue<Ascii> =
|
||||
self.token_provider.get_token().try_into().map_err(|_| {
|
||||
tonic::Status::invalid_argument("authorization token contained invalid characters")
|
||||
})?;
|
||||
|
||||
request.metadata_mut().insert("authorization", token);
|
||||
|
||||
Ok(request)
|
||||
}
|
||||
}
|
||||
|
||||
// The rest of this file is generated by the build script at ../build.rs.
|
||||
include!("includes.rs");
|
Loading…
Reference in a new issue