feat: add tui
This commit is contained in:
@@ -18,6 +18,7 @@ tracing.workspace = true
|
||||
uuid = { version = "1.19.0", features = ["serde", "v4", "v7"] }
|
||||
|
||||
[dev-dependencies]
|
||||
nocontrol-tui = { path = "../nocontrol-tui" }
|
||||
insta = "1.46.0"
|
||||
tracing-subscriber = { version = "0.3.22", features = ["env-filter"] }
|
||||
tracing-test = { version = "0.2.5", features = ["no-env-filter"] }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::io::{BufRead, Write};
|
||||
use std::time::Duration;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use nocontrol::{
|
||||
@@ -10,10 +10,10 @@ use tracing_subscriber::EnvFilter;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
// Setup logging to file
|
||||
let output_file = std::fs::File::create("target/nocontrol.log")?;
|
||||
|
||||
tracing_subscriber::fmt()
|
||||
// .pretty()
|
||||
.with_env_filter(EnvFilter::from_default_env())
|
||||
.with_writer(output_file)
|
||||
.with_file(false)
|
||||
@@ -23,109 +23,57 @@ async fn main() -> anyhow::Result<()> {
|
||||
.init();
|
||||
|
||||
let operator = MyOperator {};
|
||||
let control_plane = nocontrol::ControlPlane::new(operator);
|
||||
|
||||
let mut control_plane = nocontrol::ControlPlane::new(operator);
|
||||
// control_plane.with_deadline(std::time::Duration::from_secs(4));
|
||||
// Add initial manifest
|
||||
control_plane
|
||||
.add_manifest(Manifest {
|
||||
name: "initial-deployment".into(),
|
||||
metadata: ManifestMetadata {},
|
||||
spec: Specifications::Deployment(DeploymentControllerManifest {
|
||||
name: "initial-app".into(),
|
||||
}),
|
||||
})
|
||||
.await?;
|
||||
|
||||
// Spawn random manifest updater
|
||||
tokio::spawn({
|
||||
let control_plane = control_plane.clone();
|
||||
async move {
|
||||
control_plane
|
||||
.add_manifest(Manifest {
|
||||
name: "some-manifest".into(),
|
||||
metadata: ManifestMetadata {},
|
||||
spec: Specifications::Deployment(DeploymentControllerManifest {
|
||||
name: "some-name".into(),
|
||||
}),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
loop {
|
||||
let rand = {
|
||||
use rand::prelude::*;
|
||||
use rand::Rng;
|
||||
let mut rng = rand::rng();
|
||||
rng.random_range(2..5)
|
||||
rng.random_range(3..8)
|
||||
};
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_secs(rand)).await;
|
||||
tokio::time::sleep(Duration::from_secs(rand)).await;
|
||||
|
||||
let random = uuid::Uuid::now_v7();
|
||||
|
||||
control_plane
|
||||
let _ = control_plane
|
||||
.add_manifest(Manifest {
|
||||
name: "some-manifest".into(),
|
||||
name: "initial-deployment".into(),
|
||||
metadata: ManifestMetadata {},
|
||||
spec: Specifications::Deployment(DeploymentControllerManifest {
|
||||
name: format!("some-changed-name: {}", random),
|
||||
name: format!("app-{}", &random.to_string()[..8]),
|
||||
}),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
.await;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Debugging shell
|
||||
// Spawn control plane
|
||||
tokio::spawn({
|
||||
let control_plane = control_plane.clone();
|
||||
|
||||
async move {
|
||||
let ui = Ui {};
|
||||
|
||||
loop {
|
||||
ui.write("> ");
|
||||
let cmd = ui.read_line();
|
||||
|
||||
let items = cmd.split(" ").map(|t| t.to_string()).collect::<Vec<_>>();
|
||||
|
||||
let (command, args) = match &items[..] {
|
||||
[first, rest @ ..] => (first, rest.to_vec()),
|
||||
//[first] => (first, vec![]),
|
||||
_ => {
|
||||
ui.writeln("invalid command");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
match (command.as_str(), args.as_slice()) {
|
||||
("get", _) => {
|
||||
// get all for now
|
||||
let manifests = control_plane
|
||||
.get_manifests()
|
||||
.await
|
||||
.inspect_err(|e| ui.writeln(format!("get failed: {e:#}")))
|
||||
.unwrap();
|
||||
|
||||
ui.writeln("listing manifests");
|
||||
|
||||
for manifest in manifests {
|
||||
ui.writeln(format!(" - {}", manifest.manifest.name));
|
||||
}
|
||||
}
|
||||
("describe", [manifest_name, ..]) => {
|
||||
let manifests = control_plane
|
||||
.get_manifests()
|
||||
.await
|
||||
.inspect_err(|e| ui.writeln(format!("get failed: {e:#}")))
|
||||
.unwrap();
|
||||
|
||||
if let Some(manifest) =
|
||||
manifests.iter().find(|m| &m.manifest.name == manifest_name)
|
||||
{
|
||||
let output = serde_json::to_string_pretty(&manifest).unwrap();
|
||||
ui.writeln(output);
|
||||
}
|
||||
}
|
||||
(cmd, _) => ui.writeln(format!("command is not implemented: {}", cmd)),
|
||||
}
|
||||
|
||||
ui.writeln("");
|
||||
}
|
||||
let _ = control_plane.execute().await;
|
||||
}
|
||||
});
|
||||
|
||||
control_plane.execute().await?;
|
||||
// Run TUI
|
||||
nocontrol_tui::run(control_plane).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -180,29 +128,3 @@ impl Specification for Specifications {
|
||||
pub struct DeploymentControllerManifest {
|
||||
name: String,
|
||||
}
|
||||
|
||||
pub struct Ui {}
|
||||
|
||||
impl Ui {
|
||||
pub fn write(&self, msg: &str) {
|
||||
let mut stderr = std::io::stderr().lock();
|
||||
stderr.write_all(msg.as_bytes()).unwrap();
|
||||
stderr.flush().unwrap()
|
||||
}
|
||||
pub fn writeln(&self, msg: impl AsRef<str>) {
|
||||
let msg = msg.as_ref();
|
||||
|
||||
let mut stderr = std::io::stderr().lock();
|
||||
stderr.write_all(msg.as_bytes()).unwrap();
|
||||
writeln!(stderr).unwrap();
|
||||
stderr.flush().unwrap()
|
||||
}
|
||||
pub fn read_line(&self) -> String {
|
||||
let mut stdin = std::io::stdin().lock();
|
||||
let mut output = String::new();
|
||||
|
||||
stdin.read_line(&mut output).unwrap();
|
||||
|
||||
output.trim().to_string()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,6 +122,7 @@ impl<T: Specification> BackingStore<T> {
|
||||
events: Vec::default(),
|
||||
changes: vec![ManifestChangeEvent {
|
||||
created: now,
|
||||
handled: false,
|
||||
event: ManifestChangeEventType::Changed,
|
||||
}],
|
||||
},
|
||||
|
||||
@@ -60,11 +60,22 @@ impl<T: Operator> Reconciler<T> {
|
||||
}
|
||||
|
||||
// 4. Check desired vs actual
|
||||
for manifest in our_manifests.iter_mut() {
|
||||
'manifest: for manifest in our_manifests.iter_mut() {
|
||||
// Currently periodic sync,
|
||||
// TODO: this should also be made event based
|
||||
|
||||
if let Some(change) = manifest.status.changes.first() {
|
||||
if change.handled {
|
||||
continue 'manifest;
|
||||
}
|
||||
}
|
||||
|
||||
self.operator.reconcile(manifest).await?;
|
||||
self.store.update_state(manifest).await?;
|
||||
|
||||
if let Some(change) = manifest.status.changes.first_mut() {
|
||||
change.handled = true
|
||||
}
|
||||
}
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
|
||||
|
||||
@@ -58,6 +58,7 @@ pub enum ManifestStatusState {
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct ManifestChangeEvent {
|
||||
pub created: jiff::Timestamp,
|
||||
pub handled: bool,
|
||||
pub event: ManifestChangeEventType,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user