From 8aad63aeada5ee6ca13b7142b010b333ede7bd04 Mon Sep 17 00:00:00 2001 From: Agyei Holy Date: Wed, 22 Apr 2026 02:59:04 -0500 Subject: [PATCH 1/2] fix: support upstream specs in component query --- internal/app/azldev/cmds/component/query.go | 69 ++++++++++++++++++- .../app/azldev/cmds/component/query_test.go | 52 ++++++++++++++ 2 files changed, 118 insertions(+), 3 deletions(-) diff --git a/internal/app/azldev/cmds/component/query.go b/internal/app/azldev/cmds/component/query.go index 59985a73..dd67aff2 100644 --- a/internal/app/azldev/cmds/component/query.go +++ b/internal/app/azldev/cmds/component/query.go @@ -5,10 +5,15 @@ package component import ( "fmt" + "path/filepath" "github.com/microsoft/azure-linux-dev-tools/internal/app/azldev" "github.com/microsoft/azure-linux-dev-tools/internal/app/azldev/core/components" + "github.com/microsoft/azure-linux-dev-tools/internal/app/azldev/core/sources" "github.com/microsoft/azure-linux-dev-tools/internal/app/azldev/core/specs" + "github.com/microsoft/azure-linux-dev-tools/internal/app/azldev/core/workdir" + "github.com/microsoft/azure-linux-dev-tools/internal/projectconfig" + "github.com/microsoft/azure-linux-dev-tools/internal/providers/sourceproviders" "github.com/spf13/cobra" ) @@ -58,6 +63,15 @@ type componentDetails struct { specs.ComponentSpecDetails } +type queryComponent struct { + components.Component + config projectconfig.ComponentConfig +} + +func (c *queryComponent) GetConfig() *projectconfig.ComponentConfig { + return &c.config +} + // Queries env for component details, in accordance with options. Returns the found components. func QueryComponents( env *azldev.Env, options *QueryComponentsOptions, @@ -74,9 +88,7 @@ func QueryComponents( allDetails := make([]*componentDetails, 0, comps.Len()) for _, comp := range comps.Components() { - spec := comp.GetSpec() - - specInfo, err := spec.Parse() + specInfo, err := parseComponentSpec(env, comp) if err != nil { return nil, fmt.Errorf("failed to parse spec for component %q:\n%w", comp.GetName(), err) } @@ -90,3 +102,54 @@ func QueryComponents( return allDetails, nil } + +func parseComponentSpec(env *azldev.Env, comp components.Component) (*specs.ComponentSpecDetails, error) { + if comp.GetConfig().Spec.SourceType == projectconfig.SpecSourceTypeLocal { + return comp.GetSpec().Parse() + } + + componentForPrep := comp + if comp.GetConfig().Spec.SourceType == projectconfig.SpecSourceTypeUnspecified { + normalizedConfig := *comp.GetConfig() + normalizedConfig.Spec.SourceType = projectconfig.SpecSourceTypeUpstream + componentForPrep = &queryComponent{ + Component: comp, + config: normalizedConfig, + } + } + + distro, err := sourceproviders.ResolveDistro(env, componentForPrep) + if err != nil { + return nil, fmt.Errorf("failed to resolve distro for component %q:\n%w", comp.GetName(), err) + } + + sourceManager, err := sourceproviders.NewSourceManager(env, distro) + if err != nil { + return nil, fmt.Errorf("failed to create source manager:\n%w", err) + } + + 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) + } + + preparedConfig := *componentForPrep.GetConfig() + preparedConfig.Spec.SourceType = projectconfig.SpecSourceTypeLocal + preparedConfig.Spec.Path = filepath.Join(preparedSourcesDir, comp.GetName()+".spec") + + return specs.NewSpec(env, preparedConfig).Parse() +} diff --git a/internal/app/azldev/cmds/component/query_test.go b/internal/app/azldev/cmds/component/query_test.go index 9d605e02..5fc333f9 100644 --- a/internal/app/azldev/cmds/component/query_test.go +++ b/internal/app/azldev/cmds/component/query_test.go @@ -80,3 +80,55 @@ func TestQueryComponents_OneComponent(t *testing.T) { result := results[0] assert.Equal(t, testComponentName, result.Name) } + +func TestQueryComponents_UpstreamComponent(t *testing.T) { + const testComponentName = "test-component" + + testEnv := testutils.NewTestEnv(t) + testEnv.Config.Components[testComponentName] = projectconfig.ComponentConfig{ + Name: testComponentName, + } + + testEnv.CmdFactory.RegisterCommandInSearchPath(mock.MockBinary) + + testEnv.CmdFactory.RunHandler = func(cmd *exec.Cmd) error { + if len(cmd.Args) >= 2 && cmd.Args[0] == "git" && cmd.Args[1] == "clone" { + cloneDir := cmd.Args[len(cmd.Args)-1] + specPath := cloneDir + "/" + testComponentName + ".spec" + + return fileutils.WriteFile( + testEnv.FS(), + specPath, + []byte("Name: "+testComponentName+"\nVersion: 1.0.0\n"), + fileperms.PublicFile, + ) + } + + return nil + } + + 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 + } + + options := component.QueryComponentsOptions{ + ComponentFilter: components.ComponentFilter{ + ComponentNamePatterns: []string{testComponentName}, + }, + } + + results, err := component.QueryComponents(testEnv.Env, &options) + require.NoError(t, err) + require.Len(t, results, 1) + + result := results[0] + assert.Equal(t, testComponentName, result.Name) +} From 1748e07a3ad0b16691ab3a4aec01d316b764b648 Mon Sep 17 00:00:00 2001 From: Agyei Holy Date: Thu, 23 Apr 2026 17:40:59 -0500 Subject: [PATCH 2/2] Update internal/app/azldev/cmds/component/query_test.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../app/azldev/cmds/component/query_test.go | 121 +++++++++++------- 1 file changed, 76 insertions(+), 45 deletions(-) diff --git a/internal/app/azldev/cmds/component/query_test.go b/internal/app/azldev/cmds/component/query_test.go index 5fc333f9..60bb0deb 100644 --- a/internal/app/azldev/cmds/component/query_test.go +++ b/internal/app/azldev/cmds/component/query_test.go @@ -83,52 +83,83 @@ func TestQueryComponents_OneComponent(t *testing.T) { func TestQueryComponents_UpstreamComponent(t *testing.T) { const testComponentName = "test-component" - - testEnv := testutils.NewTestEnv(t) - testEnv.Config.Components[testComponentName] = projectconfig.ComponentConfig{ - Name: testComponentName, - } - - testEnv.CmdFactory.RegisterCommandInSearchPath(mock.MockBinary) - - testEnv.CmdFactory.RunHandler = func(cmd *exec.Cmd) error { - if len(cmd.Args) >= 2 && cmd.Args[0] == "git" && cmd.Args[1] == "clone" { - cloneDir := cmd.Args[len(cmd.Args)-1] - specPath := cloneDir + "/" + testComponentName + ".spec" - - return fileutils.WriteFile( - testEnv.FS(), - specPath, - []byte("Name: "+testComponentName+"\nVersion: 1.0.0\n"), - fileperms.PublicFile, - ) - } - - return nil - } - - 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 - } - - options := component.QueryComponentsOptions{ - ComponentFilter: components.ComponentFilter{ - ComponentNamePatterns: []string{testComponentName}, + const testUpstreamName = "test-component-upstream" + + testCases := []struct { + name string + component projectconfig.ComponentConfig + specFileName string + specNameField string + }{ + { + name: "default spec source type uses normalized component name", + component: projectconfig.ComponentConfig{ + Name: testComponentName, + }, + specFileName: testComponentName, + specNameField: testComponentName, + }, + { + name: "explicit upstream spec source type uses upstream name", + component: projectconfig.ComponentConfig{ + Name: testComponentName, + Spec: projectconfig.SpecConfig{ + SourceType: projectconfig.SpecSourceTypeUpstream, + UpstreamName: testUpstreamName, + }, + }, + specFileName: testUpstreamName, + specNameField: testUpstreamName, }, } - results, err := component.QueryComponents(testEnv.Env, &options) - require.NoError(t, err) - require.Len(t, results, 1) - - result := results[0] - assert.Equal(t, testComponentName, result.Name) + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + testEnv := testutils.NewTestEnv(t) + testEnv.Config.Components[testComponentName] = testCase.component + + testEnv.CmdFactory.RegisterCommandInSearchPath(mock.MockBinary) + + testEnv.CmdFactory.RunHandler = func(cmd *exec.Cmd) error { + if len(cmd.Args) >= 2 && cmd.Args[0] == "git" && cmd.Args[1] == "clone" { + cloneDir := cmd.Args[len(cmd.Args)-1] + specPath := cloneDir + "/" + testCase.specFileName + ".spec" + + return fileutils.WriteFile( + testEnv.FS(), + specPath, + []byte("Name: "+testCase.specNameField+"\nVersion: 1.0.0\n"), + fileperms.PublicFile, + ) + } + + return nil + } + + 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 + } + + options := component.QueryComponentsOptions{ + ComponentFilter: components.ComponentFilter{ + ComponentNamePatterns: []string{testComponentName}, + }, + } + + results, err := component.QueryComponents(testEnv.Env, &options) + require.NoError(t, err) + require.Len(t, results, 1) + + result := results[0] + assert.Equal(t, testComponentName, result.Name) + }) + } }