use std::path::PathBuf; use anyhow::Context as _; pub struct Context { /// API Accessor object pub forgejo: forgejo_api::Forgejo, /// Repository Owner pub owner: String, /// Repository Name pub repo: String, /// URL of the repository page pub repo_url: url::Url, /// URL of the repository with authentication information attached pub repo_auth_url: url::Url, /// Timestamp "now" to be used in comparisons pub timestamp: jiff::Zoned, } impl Context { pub fn new_from_env_or_dev_fallback() -> anyhow::Result { if std::env::var("CI").is_ok() { Self::new_from_env() } else { log::info!("No CI=1 in environment, using development fallback environment."); Self::new_from_dev_fallback() } } pub fn new_from_env() -> anyhow::Result { let token = get_env_value("GITHUB_TOKEN")?; let server_url = get_env_value("GITHUB_SERVER_URL")?; let repo_with_owner = get_env_value("GITHUB_REPOSITORY")?; Self::new(&token, &server_url, &repo_with_owner) } pub fn new_from_dev_fallback() -> anyhow::Result { let mut f = std::fs::File::open(&dev_environment_path()?) .context("Failed to open dev environment config")?; let config: DevEnvironment = serde_json::de::from_reader(f).context("Failed to parse dev environment")?; Self::new( &config.dev_token, &config.server_url, &config.repo_with_owner, ) } fn new(token: &str, server_url: &str, repo_with_owner: &str) -> anyhow::Result { let (owner, repo) = repo_with_owner.rsplit_once("/").with_context(|| { format!("Could not split repo slug {repo_with_owner:?} into owner and repo") })?; let server_url = url::Url::parse(&server_url).context("Failed parsing server URL")?; let repo_url = server_url .join(&repo_with_owner) .context("Failed to build server + repo URL")?; let repo_auth_url = { let mut repo_auth_url = repo_url.clone(); repo_auth_url.set_username("forgejo-actions").unwrap(); repo_auth_url.set_password(Some(token)).unwrap(); repo_auth_url }; let forgejo = forgejo_api::Forgejo::new(forgejo_api::Auth::Token(token), server_url) .context("Could not create Forgejo API access object")?; let timestamp = jiff::Zoned::now(); Ok(Self { forgejo, owner: owner.to_owned(), repo: repo.to_owned(), repo_url, repo_auth_url, timestamp, }) } } impl std::fmt::Debug for Context { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Context") .field("owner", &self.owner) .field("repo", &self.repo) .field("repo_url", &self.repo_url.to_string()) .field("repo_auth_url", &"") .finish() } } fn get_env_value(key: &str) -> anyhow::Result { std::env::var(key).with_context(|| format!("Missing ${key} environment variable")) } #[derive(serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "kebab-case")] struct DevEnvironment { pub dev_token: String, pub server_url: String, pub repo_with_owner: String, } fn dev_environment_path() -> anyhow::Result { log::warn!("Not running in CI, using .dev-environment.json metadata instead."); let p = PathBuf::from(".dev-environment.json"); if !p.exists() { log::error!( "{}", r#"When not running in CI environment, metadata has to be substituded using a `.dev-environment.json` file. Example content: { "dev-token": "", "server-url": "https://git.fa-fo.de", "repo-with-owner": "rahix/TaskBot" } "# ); anyhow::bail!("Missing development environment config.") } Ok(p) }