feat: validate remaining unsupported items of the support-matrix (#429)

* feat: validate remaining unsupported items of the support-matrix

Validate unsupported compose features: external configs, container_name,
and placement.

This makes the code match the support-matrix as of now (added comment to
that effect as well).

Signed-off-by: Miek Gieben <miek@miek.nl>

* support matrix: some deploy modes are supported

Add tests for the various non-supported deploy modes.
Prefix the warnings with `deploy ` to signal the section, and to
differentiate between top level 'labels' and 'deploy labels'.

Signed-off-by: Miek Gieben <miek@miek.nl>

---------

Signed-off-by: Miek Gieben <miek@miek.nl>
This commit is contained in:
Miek Gieben authored and GitHub committed 2026-09-04 10:40:03 +10:00
1 parent 1c29d4d5d0
commit aaf67687d2
2 files changed
+113 -5

No files matched your search

+73
View File
@@ -322,6 +322,79 @@ volumes:
`, `,
shouldErr: true, shouldErr: true,
}, },
{
name: "external configs",
composeYAML: `services:
app:
image: myapp:latest
configs:
http_config:
external: true
`,
warnCount: 1,
warnContains: []string{"external"},
},
{
name: "container_name",
composeYAML: `services:
app:
image: myapp:latest
container_name: my_container
`,
warnCount: 1,
warnContains: []string{"container_name"},
},
{
name: "deploy mode",
composeYAML: `services:
app:
image: myapp:latest
deploy:
mode: replicated-job
replicas: 2
`,
warnCount: 1,
warnContains: []string{"deploy mode must"},
},
{
name: "deploy mode",
composeYAML: `services:
app:
image: myapp:latest
deploy:
mode: replicated
labels:
- foo
replicas: 2
`,
warnCount: 1,
warnContains: []string{"deploy labels"},
},
{
name: "deploy restart_policy",
composeYAML: `services:
app:
image: myapp:latest
deploy:
restart_policy:
condition: on-failure
`,
warnCount: 1,
warnContains: []string{"deploy restart_policy"},
},
{
name: "deploy placement",
composeYAML: `services:
app:
image: myapp:latest
deploy:
placement:
preferences:
- spread: foo
`,
warnCount: 1,
warnContains: []string{"deploy placement"},
},
} }
for _, tt := range tests { for _, tt := range tests {
+40 -5
View File
@@ -454,13 +454,22 @@ func validateServicesExtensions(project *types.Project) error {
// validateServicesFeatures checks services for unsupported features and returns all found. // validateServicesFeatures checks services for unsupported features and returns all found.
func validateServicesFeatures(project *types.Project) []error { func validateServicesFeatures(project *types.Project) []error {
err := func(service, feature string) error { // XXX(miek): as of Sep 2026 these checks are in check with the support-matrix, except for a short syntax
return fmt.Errorf("service '%s': unsupported feature '%s', see %s", // check.
service, feature, "https://uncloud.run/docs/compose-file-reference/support-matrix") var errs []error
const supportmatrix = "https://uncloud.run/docs/compose-file-reference/support-matrix"
for _, config := range project.Configs {
if config.External {
errs = append(errs, fmt.Errorf("config '%s': unsupported feature '%s', see %s",
config.Name, "external", supportmatrix))
}
} }
// TODO: check other commonly used but unsupported features. err := func(service, feature string) error {
var errs []error return fmt.Errorf("service '%s': unsupported feature '%s', see %s",
service, feature, supportmatrix)
}
for _, service := range project.Services { for _, service := range project.Services {
if service.SecurityOpt != nil { if service.SecurityOpt != nil {
errs = append(errs, err(service.Name, "security_opt")) errs = append(errs, err(service.Name, "security_opt"))
@@ -489,6 +498,32 @@ func validateServicesFeatures(project *types.Project) []error {
if service.StorageOpt != nil { if service.StorageOpt != nil {
errs = append(errs, err(service.Name, "storage_opt")) errs = append(errs, err(service.Name, "storage_opt"))
} }
if service.ContainerName != "" {
errs = append(errs, err(service.Name, "container_name"))
}
if service.Deploy != nil {
if len(service.Deploy.Placement.Constraints) > 0 || len(service.Deploy.Placement.Preferences) > 0 {
errs = append(errs, fmt.Errorf(
"service '%s': deploy placement is not supported, "+
"use x-machines instead: %s", service.Name,
"https://uncloud.run/docs/compose-file-reference/extensions#x-machines"))
}
if service.Deploy.RestartPolicy != nil {
errs = append(errs, fmt.Errorf(
"service '%s': deploy restart_policy defaults to 'unless-stopped'", service.Name))
}
if service.Deploy.Mode != "" && service.Deploy.Mode != "global" && service.Deploy.Mode != "replicated" {
errs = append(errs, fmt.Errorf(
"service '%s': deploy mode must either be 'global' or 'replicated'", service.Name))
}
if service.Deploy.RollbackConfig != nil {
errs = append(errs, err(service.Name, "deploy rollback_config"))
}
if len(service.Deploy.Labels) != 0 {
errs = append(errs, err(service.Name, "deploy labels"))
}
}
// we only allow the 'default' network, nothing else. // we only allow the 'default' network, nothing else.
if x := service.Networks; x != nil { if x := service.Networks; x != nil {
if len(x) != 1 { if len(x) != 1 {