From 624f2c77ae64e524a586e1b6946fa88e192d73c8 Mon Sep 17 00:00:00 2001 From: Chingis S Date: Wed, 19 Aug 2026 17:36:14 +0300 Subject: [PATCH] Do not treat effective-value scopes as stack settings Wodby 1 exports some service configuration scopes for their effective value rather than as settings. The stack settings path skipped deployment and resources by name but converted everything else, so when implementation started arriving in that map it failed conversion and blocked the migration: "configuration value is not a supported scalar", once per instance pinning one. Replace the inline name check with a named predicate covering all three. The effect of implementation is already carried by the exported service version, and no consumer reads it, so skipping it loses nothing. Wodby 1 also stops exporting the value, but a Wodby 2 CLI meets Wodby 1 deployments that do not yet, and this is the layer that keeps a migration running against them. A genuinely unrepresentable setting still blocks; that is covered by its own test so the skip list cannot quietly become a way to lose real problems. Validation: go build ./..., go vet ./..., go test ./... (all packages pass). Tests use a template-reference value, the shape that actually failed. --- pkg/migration/wodby1/stack_configuration.go | 21 +++++++- .../wodby1/stack_configuration_test.go | 52 +++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/pkg/migration/wodby1/stack_configuration.go b/pkg/migration/wodby1/stack_configuration.go index 118ac13..4c847ce 100644 --- a/pkg/migration/wodby1/stack_configuration.go +++ b/pkg/migration/wodby1/stack_configuration.go @@ -584,7 +584,7 @@ func preparedStackSettings(appName, targetName string, items []stackConfigServic findings := []ReviewItem{} for _, item := range items { for name, raw := range item.source.Configuration { - if name == "deployment" || name == "resources" { + if !sourceConfigurationIsStackSetting(name) { continue } value, err := scalarConfigurationValue(raw) @@ -859,3 +859,22 @@ func appUsesExplicitTargetStack(plan *AppPlan) bool { } return false } + +// sourceConfigurationIsStackSetting reports whether an exported Wodby 1 service +// configuration scope becomes a Wodby 2 stack service setting. +// +// Some scopes are exported for their effective value rather than as settings: +// deployment and resources arrive as normalized capacity fields, and +// implementation names a Wodby 1 service template whose effect is already +// carried by the exported service version. None of them is a scalar setting, so +// converting them produced a blocking review item for a value nothing consumes. +// Wodby 1 also filters these, but a Wodby 2 CLI meets Wodby 1 deployments that +// do not yet. +func sourceConfigurationIsStackSetting(name string) bool { + switch name { + case "deployment", "resources", "implementation": + return false + default: + return true + } +} diff --git a/pkg/migration/wodby1/stack_configuration_test.go b/pkg/migration/wodby1/stack_configuration_test.go index 9006e49..f7f48e0 100644 --- a/pkg/migration/wodby1/stack_configuration_test.go +++ b/pkg/migration/wodby1/stack_configuration_test.go @@ -563,3 +563,55 @@ func assertPreparedStackEnvVarScoped(t *testing.T, variables []PreparedStackEnvV } t.Fatalf("env var %q is missing from %#v", name, variables) } + +// Wodby 1 exports some service configuration scopes for their effective value +// rather than as settings. They are not scalars, so treating them as stack +// settings blocked the migration on a value nothing consumes. +func TestNonSettingConfigurationScopesDoNotBecomeStackSettings(t *testing.T) { + for _, scope := range []string{"deployment", "resources", "implementation"} { + if sourceConfigurationIsStackSetting(scope) { + t.Fatalf("%q must not become a stack service setting", scope) + } + } + if !sourceConfigurationIsStackSetting("php_max_execution_time") { + t.Fatal("a genuine setting must still be migrated") + } +} + +// A Wodby 1 deployment that still exports the implementation value must not +// block the migration. +func TestImplementationOverrideFromAnOlderExportDoesNotBlock(t *testing.T) { + instance := stackConfigurationTestInstance("prod", "PROD", "production", "shared") + // The real shape: a template reference, not a scalar. + instance.Source.Services[0].Configuration = map[string]interface{}{ + "implementation": map[string]interface{}{"uuid": "tpl-1", "name": "php", "version": "8.4"}, + } + + configuration, findings, err := prepareStackConfigurationTest(stackConfigurationTestApp(instance)) + if err != nil { + t.Fatal(err) + } + if hasBlockingFindings(findings) { + t.Fatalf("an effective-value scope must not block the migration: %#v", findings) + } + if _, present := configuration.Services["php"].Settings["implementation"]; present { + t.Fatalf("settings = %#v", configuration.Services["php"].Settings) + } +} + +// A genuinely unrepresentable setting must still block, so the skip list does +// not become a way to lose real problems. +func TestUnsupportedSettingValueStillBlocks(t *testing.T) { + instance := stackConfigurationTestInstance("prod", "PROD", "production", "shared") + instance.Source.Services[0].Configuration = map[string]interface{}{ + "php_custom_setting": map[string]interface{}{"unsupported": "shape"}, + } + + _, findings, err := prepareStackConfigurationTest(stackConfigurationTestApp(instance)) + if err != nil { + t.Fatal(err) + } + if !hasBlockingFindings(findings) { + t.Fatalf("an unrepresentable setting must still block: %#v", findings) + } +}