dev: add pod runtime command override
This commit is contained in:
parent
ca9e1840cf
commit
0031953ed3
|
|
@ -3,6 +3,8 @@ use std::fmt;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
const POD_RUNTIME_COMMAND_ENV: &str = "INSOMNIA_POD_RUNTIME_COMMAND";
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct PodRuntimeCommand {
|
pub struct PodRuntimeCommand {
|
||||||
pub program: PathBuf,
|
pub program: PathBuf,
|
||||||
|
|
@ -28,9 +30,29 @@ impl PodRuntimeCommand {
|
||||||
/// Resolve the Pod runtime command used for subprocess launches.
|
/// Resolve the Pod runtime command used for subprocess launches.
|
||||||
///
|
///
|
||||||
/// The default launch path is always the current `insomnia` executable plus
|
/// The default launch path is always the current `insomnia` executable plus
|
||||||
/// the unified `pod` prefix argument.
|
/// the unified `pod` prefix argument. During development, a non-empty
|
||||||
|
/// `INSOMNIA_POD_RUNTIME_COMMAND` value replaces only the executable path;
|
||||||
|
/// the `pod` prefix is still added here and the env value is not parsed as a
|
||||||
|
/// shell command.
|
||||||
pub fn resolve() -> io::Result<Self> {
|
pub fn resolve() -> io::Result<Self> {
|
||||||
Self::for_current_exe()
|
Self::resolve_from_env_value(
|
||||||
|
std::env::var_os(POD_RUNTIME_COMMAND_ENV),
|
||||||
|
std::env::current_exe,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_from_env_value<F>(
|
||||||
|
override_program: Option<OsString>,
|
||||||
|
current_exe: F,
|
||||||
|
) -> io::Result<Self>
|
||||||
|
where
|
||||||
|
F: FnOnce() -> io::Result<PathBuf>,
|
||||||
|
{
|
||||||
|
if let Some(program) = override_program.filter(|program| !program.as_os_str().is_empty()) {
|
||||||
|
return Ok(Self::for_executable(program));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self::for_executable(current_exe()?))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn program(&self) -> &Path {
|
pub fn program(&self) -> &Path {
|
||||||
|
|
@ -98,4 +120,49 @@ mod tests {
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn resolve_uses_current_exe_when_override_is_unset() {
|
||||||
|
let command = PodRuntimeCommand::resolve_from_env_value(None, || {
|
||||||
|
Ok(PathBuf::from("/opt/insomnia/bin/insomnia"))
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
command,
|
||||||
|
PodRuntimeCommand::for_executable("/opt/insomnia/bin/insomnia")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn resolve_uses_current_exe_when_override_is_empty() {
|
||||||
|
let command = PodRuntimeCommand::resolve_from_env_value(Some(OsString::new()), || {
|
||||||
|
Ok(PathBuf::from("/opt/insomnia/bin/insomnia"))
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
command,
|
||||||
|
PodRuntimeCommand::for_executable("/opt/insomnia/bin/insomnia")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn resolve_override_replaces_only_program_and_keeps_pod_prefix() {
|
||||||
|
let command = PodRuntimeCommand::resolve_from_env_value(
|
||||||
|
Some(OsString::from("/tmp/rebuilt insomnia")),
|
||||||
|
|| panic!("override must not inspect current_exe"),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(command.program(), Path::new("/tmp/rebuilt insomnia"));
|
||||||
|
assert_eq!(command.prefix_args(), [OsString::from("pod")]);
|
||||||
|
assert_eq!(
|
||||||
|
command.argv_with(["--pod", "agent"]),
|
||||||
|
vec!["pod", "--pod", "agent"]
|
||||||
|
.into_iter()
|
||||||
|
.map(OsString::from)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,10 @@ pub enum SpawnError {
|
||||||
Io(io::Error),
|
Io(io::Error),
|
||||||
/// runtime ディレクトリが解決できなかった (環境変数未設定等)。
|
/// runtime ディレクトリが解決できなかった (環境変数未設定等)。
|
||||||
RuntimeDirUnavailable,
|
RuntimeDirUnavailable,
|
||||||
PodLaunchFailed(io::Error),
|
PodLaunchFailed {
|
||||||
|
command: PodRuntimeCommand,
|
||||||
|
source: io::Error,
|
||||||
|
},
|
||||||
PodExitedEarly {
|
PodExitedEarly {
|
||||||
stderr_tail: String,
|
stderr_tail: String,
|
||||||
},
|
},
|
||||||
|
|
@ -68,7 +71,10 @@ impl std::fmt::Display for SpawnError {
|
||||||
f,
|
f,
|
||||||
"could not resolve runtime directory (set INSOMNIA_HOME, INSOMNIA_RUNTIME_DIR, XDG_RUNTIME_DIR, or HOME)"
|
"could not resolve runtime directory (set INSOMNIA_HOME, INSOMNIA_RUNTIME_DIR, XDG_RUNTIME_DIR, or HOME)"
|
||||||
),
|
),
|
||||||
Self::PodLaunchFailed(e) => write!(f, "failed to launch pod: {e}"),
|
Self::PodLaunchFailed { command, source } => write!(
|
||||||
|
f,
|
||||||
|
"failed to launch pod runtime command `{command}`: {source}"
|
||||||
|
),
|
||||||
Self::PodExitedEarly { stderr_tail } => {
|
Self::PodExitedEarly { stderr_tail } => {
|
||||||
if stderr_tail.is_empty() {
|
if stderr_tail.is_empty() {
|
||||||
write!(f, "pod exited before becoming ready")
|
write!(f, "pod exited before becoming ready")
|
||||||
|
|
@ -85,7 +91,14 @@ impl std::fmt::Display for SpawnError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::error::Error for SpawnError {}
|
impl std::error::Error for SpawnError {
|
||||||
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||||
|
match self {
|
||||||
|
Self::Io(error) | Self::PodLaunchFailed { source: error, .. } => Some(error),
|
||||||
|
Self::RuntimeDirUnavailable | Self::PodExitedEarly { .. } | Self::Timeout => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<io::Error> for SpawnError {
|
impl From<io::Error> for SpawnError {
|
||||||
fn from(e: io::Error) -> Self {
|
fn from(e: io::Error) -> Self {
|
||||||
|
|
@ -132,7 +145,12 @@ where
|
||||||
.arg("--session-pod-name")
|
.arg("--session-pod-name")
|
||||||
.arg(&config.pod_name);
|
.arg(&config.pod_name);
|
||||||
}
|
}
|
||||||
let mut child = command.spawn().map_err(SpawnError::PodLaunchFailed)?;
|
let mut child = command
|
||||||
|
.spawn()
|
||||||
|
.map_err(|source| SpawnError::PodLaunchFailed {
|
||||||
|
command: config.runtime_command.clone(),
|
||||||
|
source,
|
||||||
|
})?;
|
||||||
|
|
||||||
// Default `kill_on_drop = false` plus `process_group(0)` makes this
|
// Default `kill_on_drop = false` plus `process_group(0)` makes this
|
||||||
// a detached Pod once startup succeeds: dropping the handle does not
|
// a detached Pod once startup succeeds: dropping the handle does not
|
||||||
|
|
|
||||||
|
|
@ -346,7 +346,13 @@ where
|
||||||
command.arg("--store").arg(store_dir);
|
command.arg("--store").arg(store_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut child = command.spawn().map_err(PodDiscoveryError::RestoreSpawn)?;
|
let mut child =
|
||||||
|
command
|
||||||
|
.spawn()
|
||||||
|
.map_err(|source| PodDiscoveryError::RestoreLaunchFailed {
|
||||||
|
command: runtime_command.clone(),
|
||||||
|
source,
|
||||||
|
})?;
|
||||||
let deadline = tokio::time::Instant::now() + RESTORE_START_TIMEOUT;
|
let deadline = tokio::time::Instant::now() + RESTORE_START_TIMEOUT;
|
||||||
loop {
|
loop {
|
||||||
if probe_socket(socket_path).await.reachable {
|
if probe_socket(socket_path).await.reachable {
|
||||||
|
|
@ -545,6 +551,12 @@ pub enum PodDiscoveryError {
|
||||||
ScopeLock(#[from] pod_registry::ScopeLockError),
|
ScopeLock(#[from] pod_registry::ScopeLockError),
|
||||||
#[error("failed to launch restore process: {0}")]
|
#[error("failed to launch restore process: {0}")]
|
||||||
RestoreSpawn(io::Error),
|
RestoreSpawn(io::Error),
|
||||||
|
#[error("failed to launch restore runtime command `{command}`: {source}")]
|
||||||
|
RestoreLaunchFailed {
|
||||||
|
command: PodRuntimeCommand,
|
||||||
|
#[source]
|
||||||
|
source: io::Error,
|
||||||
|
},
|
||||||
#[error("restore process exited before socket became reachable: {status}")]
|
#[error("restore process exited before socket became reachable: {status}")]
|
||||||
RestoreExited { status: std::process::ExitStatus },
|
RestoreExited { status: std::process::ExitStatus },
|
||||||
#[error("restore process did not become reachable before timeout")]
|
#[error("restore process did not become reachable before timeout")]
|
||||||
|
|
@ -779,6 +791,7 @@ fn discovery_error_to_tool_error(error: PodDiscoveryError) -> ToolError {
|
||||||
| PodDiscoveryError::PodStore(_)
|
| PodDiscoveryError::PodStore(_)
|
||||||
| PodDiscoveryError::ScopeLock(_)
|
| PodDiscoveryError::ScopeLock(_)
|
||||||
| PodDiscoveryError::RestoreSpawn(_)
|
| PodDiscoveryError::RestoreSpawn(_)
|
||||||
|
| PodDiscoveryError::RestoreLaunchFailed { .. }
|
||||||
| PodDiscoveryError::RestoreExited { .. }
|
| PodDiscoveryError::RestoreExited { .. }
|
||||||
| PodDiscoveryError::RestoreTimeout => ToolError::ExecutionFailed(error.to_string()),
|
| PodDiscoveryError::RestoreTimeout => ToolError::ExecutionFailed(error.to_string()),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
INSOMNIA では、プロセス境界で本当に必要な場合を除き、環境変数の利用を避ける。新しい ambient な入力を増やすより、明示的な profile / manifest / config file / typed secret reference / CLI argument を優先する。
|
INSOMNIA では、プロセス境界で本当に必要な場合を除き、環境変数の利用を避ける。新しい ambient な入力を増やすより、明示的な profile / manifest / config file / typed secret reference / CLI argument を優先する。
|
||||||
|
|
||||||
それでも、path discovery、runtime directory、外部 provider の credential 慣習との移行互換のために、一部の環境変数はまだサポートしている。この文書に載せた環境変数は公開 surface として扱う。ただし、fallback 変数は独立した設定項目ではなく、対応する main key の解決順の一部として扱う。開発・テスト都合だけの環境変数は追加しない。
|
それでも、path discovery、runtime directory、外部 provider の credential 慣習との移行互換のために、一部の環境変数はまだサポートしている。この文書に載せた通常 runtime 用の環境変数は公開 surface として扱う。ただし、fallback 変数は独立した設定項目ではなく、対応する main key の解決順の一部として扱う。開発・テスト都合だけの環境変数は、通常ユーザー向け configuration として扱わない明確な escape hatch に限る。
|
||||||
|
|
||||||
## 原則
|
## 原則
|
||||||
|
|
||||||
|
|
@ -56,6 +56,14 @@ Provider credential は、現在は manifest / profile / catalog の設定から
|
||||||
|
|
||||||
Credential env var は interoperability のために現時点では残っているが、長期的に望ましい secret mechanism ではない。現時点では適切なら `auth.file` を優先し、今後は typed secret reference へ寄せる。credential UX のために implicit `.env` loading を追加しないこと。project secret を漏らしやすく、profile ごとの credential model とも相性が悪い。
|
Credential env var は interoperability のために現時点では残っているが、長期的に望ましい secret mechanism ではない。現時点では適切なら `auth.file` を優先し、今後は typed secret reference へ寄せる。credential UX のために implicit `.env` loading を追加しないこと。project secret を漏らしやすく、profile ごとの credential model とも相性が悪い。
|
||||||
|
|
||||||
|
## Development-only escape hatches
|
||||||
|
|
||||||
|
これらは dogfooding / self-rebuild / fixture などの開発運用だけの逃げ道であり、通常ユーザー向けの configuration surface ではない。profile、manifest、CLI option の代替として案内しない。
|
||||||
|
|
||||||
|
| 変数 | Context | 備考 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `INSOMNIA_POD_RUNTIME_COMMAND` | 開発中に起動中の `insomnia` binary が rebuild され、`std::env::current_exe()` が `target/debug/insomnia (deleted)` のような stale path を返す場合の Pod runtime executable override。 | Unset または empty の場合は既定どおり current executable に `pod` prefix argument を付けて起動する。Non-empty の場合は値を executable path としてそのまま使い、`pod` prefix argument は常に自動追加する。shell parsing や argument splitting は行わないため、値に flags や `pod` を含めない。 |
|
||||||
|
|
||||||
## Build / example variables
|
## Build / example variables
|
||||||
|
|
||||||
これらは通常の application configuration ではない。
|
これらは通常の application configuration ではない。
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user