diff --git a/Cargo.lock b/Cargo.lock index 80e5887..f6cb25e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -71,6 +71,19 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "getrandom 0.2.15", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -86,6 +99,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -119,6 +138,18 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener 5.4.0", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + [[package]] name = "async-channel" version = "1.9.0" @@ -226,6 +257,28 @@ dependencies = [ "wasm-bindgen-futures", ] +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.99", +] + [[package]] name = "async-task" version = "4.7.1" @@ -255,6 +308,17 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "backon" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49fef586913a57ff189f25c9b3d034356a5bf6b3fa9a7f067588fe1698ba1f5d" +dependencies = [ + "fastrand 2.3.0", + "gloo-timers", + "tokio", +] + [[package]] name = "backtrace" version = "0.3.74" @@ -756,6 +820,18 @@ version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.99", +] + [[package]] name = "either" version = "1.15.0" @@ -777,6 +853,26 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "enum-ordinalize" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea0dcfa4e54eeb516fe454635a95753ddd39acda650ce703031c6973e315dd5" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.99", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -852,6 +948,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -869,6 +971,7 @@ checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", + "futures-executor", "futures-io", "futures-sink", "futures-task", @@ -891,6 +994,17 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.31" @@ -1043,6 +1157,11 @@ name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] [[package]] name = "headers" @@ -1129,6 +1248,17 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "hostname" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9c7c7c8ac16c798734b8a24560c1362120597c40d5e1459f09498f8f6c8f2ba" +dependencies = [ + "cfg-if", + "libc", + "windows", +] + [[package]] name = "http" version = "0.2.12" @@ -1558,6 +1688,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json-patch" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "159294d661a039f7644cea7e4d844e6b25aaf71c1ffe9d73a96d768c24b0faf4" +dependencies = [ + "jsonptr", + "serde", + "serde_json", + "thiserror 1.0.69", +] + [[package]] name = "jsonpath-rust" version = "0.7.5" @@ -1571,6 +1713,16 @@ dependencies = [ "thiserror 2.0.12", ] +[[package]] +name = "jsonptr" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5a3cc660ba5d72bce0b3bb295bf20847ccbb40fd423f3f05b61273672e561fe" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "k8s-openapi" version = "0.24.0" @@ -1594,6 +1746,7 @@ dependencies = [ "kube-client", "kube-core", "kube-derive", + "kube-runtime", ] [[package]] @@ -1642,6 +1795,7 @@ dependencies = [ "chrono", "form_urlencoded", "http 1.3.1", + "json-patch", "k8s-openapi", "schemars", "serde", @@ -1664,6 +1818,34 @@ dependencies = [ "syn 2.0.99", ] +[[package]] +name = "kube-runtime" +version = "0.99.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88f34cfab9b4bd8633062e0e85edb81df23cb09f159f2e31c60b069ae826ffdc" +dependencies = [ + "ahash", + "async-broadcast", + "async-stream", + "async-trait", + "backon", + "educe", + "futures", + "hashbrown", + "hostname", + "json-patch", + "k8s-openapi", + "kube-client", + "parking_lot", + "pin-project", + "serde", + "serde_json", + "thiserror 2.0.12", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "kv-log-macro" version = "1.0.7" @@ -1739,11 +1921,15 @@ name = "lldap-controller" version = "0.1.0" dependencies = [ "anyhow", + "base64 0.22.1", + "chrono", "cynic", + "futures", "insta", "k8s-openapi", "kube", "lldap_auth", + "passwords", "queries", "rand 0.8.5", "schemars", @@ -1751,7 +1937,10 @@ dependencies = [ "serde_json", "serde_yaml", "surf", + "thiserror 2.0.12", "tokio", + "tracing", + "tracing-subscriber", ] [[package]] @@ -1812,7 +2001,7 @@ dependencies = [ "lazy_static", "proc-macro2", "quote", - "regex-syntax", + "regex-syntax 0.8.5", "syn 2.0.99", ] @@ -1825,6 +2014,15 @@ dependencies = [ "logos-codegen", ] +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + [[package]] name = "memchr" version = "2.7.4" @@ -1867,6 +2065,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1969,6 +2177,12 @@ dependencies = [ "syn 2.0.99", ] +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "parking" version = "2.2.1" @@ -1998,6 +2212,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "passwords" +version = "3.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11407193a7c2bd14ec6b0ec3394da6fdcf7a4d5dcbc8c3cc38dfb17802c8d59c" +dependencies = [ + "random-pick", +] + [[package]] name = "pem" version = "3.0.5" @@ -2260,6 +2483,37 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "random-number" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fc8cdd49be664772ffc3dbfa743bb8c34b78f9cc6a9f50e56ae878546796067" +dependencies = [ + "proc-macro-hack", + "rand 0.8.5", + "random-number-macro-impl", +] + +[[package]] +name = "random-number-macro-impl" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5135143cb48d14289139e4615bffec0d59b4cbfd4ea2398a3770bd2abfc4aa2" +dependencies = [ + "proc-macro-hack", + "quote", + "syn 2.0.99", +] + +[[package]] +name = "random-pick" +version = "1.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c179499072da789afe44127d5f4aa6012de2c2f96ef759990196b37387a2a0f8" +dependencies = [ + "random-number", +] + [[package]] name = "redox_syscall" version = "0.5.10" @@ -2297,8 +2551,17 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", ] [[package]] @@ -2309,9 +2572,15 @@ checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.8.5", ] +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "regex-syntax" version = "0.8.5" @@ -2464,6 +2733,7 @@ version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" dependencies = [ + "chrono", "dyn-clone", "schemars_derive", "serde", @@ -2687,6 +2957,15 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.3.0" @@ -2943,6 +3222,16 @@ dependencies = [ "syn 2.0.99", ] +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "time" version = "0.2.27" @@ -3040,6 +3329,7 @@ dependencies = [ "futures-core", "futures-sink", "pin-project-lite", + "slab", "tokio", ] @@ -3120,6 +3410,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", + "valuable", ] [[package]] @@ -3132,6 +3423,48 @@ dependencies = [ "tracing", ] +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", +] + [[package]] name = "try-lock" version = "0.2.5" @@ -3214,6 +3547,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "value-bag" version = "1.10.0" @@ -3362,6 +3701,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core", + "windows-targets", +] + [[package]] name = "windows-core" version = "0.52.0" diff --git a/Cargo.toml b/Cargo.toml index a6ee4c9..f310f80 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,11 +20,18 @@ serde_json = "1.0.140" surf = "2.3.2" cynic = { workspace = true, features = ["http-surf"] } tokio = { version = "1.44.0", features = ["full"] } -kube = { version = "0.99.0", features = ["derive"] } +kube = { version = "0.99.0", features = ["derive", "runtime"] } k8s-openapi = { version = "0.24.0", features = ["v1_31"] } -schemars = "0.8.22" +schemars = { version = "0.8.22", features = ["chrono"] } serde = { version = "1.0.219", features = ["derive"] } serde_yaml = "0.9.34" +futures = "0.3.31" +tracing-subscriber = { version = "0.3.19", features = ["json", "env-filter"] } +tracing = "0.1.41" +thiserror = "2.0.12" +chrono = "0.4.40" +passwords = "3.1.16" +base64 = "0.22.1" [dev-dependencies] insta = { workspace = true } diff --git a/src/main.rs b/src/main.rs index 385dbb5..a81a877 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,111 +1,176 @@ -use std::time::Duration; +use std::{collections::BTreeMap, sync::Arc, time::Duration}; -use anyhow::anyhow; -use cynic::{http::SurfExt, MutationBuilder, QueryBuilder}; -use lldap_controller::lldap::change_password; -use queries::{ - AddUserToGroup, AddUserToGroupVariables, CreateManagedUserAttribute, CreateUser, - CreateUserVariables, DeleteUser, DeleteUserVariables, GetUserAttributes, ListManagedUsers, +use futures::StreamExt; +use k8s_openapi::api::core::v1::Secret; +use kube::{ + api::{ObjectMeta, Patch, PatchParams, PostParams}, + runtime::{ + controller::Action, + events::{Event, EventType, Recorder, Reporter}, + Controller, + }, + Api, Client, Resource, }; -use surf::{Client, Config, Url}; +use lldap_controller::resources::{ServiceUser, ServiceUserStatus}; +use passwords::PasswordGenerator; +use serde_json::json; +use tracing::{debug, info, instrument, warn}; +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Registry}; + +#[derive(thiserror::Error, Debug)] +enum Error { + #[error("Failed to commit secret: {0}")] + Commit(#[source] kube::api::entry::CommitError), + #[error("{0}")] + Kube(#[source] kube::Error), + #[error("MissingObjectKey: {0}")] + MissingObjectKey(&'static str), +} + +type Result = std::result::Result; + +struct Data { + client: Client, + recorder: Recorder, + pg: PasswordGenerator, +} + +const CONTROLLER_NAME: &str = "lldap.huizinga.dev"; + +#[instrument(skip(obj, ctx))] +async fn reconcile(obj: Arc, ctx: Arc) -> Result { + let name = obj + .metadata + .name + .clone() + .ok_or(Error::MissingObjectKey(".metadata.name"))?; + let namespace = obj + .metadata + .namespace + .clone() + .ok_or(Error::MissingObjectKey(".metadata.namespace"))?; + let oref = obj.controller_owner_ref(&()).unwrap(); + + debug!(name, "reconcile request"); + + let client = &ctx.client; + let secrets = Api::::namespaced(client.clone(), &namespace); + + // TODO: Potentially issue: someone modifies the secret and removes the pass + let mut created = false; + let mut secret = secrets + .entry(&name) + .await + .map_err(Error::Kube)? + .or_insert(|| { + debug!(name, "Generating new secret"); + + let mut contents = BTreeMap::new(); + contents.insert("username".into(), name.clone()); + contents.insert("password".into(), ctx.pg.generate_one().unwrap()); + + created = true; + + Secret { + metadata: ObjectMeta { + owner_references: Some(vec![oref]), + ..Default::default() + }, + string_data: Some(contents), + ..Default::default() + } + }); + + secret + .commit(&PostParams { + dry_run: false, + field_manager: Some(CONTROLLER_NAME.into()), + }) + .await + .map_err(Error::Commit)?; + let secret = secret; + + if created { + debug!(name, "Sending SecretCreated event"); + + // The reason this is here instead of inside the or_insert is that we + // want to send the event _after_ it successfully committed. + // Also or_insert is not async! + ctx.recorder + .publish( + &Event { + type_: EventType::Normal, + reason: "SecretCreated".into(), + note: Some(format!("Created secret '{name}'")), + action: "NewSecret".into(), + secondary: Some(secret.get().object_ref(&())), + }, + &obj.object_ref(&()), + ) + .await + .map_err(Error::Kube)?; + } + + let service_users = Api::::namespaced(client.clone(), &namespace); + let status = json!({ + "status": ServiceUserStatus { secret_created: secret.get().meta().creation_timestamp.as_ref().map(|ts| ts.0) } + }); + service_users + .patch_status(&name, &PatchParams::default(), &Patch::Merge(&status)) + .await + .map_err(Error::Kube)?; + + Ok(Action::requeue(Duration::from_secs(3600))) +} + +fn error_policy(_obj: Arc, err: &Error, _ctx: Arc) -> Action { + warn!("error: {}", err); + Action::requeue(Duration::from_secs(5)) +} #[tokio::main] async fn main() -> anyhow::Result<()> { - let token = std::env::var("LLDAP_TOKEN")?; + let logger = tracing_subscriber::fmt::layer().json(); + let env_filter = EnvFilter::try_from_default_env() + .or_else(|_| EnvFilter::try_new("info")) + .unwrap(); - let base_url = "http://localhost:17170"; - let users = [ - "authelia".to_owned(), - "grafana".to_owned(), - "gitea".to_owned(), - ]; + Registry::default().with(logger).with(env_filter).init(); - let client: Client = Config::new() - .set_base_url(Url::parse(base_url)?) - .set_timeout(Some(Duration::from_secs(1))) - .add_header("Authorization", format!("Bearer {token}")) - .map_err(|e| anyhow!(e))? - .try_into()?; + info!("Starting controller"); - let operation = GetUserAttributes::build(()); - let response = client - .post("/api/graphql") - .run_graphql(operation) - .await - .map_err(|e| anyhow!(e))?; + let client = Client::try_default().await?; - let has_managed = response - .data - .as_ref() - .expect("Should get data") - .schema - .user_schema - .attributes - .iter() - .any(|attr| attr.name == "managed"); + let reporter: Reporter = CONTROLLER_NAME.into(); + let recorder = Recorder::new(client.clone(), reporter); - if !has_managed { - let operation = CreateManagedUserAttribute::build(()); - let _response = client - .post("/api/graphql") - .run_graphql(operation) - .await - .map_err(|e| anyhow!(e))?; - } + let pg = PasswordGenerator::new() + .length(32) + .uppercase_letters(true) + .strict(true); - let operation = ListManagedUsers::build(()); - let response = client - .post("/api/graphql") - .run_graphql(operation) - .await - .map_err(|e| anyhow!(e))?; + let service_users = Api::::all(client.clone()); + let secrets = Api::::all(client.clone()); - let (existing, remove): (Vec<_>, Vec<_>) = response - .data - .expect("Should get data") - .users - .into_iter() - .map(|user| user.id) - .partition(|user| users.contains(user)); - - let (update, create): (Vec<_>, Vec<_>) = users.iter().partition(|user| existing.contains(user)); - - for id in &remove { - println!("Removing '{id}"); - - let operation = DeleteUser::build(DeleteUserVariables { id }); - let _response = client - .post("/api/graphql") - .run_graphql(operation) - .await - .map_err(|e| anyhow!(e))?; - } - - for id in create { - println!("Creating '{id}'"); - - let operation = CreateUser::build(CreateUserVariables { id }); - let _response = client - .post("/api/graphql") - .run_graphql(operation) - .await - .map_err(|e| anyhow!(e))?; - - let operation = AddUserToGroup::build(AddUserToGroupVariables { id, group: 3 }); - let _response = client - .post("/api/graphql") - .run_graphql(operation) - .await - .map_err(|e| anyhow!(e))?; - - change_password(&client, id, "JustATest").await?; - } - - for id in update { - println!("Updating '{id}'"); - - change_password(&client, id, "JustATest").await?; - } + Controller::new(service_users.clone(), Default::default()) + .owns(secrets, Default::default()) + .shutdown_on_signal() + .run( + reconcile, + error_policy, + Arc::new(Data { + client, + recorder, + pg, + }), + ) + .for_each(|res| async move { + match res { + Ok(obj) => debug!("reconciled {:?}", obj.0.name), + Err(err) => warn!("reconcile failed: {}", err), + } + }) + .await; Ok(()) } diff --git a/src/resources.rs b/src/resources.rs index b4ae438..bb1528d 100644 --- a/src/resources.rs +++ b/src/resources.rs @@ -1,3 +1,4 @@ +use chrono::{DateTime, Utc}; use kube::CustomResource; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -13,9 +14,9 @@ use serde::{Deserialize, Serialize}; #[kube( shortname = "lsu", doc = "Custom resource for managing Service Users inside of LLDAP", - printcolumn = r#"{"name":"Exists", "type":"boolean", "description":"Does the service user exist in LLDAP", "jsonPath":".status.exists"}"#, printcolumn = r#"{"name":"Manager", "type":"boolean", "description":"Can the service user manage passwords", "jsonPath":".spec.passwordManager"}"#, - printcolumn = r#"{"name":"Age", "type":"date", "jsonPath":".metadata.creationTimestamp"}"# + printcolumn = r#"{"name":"Age", "type":"date", "jsonPath":".metadata.creationTimestamp"}"#, + printcolumn = r#"{"name":"Secret", "type":"date", "description":"Secret creation timestamp", "jsonPath":".status.secret_created"}"# )] #[serde(rename_all = "camelCase")] pub struct ServiceUserSpec { @@ -27,7 +28,7 @@ pub struct ServiceUserSpec { #[derive(Deserialize, Serialize, Clone, Default, Debug, JsonSchema)] pub struct ServiceUserStatus { - pub exists: bool, + pub secret_created: Option>, } #[cfg(test)] diff --git a/src/snapshots/lldap_controller__resources__tests__service_user_crd_output.snap b/src/snapshots/lldap_controller__resources__tests__service_user_crd_output.snap index ae678ab..b6a1745 100644 --- a/src/snapshots/lldap_controller__resources__tests__service_user_crd_output.snap +++ b/src/snapshots/lldap_controller__resources__tests__service_user_crd_output.snap @@ -18,10 +18,6 @@ spec: scope: Namespaced versions: - additionalPrinterColumns: - - description: Does the service user exist in LLDAP - jsonPath: ".status.exists" - name: Exists - type: boolean - description: Can the service user manage passwords jsonPath: ".spec.passwordManager" name: Manager @@ -29,6 +25,10 @@ spec: - jsonPath: ".metadata.creationTimestamp" name: Age type: date + - description: Secret creation timestamp + jsonPath: ".status.secret_created" + name: Secret + type: date name: v1 schema: openAPIV3Schema: @@ -48,10 +48,10 @@ spec: status: nullable: true properties: - exists: - type: boolean - required: - - exists + secret_created: + format: date-time + nullable: true + type: string type: object required: - spec