Skip to content

fix: support upstream specs in component query - #110

Open
Agyei Holy (holly-agyei) wants to merge 3 commits into
microsoft:mainfrom
holly-agyei:fix-comp-query-upstream-spec
Open

fix: support upstream specs in component query#110
Agyei Holy (holly-agyei) wants to merge 3 commits into
microsoft:mainfrom
holly-agyei:fix-comp-query-upstream-spec

Conversation

@holly-agyei

Copy link
Copy Markdown

Fixes #19

Summary

  • prepare upstream component sources before parsing specs in component query
  • normalize bare/unspecified spec sources to upstream in this query-only path
  • add a regression test covering the default upstream component case

Test plan

  • GOCACHE=/tmp/azldev-gocache GOMODCACHE=/tmp/azldev-gomodcache go test ./internal/app/azldev/cmds/component -run 'Test(NewComponentQueryCommand|ComponentQueryCmd_NoMatch|QueryComponents_.*)$' -count=1

Copilot AI lite review requested due to automatic review settings April 22, 2026 08:00
@holly-agyei

Copy link
Copy Markdown
Author

reuben olinsky (@reubeno) This PR is ready for review. It adds support for upstream/default specs in component query by preparing upstream sources before parsing, and it includes regression coverage for the bare upstream component case.

Copilot AI 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

Fixes azldev component query for upstream/default-upstream components by ensuring specs are made locally available before parsing, aligning behavior with other component subcommands.

Changes:

  • Added a spec-preparation step in component query for non-local spec sources before parsing.
  • Normalized SpecSourceTypeUnspecified to upstream for the query-only path.
  • Added a regression test covering the default/unspecified upstream component case.

Reviewed changes

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

File Description
internal/app/azldev/cmds/component/query.go Adds upstream source preparation + normalization so query can parse specs from prepared local paths.
internal/app/azldev/cmds/component/query_test.go Adds a regression test for querying a default upstream (unspecified) component.

Comment on lines +131 to +147
preparer, err := sources.NewPreparer(sourceManager, env.FS(), env, env, sources.WithSkipLookaside())
if err != nil {
return nil, fmt.Errorf("failed to create source preparer:\n%w", err)
}

workDirFactory, err := workdir.NewFactory(env.FS(), env.WorkDir(), env.ConstructionTime())
if err != nil {
return nil, fmt.Errorf("failed to create work dir factory:\n%w", err)
}

preparedSourcesDir, err := workDirFactory.Create(comp.GetName(), "query-spec")
if err != nil {
return nil, fmt.Errorf("failed to create work dir for component %#q:\n%w", comp.GetName(), err)
}

