Converting a future to a stream in kube-rs library - rust

I'm trying to implement a project where I can tail the logs of multiple Kubernetes container logs simultaneously. Think tmux split pane with two tails in each pane. Anyway, I'm far far away from my actual project because I'm stuck right at the beginning. If you look at the following code then the commented out line for lp.follow = true will keep the log stream open and stream logs forever. I'm not sure how to actually use this. I found a function called .into_stream() that I can tack onto the pods.log function, but then I'm not sure how to actually use the stream. I'm not experienced enough to know if this is a limitation of the kube library, or if I'm just doing something wrong. Anyway, here is the repo if you want to look at anything else. https://github.com/bloveless/kube-logger
I'd be forever grateful for any advice or resources I can look at. Thanks!
use kube::{
api::Api,
client::APIClient,
config,
};
use kube::api::{LogParams, RawApi};
use futures::{FutureExt, Stream, future::IntoStream, StreamExt};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
std::env::set_var("RUST_LOG", "info,kube=trace");
let config = config::load_kube_config().await?;
let client = APIClient::new(config);
// Manage pods
let pods = Api::v1Pod(client).within("fritzandandre");
let mut lp = LogParams::default();
lp.container = Some("php".to_string());
// lp.follow = true;
lp.tail_lines = Some(100);
let log_string = pods.log("fritzandandre-php-0", &lp).await?;
println!("FnA Log: {}", log_string);
Ok(())
}
Originally posted here https://www.reddit.com/r/learnrust/comments/eg49tx/help_with_futuresstreams_and_the_kubers_library/

Related

Can't Pass Messages with Servo/Ipc_channel for Rust

I'm stuck on implementing Servo/ipc_channel. I'm looking for a trivial example to do the following. I've read through all the documentation and I just can't find an example that makes sense to me either from the docs or from the test.rs.
Here's what I want to do in pseudocode -
I have a cargo crate that acts as a main process that creates a spawned process.
The main process would look like this -
let servername = CREATESERVER();
let (sender, receiver) = GETSERVER(servername);
let message = <Some message>
sender(<Send message>)
SPAWNSERVER(servername)
loop{
print(receiver)
}
The spawned process should look like this -
let (sender, receiver) = GETSERVER(servernamefrommainprocess)
let messagespawn = <Some message in spawn process>
sender(messagespawn)
loop{
print(receiver)
}
The issue is that IpcSender can connect to a server by name, but IpcReceiver can't, so I don't know how to receive messages from an IpcOneShotServer. The examples are wrapping IpcSenders within other channels in a way I don't understand.
Here's what I have for a trivial working repo, which is close to what I want and should give you an idea of what I'm looking to do.

How to call contract's method from crate

I need to call the contract's method from my Indexer. Now I use tokio::process and near-cli written on NodeJs. It looks soundless, and I would like to do that from Rust.
Recommended way
NEAR JSON-RPC Client RS is the recommended way to interact with NEAR Protocol from within the Rust code.
Example from the README
use near_jsonrpc_client::{methods, JsonRpcClient};
use near_jsonrpc_primitives::types::transactions::TransactionInfo;
let mainnet_client = JsonRpcClient::connect("https://archival-rpc.mainnet.near.org");
let tx_status_request = methods::tx::RpcTransactionStatusRequest {
transaction_info: TransactionInfo::TransactionId {
hash: "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8U".parse()?,
account_id: "miraclx.near".parse()?,
},
};
// call a method on the server via the connected client
let tx_status = mainnet_client.call(tx_status_request).await?;
println!("{:?}", tx_status);
In the examples folder of the repo, you will find different use cases, and hopefully, you'll find yours there as well.
near-jsonrpc-client-rs is the best option.
Alternative way
NB! This way is using non-documented APIs. It is not recommended way, because using these assumes you will dig into the code and find out how to use it by yourself.
If you're using the NEAR Indexer Framework you're literally running a nearcore node which includes:
JSON RPC server
ClientActor and ViewClient
Based on the kind of call you need to perform to your contract: change method or view method, you can use ClientActor or ViewClient.
ViewClient example
Code is for understanding the concept, not a working example.
let indexer = near_indexer::Indexer::new(indexer_config);
let view_client = indexer.client_actors().0;
let block_response = view_client
.send(query)
.await
.context("Failed to deliver response")?
.context("Invalid request")?;
You can find the usage in NEAR Indexer for Explorer starting from here
ClientActor example
ClientActor is used to send a transaction. I guess here's a good starting point to look for ClientActor example.
async fn send_tx_async(
&self,
request_data: near_jsonrpc_primitives::types::transactions::RpcBroadcastTransactionRequest,
) -> CryptoHash {
let tx = request_data.signed_transaction;
let hash = tx.get_hash().clone();
self.client_addr.do_send(NetworkClientMessages::Transaction {
transaction: tx,
is_forwarded: false,
check_only: false, // if we set true here it will not actually send the transaction
});
hash
}

Iterating over Stream in Rust

