feat: add common queue
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
parent
ee323e99e8
commit
ea5adb2f93
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -263,6 +263,7 @@ dependencies = [
|
|||||||
"reqwest",
|
"reqwest",
|
||||||
"rusqlite",
|
"rusqlite",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"toml",
|
"toml",
|
||||||
|
@ -28,3 +28,4 @@ toml = "0.8.19"
|
|||||||
dirs = "5.0.1"
|
dirs = "5.0.1"
|
||||||
futures = "0.3.31"
|
futures = "0.3.31"
|
||||||
reqwest = { version = "0.12.9", features = ["json"] }
|
reqwest = { version = "0.12.9", features = ["json"] }
|
||||||
|
serde_json = "1.0.133"
|
||||||
|
@ -30,5 +30,6 @@ message ListenEventsRequest {
|
|||||||
optional string id = 2;
|
optional string id = 2;
|
||||||
}
|
}
|
||||||
message ListenEventsResponse {
|
message ListenEventsResponse {
|
||||||
string value = 1;
|
string id = 1;
|
||||||
|
string value = 2;
|
||||||
}
|
}
|
||||||
|
@ -4,12 +4,20 @@ use refresh::AgentRefresh;
|
|||||||
|
|
||||||
pub use config::setup_config;
|
pub use config::setup_config;
|
||||||
|
|
||||||
|
pub mod models;
|
||||||
|
|
||||||
mod agent_state;
|
mod agent_state;
|
||||||
mod config;
|
mod config;
|
||||||
mod discovery_client;
|
mod discovery_client;
|
||||||
mod event_handler;
|
mod event_handler;
|
||||||
mod grpc_client;
|
mod grpc_client;
|
||||||
|
mod queue;
|
||||||
mod refresh;
|
mod refresh;
|
||||||
|
mod scheduler;
|
||||||
|
|
||||||
|
mod handlers;
|
||||||
|
|
||||||
|
mod actions;
|
||||||
|
|
||||||
pub async fn execute() -> anyhow::Result<()> {
|
pub async fn execute() -> anyhow::Result<()> {
|
||||||
let state = AgentState::new().await?;
|
let state = AgentState::new().await?;
|
||||||
@ -17,6 +25,7 @@ pub async fn execute() -> anyhow::Result<()> {
|
|||||||
notmad::Mad::builder()
|
notmad::Mad::builder()
|
||||||
.add(AgentRefresh::new(&state))
|
.add(AgentRefresh::new(&state))
|
||||||
.add(EventHandler::new(&state))
|
.add(EventHandler::new(&state))
|
||||||
|
.add(state.queue.clone())
|
||||||
.cancellation(Some(std::time::Duration::from_secs(2)))
|
.cancellation(Some(std::time::Duration::from_secs(2)))
|
||||||
.run()
|
.run()
|
||||||
.await?;
|
.await?;
|
||||||
|
59
crates/churn/src/agent/actions.rs
Normal file
59
crates/churn/src/agent/actions.rs
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
use anyhow::Context;
|
||||||
|
|
||||||
|
pub struct Plan {}
|
||||||
|
impl Plan {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn tasks(&self) -> anyhow::Result<Vec<Task>> {
|
||||||
|
Ok(vec![Task::new()])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Task {}
|
||||||
|
|
||||||
|
impl Task {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn id(&self) -> String {
|
||||||
|
"apt".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn should_run(&self) -> anyhow::Result<bool> {
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn execute(&self) -> anyhow::Result<()> {
|
||||||
|
let mut cmd = tokio::process::Command::new("apt-get");
|
||||||
|
cmd.args(["update", "-q"]);
|
||||||
|
let output = cmd.output().await.context("failed to run apt update")?;
|
||||||
|
match output.status.success() {
|
||||||
|
true => tracing::info!("successfully ran apt update"),
|
||||||
|
false => {
|
||||||
|
anyhow::bail!(
|
||||||
|
"failed to run apt update: {}",
|
||||||
|
std::str::from_utf8(&output.stderr)?
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut cmd = tokio::process::Command::new("apt-get");
|
||||||
|
cmd.env("DEBIAN_FRONTEND", "noninteractive")
|
||||||
|
.args(["upgrade", "-y"]);
|
||||||
|
let output = cmd.output().await.context("failed to run apt upgrade")?;
|
||||||
|
match output.status.success() {
|
||||||
|
true => tracing::info!("successfully ran apt upgrade"),
|
||||||
|
false => {
|
||||||
|
anyhow::bail!(
|
||||||
|
"failed to run apt upgrade: {}",
|
||||||
|
std::str::from_utf8(&output.stderr)?
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,14 @@ use std::{ops::Deref, sync::Arc};
|
|||||||
|
|
||||||
use crate::api::Discovery;
|
use crate::api::Discovery;
|
||||||
|
|
||||||
use super::{config::AgentConfig, discovery_client::DiscoveryClient, grpc_client::GrpcClient};
|
use super::{
|
||||||
|
config::AgentConfig,
|
||||||
|
discovery_client::DiscoveryClient,
|
||||||
|
grpc_client::GrpcClient,
|
||||||
|
handlers::scheduled_tasks::{self, ScheduledTasks},
|
||||||
|
queue::AgentQueue,
|
||||||
|
scheduler::Scheduler,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct AgentState(Arc<State>);
|
pub struct AgentState(Arc<State>);
|
||||||
@ -31,20 +38,23 @@ pub struct State {
|
|||||||
pub grpc: GrpcClient,
|
pub grpc: GrpcClient,
|
||||||
pub config: AgentConfig,
|
pub config: AgentConfig,
|
||||||
pub discovery: Discovery,
|
pub discovery: Discovery,
|
||||||
|
pub queue: AgentQueue,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
pub async fn new() -> anyhow::Result<Self> {
|
pub async fn new() -> anyhow::Result<Self> {
|
||||||
let config = AgentConfig::new().await?;
|
let config = AgentConfig::new().await?;
|
||||||
let discovery = DiscoveryClient::new(&config.discovery);
|
let discovery = DiscoveryClient::new(&config.discovery).discover().await?;
|
||||||
let discovery = discovery.discover().await?;
|
|
||||||
|
|
||||||
let grpc = GrpcClient::new(&discovery.process_host);
|
let grpc = GrpcClient::new(&discovery.process_host);
|
||||||
|
let scheduled_tasks = ScheduledTasks::new();
|
||||||
|
let scheduler = Scheduler::new(scheduled_tasks);
|
||||||
|
let queue = AgentQueue::new(scheduler);
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
grpc,
|
grpc,
|
||||||
config,
|
config,
|
||||||
discovery,
|
discovery,
|
||||||
|
queue,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,16 @@
|
|||||||
use notmad::{Component, MadError};
|
use notmad::{Component, MadError};
|
||||||
|
|
||||||
use super::{agent_state::AgentState, config::AgentConfig, grpc_client::GrpcClient};
|
use crate::agent::models::Commands;
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
agent_state::AgentState, config::AgentConfig, grpc_client::GrpcClient, queue::AgentQueue,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct EventHandler {
|
pub struct EventHandler {
|
||||||
config: AgentConfig,
|
config: AgentConfig,
|
||||||
grpc: GrpcClient,
|
grpc: GrpcClient,
|
||||||
|
queue: AgentQueue,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventHandler {
|
impl EventHandler {
|
||||||
@ -15,6 +20,7 @@ impl EventHandler {
|
|||||||
Self {
|
Self {
|
||||||
config: state.config.clone(),
|
config: state.config.clone(),
|
||||||
grpc: state.grpc.clone(),
|
grpc: state.grpc.clone(),
|
||||||
|
queue: state.queue.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -46,7 +52,11 @@ impl Component for EventHandler {
|
|||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl super::grpc_client::ListenEventsExecutor for EventHandler {
|
impl super::grpc_client::ListenEventsExecutor for EventHandler {
|
||||||
async fn execute(&self, event: crate::grpc::ListenEventsResponse) -> anyhow::Result<()> {
|
async fn execute(&self, event: crate::grpc::ListenEventsResponse) -> anyhow::Result<()> {
|
||||||
tracing::info!(value = event.value, "received event");
|
tracing::info!(value = event.id, "received event");
|
||||||
|
|
||||||
|
let event: Commands = serde_json::from_str(&event.value)?;
|
||||||
|
|
||||||
|
self.queue.publish(event).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
1
crates/churn/src/agent/handlers.rs
Normal file
1
crates/churn/src/agent/handlers.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub mod scheduled_tasks;
|
36
crates/churn/src/agent/handlers/scheduled_tasks.rs
Normal file
36
crates/churn/src/agent/handlers/scheduled_tasks.rs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use crate::agent::actions::Plan;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ScheduledTasks {}
|
||||||
|
impl ScheduledTasks {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handle(
|
||||||
|
&self,
|
||||||
|
task: &str,
|
||||||
|
_properties: BTreeMap<String, String>,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
// Get plan
|
||||||
|
let plan = Plan::new();
|
||||||
|
let tasks = plan.tasks().await?;
|
||||||
|
|
||||||
|
// For task
|
||||||
|
for task in tasks {
|
||||||
|
// Check idempotency rules
|
||||||
|
if !task.should_run().await? {
|
||||||
|
tracing::debug!(task = task.id(), "skipping run");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run task if not valid
|
||||||
|
tracing::info!(task = task.id(), "executing task");
|
||||||
|
task.execute().await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
20
crates/churn/src/agent/models.rs
Normal file
20
crates/churn/src/agent/models.rs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
use std::{collections::BTreeMap, fmt::Display};
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub enum Commands {
|
||||||
|
ScheduleTask {
|
||||||
|
task: String,
|
||||||
|
properties: BTreeMap<String, String>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Commands {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str(match self {
|
||||||
|
Commands::ScheduleTask { .. } => "schedule_task",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
67
crates/churn/src/agent/queue.rs
Normal file
67
crates/churn/src/agent/queue.rs
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use notmad::{Component, MadError};
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use super::{handlers::scheduled_tasks::ScheduledTasks, models::Commands, scheduler::Scheduler};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct AgentQueue {
|
||||||
|
sender: Arc<tokio::sync::mpsc::Sender<Commands>>,
|
||||||
|
receiver: Arc<Mutex<tokio::sync::mpsc::Receiver<Commands>>>,
|
||||||
|
|
||||||
|
scheduler: Scheduler,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AgentQueue {
|
||||||
|
pub fn new(scheduler: Scheduler) -> Self {
|
||||||
|
let (tx, rx) = tokio::sync::mpsc::channel(5);
|
||||||
|
Self {
|
||||||
|
sender: Arc::new(tx),
|
||||||
|
receiver: Arc::new(Mutex::new(rx)),
|
||||||
|
|
||||||
|
scheduler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handler(&self, command: Commands) -> anyhow::Result<()> {
|
||||||
|
tracing::debug!("handling task");
|
||||||
|
|
||||||
|
self.scheduler.handle(command).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn publish(&self, command: Commands) -> anyhow::Result<()> {
|
||||||
|
tracing::debug!("publishing task: {}", command.to_string());
|
||||||
|
|
||||||
|
self.sender.send(command).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Component for AgentQueue {
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
cancellation_token: tokio_util::sync::CancellationToken,
|
||||||
|
) -> Result<(), notmad::MadError> {
|
||||||
|
loop {
|
||||||
|
let mut recv = self.receiver.lock().await;
|
||||||
|
|
||||||
|
tokio::select! {
|
||||||
|
res = recv.recv() => {
|
||||||
|
if let Some(res) = res {
|
||||||
|
self.handler(res).await.map_err(MadError::Inner)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = cancellation_token.cancelled() => {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,15 @@
|
|||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
|
|
||||||
use super::agent_state::AgentState;
|
use crate::agent::models::Commands;
|
||||||
|
|
||||||
|
use super::{agent_state::AgentState, queue::AgentQueue};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct AgentRefresh {
|
pub struct AgentRefresh {
|
||||||
process_host: String,
|
process_host: String,
|
||||||
|
queue: AgentQueue,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AgentRefresh {
|
impl AgentRefresh {
|
||||||
@ -12,6 +17,7 @@ impl AgentRefresh {
|
|||||||
let state: AgentState = state.into();
|
let state: AgentState = state.into();
|
||||||
Self {
|
Self {
|
||||||
process_host: state.discovery.process_host.clone(),
|
process_host: state.discovery.process_host.clone(),
|
||||||
|
queue: state.queue.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -44,80 +50,12 @@ impl nodrift::Drifter for AgentRefresh {
|
|||||||
async fn execute(&self, _token: tokio_util::sync::CancellationToken) -> anyhow::Result<()> {
|
async fn execute(&self, _token: tokio_util::sync::CancellationToken) -> anyhow::Result<()> {
|
||||||
tracing::info!(process_host = self.process_host, "refreshing agent");
|
tracing::info!(process_host = self.process_host, "refreshing agent");
|
||||||
|
|
||||||
// Get plan
|
self.queue
|
||||||
let plan = Plan::new();
|
.publish(Commands::ScheduleTask {
|
||||||
let tasks = plan.tasks().await?;
|
task: "update".into(),
|
||||||
|
properties: BTreeMap::default(),
|
||||||
// For task
|
})
|
||||||
for task in tasks {
|
.await?;
|
||||||
// Check idempotency rules
|
|
||||||
if !task.should_run().await? {
|
|
||||||
tracing::debug!(task = task.id(), "skipping run");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run task if not valid
|
|
||||||
tracing::info!(task = task.id(), "executing task");
|
|
||||||
task.execute().await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Plan {}
|
|
||||||
impl Plan {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn tasks(&self) -> anyhow::Result<Vec<Task>> {
|
|
||||||
Ok(vec![Task::new()])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Task {}
|
|
||||||
|
|
||||||
impl Task {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn id(&self) -> String {
|
|
||||||
"apt".into()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn should_run(&self) -> anyhow::Result<bool> {
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn execute(&self) -> anyhow::Result<()> {
|
|
||||||
let mut cmd = tokio::process::Command::new("apt-get");
|
|
||||||
cmd.args(["update", "-q"]);
|
|
||||||
let output = cmd.output().await.context("failed to run apt update")?;
|
|
||||||
match output.status.success() {
|
|
||||||
true => tracing::info!("successfully ran apt update"),
|
|
||||||
false => {
|
|
||||||
anyhow::bail!(
|
|
||||||
"failed to run apt update: {}",
|
|
||||||
std::str::from_utf8(&output.stderr)?
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut cmd = tokio::process::Command::new("apt-get");
|
|
||||||
cmd.env("DEBIAN_FRONTEND", "noninteractive")
|
|
||||||
.args(["upgrade", "-y"]);
|
|
||||||
let output = cmd.output().await.context("failed to run apt upgrade")?;
|
|
||||||
match output.status.success() {
|
|
||||||
true => tracing::info!("successfully ran apt upgrade"),
|
|
||||||
false => {
|
|
||||||
anyhow::bail!(
|
|
||||||
"failed to run apt upgrade: {}",
|
|
||||||
std::str::from_utf8(&output.stderr)?
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
22
crates/churn/src/agent/scheduler.rs
Normal file
22
crates/churn/src/agent/scheduler.rs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
use super::{handlers::scheduled_tasks::ScheduledTasks, models::Commands};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Scheduler {
|
||||||
|
scheduled_tasks: ScheduledTasks,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Scheduler {
|
||||||
|
pub fn new(scheduled_tasks: ScheduledTasks) -> Self {
|
||||||
|
Self { scheduled_tasks }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handle(&self, command: Commands) -> anyhow::Result<()> {
|
||||||
|
match command {
|
||||||
|
Commands::ScheduleTask { task, properties } => {
|
||||||
|
self.scheduled_tasks.handle(&task, properties).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
@ -44,6 +44,8 @@ pub struct ListenEventsRequest {
|
|||||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
pub struct ListenEventsResponse {
|
pub struct ListenEventsResponse {
|
||||||
#[prost(string, tag="1")]
|
#[prost(string, tag="1")]
|
||||||
|
pub id: ::prost::alloc::string::String,
|
||||||
|
#[prost(string, tag="2")]
|
||||||
pub value: ::prost::alloc::string::String,
|
pub value: ::prost::alloc::string::String,
|
||||||
}
|
}
|
||||||
include!("churn.v1.tonic.rs");
|
include!("churn.v1.tonic.rs");
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
use std::{net::SocketAddr, pin::Pin};
|
use std::{collections::BTreeMap, net::SocketAddr, pin::Pin};
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use futures::Stream;
|
use futures::Stream;
|
||||||
use notmad::{Component, MadError};
|
use notmad::{Component, MadError};
|
||||||
use tonic::transport::Server;
|
use tonic::transport::Server;
|
||||||
|
|
||||||
use crate::grpc::*;
|
use crate::{agent::models::Commands, grpc::*};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct GrpcServer {
|
pub struct GrpcServer {
|
||||||
@ -70,9 +70,18 @@ impl crate::grpc::churn_server::Churn for GrpcServer {
|
|||||||
loop {
|
loop {
|
||||||
interval.tick().await;
|
interval.tick().await;
|
||||||
|
|
||||||
|
let Ok(schedule_task) = serde_json::to_string(&Commands::ScheduleTask {
|
||||||
|
task: "refresh".into(),
|
||||||
|
properties: BTreeMap::default(),
|
||||||
|
}) else {
|
||||||
|
tracing::warn!("failed to serialize event");
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
if let Err(e) = tx
|
if let Err(e) = tx
|
||||||
.send(Ok(ListenEventsResponse {
|
.send(Ok(ListenEventsResponse {
|
||||||
value: uuid::Uuid::new_v4().to_string(),
|
id: uuid::Uuid::new_v4().to_string(),
|
||||||
|
value: schedule_task,
|
||||||
}))
|
}))
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user