#![cfg_attr(
not(test),
deny(clippy::unwrap_used, clippy::panic, clippy::indexing_slicing)
)]
use std::ffi::OsString;
use std::path::PathBuf;
use std::process::Command;
use clanker::error::ClankerError;
use clanker::{RuntimeContext, command_exit_code, invocation_name, run_symlink_mode};
use tftio_cli_common::{JsonOutput, ProcessEnv, run_with_display_error_handler};
fn main() {
let argv: Vec<OsString> = std::env::args_os().collect();
let invocation = argv
.first()
.map_or_else(|| "clanker".to_string(), |value| invocation_name(value));
let process_env = process_env();
let runtime = match runtime_context() {
Ok(runtime) => runtime,
Err(error) => {
eprintln!("error: {error}");
std::process::exit(1);
}
};
let exit_code = if invocation.ends_with("-launch") {
let harness_arguments = argv.into_iter().skip(1).collect();
run_with_display_error_handler("launch", JsonOutput::Text, || {
run_symlink_mode(&invocation, harness_arguments, &runtime)
})
} else {
command_exit_code(argv, &process_env, &runtime)
};
std::process::exit(exit_code);
}
#[allow(
clippy::disallowed_methods,
reason = "process environment is read once at the binary edge"
)]
fn process_env() -> ProcessEnv {
ProcessEnv {
agent: tftio_cli_common::AgentModeContext::from_tokens(
std::env::var(tftio_cli_common::AGENT_TOKEN_ENV).ok(),
std::env::var(tftio_cli_common::AGENT_TOKEN_EXPECTED_ENV).ok(),
),
home: std::env::var_os("HOME").map(PathBuf::from),
}
}
#[allow(
clippy::disallowed_methods,
reason = "launch environment and working directory are read once at the binary edge"
)]
fn runtime_context() -> Result<RuntimeContext, ClankerError> {
let home = std::env::var_os("HOME").map(PathBuf::from);
let current_dir = std::env::current_dir().map_err(ClankerError::CurrentDirectory)?;
let hostname = short_hostname().map_err(|error| error.to_string());
let inherited_environment = std::env::vars_os().collect();
Ok(RuntimeContext {
home,
current_dir,
hostname,
config_environment: std::env::var_os("CLANKER_CONFIG").map(PathBuf::from),
context_override: environment_text("CONTEXT_OVERRIDE")?,
context_environment: environment_text("CONTEXT")?,
domain_override: environment_text("CLANKER_DOMAIN_OVERRIDE")?,
domain_environment: environment_text("CLANKER_DOMAIN")?,
model_environment: environment_text("CLANKER_MODEL")?,
inherited_path: std::env::var_os("PATH"),
inherited_environment,
})
}
#[allow(
clippy::disallowed_methods,
reason = "environment value is parsed at the binary edge"
)]
fn environment_text(name: &'static str) -> Result<Option<String>, ClankerError> {
std::env::var_os(name)
.map(|value| {
value
.into_string()
.map_err(|_| ClankerError::NonUtf8Environment(name))
})
.transpose()
}
fn short_hostname() -> Result<String, ClankerError> {
let output = Command::new("hostname")
.arg("-s")
.output()
.map_err(|error| ClankerError::Hostname(error.to_string()))?;
if !output.status.success() {
return Err(ClankerError::Hostname(format!(
"`hostname -s` exited with {}",
output.status
)));
}
let hostname = String::from_utf8(output.stdout)
.map_err(|error| ClankerError::Hostname(error.to_string()))?;
let hostname = hostname.trim();
if hostname.is_empty() {
Err(ClankerError::Hostname(
"`hostname -s` returned an empty value".to_string(),
))
} else {
Ok(hostname.to_string())
}
}