Skip to content

feat(overlay-files): support {component} placeholder for pattern-discovered components - #269

Open
Nan Liu (liunan-ms) wants to merge 1 commit into
microsoft:mainfrom
liunan-ms:liunan/overlayfiles-pattern
Open

feat(overlay-files): support {component} placeholder for pattern-discovered components#269
Nan Liu (liunan-ms) wants to merge 1 commit into
microsoft:mainfrom
liunan-ms:liunan/overlayfiles-pattern

Conversation

@liunan-ms

@liunan-msNan Liu (liunan-ms) commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a {component} placeholder for project-level overlay-files entries. The resolver globs matching paths, extracts each captured segment as a component name, and synthesizes components not otherwise declared. Explicit component declarations shadow pattern-discovered matches.

Details

  • projectconfig: validates the placeholder per scope. Non-project scopes (distro version, component group, per-component) reject the placeholder. Project scope accepts it but rejects \ as a path separator and glob metachars (*?[) before the placeholder (permissive in the suffix).
  • components: new pattern_discovery.go walks project-level overlay-files entries containing {component}, resolves matching filesystem paths via doublestar, and captures the component name from the placeholder position. Resolver caches discovery results per-instance so repeated calls don't re-glob.
  • resolver: FindAllComponents unions the three sources — group members, loose declared components, pattern-discovered components — and emits sorted output for determinism.
  • schema / docs: JSON schema regenerated; user-facing docs/user/reference/config/overlays.md documents the placeholder.

CopilotAI review requested due to automatic review settings July 7, 2026 22:51

CopilotAI 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.

Pull request overview

This PR introduces a {component} placeholder for project-level default-component-config.overlay-files to support pattern-based discovery of components and per-component overlay-file resolution, and updates schema/snapshots/docs accordingly.

Changes:

  • Add projectconfig placeholder validation/splitting/substitution helpers and scope-based validation ({component} required at project scope, forbidden elsewhere).
  • Add pattern-based component discovery from project-level overlay-files entries with resolver-level caching and deterministic unioning into FindAllComponents.
  • Regenerate JSON schema / scenario snapshot and document the new placeholder behavior in the overlays config reference.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
schemas/azldev.schema.jsonUpdates schema description for overlay-files to document {component} rules.
scenario/snapshots/TestSnapshots_config_generate-schema_stdout_1.snapUpdates snapshot to match regenerated schema output.
internal/projectconfig/overlay_files_placeholder.goImplements {component} placeholder validation, splitting, substitution, and absolutization helpers.
internal/projectconfig/overlay_files_placeholder_test.goUnit tests for placeholder validation/splitting/substitution helpers.
internal/projectconfig/overlay_file.goSubstitutes {component} with component name before overlay glob expansion.
internal/projectconfig/configfile.goAdds config-file validation enforcing placeholder rules by scope.
internal/projectconfig/configfile_test.goAdds validation tests for allowed/forbidden placeholder usage across scopes.
internal/projectconfig/component.goUpdates OverlayFiles docs/schema description and absolutizes placeholder entries during WithAbsolutePaths.
internal/app/azldev/core/components/resolver.goUnions pattern-discovered components into FindAllComponents with caching and shadowing behavior.
internal/app/azldev/core/components/pattern_discovery.goImplements filesystem globbing + capture of {component} segment into synthesized components.
internal/app/azldev/core/components/pattern_discovery_test.goTests discovery, inheritance/override semantics, and same-scope collision errors.
docs/user/reference/config/overlays.mdDocuments {component} placeholder discovery/inheritance/shadowing behavior for users.

Comment threadinternal/app/azldev/core/components/pattern_discovery.go
Comment threadinternal/app/azldev/core/components/resolver.go
@liunan-ms
Nan Liu (liunan-ms)force-pushed the liunan/overlayfiles-pattern branch from 4a56921 to f2c8de7CompareJuly 7, 2026 23:33
}

