Package
@microsoft/vscode-container-client
Summary
parseDockerLikeLabels corrupts any label value that contains a comma, because it splits the entire raw label string on ,. The most common real-world victim is com.docker.compose.project.config_files for a compose project started with multiple -f files, where the value is itself a comma-separated list of paths.
File: packages/vscode-container-client/src/clients/DockerClientBase/parseDockerLikeLabels.ts
export function parseDockerLikeLabels(rawLabels: string): Labels {
return rawLabels.split(',').reduce((labels, labelPair) => {
const index = labelPair.indexOf('=');
labels[labelPair.substring(0, index)] = labelPair.substring(index + 1);
return labels;
}, {} as Labels);
}
docker container ls --format '{{json .}}' (and the equivalent ls commands for networks/volumes) return the Labels field as a single comma-joined key=value,key=value string with no escaping of commas inside values. So for a container with:
com.docker.compose.project.config_files=/abs/path/docker-compose.base.yml,/abs/path/docker-compose.local.yml
the raw Labels string contains ...config_files=/abs/path/docker-compose.base.yml,/abs/path/docker-compose.local.yml,.... Splitting on , yields the fragments:
com.docker.compose.project.config_files=/abs/path/docker-compose.base.yml → key set correctly, value = only the first file
/abs/path/docker-compose.local.yml → has no =, so indexOf('=') returns -1; substring(0, -1) is '' and substring(0) is the whole fragment. This writes a bogus labels[''] = '/abs/path/docker-compose.local.yml' and, worse, silently drops the second file from the intended label.
Net effect: the config_files label value is truncated to the first file.
Downstream impact
microsoft/vscode-containers#522 — "Compose Start / Compose Stop only use the first compose file when multiple files are in com.docker.compose.project.config_files". The extension reads the (truncated) list label and only passes the first --file to docker compose start/stop, so services defined only in later compose files are never started/stopped.
That extension is adding a workaround (inspecting the container, whose Config.Labels is a proper JSON map, to recover the accurate value), but the underlying ls-based parser is lossy for every consumer.
Steps to reproduce
parseDockerLikeLabels('com.docker.compose.project.config_files=/a/base.yml,/a/local.yml,com.docker.compose.project=demo')
Actual:
{
'com.docker.compose.project.config_files': '/a/base.yml',
'': '/a/local.yml',
'com.docker.compose.project': 'demo'
}
Expected:
{
'com.docker.compose.project.config_files': '/a/base.yml,/a/local.yml',
'com.docker.compose.project': 'demo'
}
Proposed fix
Treat a fragment with no = as a continuation of the previous label's value (re-joining the comma that split removed), rather than as a new label:
export function parseDockerLikeLabels(rawLabels: string): Labels {
const labels: Labels = {};
let lastKey: string | undefined;
for (const fragment of rawLabels.split(',')) {
const index = fragment.indexOf('=');
if (index < 0) {
// No '=' means this fragment is a continuation of the previous label's
// value, which itself contained a comma (e.g. multiple compose config
// files in `com.docker.compose.project.config_files`). `docker ... ls`
// joins labels with commas and does not escape commas inside values, so
// we stitch the value back together here.
if (lastKey !== undefined) {
labels[lastKey] += `,${fragment}`;
}
continue;
}
lastKey = fragment.substring(0, index);
labels[lastKey] = fragment.substring(index + 1);
}
return labels;
}
Known limitation
This is a heuristic. It fully recovers values that contain commas (the overwhelmingly common case, including config_files), but a value containing both a comma and an = (e.g. key=a,b=c) would still be misparsed, because the b=c fragment is indistinguishable from a new label in the flattened ls output. The only fully robust source is docker inspect (proper JSON label map), so consumers that need exactness for comma-bearing values should prefer inspect. Happy to open a PR with the above change plus a unit test if it looks good.
Package
@microsoft/vscode-container-clientSummary
parseDockerLikeLabelscorrupts any label value that contains a comma, because it splits the entire raw label string on,. The most common real-world victim iscom.docker.compose.project.config_filesfor a compose project started with multiple-ffiles, where the value is itself a comma-separated list of paths.File:
packages/vscode-container-client/src/clients/DockerClientBase/parseDockerLikeLabels.tsdocker container ls --format '{{json .}}'(and the equivalentlscommands for networks/volumes) return theLabelsfield as a single comma-joinedkey=value,key=valuestring with no escaping of commas inside values. So for a container with:the raw
Labelsstring contains...config_files=/abs/path/docker-compose.base.yml,/abs/path/docker-compose.local.yml,.... Splitting on,yields the fragments:com.docker.compose.project.config_files=/abs/path/docker-compose.base.yml→ key set correctly, value = only the first file/abs/path/docker-compose.local.yml→ has no=, soindexOf('=')returns-1;substring(0, -1)is''andsubstring(0)is the whole fragment. This writes a boguslabels[''] = '/abs/path/docker-compose.local.yml'and, worse, silently drops the second file from the intended label.Net effect: the
config_fileslabel value is truncated to the first file.Downstream impact
microsoft/vscode-containers#522 — "Compose Start / Compose Stop only use the first compose file when multiple files are in
com.docker.compose.project.config_files". The extension reads the (truncated) list label and only passes the first--filetodocker compose start/stop, so services defined only in later compose files are never started/stopped.That extension is adding a workaround (inspecting the container, whose
Config.Labelsis a proper JSON map, to recover the accurate value), but the underlyingls-based parser is lossy for every consumer.Steps to reproduce
Actual:
Expected:
Proposed fix
Treat a fragment with no
=as a continuation of the previous label's value (re-joining the comma thatsplitremoved), rather than as a new label:Known limitation
This is a heuristic. It fully recovers values that contain commas (the overwhelmingly common case, including
config_files), but a value containing both a comma and an=(e.g.key=a,b=c) would still be misparsed, because theb=cfragment is indistinguishable from a new label in the flattenedlsoutput. The only fully robust source isdocker inspect(proper JSON label map), so consumers that need exactness for comma-bearing values should prefer inspect. Happy to open a PR with the above change plus a unit test if it looks good.