diff --git a/crates/dev_container/src/devcontainer_json.rs b/crates/dev_container/src/devcontainer_json.rs index 42e6c6f316c..6033ca29f0b 100644 --- a/crates/dev_container/src/devcontainer_json.rs +++ b/crates/dev_container/src/devcontainer_json.rs @@ -211,7 +211,7 @@ pub(crate) struct DevContainer { #[serde(rename = "updateRemoteUserUID")] pub(crate) update_remote_user_uid: Option, user_env_probe: Option, - override_command: Option, + pub(crate) override_command: Option, shutdown_action: Option, init: Option, pub(crate) privileged: Option, diff --git a/crates/dev_container/src/devcontainer_manifest.rs b/crates/dev_container/src/devcontainer_manifest.rs index c88c4df995a..cc8fbedb14d 100644 --- a/crates/dev_container/src/devcontainer_manifest.rs +++ b/crates/dev_container/src/devcontainer_manifest.rs @@ -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, privileged: bool, - entrypoint_script: String, + entrypoint_script: Option, } #[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