Skip to content

Commit 2d3f49e

Browse files
authored
dev_container: Handle devcontainer.metadata label as JSON object or array (#53557)
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 ## Details - The [devcontainer CLI writes the `devcontainer.metadata` label as a bare JSON object](devcontainers/cli#1054) when there is only one metadata entry (e.g. docker-compose devcontainer with a Dockerfile and no features) - Zed's `deserialize_metadata` only accepted a JSON array, causing deserialization to fail with `invalid type: map, expected a sequence` - This made it impossible to attach to existing docker-compose devcontainers created by the devcontainer CLI or VS Code The fix tries parsing as an array first, then falls back to parsing as a single object wrapped in a vec. This mirrors how the [devcontainer CLI itself reads the label](https://github.com/devcontainers/cli/blob/main/src/spec-node/imageMetadata.ts#L476-L493). An upstream fix has also been submitted: devcontainers/cli#1199 ## Reproduction 1. Create a docker-compose devcontainer with a Dockerfile and no features: `.devcontainer/devcontainer.json`: ```json { "name": "repro", "dockerComposeFile": "docker-compose.yml", "service": "app", "remoteUser": "root" } ``` `.devcontainer/docker-compose.yml`: ```yaml services: app: build: context: . dockerfile: Dockerfile command: sleep infinity volumes: - ..:/workspace ``` `.devcontainer/Dockerfile`: ```dockerfile FROM ubuntu:24.04 ``` 2. `devcontainer up --workspace-folder .` 3. Open the folder in Zed, fails with metadata deserialization error Release Notes: - Fixed attaching to a devcontainer that has a single metadata element which was started with `devcontainer-cli`
1 parent 5a9f825 commit 2d3f49e

1 file changed

Lines changed: 35 additions & 3 deletions

File tree

crates/dev_container/src/docker.rs

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -487,10 +487,18 @@ where
487487
let s: Option<String> = Option::deserialize(deserializer)?;
488488
match s {
489489
Some(json_string) => {
490+
// The devcontainer metadata label can be either a JSON array (e.g. from
491+
// image-based devcontainers) or a single JSON object (e.g. from
492+
// docker-compose-based devcontainers created by the devcontainer CLI).
493+
// Handle both formats.
490494
let parsed: Vec<HashMap<String, serde_json_lenient::Value>> =
491-
serde_json_lenient::from_str(&json_string).map_err(|e| {
492-
log::error!("Error deserializing metadata: {e}");
493-
serde::de::Error::custom(e)
495+
serde_json_lenient::from_str(&json_string).or_else(|_| {
496+
let single: HashMap<String, serde_json_lenient::Value> =
497+
serde_json_lenient::from_str(&json_string).map_err(|e| {
498+
log::error!("Error deserializing metadata: {e}");
499+
serde::de::Error::custom(e)
500+
})?;
501+
Ok(vec![single])
494502
})?;
495503
Ok(Some(parsed))
496504
}
@@ -936,6 +944,30 @@ mod test {
936944
assert_eq!(target_dir.unwrap(), "/workspaces/cli/".to_string());
937945
}
938946

947+
#[test]
948+
fn should_deserialize_object_metadata_from_docker_compose_container() {
949+
// The devcontainer CLI writes metadata as a bare JSON object (not an array)
950+
// when there is only one metadata entry (e.g. docker-compose with no features).
951+
// See https://github.com/devcontainers/cli/issues/1054
952+
let given_config = r#"
953+
{
954+
"Id": "dc4e7b8ff4bf",
955+
"Config": {
956+
"Labels": {
957+
"devcontainer.metadata": "{\"remoteUser\":\"ubuntu\"}"
958+
}
959+
}
960+
}
961+
"#;
962+
let config = serde_json_lenient::from_str::<DockerInspect>(given_config).unwrap();
963+
964+
assert!(config.config.labels.metadata.is_some());
965+
let metadata = config.config.labels.metadata.unwrap();
966+
assert_eq!(metadata.len(), 1);
967+
assert!(metadata[0].contains_key("remoteUser"));
968+
assert_eq!(metadata[0]["remoteUser"], "ubuntu");
969+
}
970+
939971
#[test]
940972
fn should_deserialize_docker_compose_config() {
941973
let given_config = r#"

0 commit comments

Comments
 (0)