Respect override command in devcontainer (#57204)

Self-Review Checklist:

- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

Closes #56357

Release Notes:

- Fixed bug where devcontainers were not respecting override_command
This commit is contained in:
KyleBarton 2026-05-28 04:22:41 -07:00 committed by GitHub
parent 6049ceaecf
commit 518502e5ef
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 89 additions and 26 deletions

View file

@ -211,7 +211,7 @@ pub(crate) struct DevContainer {
#[serde(rename = "updateRemoteUserUID")]
pub(crate) update_remote_user_uid: Option<bool>,
user_env_probe: Option<UserEnvProbe>,
override_command: Option<bool>,
pub(crate) override_command: Option<bool>,
shutdown_action: Option<ShutdownAction>,
init: Option<bool>,
pub(crate) privileged: Option<bool>,

View file

@ -794,24 +794,30 @@ RUN sed -i -E 's/((^|\s)PATH=)([^\$]*)$/\1\${{PATH:-\3}}/g' /etc/profile || true
let privileged = dev_container.privileged.unwrap_or(false)
|| self.features.iter().any(|f| f.privileged());
let mut entrypoint_script_lines = vec![
"echo Container started".to_string(),
"trap \"exit 0\" 15".to_string(),
];
let entrypoint_script = if dev_container.override_command == Some(false) {
None
} else {
let mut entrypoint_script_lines = vec![
"echo Container started".to_string(),
"trap \"exit 0\" 15".to_string(),
];
for entrypoint in self.features.iter().filter_map(|f| f.entrypoint()) {
entrypoint_script_lines.push(entrypoint.clone());
}
entrypoint_script_lines.append(&mut vec![
"exec \"$@\"".to_string(),
"while sleep 1 & wait $!; do :; done".to_string(),
]);
for entrypoint in self.features.iter().filter_map(|f| f.entrypoint()) {
entrypoint_script_lines.push(entrypoint.clone());
}
entrypoint_script_lines.append(&mut vec![
"exec \"$@\"".to_string(),
"while sleep 1 & wait $!; do :; done".to_string(),
]);
Some(entrypoint_script_lines.join("\n").trim().to_string())
};
Ok(DockerBuildResources {
image: base_image,
additional_mounts: mounts,
privileged,
entrypoint_script: entrypoint_script_lines.join("\n").trim().to_string(),
entrypoint_script,
})
}
@ -1255,13 +1261,17 @@ RUN sed -i -E 's/((^|\s)PATH=)([^\$]*)$/\1\${{PATH:-\3}}/g' /etc/profile || true
})
.collect();
let mut main_service = DockerComposeService {
entrypoint: Some(vec![
let entrypoint = resources.entrypoint_script.map(|script| {
vec![
"/bin/sh".to_string(),
"-c".to_string(),
resources.entrypoint_script,
script,
"-".to_string(),
]),
]
});
let mut main_service = DockerComposeService {
entrypoint,
cap_add: Some(vec!["SYS_PTRACE".to_string()]),
security_opt: Some(vec!["seccomp=unconfined".to_string()]),
labels: Some(runtime_labels),
@ -1977,13 +1987,16 @@ RUN sed -i -E 's/((^|\s)PATH=)([^\$]*)$/\1\${PATH:-\3}/g' /etc/profile || true
command.arg(app_port);
}
command.arg("--entrypoint");
command.arg("/bin/sh");
command.arg(&build_resources.image.id);
command.arg("-c");
command.arg(build_resources.entrypoint_script);
command.arg("-");
if let Some(entrypoint_script) = build_resources.entrypoint_script {
command.arg("--entrypoint");
command.arg("/bin/sh");
command.arg(&build_resources.image.id);
command.arg("-c");
command.arg(entrypoint_script);
command.arg("-");
} else {
command.arg(&build_resources.image.id);
}
Ok(command)
}
@ -2409,7 +2422,7 @@ struct DockerBuildResources {
image: DockerInspect,
additional_mounts: Vec<MountDefinition>,
privileged: bool,
entrypoint_script: String,
entrypoint_script: Option<String>,
}
#[derive(Debug)]
@ -3166,7 +3179,7 @@ mod test {
},
additional_mounts: vec![],
privileged: false,
entrypoint_script: "echo Container started\n trap \"exit 0\" 15\n exec \"$@\"\n while sleep 1 & wait $!; do :; done".to_string(),
entrypoint_script: Some("echo Container started\n trap \"exit 0\" 15\n exec \"$@\"\n while sleep 1 & wait $!; do :; done".to_string()),
};
let docker_run_command = devcontainer_manifest.create_docker_run_command(build_resources);
@ -3212,6 +3225,56 @@ mod test {
)
}
#[gpui::test]
async fn should_not_override_entrypoint_when_override_command_is_false(
cx: &mut TestAppContext,
) {
let (_, mut devcontainer_manifest) = init_default_devcontainer_manifest(
cx,
r#"{
"name": "test",
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
"overrideCommand": false
}"#,
)
.await
.unwrap();
devcontainer_manifest.parse_nonremote_vars().unwrap();
let base_image = DockerInspect {
id: "mcr.microsoft.com/devcontainers/base:ubuntu".to_string(),
config: DockerInspectConfig {
labels: DockerConfigLabels { metadata: None },
image_user: None,
env: Vec::new(),
},
mounts: None,
state: None,
};
let resources = devcontainer_manifest
.build_merged_resources(base_image)
.unwrap();
assert!(
resources.entrypoint_script.is_none(),
"overrideCommand: false must not produce an entrypoint script"
);
let docker_run_command = devcontainer_manifest
.create_docker_run_command(resources)
.unwrap();
let args: Vec<&OsStr> = docker_run_command.get_args().collect();
assert!(
!args.contains(&OsStr::new("--entrypoint")),
"overrideCommand: false must not pass --entrypoint to docker run"
);
assert!(
args.contains(&OsStr::new("mcr.microsoft.com/devcontainers/base:ubuntu")),
"image id must still be present in docker run command"
);
}
#[gpui::test]
async fn should_find_primary_service_in_docker_compose(cx: &mut TestAppContext) {
// State where service not defined in dev container