1 parent
536c55f27b
commit
99d1c2feef
16 files changed
+1537
-12
No files matched your search
@@ -5,6 +5,10 @@ use std::{
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
mod container;
|
||||
|
||||
pub use container::{Container, ContainerError, ContainerRuntime, ExecOutput, normalize};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct DevContainerBuildSchema {
|
||||
#[serde(default)]
|
||||
@@ -30,6 +34,12 @@ pub struct DevContainerSchema {
|
||||
|
||||
#[serde(rename = "postStartCommand", default)]
|
||||
pub post_start_command: Option<String>,
|
||||
|
||||
#[serde(rename = "remoteUser", default)]
|
||||
pub remote_user: Option<String>,
|
||||
|
||||
#[serde(rename = "runArgs", default)]
|
||||
pub run_args: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -41,6 +51,8 @@ pub struct DevContainer {
|
||||
pub workspace_folder: Option<String>,
|
||||
pub post_create_command: Option<String>,
|
||||
pub post_start_command: Option<String>,
|
||||
pub remote_user: Option<String>,
|
||||
pub run_args: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
@@ -80,18 +92,72 @@ impl TryFrom<(DevContainerSchema, PathBuf)> for DevContainer {
|
||||
return Err(ParseError::ContainerFileNotFound(container_file_path));
|
||||
}
|
||||
|
||||
let build_args = schema
|
||||
.build
|
||||
.args
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k, substitute_local_env(&v)))
|
||||
.collect();
|
||||
|
||||
let container_env = schema
|
||||
.container_env
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k, substitute_local_env(&v)))
|
||||
.collect();
|
||||
|
||||
Ok(Self {
|
||||
container_file_path,
|
||||
name: schema.name,
|
||||
build_args: schema.build.args,
|
||||
container_env: schema.container_env,
|
||||
build_args,
|
||||
container_env,
|
||||
workspace_folder: schema.workspace_folder,
|
||||
post_create_command: schema.post_create_command,
|
||||
post_start_command: schema.post_start_command,
|
||||
remote_user: schema.remote_user,
|
||||
run_args: schema.run_args,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolves `${localEnv:VAR}` and `${localEnv:VAR:default}` references using the
|
||||
/// current process environment, as described by the devcontainer specification.
|
||||
/// Unresolved variables without a default expand to an empty string.
|
||||
fn substitute_local_env(input: &str) -> String {
|
||||
const PREFIX: &str = "${localEnv:";
|
||||
|
||||
let mut out = String::with_capacity(input.len());
|
||||
let mut rest = input;
|
||||
|
||||
while let Some(start) = rest.find(PREFIX) {
|
||||
out.push_str(&rest[..start]);
|
||||
let after = &rest[start + PREFIX.len()..];
|
||||
|
||||
match after.find('}') {
|
||||
Some(end) => {
|
||||
let inner = &after[..end];
|
||||
let (key, default) = match inner.split_once(':') {
|
||||
Some((key, default)) => (key, Some(default)),
|
||||
None => (inner, None),
|
||||
};
|
||||
|
||||
match std::env::var(key) {
|
||||
Ok(value) => out.push_str(&value),
|
||||
Err(_) => out.push_str(default.unwrap_or("")),
|
||||
}
|
||||
|
||||
rest = &after[end + 1..];
|
||||
}
|
||||
None => {
|
||||
out.push_str(PREFIX);
|
||||
rest = after;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out.push_str(rest);
|
||||
out
|
||||
}
|
||||
|
||||
pub async fn parse(path: impl AsRef<Path>) -> Result<DevContainer, ParseError> {
|
||||
let path = path.as_ref().to_path_buf();
|
||||
let contents = tokio::fs::read_to_string(&path)
|
||||
@@ -136,7 +202,9 @@ mod tests {
|
||||
"workspaceFolder": "/workspace",
|
||||
"containerEnv": {
|
||||
"RUST_LOG": "debug"
|
||||
}
|
||||
},
|
||||
"remoteUser": "dev",
|
||||
"runArgs": ["--userns=keep-id"]
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
@@ -148,5 +216,33 @@ mod tests {
|
||||
assert_eq!(config.build_args.get("VERSION").unwrap(), "1");
|
||||
assert_eq!(config.container_env.get("RUST_LOG").unwrap(), "debug");
|
||||
assert_eq!(config.workspace_folder.as_deref(), Some("/workspace"));
|
||||
assert_eq!(config.remote_user.as_deref(), Some("dev"));
|
||||
assert_eq!(config.run_args, vec!["--userns=keep-id"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn substitutes_local_env_with_default() {
|
||||
unsafe { std::env::set_var("DEVCONTAINER_TEST_UID", "1000") };
|
||||
|
||||
assert_eq!(
|
||||
substitute_local_env("${localEnv:DEVCONTAINER_TEST_UID}"),
|
||||
"1000"
|
||||
);
|
||||
assert_eq!(
|
||||
substitute_local_env("uid=${localEnv:DEVCONTAINER_TEST_UID}"),
|
||||
"uid=1000"
|
||||
);
|
||||
assert_eq!(
|
||||
substitute_local_env("${localEnv:DEVCONTAINER_TEST_MISSING:fallback}"),
|
||||
"fallback"
|
||||
);
|
||||
assert_eq!(
|
||||
substitute_local_env("${localEnv:DEVCONTAINER_TEST_MISSING}"),
|
||||
""
|
||||
);
|
||||
assert_eq!(
|
||||
substitute_local_env("no variables here"),
|
||||
"no variables here"
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user