I'm trying to iterate over logs from a docker container by using the bollard crate.
Here's my code:
use std::default::Default;
use bollard::container::LogsOptions;
use bollard::Docker;
fn main() {
let docker = Docker::connect_with_http_defaults().unwrap();
let options = Some(LogsOptions::<String>{
stdout: true,
..Default::default()
});
let data = docker.logs("2f6c52410d", options);
// ...
}
docker.logs() returns impl Stream<Item = Result<LogOutput, Error>>. I'd like to iterate over the results, but I have no idea how to do that. I've managed to find an example that uses try_collect::<Vec<LogOutput>>() from the future_utils crate, but I'd like to iterate over the results in a while loop instead of collecting the results in a vector. I know that I can iterate over a vector, but performing tasks in a loop will be better for my use case.
I've tried to call poll_next() method for the stream, but it requires a mysterious Context object which I don't understand. The poll_next() method was unavailable until I've used pin_mut!() macro on the stream.
How do I iterate over stream? What should I read to understand what's going on here? I know that the streams are related to Futures, but calling await or next() doesn't work here.
You typically bring in your library of choice's StreamExt trait, and then do something like
while let Some(foo) = stream.next().await {
// ...
}

Why don't I get traces when sending OpenTelemetry to Jaeger?

I'm learning tracing and open-telemetry in Rust. I feel there are too many concepts and too many crates (at least in Rust) to see traces.
I wrote a simple lib app that adds two u32s:
use std::ops::Add;
pub fn add(f: u32, s: u32) -> u32 {
let span = tracing::info_span!("Add function", ?f, ?s);
let _guard = span.enter();
tracing::info!("Info event");
f.add(s)
}
And then I'm using the lib in my binary app:
use TracedLibrary::add;
use tracing_opentelemetry::OpenTelemetryLayer;
use tracing_subscriber::util::SubscriberInitExt;
use opentelemetry::{global, sdk::propagation::TraceContextPropagator};
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::Registry;
fn main() {
setup_global_subscriber();
let sum = add::add(1, 2);
}
fn setup_global_subscriber() {
global::set_text_map_propagator(TraceContextPropagator::new());
let (tracer, _uninstall) = opentelemetry_jaeger::new_pipeline()
.with_service_name("trace_demo_2")
.install().expect("Error initializing Jaeger exporter");
let telemetry = tracing_opentelemetry::layer().with_tracer(tracer);
Registry::default()
.with(telemetry).init();
}
The most confusing part is my apps Cargo.toml which looks like
tracing-subscriber = { version = "0.2.15" }
tracing-opentelemetry = { version= "0.11.0"}
opentelemetry = "0.12.0"
opentelemetry-jaeger = {version = "0.11.0" }
What on earth are those different crates are for? The only crate that makes sense is opentelemetry-jaeger. Are others even required?
And to my main question: I'm running Jaeger's all-in-one docker container. But when I visit http://localhost:16686, I see no traces.
Does anyone know what's happening?
Turns out when I create the Jaeger pipeline in setup_global_subscriber(), the _uninstall being returned gets dropped at the end of the function.
And when it gets dropped, collector shuts down.
To get traces I had to move contents of setup_global_subscriber() in main().
I think this is what you need.
tracing::subscriber::set_global_default(subscriber);
This sets a global Dispatcher, which Dispatcher.inner is your subscriber.

How to prevent "type-ahead"

I want to use the crate dialoguer to let the user check and correct some suggested data.
Cargo.toml dependencies
[dependencies]
dialoguer = "0.7"
src/main.rs
use dialoguer::Input;
fn main() {
// searching the web
std::thread::sleep(std::time::Duration::from_secs(5));
let suggestion = "Catpictures".to_string();
let data : String = Input::new()
.with_prompt("suggested")
.with_initial_text(suggestion)
.interact_text()
.expect("failed to correct suggestion");
}
My problem is that, while the program searches for the suggestion, the user might start typing and may press ENTER.
Then the program displays the suggestion and immediately accepts the answer.
I would like to prevent this behavior.
Current behavior:
starting program
hitting enter (for what ever reason)
program displays suggestion and immediately accepts suggestion
desired behavior:
starting program
hitting enter (for what ever reason)
program displays suggestion
user can edit suggestion and accept with enter
Is there a way to clear the input?
Neither the standard library nor dialoguer features functionality for clearing stdin.
One workaround is to use the AsyncReader from the crossterm_input crate, in which you can poll input (events) from the stdin. That way you'll be able to clear any pending input, before using dialoguer.
// crossterm_input = "0.5"
fn clear_stdin() {
let input = crossterm_input::input();
let mut async_stdin = input.read_async();
while let Some(_) = async_stdin.next() {}
}
Your updated example, will then look like this:
use dialoguer::Input;
fn main() {
// searching the web
thread::sleep(time::Duration::from_millis(200));
let suggestion = "Catpictures".to_string();
clear_stdin();
let data = Input::new()
.with_prompt("suggested")
.with_initial_text(suggestion)
.interact_text()
.expect("failed to correct suggestion");
}

Resources