if err := preparer.PrepareSources(env, componentForPrep, preparedSourcesDir, true /* applyOverlays */); err != nil {
return nil, fmt.Errorf("failed to prepare sources for component %#q:\n%w", comp.GetName(), err)

Copilot AI Apr 22, 2026

Copy link

Choose a reason for hiding this comment

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

parseComponentSpec prepares upstream sources without enabling dist-git / synthetic history (sources.WithGitRepo). In other paths (e.g., component render) WithGitRepo is used so rpmautospec can expand %autorelease / %autochangelog correctly and to keep release numbering consistent with overlay commit count. As written, component query may produce incorrect release/changelog values (or fail) for specs that rely on rpmautospec because the upstream .git dir is removed and no synthetic commits are created. Consider aligning this with render by adding sources.WithGitRepo(env.Config().Project.DefaultAuthorEmail) (or otherwise preserving .git + synthetic history) for this query-only prep path, or explicitly disabling rpmautospec-dependent expansion and documenting the limitation.

Copilot uses AI. Check for mistakes.
Comment thread internal/app/azldev/cmds/component/query_test.go Outdated
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 23, 2026 22:41

Copilot AI 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 2 out of 2 changed files in this pull request and generated 2 comments.

Comment on lines +104 to +110
component: projectconfig.ComponentConfig{
Name: testComponentName,
Spec: projectconfig.SpecConfig{
SourceType: projectconfig.SpecSourceTypeUpstream,
UpstreamName: testUpstreamName,
},
},

Copilot AI Apr 23, 2026

Copy link

Choose a reason for hiding this comment

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

projectconfig.SpecConfig does not exist (the ComponentConfig.Spec field is projectconfig.SpecSource). This won’t compile; use the same spec struct type as in TestQueryComponents_OneComponent when setting SourceType/UpstreamName.

Copilot uses AI. Check for mistakes.

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.

Copilot apply changes based on this feedback

Comment on lines +139 to +149
testEnv.CmdFactory.RunAndGetOutputHandler = func(cmd *exec.Cmd) (string, error) {
if len(cmd.Args) >= 5 &&
cmd.Args[0] == "git" &&
cmd.Args[1] == "-C" &&
cmd.Args[3] == "rev-parse" &&
cmd.Args[4] == "HEAD" {
return "head123abc\n", nil
}

return "name=test-component\nepoch=0\nversion=1.0.0\nrelease=1.azl3\n", nil
}

Copilot AI Apr 23, 2026

Copy link

Choose a reason for hiding this comment

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

This test writes different Name: values into the generated .spec, but the mocked rpmspec output is hard-coded to name=test-component and the assertions don’t check the returned spec name/version fields. As written, the test won’t fail if the query path parses the wrong spec (or never reads it). Consider making the mock output depend on the test case and asserting on result.Name (and/or other parsed fields) to actually validate upstream vs default behavior.

Copilot uses AI. Check for mistakes.

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.

Copilot apply changes based on this feedback

Copilot AI review requested due to automatic review settings August 20, 2026 22:45

Copilot AI 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 2 out of 2 changed files in this pull request and generated no new comments.

Suppressed comments (3)

internal/app/azldev/cmds/component/query_test.go:109

  • projectconfig.ComponentConfig.Spec is a projectconfig.SpecSource, but this test uses projectconfig.SpecConfig (which doesn’t exist). This won’t compile; set Spec using projectconfig.SpecSource like the other tests.
				Spec: projectconfig.SpecConfig{
					SourceType:   projectconfig.SpecSourceTypeUpstream,
					UpstreamName: testUpstreamName,
				},

internal/app/azldev/cmds/component/query_test.go:149

  • The mocked spec-query output is hard-coded to name=test-component, so the two test cases can’t distinguish between default/normalized upstream behavior vs explicit UpstreamName. Make the mocked output depend on the test case so the assertions can catch regressions.
				return "name=test-component\nepoch=0\nversion=1.0.0\nrelease=1.azl3\n", nil
			}

internal/app/azldev/cmds/component/query_test.go:162

  • This assertion only checks result.Name == testComponentName, which will pass even when the upstream spec name differs (and even if the query path accidentally always uses the normalized name). Assert against the per-test expected name so the test actually validates the behavior under test.
			result := results[0]
			assert.Equal(t, testComponentName, result.Name)

@liunan-ms Nan Liu (liunan-ms) 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.

Agyei Holy (@holly-agyei) Thanks for contributing to azure-linux-dev-tools! I left some comments to improve the fix.

preparedSourcesDir, err := workDirFactory.Create(comp.GetName(), "query-spec")
if err != nil {
return nil, fmt.Errorf("failed to create work dir for component %#q:\n%w", comp.GetName(), err)
}

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.

[blocking]: The prepared work dir is leaked. workDirFactory.Create returns a real directory under WorkDir() and doesn't self-clean, and this function never removes preparedSourcesDir. Since query runs this per component in the loop, every invocation leaves one clone/prepared tree per non-local component behind, growing without bound. render.go pairs its staging dir with a deferred cleanup and the same cleanup can be done here:

preparedSourcesDir, err := workDirFactory.Create(comp.GetName(), "query-spec")
if err != nil {
    return nil, fmt.Errorf("failed to create work dir for component %#q:\n%w", comp.GetName(), err)
}
defer func() {
    if rmErr := env.FS().RemoveAll(preparedSourcesDir); rmErr != nil {
        slog.Debug("failed to remove query work dir", "path", preparedSourcesDir, "error", rmErr)
    }
}()


preparedConfig := *componentForPrep.GetConfig()
preparedConfig.Spec.SourceType = projectconfig.SpecSourceTypeLocal
preparedConfig.Spec.Path = filepath.Join(preparedSourcesDir, comp.GetName()+".spec")

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.

The hardcoded <name>.spec join is correct here, but render.go already has a findSpecFile helper that does this join plus an existence check. Reusing it would keep the two paths consistent and give a clearer error if the spec is missing after preparation.

results, err := component.QueryComponents(testEnv.Env, &options)
require.NoError(t, err)
require.Len(t, results, 1)

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.

Given the work-dir leak noted in query.go, please add an assertion that the prepared sources dir is cleaned up after QueryComponents returns (e.g. assert WorkDir() contains no leftover query-spec tree). That locks in the cleanup fix and prevents a future regression. An error-path case (PrepareSources failing) would be a nice-to-have too.

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.

comp query fails for upstream-type components with "invalid spec source type: upstream"

3 participants