Repository navigation
loader: promote x-* extensions adopted by the specification - #935
Conversation
An attribute adopted by the specification, such as develop, was previously used as an x-* extension. Files written with the extension silently stop working unless each consumer reads both spellings. Declare such extensions in a table and, while loading each compose file, right after interpolation and before include, extends and merge, promote them to the attribute they stand for when it is not already set. Promoting file by file lets an override written with the extension still override its base file. The table starts with x-develop and x-initialSync. Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
β¦ds for The paths recorded for !reset and !override name the extension as written in the file, while earlier files hold the attribute under its own name once their extensions are promoted, so the tag had no effect on them. Record the resolved path next to the original one. Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
5c8d84d to
d29c74a
Compare
glours
left a comment
There was a problem hiding this comment.
loader/tests/extensions_test.go β TestExtensionAliasWithReuse/extends
Nothing pins the position of promoteAliases (loader/loader.go:513). Moving the call to just before override.Merge(dict, cfg) keeps ./loader/... fully green, but changes the result:
basehasdevelop.watch=[a],webextends it and setsx-develop.watch=[b]: expected[a, b], got[a]. The user'sx-developis silently ignored and left as an extension.basehasx-develop.watch=[a],webextends it and has its owndevelop.watch=[b]: expected[a, b], got[b], with a leftoverx-develop.
The current extends subtest doesn't catch it because web has no develop of its own, so promotion still lands before the merge. Moving the call after the merge or removing it is caught (8 subtests fail), so the gap is only "before merge but after extends".
Suggested case, next to the existing extends subtest:
name: test
services:
base:
image: app
develop:
watch:
- path: src
action: sync
target: /app
web:
extends:
service: base
x-develop:
watch:
- path: other
action: rebuild
expected web.develop.watch = [src, other].
Cover an extension set on the extending service or on the base service, so that promoting after the extends merge is caught. Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
When a service sets both the extension and the attribute, promote the extension and merge it under the attribute with the usual merge rules instead of leaving it aside. The attribute still wins on conflicting values, and incompatible shapes are reported. Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
deepClone (extends.go) already does the same deep copy; drop the duplicate helper and call it from promoteAliases. Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
|
@glours re your review: the ordering concern (nothing pinned promoteAliases's position relative to extends, so a future refactor could silently drop the alias) is addressed in d2ffe87 β it adds the two suggested regression cases ("extends with the extension on the extending service" and "extends with the extension on the base service"), which pass with the current implementation. |
What this PR does, in one sentence
Promotes an
x-*extension to the attribute the specification adopted it as, when that attribute is not already set.Context
Compose lets experiments use
x-*attributes without any check. When one of them is adopted (x-developbecamedevelop,x-initialSyncbecameinitial_sync), files written with the extension silently stop working, unless every consumer reads both spellings. The adoption ofinitial_syncis an example: the loader now only knows the new name.What the PR brings
x-developandx-initialSync. Adopting the next attribute is one line, and a test checks that each entry targets an attribute of the schema.include,extendsand merge: an override written with the extension still overrides its base file, and included or extended services are promoted too.!resetand!overridewritten on the extension act on what earlier files declared under the attribute's name.Why this shape
The table lives in the loader rather than in the schema: it only concerns how this library reads files written before an adoption, and it needs no change in the specification. Should an adoption change the shape of the value, the entry is where a conversion would go, as a follow-up.
π€ Generated with Claude Code