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
16 changes: 16 additions & 0 deletions cmd/devcontainer/src/runtime/compose/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,22 @@ fn compose_project_name_defaults_to_workspace_devcontainer() {
let _ = fs::remove_dir_all(root);
}

#[test]
fn compose_project_name_defaults_to_compose_working_dir_basename() {
let root = unique_temp_dir("devcontainer-compose-test");
let compose_file = root.join("docker-compose.yml");
fs::create_dir_all(&root).expect("compose dir");
fs::write(&compose_file, "services:\n app:\n image: alpine:3.20\n").expect("compose");

let project_name = compose_project_name(&[compose_file]).expect("project name");

assert_eq!(
project_name,
root.file_name().unwrap().to_string_lossy().to_lowercase()
);
let _ = fs::remove_dir_all(root);
}

#[test]
fn compose_name_from_file_reads_top_level_name() {
let root = unique_temp_dir("devcontainer-compose-test");
Expand Down
40 changes: 39 additions & 1 deletion cmd/devcontainer/src/runtime/container/uid_update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ use super::super::paths::unique_temp_path;

const UID_UPDATE_IMAGE_INSPECT_FORMAT: &str =
"{{.Config.User}}\n{{.Os}}/{{.Architecture}}{{if .Variant}}/{{.Variant}}{{end}}";
const UID_UPDATE_IMAGE_INSPECT_FORMAT_NO_VARIANT: &str =
"{{.Config.User}}\n{{.Os}}/{{.Architecture}}";

#[derive(Debug, Eq, PartialEq)]
struct UidUpdateDetails {
Expand Down Expand Up @@ -263,13 +265,44 @@ fn inspect_image_details_for_uid_update_once(
)?;
if result.status_code != 0 {
let error = engine::stderr_or_stdout(&result);
if is_missing_variant_template_error(&error) {
return inspect_image_details_without_variant(args, image_name);
}
if is_missing_local_image_inspect_error(&error) {
return Ok(None);
}
return Err(error);
}

let mut lines = result.stdout.lines();
parse_image_inspect_details(&result.stdout)
}

fn inspect_image_details_without_variant(
args: &[String],
image_name: &str,
) -> Result<Option<ImageInspectDetails>, String> {
let result = engine::run_engine(
args,
vec![
"image".to_string(),
"inspect".to_string(),
"--format".to_string(),
UID_UPDATE_IMAGE_INSPECT_FORMAT_NO_VARIANT.to_string(),
image_name.to_string(),
],
)?;
if result.status_code != 0 {
let error = engine::stderr_or_stdout(&result);
if is_missing_local_image_inspect_error(&error) {
return Ok(None);
}
return Err(error);
}
parse_image_inspect_details(&result.stdout)
}

fn parse_image_inspect_details(stdout: &str) -> Result<Option<ImageInspectDetails>, String> {
let mut lines = stdout.lines();
let user = lines
.next()
.map(str::trim)
Expand Down Expand Up @@ -298,6 +331,11 @@ fn is_missing_local_image_inspect_error(error: &str) -> bool {
error.contains("no such image") || error.contains("image not known")
}

fn is_missing_variant_template_error(error: &str) -> bool {
let error = error.to_ascii_lowercase();
error.contains("can't evaluate field variant")
}

fn uid_update_image_name(workspace_folder: &Path, image_name: &str) -> String {
let local_image_name = uid_update_local_image_name(workspace_folder);
let base_image_name = if image_name.starts_with(&local_image_name) {
Expand Down
63 changes: 63 additions & 0 deletions cmd/devcontainer/src/runtime/container/uid_update/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,48 @@ fn prepare_up_image_prefixes_local_podman_base_images_with_localhost() {
)));
}

#[test]
fn prepare_up_image_retries_image_inspect_without_variant_template_for_podman() {
let fixture = FakeEngineFixture::new();
fixture.write("image-inspect-with-variant.exit", "1\n");
fixture.write(
"image-inspect-with-variant.stderr",
"Error: template: inspect:2:30: executing \"inspect\" at <.Variant>: can't evaluate field Variant in type interface {}\n",
);
fixture.write(
"image-inspect-without-variant.stdout",
&image_inspect_output("node", Some("linux/amd64")),
);

let workspace = fixture.root.join("workspace");
fs::create_dir_all(&workspace).expect("workspace dir");
let resolved = resolved_config(
json!({
"remoteUser": "vscode"
}),
&workspace,
);

let updated_image = prepare_up_image_for_platform(
&resolved,
&fixture.args_with_podman_name(),
"ghcr.io/example/app:latest",
true,
)
.expect("prepare up image");

assert!(updated_image.ends_with("-uid"));
let invocations = fixture.invocations();
assert!(invocations.contains(&format!(
"image inspect --format {} ghcr.io/example/app:latest",
super::UID_UPDATE_IMAGE_INSPECT_FORMAT
)));
assert!(invocations.contains(&format!(
"image inspect --format {} ghcr.io/example/app:latest",
super::UID_UPDATE_IMAGE_INSPECT_FORMAT_NO_VARIANT
)));
}

#[test]
fn prepare_up_image_uses_compose_service_user_for_uid_update_selection() {
let fixture = FakeEngineFixture::new();
Expand Down Expand Up @@ -333,6 +375,27 @@ case "$COMMAND" in
shift || true
case "$SUBCOMMAND" in
inspect)
if [ "${*}" != "${*#*'.Variant'*}" ]; then
if [ -f "$ROOT/image-inspect-with-variant.stdout" ]; then
cat "$ROOT/image-inspect-with-variant.stdout"
fi
if [ -f "$ROOT/image-inspect-with-variant.stderr" ]; then
cat "$ROOT/image-inspect-with-variant.stderr" >&2
fi
if [ -f "$ROOT/image-inspect-with-variant.exit" ]; then
exit "$(tr -d '\n' < "$ROOT/image-inspect-with-variant.exit")"
fi
else
if [ -f "$ROOT/image-inspect-without-variant.stdout" ]; then
cat "$ROOT/image-inspect-without-variant.stdout"
fi
if [ -f "$ROOT/image-inspect-without-variant.stderr" ]; then
cat "$ROOT/image-inspect-without-variant.stderr" >&2
fi
if [ -f "$ROOT/image-inspect-without-variant.exit" ]; then
exit "$(tr -d '\n' < "$ROOT/image-inspect-without-variant.exit")"
fi
fi
if [ -f "$ROOT/pulled" ]; then
if [ -f "$ROOT/image-inspect-after-pull.stdout" ]; then
cat "$ROOT/image-inspect-after-pull.stdout"
Expand Down
Loading