count := strings.Count(entry, OverlayFilesComponentPlaceholder)
if count == 0 {

@dmcilvaneyDaniel McIlvaney (dmcilvaney)Jul 8, 2026

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.

I think this stops us having non-component placeholder entries, ie have overlay-files = ["static/overlays/*.toml"]. If this is expected, docs need to be updated (overlays.md:166) to match.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

This is intentional. Project-level entries without {component} are rejected because they would silently apply the same files to every component. Updated the doc.

{name: "star before placeholder", entry: "comps*/{component}/*.overlay.toml", wantErr: true},
{name: "whole segment at start", entry: "{component}/overlays/*.overlay.toml", wantErr: false},
{name: "whole segment in middle", entry: "comps/{component}/overlays/*.overlay.toml", wantErr: false},
{name: "whole segment at end", entry: "comps/overlays/{component}", wantErr: false},

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.

I think this might be an error, won't it glob files with the name of the component, not directories? I think it probably always needs to be afolder right?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Great catch, updated the check and test.

// declaration overrides pattern-discovered overlay files for the same name.
func logShadowedByDeclaration(shadowed *patternDiscoveredComponent) {
slog.Warn(
"project 'overlay-files' pattern match shadowed by explicit component declaration; overlay files will not be applied",

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.

I think this might miss shadowing from project defaults by groups. Not sure if thats something worth an error though.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

This would be resolved by the scope restriction, now rejecting overlay-files in distro-version and component-group default-component-config entirely.

// which suppresses inheritance (see [projectconfig.ComponentConfig.MergeUpdatesFrom]).
// A declaration with no overlay-files inherits the pattern and the
// discovered files still apply, so there's nothing to warn about.
if declared, ok := r.env.Config().Components[name]; ok && declared.OverlayFiles != nil {

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.

Might want to use len(declared.OverlayFiles) > 0 rather than nil, if I recall from the previous PR the way to disable inheritance is to set overlay-files=[]. 50/50 on this, we could still warn because thats weird, but it is intentionally explicitly saying "no overlays" for a component.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Fixed. Changed to len(declared.OverlayFiles) > 0.

}

prefix := entry[:idx]
if globMetaIdx := strings.IndexAny(prefix, "*?["); globMetaIdx >= 0 {

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.

Might be able to add https://pkg.go.dev/github.com/bmatcuk/doublestar/v4#ValidatePattern here as well, get early signal back on a bad glob pattern.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Now using doublestar.ValidatePattern on the substituted glob validation.

err error
}

func (c *discoveredComponentsCache) load(env *azldev.Env) (map[string]*patternDiscoveredComponent, error) {

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.

Found this a while back, might do the trick here: https://pkg.go.dev/sync#OnceValues

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

switched to using sync.OnceValues

}

byName := make(map[string]*patternDiscoveredComponent)
// Deduplicate files per component before appending to preserve stable ordering.

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.

Curious if this is needed, my understanding was that glob would deduplicate internally.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

doublestar.Glob returns unique matches per call so the seen map was dead code and has been removed.

CopilotAI review requested due to automatic review settings July 8, 2026 21:38
@liunan-ms
Nan Liu (liunan-ms)force-pushed the liunan/overlayfiles-pattern branch from f2c8de7 to 1f37e09CompareJuly 8, 2026 21:38

CopilotAI 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.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 4 comments.

Comment threadinternal/projectconfig/configfile.go
Comment threadinternal/projectconfig/configfile.go
Comment threadinternal/projectconfig/overlay_files_placeholder.go
Comment threaddocs/user/reference/config/overlays.md Outdated
CopilotAI review requested due to automatic review settings July 8, 2026 21:46
@liunan-ms
Nan Liu (liunan-ms)force-pushed the liunan/overlayfiles-pattern branch from 1f37e09 to 27e7ffbCompareJuly 8, 2026 21:46

CopilotAI 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.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 4 comments.

Comment threadinternal/projectconfig/overlay_files_placeholder.go
Comment threadinternal/projectconfig/overlay_files_placeholder_test.go
Comment threadinternal/app/azldev/core/components/resolver.go Outdated
Comment threaddocs/user/reference/config/overlays.md Outdated
CopilotAI review requested due to automatic review settings July 8, 2026 21:55
@liunan-ms
Nan Liu (liunan-ms)force-pushed the liunan/overlayfiles-pattern branch from 27e7ffb to 594c51bCompareJuly 8, 2026 21:55
@liunan-ms

Copy link
Copy Markdown
ContributorAuthor

Thanks Daniel McIlvaney (@dmcilvaney) for the review! After thinking about the scope and usage of overlay-files in azurelinux, I think it'd be better to restrict overlay-files scope to project + component only, a few reasons:

  • All components now live in base/comps/ with per-component overlays, so a single project-level {component} pattern is enough for every existing case. Broader scopes (distro/group) are out-of-scope for this PR and can be added later if a real requirement appears.
  • Simpler validation and no risk of users accidentally declaring overlay-files at multiple levels and having to reason about merge/override precedence.

I update the implementation and now the scope rule is enforced. overlay-files allowed in:

  • project default-component-config: must contain {component} (pattern-discovery + inheritance)
  • [components.X]: plain globs relative to the declaring .comp.toml
  • Reject (with clear errors) in distro-version and component-groupdefault-component-config.

CopilotAI 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.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.

Comment threadinternal/app/azldev/core/components/pattern_discovery.go
Comment threadinternal/app/azldev/core/components/resolver.go Outdated
CopilotAI review requested due to automatic review settings July 8, 2026 22:09
@liunan-ms
Nan Liu (liunan-ms)force-pushed the liunan/overlayfiles-pattern branch from 594c51b to fc2fd2bCompareJuly 8, 2026 22:09

CopilotAI 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.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 4 comments.

Comment threadinternal/app/azldev/core/components/pattern_discovery.go Outdated
Comment threadinternal/app/azldev/core/components/pattern_discovery.go
Comment threadinternal/projectconfig/component.go Outdated
Comment threaddocs/user/reference/config/overlays.md Outdated
…overed components
Add '{component}' placeholder in project-level overlay-files entries. The resolver globs matching paths, extracts each captured segment as a component name, and synthesizes components not otherwise declared. Explicit component declarations shadow pattern-discovered matches (warning emitted).
- projectconfig: validate placeholder syntax per scope; reject backslash separators and glob metachars before the placeholder
- components: pattern_discovery walks overlay-files entries; Resolver caches discovery results per instance
- schema/docs: user-facing overlays.md updated
CopilotAI review requested due to automatic review settings July 8, 2026 22:29

CopilotAI 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.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.

Comment on lines +151 to +156
func absolutizeOverlayFilesPlaceholderEntries(referenceDir string, entries []string) []string {
if entries == nil {
return nil
}

result := make([]string, len(entries))
Comment on lines +176 to +180
// - Distro-version and component-group 'default-component-config':
// 'overlay-files' is not allowed at all. Broad-scope defaults for this
// field silently displace project-level discovery and per-component
// overrides, so we require callers to place the list at project or
// component scope where the intent is unambiguous.
Sign up for freeto 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.

3 participants

@liunan-ms@dmcilvaney