8000 feat(minimald): rip out arbitrary session exec by twitchyliquid64 · Pull Request #709 · gominimal/minimal · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

92 changes: 67 additions & 25 deletions crates/mctx/src/env.rs
< 8000 details class="js-file-header-dropdown dropdown details-overlay details-reset pr-2 pl-2"> View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,36 @@ impl EnvChannel<'_> {
Err(e) => return EnvChannel::write_error(e, stream),
Ok(ctx) => ctx,
};

// An `echo` task carries its whole output in its declaration, so it
// needs no package graph or sandbox — service it straight from the
// mfile. Only mfile-local tasks are matched here; a stack-provided
// echo task falls through to the normal (sandboxed) path below.
if let Some(task) = build_ctx.minimal_file().task(task_name)
&& task.action.as_echo().is_some()
{
let parsed_args = if task.args.is_empty() {
None
} else {
match task.args.parse(args) {
Err(e) => {
for line in format!("{}", e.render().ansi()).lines() {
writeln!(stream, "msg:{}", line).ok();
}
writeln!(stream, "error: failed parsing arguments for task").ok();
return;
}
Ok(args) => Some(args),
}
};
match crate::interpolate_task_strings(&task, parsed_args.as_ref()) {
Err(e) => return EnvChannel::write_error(e, stream),
// `msg:` framing matches how task stdout lines reach the client.
Ok(t) => writeln!(stream, "msg:{}", t.action.as_echo().unwrap_or_default()).ok(),
};
return;
}

let graph = match build_ctx.graph_from_all_packages() {
Err(e) => return EnvChannel::write_error(e, stream),
Ok(g) => g,
Expand Down Expand Up @@ -499,6 +529,42 @@ pub struct EnvArgs<'a> {
pub ot: Option<OpTracker>,
}

/// Returns a clone of `task` with every action string interpolated against
/// the task's parameters — `task_packages` plus any `parsed_args` — so that
/// `%{name}`-style placeholders are resolved.
///
/// Shared by the invocation path ([`Env::task_invocations`]) and the
/// sandbox-free `echo` short-circuits, so an echoed string is interpolated
/// the same way an `exec`/`bash` action would be.
pub fn interpolate_task_strings(
task: &mfile::Task,
parsed_args: Option<&args::ArgsSet>,
) -> Result<mfile::Task, Error> {
let base = [(
"task_packages",
args::Arg::Array(
task.packages
.iter()
.map(|s| args::ScalarArg::String(s.clone()))
.collect(),
),
)]
.into_iter();
let var_ctx = if let Some(args) = parsed_args {
common::ncl_eval::VarCtx::from_iter(
base.chain(args.iter().map(|(k, v)| (k.as_str(), v.clone()))),
)
} else {
common::ncl_eval::VarCtx::from_iter(base)
};
task.map_exec_strings(|s| {
var_ctx
.eval_string(s)
.map_err(|_| anyhow::anyhow!("nickel eval failed for string: {}", s))
})
.map_err(Error::Other)
}

/// A successfully-configured runtime environment.
pub struct Env<'a> {
sandbox: sandbox2::Sandbox<EnvChannel<'a>>,
Expand Down Expand Up @@ -628,31 +694,7 @@ impl<'a> Env<'a> {
task: &mfile::Task,
parsed_args: Option<&args::ArgsSet>,
) -> Result<(bool, Vec<Invocation>), Error> {
let base = [(
"task_packages",
args::Arg::Array(
task.packages
.iter()
.map(|s| args::ScalarArg::String(s.clone()))
.collect(),
),
)]
.into_iter();
let mapped_task = {
let var_ctx = if let Some(args) = parsed_args {
common::ncl_eval::VarCtx::from_iter(
base.chain(args.iter().map(|(k, v)| (k.as_str(), v.clone()))),
)
} else {
common::ncl_eval::VarCtx::from_iter(base)
};
task.map_exec_strings(|s| {
var_ctx
.eval_string(s)
.map_err(|_| anyhow::anyhow!("nickel eval failed for string: {}", s))
})
.map_err(Error::Other)?
};
let mapped_task = interpolate_task_strings(task, parsed_args)?;

Ok((
task.interactive,
Expand Down
2 changes: 1 addition & 1 deletion crates/mctx/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ pub use scaffold::scaffold_default_mfile;
mod project_setup;
pub use project_setup::ProjectSetup;

pub use env::Env;
pub use env::{Env, interpolate_task_strings};
use tokio::sync::Semaphore;
use toml_edit::{Array, DocumentMut, Item, TableLike, Value};

Expand Down
29 changes: 29 additions & 0 deletions crates/mfile/src/tasks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ impl Task {
v.iter().map(|s| f(s)).collect::<Result<Vec<_>, _>>()?,
)),
TaskAction::Bash(cmd) => TaskAction::Bash(f(cmd)?),
TaskAction::Echo(s) => TaskAction::Echo(f(s)?),
TaskAction::CmdCmd(argv) => {
TaskAction::CmdCmd(argv.iter().map(|s| f(s)).collect::<Result<Vec<_>, _>>()?)
}
Expand Down Expand Up @@ -101,6 +102,7 @@ impl Task {
TaskAction::Bash(cmd) => {
Some(("/bin/bash".to_string(), vec!["-c".to_string(), cmd.clone()]))
}
TaskAction::Echo(s) => Some(("/bin/echo".to_string(), vec![s.clone()])),
TaskAction::CmdCmd(_) => None,
}
}
Expand All @@ -120,6 +122,8 @@ pub enum TaskAction {
/// The invoked program should return each command to run on stdout,
/// one invocation per line.
CmdCmd(Vec<String>),
/// Echo's the given string.
Echo(String),
}

impl Default for TaskAction {
Expand All @@ -133,6 +137,19 @@ impl TaskAction {
pub fn exec_from_str(s: &str) -> Self {
Self::Exec(StrOrList::Single(s.to_string()))
}

/// Returns the text to emit if this is an [TaskAction::Echo], else `None`.
///
/// Callers that can service an echo without a sandbox (the ssh and
/// env-socket `min run` paths) branch on this to short-circuit the
/// package/graph machinery entirely. The returned string may still
/// contain interpolations that need resolving.
pub fn as_echo(&self) -> Option<&str> {
match self {
TaskAction::Echo(s) => Some(s),
_ => None,
}
}
}

#[cfg(test)]
Expand Down Expand Up @@ -190,6 +207,18 @@ mod tests {
);
}

#[test]
fn echo_str() {
let t: Task = toml::from_str(indoc! {
r#"
echo = "hello world"
"#
})
.unwrap();
assert_eq!(t.action, TaskAction::Echo("hello world".to_string()));
assert_eq!(t.action.as_echo(), Some("hello world"));
}

#[test]
fn map_exec_strings_exec() {
let t: Task = toml::from_str(indoc! {
Expand Down
Loading
Loading
0