mirror of
https://github.com/zed-industries/zed.git
synced 2026-05-31 19:05:00 +07:00
dev_container: Respect runServices for Docker Compose (#56293)
This PR fixes Docker Compose dev containers starting every service in
the compose project, even when `devcontainer.json` specifies
`runServices`.
Previously, Zed deserialized `runServices` but did not use it when
invoking Docker Compose. The startup command was:
```sh
docker compose ... up -d
```
With no service operands, Compose starts every enabled service in the
project. This means unrelated services are started even when the
devcontainer config asks to run only the primary service and its
dependencies.
The fix propagates `runServices` into the Docker Compose build/start
path so Zed invokes Compose with the requested services:
```sh
docker compose ... up -d devcontainer
```
Compose will still start services required by `depends_on`, but
unrelated services are left untouched.
**Reproduction**
`.devcontainer/devcontainer.json`:
```json
{
"name": "Run Services",
"dockerComposeFile": "../compose.yml",
"service": "devcontainer",
"runServices": ["devcontainer"],
"workspaceFolder": "/workspace"
}
```
`compose.yml`:
```yaml
services:
devcontainer:
image: ubuntu:24.04
command: sleep infinity
volumes:
- .:/workspace
depends_on:
- database
database:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: postgres
unrelated:
image: nginx:alpine
```
**Expected**: Zed starts `devcontainer` and `database`.
**Before this fix**: Zed also starts `unrelated`.
**After this fix**: `unrelated` remains stopped.
Closes: https://github.com/zed-industries/zed/issues/57279
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
Release Notes:
- Fixed Docker Compose dev containers starting services not listed in
`runServices`.
This commit is contained in:
parent
e5f5767d2c
commit
d3070bbc0d
3 changed files with 138 additions and 3 deletions
|
|
@ -232,7 +232,7 @@ pub(crate) struct DevContainer {
|
|||
#[serde(default, deserialize_with = "deserialize_string_or_array")]
|
||||
pub(crate) docker_compose_file: Option<Vec<String>>,
|
||||
pub(crate) service: Option<String>,
|
||||
run_services: Option<Vec<String>>,
|
||||
pub(crate) run_services: Option<Vec<String>>,
|
||||
pub(crate) initialize_command: Option<LifecycleScript>,
|
||||
pub(crate) on_create_command: Option<LifecycleScript>,
|
||||
pub(crate) update_content_command: Option<LifecycleScript>,
|
||||
|
|
|
|||
|
|
@ -1058,7 +1058,11 @@ RUN sed -i -E 's/((^|\s)PATH=)([^\$]*)$/\1\${{PATH:-\3}}/g' /etc/profile || true
|
|||
|
||||
let project_name = self.project_name().await?;
|
||||
self.docker_client
|
||||
.docker_compose_build(&docker_compose_resources.files, &project_name)
|
||||
.docker_compose_build(
|
||||
&docker_compose_resources.files,
|
||||
&project_name,
|
||||
dev_container.run_services.as_ref(),
|
||||
)
|
||||
.await?;
|
||||
(
|
||||
self.docker_client
|
||||
|
|
@ -1151,7 +1155,11 @@ RUN sed -i -E 's/((^|\s)PATH=)([^\$]*)$/\1\${{PATH:-\3}}/g' /etc/profile || true
|
|||
|
||||
let project_name = self.project_name().await?;
|
||||
self.docker_client
|
||||
.docker_compose_build(&docker_compose_resources.files, &project_name)
|
||||
.docker_compose_build(
|
||||
&docker_compose_resources.files,
|
||||
&project_name,
|
||||
dev_container.run_services.as_ref(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
(
|
||||
|
|
@ -1785,6 +1793,9 @@ RUN sed -i -E 's/((^|\s)PATH=)([^\$]*)$/\1\${PATH:-\3}/g' /etc/profile || true
|
|||
command.args(&["-f", &docker_compose_file.display().to_string()]);
|
||||
}
|
||||
command.args(&["up", "-d"]);
|
||||
if let Some(run_services) = self.dev_container().run_services.as_ref() {
|
||||
command.args(run_services);
|
||||
}
|
||||
|
||||
let output = self
|
||||
.command_runner
|
||||
|
|
@ -4783,6 +4794,111 @@ ENV DOCKER_BUILDKIT=1
|
|||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_spawns_only_requested_compose_services(cx: &mut TestAppContext) {
|
||||
cx.executor().allow_parking();
|
||||
env_logger::try_init().ok();
|
||||
let given_devcontainer_contents = r#"
|
||||
{
|
||||
"name": "Devcontainer and PostgreSQL",
|
||||
"dockerComposeFile": "docker-compose.yml",
|
||||
"service": "devcontainer",
|
||||
"runServices": ["devcontainer", "db"],
|
||||
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
|
||||
"updateRemoteUserUID": false
|
||||
}
|
||||
"#;
|
||||
let (test_dependencies, mut devcontainer_manifest) =
|
||||
init_default_devcontainer_manifest(cx, given_devcontainer_contents)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
test_dependencies
|
||||
.fs
|
||||
.atomic_write(
|
||||
PathBuf::from(TEST_PROJECT_PATH).join(".devcontainer/docker-compose.yml"),
|
||||
r#"
|
||||
version: '3.8'
|
||||
|
||||
x-base: &base
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
env_file:
|
||||
- .env
|
||||
|
||||
volumes:
|
||||
postgres-data:
|
||||
|
||||
services:
|
||||
app:
|
||||
<<: *base
|
||||
ports:
|
||||
- "3000:3000"
|
||||
|
||||
devcontainer:
|
||||
<<: *base
|
||||
ports:
|
||||
- "3000:3000"
|
||||
volumes:
|
||||
- ../..:/workspaces:cached
|
||||
|
||||
db:
|
||||
image: postgres:14.1
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
env_file:
|
||||
- .env
|
||||
"#
|
||||
.trim()
|
||||
.to_string(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
test_dependencies
|
||||
.fs
|
||||
.atomic_write(
|
||||
PathBuf::from(TEST_PROJECT_PATH).join(".devcontainer/Dockerfile"),
|
||||
r#"
|
||||
FROM mcr.microsoft.com/devcontainers/rust:2-1-bookworm
|
||||
|
||||
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
||||
&& apt-get -y install clang lld \
|
||||
&& apt-get autoremove -y && apt-get clean -y
|
||||
"#
|
||||
.trim()
|
||||
.to_string(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
devcontainer_manifest.parse_nonremote_vars().unwrap();
|
||||
let _devcontainer_up = devcontainer_manifest.build_and_run().await.unwrap();
|
||||
|
||||
let docker_commands = test_dependencies
|
||||
.command_runner
|
||||
.commands_by_program("docker");
|
||||
let compose_up = docker_commands
|
||||
.iter()
|
||||
.find(|c| {
|
||||
c.args.first().map(String::as_str) == Some("compose")
|
||||
&& c.args.iter().any(|a| a == "up")
|
||||
})
|
||||
.expect("docker compose up command recorded");
|
||||
assert!(
|
||||
compose_up.args.ends_with(&[
|
||||
"up".to_string(),
|
||||
"-d".to_string(),
|
||||
"devcontainer".to_string(),
|
||||
"db".to_string(),
|
||||
]),
|
||||
"compose up should target only the requested service, got: {:?}",
|
||||
compose_up.args
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
#[gpui::test]
|
||||
async fn test_spawns_devcontainer_with_docker_compose_and_podman(cx: &mut TestAppContext) {
|
||||
|
|
@ -6067,6 +6183,19 @@ FROM docker.io/hexpm/elixir:1.21-erlang-28.4.1-debian-trixie-20260316-slim AS de
|
|||
return Ok(Some(DockerComposeConfig {
|
||||
name: None,
|
||||
services: HashMap::from([
|
||||
(
|
||||
"devcontainer".to_string(),
|
||||
DockerComposeService {
|
||||
image: Some("test_image:latest".to_string()),
|
||||
volumes: vec![MountDefinition {
|
||||
source: Some("../..".to_string()),
|
||||
target: "/workspaces".to_string(),
|
||||
mount_type: Some("bind".to_string()),
|
||||
}],
|
||||
command: vec!["sleep".to_string(), "infinity".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
(
|
||||
"app".to_string(),
|
||||
DockerComposeService {
|
||||
|
|
@ -6193,6 +6322,7 @@ FROM docker.io/hexpm/elixir:1.21-erlang-28.4.1-debian-trixie-20260316-slim AS de
|
|||
&self,
|
||||
_config_files: &Vec<PathBuf>,
|
||||
_project_name: &str,
|
||||
_services: Option<&Vec<String>>,
|
||||
) -> Result<(), DevContainerError> {
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -291,6 +291,7 @@ impl DockerClient for Docker {
|
|||
&self,
|
||||
config_files: &Vec<PathBuf>,
|
||||
project_name: &str,
|
||||
services: Option<&Vec<String>>,
|
||||
) -> Result<(), DevContainerError> {
|
||||
let mut command = Command::new(&self.docker_cli);
|
||||
if !self.is_podman() {
|
||||
|
|
@ -301,6 +302,9 @@ impl DockerClient for Docker {
|
|||
command.args(&["-f", &docker_compose_file.display().to_string()]);
|
||||
}
|
||||
command.arg("build");
|
||||
if let Some(services) = services {
|
||||
command.args(services);
|
||||
}
|
||||
|
||||
let output = command.output().await.map_err(|e| {
|
||||
log::error!("Error running docker compose up: {e}");
|
||||
|
|
@ -457,6 +461,7 @@ pub(crate) trait DockerClient {
|
|||
&self,
|
||||
config_files: &Vec<PathBuf>,
|
||||
project_name: &str,
|
||||
services: Option<&Vec<String>>,
|
||||
) -> Result<(), DevContainerError>;
|
||||
async fn run_docker_exec(
|
||||
&self,
|
||||
|
|
|
|||
Loading…
Reference in a new issue