don't
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor(knot): use clap more for hooks

Signed-off-by: tjh <x@tjh.dev>

tjh 87402ca4 1bf4f3a9

+71 -32
+30 -1
crates/knot/src/cli.rs
··· 216 216 jetstream::PUBLIC_JETSTREAM_INSTANCES.join(",") 217 217 } 218 218 219 - #[derive(Clone, Debug, Args)] 219 + /// Forward a git hook to the internal API. 220 + /// 221 + /// This command is expected to be invoked by git during operations via 222 + /// the global hook shims. 223 + #[derive(Clone, Args)] 220 224 pub struct HookArguments { 225 + /// Internal API endpoints. 226 + #[arg(long, value_delimiter = ',', env = knot::private::ENV_PRIVATE_ENDPOINTS)] 227 + pub api: Vec<Url>, 228 + 229 + /// DID of the repository owner. 230 + #[arg(long, env = knot::private::ENV_REPO_DID)] 231 + pub repo_did: OwnedDid, 232 + 233 + /// Record key of the repository. 234 + #[arg(long, env = knot::private::ENV_REPO_RKEY)] 235 + pub repo_rkey: String, 236 + 237 + /// Name of the hook to forward. 221 238 pub hook: HookName, 239 + } 240 + 241 + impl fmt::Debug for HookArguments { 242 + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 243 + f.debug_struct("HookArguments") 244 + // Suppress `url::Url`'s god-awful debug output. 245 + .field("api", &self.api.iter().map(Url::as_str).collect::<Vec<_>>()) 246 + .field("repo_did", &self.repo_did) 247 + .field("repo_rkey", &self.repo_rkey) 248 + .field("hook", &self.hook) 249 + .finish() 250 + } 222 251 } 223 252 224 253 #[derive(Clone, Copy, Debug, ValueEnum)]
+39 -29
crates/knot/src/hooks.rs
··· 7 7 path::Path, 8 8 }; 9 9 10 - use atproto::did::OwnedDid; 11 10 use axum::http::{HeaderMap, HeaderName, HeaderValue, header::InvalidHeaderName}; 12 11 use bytes::Bytes; 13 12 use knot::private; 14 - use url::Url; 15 13 16 - use crate::cli::HookName; 14 + use crate::cli::{HookArguments, HookName}; 17 15 18 16 /// Setup the global hooks directory at `path`. 19 17 pub fn setup_global_hooks<P: AsRef<Path>>(path: P) -> io::Result<()> { ··· 35 37 Ok(()) 36 38 } 37 39 38 - #[tracing::instrument] 39 - pub async fn run_hook(hook: HookName) -> anyhow::Result<()> { 40 + /// [`core::fmt::Debug`] an [`url::Url`] without causing eye-cancer. 41 + #[repr(transparent)] 42 + struct DebugUrl(url::Url); 43 + 44 + impl core::fmt::Debug for DebugUrl { 45 + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 46 + core::fmt::Display::fmt(&self.0, f) 47 + } 48 + } 49 + 50 + /// [`core::fmt::Debug`] a slice [`url::Url`] without causing eye-cancer. 51 + pub struct DebugUrls<'a>(pub &'a [url::Url]); 52 + 53 + impl<'a> core::fmt::Debug for DebugUrls<'a> { 54 + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 55 + let urls = unsafe { 56 + // SAFETY: Close your eyes an pray! 57 + &*(self.0 as *const [url::Url] as *const [DebugUrl]) 58 + }; 59 + core::fmt::Debug::fmt(&urls, f) 60 + } 61 + } 62 + 63 + #[tracing::instrument(fields(api = ?DebugUrls(&api)))] 64 + pub async fn run_hook( 65 + HookArguments { 66 + api, 67 + repo_did, 68 + repo_rkey, 69 + hook, 70 + }: HookArguments, 71 + ) -> anyhow::Result<()> { 72 + if api.is_empty() { 73 + tracing::warn!("internal API not specified, skipping hook"); 74 + return Ok(()); 75 + }; 76 + 40 77 let mut environment_vars: HashMap<_, _> = env::vars() 41 78 .filter(|(key, _)| !key.trim().is_empty()) 42 79 .collect(); 43 80 44 - // Take the environment variables we need to post the hook to the internal API. 45 - let Ok(endpoints) = take_var(&mut environment_vars, private::ENV_PRIVATE_ENDPOINTS) else { 46 - // We haven't been provided with the address for the internal API. 47 - tracing::warn!( 48 - "'{}' not set, skipping hook", 49 - private::ENV_PRIVATE_ENDPOINTS 50 - ); 51 - 52 - // @TODO Just run any hook scripts. 53 - 54 - return Ok(()); 55 - }; 56 - 57 - let repo_did: OwnedDid = take_var(&mut environment_vars, private::ENV_REPO_DID)?.parse()?; 58 - let repo_rkey = take_var(&mut environment_vars, private::ENV_REPO_RKEY)?; 59 81 let request_id = take_var(&mut environment_vars, "X_REQUEST_ID").ok(); 60 82 61 83 // Build a header map with the remaining environment variables. ··· 96 78 97 79 let client = reqwest::Client::new(); 98 80 let url_path = format!("/hook/{repo_did}/{repo_rkey}/{hook}"); 99 - for endpoint in endpoints.split_whitespace() { 100 - let mut hook_url = match Url::parse(endpoint) { 101 - Ok(hook_url) => hook_url, 102 - Err(error) => { 103 - tracing::error!(?error, ?endpoint, "failed to parse internal endpoint"); 104 - continue; 105 - } 106 - }; 107 - 81 + for mut hook_url in api { 108 82 hook_url.set_path(&url_path); 109 83 let response = client 110 84 .post(hook_url)
+1 -1
crates/knot/src/main.rs
··· 54 54 match cli::parse() { 55 55 cli::KnotCommand::Generate(_) => unreachable!("Handled by cli module"), 56 56 cli::KnotCommand::Serve(arguments) => runtime.block_on(knot_main(arguments)), 57 - cli::KnotCommand::Hook(arguments) => runtime.block_on(hooks::run_hook(arguments.hook)), 57 + cli::KnotCommand::Hook(arguments) => runtime.block_on(hooks::run_hook(arguments)), 58 58 } 59 59 } 60 60
+1 -1
crates/knot/src/model/knot_state.rs
··· 106 106 .into_iter() 107 107 .map(|socket| format!("http://{socket}/")) 108 108 .collect::<Vec<_>>() 109 - .join(" "); 109 + .join(","); 110 110 111 111 let repo_cache = CacheBuilder::new(config.repo_cache.size) 112 112 .name("repository_cache")