First part of actual controller

This commit is contained in:
Dreaded_X 2025-03-14 04:21:37 +01:00
parent d9cb4b4598
commit 280ac723b5
Signed by: Dreaded_X
GPG Key ID: FA5F485356B0D2D4
5 changed files with 536 additions and 114 deletions

359
Cargo.lock generated
View File

@ -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"

View File

@ -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 }

View File

@ -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<T, E = Error> = std::result::Result<T, E>;
struct Data {
client: Client,
recorder: Recorder,
pg: PasswordGenerator,
}
const CONTROLLER_NAME: &str = "lldap.huizinga.dev";
#[instrument(skip(obj, ctx))]
async fn reconcile(obj: Arc<ServiceUser>, ctx: Arc<Data>) -> Result<Action> {
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::<Secret>::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::<ServiceUser>::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<ServiceUser>, err: &Error, _ctx: Arc<Data>) -> 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::<ServiceUser>::all(client.clone());
let secrets = Api::<Secret>::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(())
}

View File

@ -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<DateTime<Utc>>,
}
#[cfg(test)]

View File

@ -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