Skip to content

fix(projectloader): unescape service names in unsupported attribute warnings - #14380

Open
Huang-404-Q wants to merge 2 commits into
docker:mainfrom
Huang-404-Q:fix/unsupported-attr-service-dot
Open

Huang-404-Q wants to merge 2 commits into
docker:mainfrom
Huang-404-Q:fix/unsupported-attr-service-dot

Conversation

@Huang-404-Q

Copy link
Copy Markdown

What

compose-go represents a literal dot inside a key segment with an internal sentinel (👻) so that tree.Path can split on real separators. splitServicePath returned parts[1] raw, so a legal service name containing a dot was passed to api.UnsupportedAttribute.Service with the sentinel still embedded:

services:
  my.service:
    image: alpine
    deploy:
      mode: replicated
WARN service "my👻service": deploy.mode: deploy.mode is only honored in Swarm mode

Expected: service "my.service": ...

Fix

Unescape the segment with tree.Path(parts[1]).String(). The remaining dotted path is unchanged.

Tests

Added a subtest to TestUnsupportedAttributes using my.service and asserting the reported service is my.service, plus the existing web/deploy.mode controls.

go test ./internal/projectloader -run 'TestUnsupportedAttributes$' -count=1

Before the fix the new subtest fails with Service: "my👻service"; after it passes and the full TestUnsupportedAttributes suite is green.

@Huang-404-Q
Huang-404-Q requested review from a team as code owners October 23, 2026 03:22
…arnings

compose-go escapes a literal dot in a path segment as an internal
sentinel, and splitServicePath returned the raw segment. A service named
"my.service" was therefore reported as "my👻service" in warnings.

Unescape the segment with tree.Path(...).String().

Signed-off-by: huang <13165998033@163.com>
@Huang-404-Q
Huang-404-Q force-pushed the fix/unsupported-attr-service-dot branch from bfc2a16 to a9a3808 Compare October 23, 2026 03:31
ndeloof
ndeloof previously approved these changes Oct 23, 2026

@ndeloof ndeloof left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the fix, LGTM. I checked that the new subtest fails without the change (Service: "my👻service") and passes with it, and that golangci-lint is clean on the package.

Two optional suggestions below; I validated both locally (package tests and lint pass with them applied).

parts := path.Parts()
if len(parts) >= 2 && parts[0] == "services" {
return parts[1], strings.Join(parts[2:], ".")
return tree.Path(parts[1]).String(), strings.Join(parts[2:], ".")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: the project-scoped branch below returns path.String() (unescaped), whereas the remaining service path is still joined from the raw parts, so a dotted key deeper in the path would keep the 👻 sentinel. I could not find a reachable case today, but a future generic finding under a free-key map would leak it the same way. Unescaping both keeps the two branches consistent:

Suggested change
return tree.Path(parts[1]).String(), strings.Join(parts[2:], ".")
return tree.Path(parts[1]).String(), tree.Path(strings.Join(parts[2:], ".")).String()

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied in 926eea4: both branches now unescape through tree.Path(...).String(), so a dotted key deeper in the path can't leak the sentinel.

Comment on lines +115 to +117
mode: replicated
`)
assertFindings(t, got, []findingKey{{Service: "my.service", Path: "deploy.mode"}})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional: the value-conditional branch (ports[].mode: host) also receives service from splitServicePath, so it benefits from this fix but is not covered with a dotted name. Extending the subtest covers both reporting paths:

Suggested change
mode: replicated
`)
assertFindings(t, got, []findingKey{{Service: "my.service", Path: "deploy.mode"}})
mode: replicated
ports:
- target: 80
published: "8080"
protocol: tcp
mode: host
`)
assertFindings(t, got, []findingKey{
{Service: "my.service", Path: "deploy.mode"},
{Service: "my.service", Path: "ports[80/tcp].mode"},
})

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extended in 926eea4: the dotted-service subtest now also covers the ports[].mode reporting path (ports[80/tcp].mode). Package tests and gofmt pass.

@codecov

codecov Bot commented Oct 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Unescape the remaining path in both branches and cover the
ports[].mode reporting path with a dotted service name.

Signed-off-by: huang <13165998033@163.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants