···216216 jetstream::PUBLIC_JETSTREAM_INSTANCES.join(",")217217}218218219219-#[derive(Clone, Debug, Args)]219219+/// Forward a git hook to the internal API.220220+///221221+/// This command is expected to be invoked by git during operations via222222+/// the global hook shims.223223+#[derive(Clone, Args)]220224pub struct HookArguments {225225+ /// Internal API endpoints.226226+ #[arg(long, value_delimiter = ',', env = knot::private::ENV_PRIVATE_ENDPOINTS)]227227+ pub api: Vec<Url>,228228+229229+ /// DID of the repository owner.230230+ #[arg(long, env = knot::private::ENV_REPO_DID)]231231+ pub repo_did: OwnedDid,232232+233233+ /// Record key of the repository.234234+ #[arg(long, env = knot::private::ENV_REPO_RKEY)]235235+ pub repo_rkey: String,236236+237237+ /// Name of the hook to forward.221238 pub hook: HookName,239239+}240240+241241+impl fmt::Debug for HookArguments {242242+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {243243+ f.debug_struct("HookArguments")244244+ // Suppress `url::Url`'s god-awful debug output.245245+ .field("api", &self.api.iter().map(Url::as_str).collect::<Vec<_>>())246246+ .field("repo_did", &self.repo_did)247247+ .field("repo_rkey", &self.repo_rkey)248248+ .field("hook", &self.hook)249249+ .finish()250250+ }222251}223252224253#[derive(Clone, Copy, Debug, ValueEnum)]
+39-29
crates/knot/src/hooks.rs
···77 path::Path,88};991010-use atproto::did::OwnedDid;1110use axum::http::{HeaderMap, HeaderName, HeaderValue, header::InvalidHeaderName};1211use bytes::Bytes;1312use knot::private;1414-use url::Url;15131616-use crate::cli::HookName;1414+use crate::cli::{HookArguments, HookName};17151816/// Setup the global hooks directory at `path`.1917pub fn setup_global_hooks<P: AsRef<Path>>(path: P) -> io::Result<()> {···3537 Ok(())3638}37393838-#[tracing::instrument]3939-pub async fn run_hook(hook: HookName) -> anyhow::Result<()> {4040+/// [`core::fmt::Debug`] an [`url::Url`] without causing eye-cancer.4141+#[repr(transparent)]4242+struct DebugUrl(url::Url);4343+4444+impl core::fmt::Debug for DebugUrl {4545+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {4646+ core::fmt::Display::fmt(&self.0, f)4747+ }4848+}4949+5050+/// [`core::fmt::Debug`] a slice [`url::Url`] without causing eye-cancer.5151+pub struct DebugUrls<'a>(pub &'a [url::Url]);5252+5353+impl<'a> core::fmt::Debug for DebugUrls<'a> {5454+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {5555+ let urls = unsafe {5656+ // SAFETY: Close your eyes an pray!5757+ &*(self.0 as *const [url::Url] as *const [DebugUrl])5858+ };5959+ core::fmt::Debug::fmt(&urls, f)6060+ }6161+}6262+6363+#[tracing::instrument(fields(api = ?DebugUrls(&api)))]6464+pub async fn run_hook(6565+ HookArguments {6666+ api,6767+ repo_did,6868+ repo_rkey,6969+ hook,7070+ }: HookArguments,7171+) -> anyhow::Result<()> {7272+ if api.is_empty() {7373+ tracing::warn!("internal API not specified, skipping hook");7474+ return Ok(());7575+ };7676+4077 let mut environment_vars: HashMap<_, _> = env::vars()4178 .filter(|(key, _)| !key.trim().is_empty())4279 .collect();43804444- // Take the environment variables we need to post the hook to the internal API.4545- let Ok(endpoints) = take_var(&mut environment_vars, private::ENV_PRIVATE_ENDPOINTS) else {4646- // We haven't been provided with the address for the internal API.4747- tracing::warn!(4848- "'{}' not set, skipping hook",4949- private::ENV_PRIVATE_ENDPOINTS5050- );5151-5252- // @TODO Just run any hook scripts.5353-5454- return Ok(());5555- };5656-5757- let repo_did: OwnedDid = take_var(&mut environment_vars, private::ENV_REPO_DID)?.parse()?;5858- let repo_rkey = take_var(&mut environment_vars, private::ENV_REPO_RKEY)?;5981 let request_id = take_var(&mut environment_vars, "X_REQUEST_ID").ok();60826183 // Build a header map with the remaining environment variables.···96789779 let client = reqwest::Client::new();9880 let url_path = format!("/hook/{repo_did}/{repo_rkey}/{hook}");9999- for endpoint in endpoints.split_whitespace() {100100- let mut hook_url = match Url::parse(endpoint) {101101- Ok(hook_url) => hook_url,102102- Err(error) => {103103- tracing::error!(?error, ?endpoint, "failed to parse internal endpoint");104104- continue;105105- }106106- };107107-8181+ for mut hook_url in api {10882 hook_url.set_path(&url_path);10983 let response = client11084 .post(hook_url)
+1-1
crates/knot/src/main.rs
···5454 match cli::parse() {5555 cli::KnotCommand::Generate(_) => unreachable!("Handled by cli module"),5656 cli::KnotCommand::Serve(arguments) => runtime.block_on(knot_main(arguments)),5757- cli::KnotCommand::Hook(arguments) => runtime.block_on(hooks::run_hook(arguments.hook)),5757+ cli::KnotCommand::Hook(arguments) => runtime.block_on(hooks::run_hook(arguments)),5858 }5959}6060