diff --git a/docs/user/how-to/inspect-package-config.md b/docs/user/how-to/inspect-package-config.md new file mode 100644 index 0000000..7e27850 --- /dev/null +++ b/docs/user/how-to/inspect-package-config.md @@ -0,0 +1,99 @@ +# How To: Inspect Resolved Package Configuration + +This guide shows how to use `azldev package list` to audit the effective +binary-package configuration for your project without running a build. + +## Background + +Binary package configuration in azldev is assembled from up to four layers +(see [Package Groups](../reference/config/package-groups.md) for details): + +1. Project `default-package-config` +2. Package group `default-package-config` +3. Component `default-package-config` +4. Component `packages.` override (highest priority) + +`azldev package list` resolves all of these layers and prints the effective +configuration for each package you ask about. + +## List All Explicitly-Configured Packages + +Use `-a` to enumerate every package that appears in any package-group or +component `packages` map: + +```bash +azldev package list -a +``` + +Example output: + +``` +╭──────────────────┬────────────────┬───────────┬──────────────╮ +│ PACKAGE │ GROUP │ COMPONENT │ CHANNEL │ +├──────────────────┼────────────────┼───────────┼──────────────┤ +│ curl-debugsource │ debug-packages │ │ rpm-debug │ +│ libcurl │ base-packages │ │ rpm-base │ +│ libcurl-devel │ devel-packages │ curl │ rpm-base │ +│ wget2-wget │ │ wget2 │ rpm-base │ +╰──────────────────┴────────────────┴───────────┴──────────────╯ +``` + +### Column meanings + +| Column | Meaning | +|--------|---------| +| **Package** | Binary package name (RPM `Name` tag) | +| **Group** | Package-group whose `packages` list contains this package, if any | +| **Component** | Component that has an explicit `packages.` override for this package, if any | +| **Channel** | Effective publish channel after all config layers are applied | + +> **Note:** A non-empty **Component** column means the component has an explicit +> per-package entry in its `packages` map — it does **not** mean "the component +> whose spec produces this package". Packages that get their configuration only +> from the project default or a package-group will show an empty Component. + +## Look Up Specific Packages + +Use `-p` to look up one or more packages by exact name. Packages that are not +in any explicit configuration are still shown — they resolve using only the +project default: + +```bash +azldev package list -p libcurl -p libcurl-devel -p curl-debugsource +``` + +Positional arguments are equivalent to `-p`: + +```bash +azldev package list libcurl libcurl-devel curl-debugsource +``` + +You can combine `-a` and `-p` — the results are the union of both selections. + +## Machine-Readable Output + +Pass `-q -O json` to get JSON output suitable for scripting: + +```bash +azldev package list -a -q -O json +``` + +```json +[ + { + "packageName": "libcurl", + "group": "base-packages", + "component": "", + "channel": "rpm-base" + }, + ... +] +``` + +## Alias + +`pkg` is an alias for the `package` subcommand: + +```bash +azldev pkg list -a +``` diff --git a/docs/user/reference/cli/azldev.md b/docs/user/reference/cli/azldev.md index 948238c..00c8b4d 100644 --- a/docs/user/reference/cli/azldev.md +++ b/docs/user/reference/cli/azldev.md @@ -39,6 +39,7 @@ lives), or use -C to point to one. * [azldev config](azldev_config.md) - Manage tool configuration * [azldev docs](azldev_docs.md) - Documentation commands * [azldev image](azldev_image.md) - Manage Azure Linux images +* [azldev package](azldev_package.md) - Manage binary package configuration * [azldev project](azldev_project.md) - Manage Azure Linux projects * [azldev version](azldev_version.md) - Print the CLI version diff --git a/docs/user/reference/cli/azldev_package.md b/docs/user/reference/cli/azldev_package.md new file mode 100644 index 0000000..28a583c --- /dev/null +++ b/docs/user/reference/cli/azldev_package.md @@ -0,0 +1,41 @@ + + +## azldev package + +Manage binary package configuration + +### Synopsis + +Manage binary package configuration in an Azure Linux project. + +Binary packages are the RPMs produced by building components. Use subcommands +to inspect and query the resolved configuration for packages, including metadata such as +publish channel assignments derived from package groups and component overrides. + +### Options + +``` + -h, --help help for package +``` + +### Options inherited from parent commands + +``` + -y, --accept-all accept all prompts + --color mode output colorization mode {always, auto, never} (default auto) + --config-file stringArray additional TOML config file(s) to merge (may be repeated) + -n, --dry-run dry run only (do not take action) + --network-retries int maximum number of attempts for network operations (minimum 1) (default 3) + --no-default-config disable default configuration + -O, --output-format fmt output format {csv, json, markdown, table} (default table) + --permissive-config do not fail on unknown fields in TOML config files + -C, --project string path to Azure Linux project + -q, --quiet only enable minimal output + -v, --verbose enable verbose output +``` + +### SEE ALSO + +* [azldev](azldev.md) - 🐧 Azure Linux Dev Tool +* [azldev package list](azldev_package_list.md) - List resolved configuration for binary packages + diff --git a/docs/user/reference/cli/azldev_package_list.md b/docs/user/reference/cli/azldev_package_list.md new file mode 100644 index 0000000..144e9a2 --- /dev/null +++ b/docs/user/reference/cli/azldev_package_list.md @@ -0,0 +1,69 @@ + + +## azldev package list + +List resolved configuration for binary packages + +### Synopsis + +List resolved configuration for binary packages. + +Use -a to enumerate all packages that have explicit configuration (via +package-groups or component package overrides). Use -p (or positional args) +to look up one or more specific packages by exact name — including packages +that are not explicitly configured (they resolve using only project defaults). + +Resolution order (lowest to highest priority): + 1. Project default-package-config + 2. Package group default-package-config + 3. Component default-package-config + 4. Component packages. override + +``` +azldev package list [package-name...] [flags] +``` + +### Examples + +``` + # List all explicitly-configured packages + azldev package list -a + + # Look up a specific package + azldev package list -p curl + + # Look up multiple packages + azldev package list -p curl -p wget + + # Output as JSON for scripting + azldev package list -a -q -O json +``` + +### Options + +``` + -a, --all-packages List all explicitly-configured binary packages + -h, --help help for list + -p, --package stringArray Package name to look up (repeatable) +``` + +### Options inherited from parent commands + +``` + -y, --accept-all accept all prompts + --color mode output colorization mode {always, auto, never} (default auto) + --config-file stringArray additional TOML config file(s) to merge (may be repeated) + -n, --dry-run dry run only (do not take action) + --network-retries int maximum number of attempts for network operations (minimum 1) (default 3) + --no-default-config disable default configuration + -O, --output-format fmt output format {csv, json, markdown, table} (default table) + --permissive-config do not fail on unknown fields in TOML config files + -C, --project string path to Azure Linux project + -q, --quiet only enable minimal output + -v, --verbose enable verbose output +``` + +### SEE ALSO + +* [azldev package](azldev_package.md) - Manage binary package configuration + diff --git a/docs/user/reference/config/project.md b/docs/user/reference/config/project.md index ff1d301..4a7d7f0 100644 --- a/docs/user/reference/config/project.md +++ b/docs/user/reference/config/project.md @@ -2,7 +2,9 @@ The `[project]` section defines metadata and directory layout for an azldev project. It is typically defined in a project-level config file (e.g., `base/project.toml`) rather than the root `azldev.toml`. -## Field Reference +## `[project]` Field Reference + +The following fields are nested under the `[project]` TOML section: | Field | TOML Key | Type | Required | Description | |-------|----------|------|----------|-------------| @@ -11,8 +13,8 @@ The `[project]` section defines metadata and directory layout for an azldev proj | Work directory | `work-dir` | string | No | Path to the temporary working directory for build artifacts (relative to this config file) | | Output directory | `output-dir` | string | No | Path to the directory where final build outputs (RPMs, SRPMs) are placed (relative to this config file) | | Default distro | `default-distro` | [DistroReference](distros.md#distro-references) | No | The default distro and version to use when building components | -| Default package config | `default-package-config` | [PackageConfig](package-groups.md#package-config) | No | Project-wide default applied to every binary package before group and component overrides | -| Package groups | `package-groups` | map of string → [PackageGroupConfig](package-groups.md) | No | Named groups of binary packages with shared configuration | + +> **Note:** `[default-package-config]` and `[package-groups]` are **top-level** TOML sections — they are not nested under `[project]`. They are documented in the sections below. ## Directory Paths @@ -37,7 +39,7 @@ Components inherit their spec source and build environment from the default dist ## Default Package Config -The `[default-package-config]` section defines the lowest-priority configuration layer applied to every binary package produced by any component in the project. It is overridden by [package groups](package-groups.md), [component-level defaults](components.md#package-configuration), and explicit per-package overrides. +The `[default-package-config]` section is a **top-level** TOML section (not nested under `[project]`). It defines the lowest-priority configuration layer applied to every binary package produced by any component in the project. It is overridden by [package groups](package-groups.md), [component-level defaults](components.md#package-configuration), and explicit per-package overrides. The most common use is to set a project-wide default publish channel: @@ -50,7 +52,7 @@ See [Package Groups](package-groups.md#resolution-order) for the full resolution ## Package Groups -The `[package-groups.]` section defines named groups of binary packages. Each group lists its members explicitly in the `packages` field and provides a `default-package-config` that is applied to all listed packages. +The `[package-groups.]` section is a **top-level** TOML section (not nested under `[project]`). It defines named groups of binary packages. Each group lists its members explicitly in the `packages` field and provides a `default-package-config` that is applied to all listed packages. This is currently used to route different types of packages (e.g., `-devel`, `-debuginfo`) to different publish channels, though groups can also carry other future configuration. diff --git a/internal/app/azldev/cmds/component/build.go b/internal/app/azldev/cmds/component/build.go index 653415e..8e60930 100644 --- a/internal/app/azldev/cmds/component/build.go +++ b/internal/app/azldev/cmds/component/build.go @@ -323,7 +323,7 @@ func buildComponentUsingBuilder( // Enrich each RPM with its binary package name and resolved publish channel. results.RPMs, err = resolveRPMResults(env.FS(), results.RPMPaths, env.Config(), component.GetConfig()) if err != nil { - return results, fmt.Errorf("failed to resolve publish channels for %q:\n%w", component.GetName(), err) + return results, fmt.Errorf("failed to resolve publish channels for %#q:\n%w", component.GetName(), err) } // Populate the parallel Channels slice for table display. diff --git a/internal/app/azldev/cmds/pkg/list.go b/internal/app/azldev/cmds/pkg/list.go new file mode 100644 index 0000000..dc3eb33 --- /dev/null +++ b/internal/app/azldev/cmds/pkg/list.go @@ -0,0 +1,195 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package pkg + +import ( + "fmt" + "log/slog" + "sort" + + "github.com/microsoft/azure-linux-dev-tools/internal/app/azldev" + "github.com/microsoft/azure-linux-dev-tools/internal/projectconfig" + "github.com/spf13/cobra" +) + +// ListPackageOptions controls which packages are enumerated by [ListPackages]. +type ListPackageOptions struct { + // All selects all packages that appear in any package-group or component package override. + All bool + + // PackageNames contains specific binary package names to look up. + // If a package is not in any explicit config it is still resolved using project defaults. + PackageNames []string +} + +func listOnAppInit(_ *azldev.App, parent *cobra.Command) { + parent.AddCommand(NewPackageListCommand()) +} + +// NewPackageListCommand constructs the [cobra.Command] for "package list". +func NewPackageListCommand() *cobra.Command { + options := &ListPackageOptions{} + + cmd := &cobra.Command{ + Use: "list [package-name...]", + Short: "List resolved configuration for binary packages", + Long: `List resolved configuration for binary packages. + +Use -a to enumerate all packages that have explicit configuration (via +package-groups or component package overrides). Use -p (or positional args) +to look up one or more specific packages by exact name — including packages +that are not explicitly configured (they resolve using only project defaults). + +Resolution order (lowest to highest priority): + 1. Project default-package-config + 2. Package group default-package-config + 3. Component default-package-config + 4. Component packages. override`, + Example: ` # List all explicitly-configured packages + azldev package list -a + + # Look up a specific package + azldev package list -p curl + + # Look up multiple packages + azldev package list -p curl -p wget + + # Output as JSON for scripting + azldev package list -a -q -O json`, + RunE: azldev.RunFuncWithExtraArgs(func(env *azldev.Env, args []string) (interface{}, error) { + options.PackageNames = append(args, options.PackageNames...) + + return ListPackages(env, options) + }), + } + + cmd.Flags().BoolVarP(&options.All, "all-packages", "a", false, "List all explicitly-configured binary packages") + cmd.Flags().StringArrayVarP(&options.PackageNames, "package", "p", []string{}, "Package name to look up (repeatable)") + + azldev.ExportAsMCPTool(cmd) + + return cmd +} + +// PackageListResult holds the resolved configuration for a single binary package. +type PackageListResult struct { + // PackageName is the binary package name (RPM Name tag). + PackageName string `json:"packageName" table:"Package"` + + // Group is the package-group this package belongs to, or empty if it is not in any group. + Group string `json:"group" table:"Group"` + + // Component is the component that has an explicit per-package override for this package, + // or empty if the package is only configured via a group or project default. + Component string `json:"component" table:"Component"` + + // Channel is the resolved publish channel after applying all config layers. + // Empty means no channel has been configured. + Channel string `json:"channel" table:"Channel"` +} + +// buildComponentPackageIndex builds a map from binary package name to the component +// that declares an explicit override for it in its [projectconfig.ComponentConfig.Packages] map. +// Returns an error if the same package name appears in more than one component, which would +// otherwise make the resolved configuration nondeterministic. +func buildComponentPackageIndex(components map[string]projectconfig.ComponentConfig) (map[string]string, error) { + compOf := make(map[string]string) + + for compName, comp := range components { + for pkg := range comp.Packages { + if existingComp, exists := compOf[pkg]; exists && existingComp != compName { + return nil, fmt.Errorf( + "package %#q has component overrides in multiple components: %#+q and %#+q", + pkg, existingComp, compName, + ) + } + + compOf[pkg] = compName + } + } + + return compOf, nil +} + +// ListPackages returns the resolved [PackageListResult] for the packages selected by options. +// +// If [ListPackageOptions.All] is true, all packages with explicit configuration (via +// package groups or component [projectconfig.ComponentConfig.Packages] maps) are included. +// Specific package names in [ListPackageOptions.PackageNames] are always included regardless +// of whether they have explicit configuration. +func ListPackages(env *azldev.Env, options *ListPackageOptions) ([]PackageListResult, error) { + proj := env.Config() + + // Build an index: pkgName → groupName for packages in any package-group. + groupOf := make(map[string]string) + + for groupName, group := range proj.PackageGroups { + for _, pkg := range group.Packages { + groupOf[pkg] = groupName + } + } + + // Build an index: pkgName → componentName for packages with explicit component overrides. + compOf, err := buildComponentPackageIndex(proj.Components) + if err != nil { + return nil, err + } + + // Collect the set of package names to resolve. + toResolve := make(map[string]struct{}) + + if options.All { + for pkg := range groupOf { + toResolve[pkg] = struct{}{} + } + + for pkg := range compOf { + toResolve[pkg] = struct{}{} + } + } + + for _, name := range options.PackageNames { + toResolve[name] = struct{}{} + } + + if len(toResolve) == 0 { + slog.Warn("No package selection options were given, no packages will be listed.") + + return nil, nil + } + + results := make([]PackageListResult, 0, len(toResolve)) + + for pkgName := range toResolve { + // Resolve using the component's config if one is known; otherwise use an empty config + // so that only the project default and group layers are applied. + compName := compOf[pkgName] + compConfig := &projectconfig.ComponentConfig{} + + if compName != "" { + if c, ok := proj.Components[compName]; ok { + compConfig = &c + } + } + + pkgConfig, err := projectconfig.ResolvePackageConfig(pkgName, compConfig, proj) + if err != nil { + return nil, fmt.Errorf("failed to resolve config for package %#q:\n%w", pkgName, err) + } + + results = append(results, PackageListResult{ + PackageName: pkgName, + Group: groupOf[pkgName], + Component: compName, + Channel: pkgConfig.Publish.Channel, + }) + } + + // Sort by package name for deterministic, readable output. + sort.Slice(results, func(i, j int) bool { + return results[i].PackageName < results[j].PackageName + }) + + return results, nil +} diff --git a/internal/app/azldev/cmds/pkg/list_test.go b/internal/app/azldev/cmds/pkg/list_test.go new file mode 100644 index 0000000..62ba6c1 --- /dev/null +++ b/internal/app/azldev/cmds/pkg/list_test.go @@ -0,0 +1,215 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package pkg_test + +import ( + "testing" + + pkgcmds "github.com/microsoft/azure-linux-dev-tools/internal/app/azldev/cmds/pkg" + "github.com/microsoft/azure-linux-dev-tools/internal/app/azldev/core/testutils" + "github.com/microsoft/azure-linux-dev-tools/internal/projectconfig" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewPackageListCommand(t *testing.T) { + cmd := pkgcmds.NewPackageListCommand() + require.NotNil(t, cmd) + assert.Equal(t, "list [package-name...]", cmd.Use) + assert.NotNil(t, cmd.RunE) +} + +func TestListPackages_NoCriteria(t *testing.T) { + testEnv := testutils.NewTestEnv(t) + testEnv.Config.PackageGroups = map[string]projectconfig.PackageGroupConfig{ + "g": {Packages: []string{"curl"}}, + } + + // Neither All nor PackageNames → empty result (packages exist but no selection made). + results, err := pkgcmds.ListPackages(testEnv.Env, &pkgcmds.ListPackageOptions{}) + + require.NoError(t, err) + assert.Empty(t, results) +} + +func TestListPackages_Empty(t *testing.T) { + testEnv := testutils.NewTestEnv(t) + + results, err := pkgcmds.ListPackages(testEnv.Env, &pkgcmds.ListPackageOptions{All: true}) + + require.NoError(t, err) + assert.Empty(t, results) +} + +func TestListPackages_FromPackageGroup(t *testing.T) { + testEnv := testutils.NewTestEnv(t) + testEnv.Config.PackageGroups = map[string]projectconfig.PackageGroupConfig{ + "devel-packages": { + Packages: []string{"curl-devel", "wget2-devel"}, + DefaultPackageConfig: projectconfig.PackageConfig{ + Publish: projectconfig.PackagePublishConfig{Channel: "devel"}, + }, + }, + } + + results, err := pkgcmds.ListPackages(testEnv.Env, &pkgcmds.ListPackageOptions{All: true}) + + require.NoError(t, err) + require.Len(t, results, 2) + + // Results are sorted by package name. + assert.Equal(t, "curl-devel", results[0].PackageName) + assert.Equal(t, "devel-packages", results[0].Group) + assert.Empty(t, results[0].Component) + assert.Equal(t, "devel", results[0].Channel) + + assert.Equal(t, "wget2-devel", results[1].PackageName) + assert.Equal(t, "devel-packages", results[1].Group) + assert.Equal(t, "devel", results[1].Channel) +} + +func TestListPackages_FromComponentPackageOverride(t *testing.T) { + testEnv := testutils.NewTestEnv(t) + testEnv.Config.Components["curl"] = projectconfig.ComponentConfig{ + Name: "curl", + Packages: map[string]projectconfig.PackageConfig{ + "curl-minimal": { + Publish: projectconfig.PackagePublishConfig{Channel: "none"}, + }, + }, + } + + results, err := pkgcmds.ListPackages(testEnv.Env, &pkgcmds.ListPackageOptions{All: true}) + + require.NoError(t, err) + require.Len(t, results, 1) + assert.Equal(t, "curl-minimal", results[0].PackageName) + assert.Empty(t, results[0].Group) + assert.Equal(t, "curl", results[0].Component) + assert.Equal(t, "none", results[0].Channel) +} + +func TestListPackages_ComponentOverrideWinsOverGroup(t *testing.T) { + testEnv := testutils.NewTestEnv(t) + testEnv.Config.PackageGroups = map[string]projectconfig.PackageGroupConfig{ + "base-packages": { + Packages: []string{"curl-devel"}, + DefaultPackageConfig: projectconfig.PackageConfig{ + Publish: projectconfig.PackagePublishConfig{Channel: "base"}, + }, + }, + } + testEnv.Config.Components["curl"] = projectconfig.ComponentConfig{ + Name: "curl", + Packages: map[string]projectconfig.PackageConfig{ + "curl-devel": { + Publish: projectconfig.PackagePublishConfig{Channel: "none"}, + }, + }, + } + + results, err := pkgcmds.ListPackages(testEnv.Env, &pkgcmds.ListPackageOptions{All: true}) + + require.NoError(t, err) + require.Len(t, results, 1) + assert.Equal(t, "curl-devel", results[0].PackageName) + assert.Equal(t, "base-packages", results[0].Group) + assert.Equal(t, "curl", results[0].Component) + // Component override (none) wins over group (base). + assert.Equal(t, "none", results[0].Channel) +} + +func TestListPackages_SortedByName(t *testing.T) { + testEnv := testutils.NewTestEnv(t) + testEnv.Config.PackageGroups = map[string]projectconfig.PackageGroupConfig{ + "g": { + Packages: []string{"zzz-pkg", "aaa-pkg", "mmm-pkg"}, + }, + } + + results, err := pkgcmds.ListPackages(testEnv.Env, &pkgcmds.ListPackageOptions{All: true}) + + require.NoError(t, err) + require.Len(t, results, 3) + assert.Equal(t, "aaa-pkg", results[0].PackageName) + assert.Equal(t, "mmm-pkg", results[1].PackageName) + assert.Equal(t, "zzz-pkg", results[2].PackageName) +} + +func TestListPackages_ByName_InExplicitConfig(t *testing.T) { + testEnv := testutils.NewTestEnv(t) + testEnv.Config.PackageGroups = map[string]projectconfig.PackageGroupConfig{ + "devel-packages": { + Packages: []string{"curl-devel", "wget2-devel"}, + DefaultPackageConfig: projectconfig.PackageConfig{ + Publish: projectconfig.PackagePublishConfig{Channel: "devel"}, + }, + }, + } + + // Only ask for one of the two packages in the group. + results, err := pkgcmds.ListPackages(testEnv.Env, &pkgcmds.ListPackageOptions{PackageNames: []string{"curl-devel"}}) + + require.NoError(t, err) + require.Len(t, results, 1) + assert.Equal(t, "curl-devel", results[0].PackageName) + assert.Equal(t, "devel-packages", results[0].Group) + assert.Empty(t, results[0].Component) + assert.Equal(t, "devel", results[0].Channel) +} + +func TestListPackages_ByName_NotInExplicitConfig(t *testing.T) { + testEnv := testutils.NewTestEnv(t) + testEnv.Config.DefaultPackageConfig = projectconfig.PackageConfig{ + Publish: projectconfig.PackagePublishConfig{Channel: "default-channel"}, + } + + // Look up a package that has no explicit config; it still resolves via project defaults. + results, err := pkgcmds.ListPackages(testEnv.Env, &pkgcmds.ListPackageOptions{PackageNames: []string{"unknown-pkg"}}) + + require.NoError(t, err) + require.Len(t, results, 1) + assert.Equal(t, "unknown-pkg", results[0].PackageName) + assert.Empty(t, results[0].Group) + assert.Empty(t, results[0].Component) + assert.Equal(t, "default-channel", results[0].Channel) +} + +func TestListPackages_ByName_MultipleNames(t *testing.T) { + testEnv := testutils.NewTestEnv(t) + + // Ask for two packages; neither has explicit config, so both resolve from project defaults. + opts := &pkgcmds.ListPackageOptions{PackageNames: []string{"zzz", "aaa"}} + results, err := pkgcmds.ListPackages(testEnv.Env, opts) + + require.NoError(t, err) + require.Len(t, results, 2) + // Results are sorted by name even when supplied in reverse order. + assert.Equal(t, "aaa", results[0].PackageName) + assert.Equal(t, "zzz", results[1].PackageName) +} + +func TestListPackages_DuplicatePackageAcrossComponents_ReturnsError(t *testing.T) { + testEnv := testutils.NewTestEnv(t) + testEnv.Config.Components["curl"] = projectconfig.ComponentConfig{ + Name: "curl", + Packages: map[string]projectconfig.PackageConfig{ + "shared-pkg": {Publish: projectconfig.PackagePublishConfig{Channel: "base"}}, + }, + } + testEnv.Config.Components["other"] = projectconfig.ComponentConfig{ + Name: "other", + Packages: map[string]projectconfig.PackageConfig{ + "shared-pkg": {Publish: projectconfig.PackagePublishConfig{Channel: "none"}}, + }, + } + + _, err := pkgcmds.ListPackages(testEnv.Env, &pkgcmds.ListPackageOptions{All: true}) + + require.Error(t, err) + assert.Contains(t, err.Error(), "shared-pkg") + assert.Contains(t, err.Error(), "component overrides in multiple components") + assert.Contains(t, err.Error(), "curl") + assert.Contains(t, err.Error(), "other") +} diff --git a/internal/app/azldev/cmds/pkg/package.go b/internal/app/azldev/cmds/pkg/package.go new file mode 100644 index 0000000..020dc13 --- /dev/null +++ b/internal/app/azldev/cmds/pkg/package.go @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package pkg + +import ( + "github.com/microsoft/azure-linux-dev-tools/internal/app/azldev" + "github.com/spf13/cobra" +) + +// Called once when the app is initialized; registers any commands or callbacks with the app. +func OnAppInit(app *azldev.App) { + cmd := &cobra.Command{ + Use: "package", + Aliases: []string{"pkg"}, + Short: "Manage binary package configuration", + Long: `Manage binary package configuration in an Azure Linux project. + +Binary packages are the RPMs produced by building components. Use subcommands +to inspect and query the resolved configuration for packages, including metadata such as +publish channel assignments derived from package groups and component overrides.`, + } + + app.AddTopLevelCommand(cmd) + listOnAppInit(app, cmd) +} diff --git a/internal/projectconfig/project.go b/internal/projectconfig/project.go index 1172cfb..d06ca5c 100644 --- a/internal/projectconfig/project.go +++ b/internal/projectconfig/project.go @@ -68,7 +68,7 @@ func (cfg *ProjectConfig) Validate() error { } // validatePackageGroupMembership checks that no binary package name appears in more than one -// package group. A packagemay belong to at most one group to keep routing unambiguous, but it +// package group. A package may belong to at most one group to keep routing unambiguous, but it // may also be left ungrouped. func validatePackageGroupMembership(groups map[string]PackageGroupConfig) error { // Track which group each package name was first seen in. diff --git a/pkg/app/azldev_cli/azldev.go b/pkg/app/azldev_cli/azldev.go index 56d8bf8..90bd99f 100644 --- a/pkg/app/azldev_cli/azldev.go +++ b/pkg/app/azldev_cli/azldev.go @@ -12,6 +12,7 @@ import ( "github.com/microsoft/azure-linux-dev-tools/internal/app/azldev/cmds/config" "github.com/microsoft/azure-linux-dev-tools/internal/app/azldev/cmds/docs" "github.com/microsoft/azure-linux-dev-tools/internal/app/azldev/cmds/image" + "github.com/microsoft/azure-linux-dev-tools/internal/app/azldev/cmds/pkg" "github.com/microsoft/azure-linux-dev-tools/internal/app/azldev/cmds/project" "github.com/microsoft/azure-linux-dev-tools/internal/app/azldev/cmds/version" ) @@ -38,6 +39,7 @@ func InstantiateApp() *azldev.App { config.OnAppInit(app) docs.OnAppInit(app) image.OnAppInit(app) + pkg.OnAppInit(app) project.OnAppInit(app) version.OnAppInit(app) diff --git a/pkg/app/azldev_cli/azldev_test.go b/pkg/app/azldev_cli/azldev_test.go index 1e7ab03..5a539ff 100644 --- a/pkg/app/azldev_cli/azldev_test.go +++ b/pkg/app/azldev_cli/azldev_test.go @@ -27,6 +27,7 @@ func TestInstantiateApp(t *testing.T) { "config", "docs", "image", + "package", "project", "version", }, diff --git a/scenario/__snapshots__/TestMCPServerMode_1.snap.json b/scenario/__snapshots__/TestMCPServerMode_1.snap.json index 63957fa..47931a4 100755 --- a/scenario/__snapshots__/TestMCPServerMode_1.snap.json +++ b/scenario/__snapshots__/TestMCPServerMode_1.snap.json @@ -482,6 +482,85 @@ "type": "object" }, "name": "image-list" + }, + { + "annotations": { + "destructiveHint": true, + "idempotentHint": false, + "openWorldHint": true, + "readOnlyHint": false + }, + "description": "List resolved configuration for binary packages", + "inputSchema": { + "properties": { + "accept-all": { + "default": false, + "description": "accept all prompts", + "type": "boolean" + }, + "all-packages": { + "default": false, + "description": "List all explicitly-configured binary packages", + "type": "boolean" + }, + "color": { + "description": "output colorization mode {always, auto, never}", + "type": "string" + }, + "config-file": { + "description": "additional TOML config file(s) to merge (may be repeated)", + "type": "string" + }, + "dry-run": { + "default": false, + "description": "dry run only (do not take action)", + "type": "boolean" + }, + "network-retries": { + "default": 3, + "description": "maximum number of attempts for network operations (minimum 1)", + "type": "number" + }, + "no-default-config": { + "default": false, + "description": "disable default configuration", + "type": "boolean" + }, + "output-format": { + "description": "output format {csv, json, markdown, table}", + "type": "string" + }, + "package": { + "description": "Package name to look up (repeatable)", + "type": "string" + }, + "permissive-config": { + "default": false, + "description": "do not fail on unknown fields in TOML config files", + "type": "boolean" + }, + "project": { + "default": "", + "description": "path to Azure Linux project", + "type": "string" + }, + "quiet": { + "default": false, + "description": "only enable minimal output", + "type": "boolean" + }, + "verbose": { + "default": false, + "description": "enable verbose output", + "type": "boolean" + } + }, + "required": [ + "project" + ], + "type": "object" + }, + "name": "package-list" } ] } diff --git a/scenario/__snapshots__/TestSnapshotsContainer_--bogus-flag_stderr_1.snap b/scenario/__snapshots__/TestSnapshotsContainer_--bogus-flag_stderr_1.snap index ded02ea..719f467 100755 --- a/scenario/__snapshots__/TestSnapshotsContainer_--bogus-flag_stderr_1.snap +++ b/scenario/__snapshots__/TestSnapshotsContainer_--bogus-flag_stderr_1.snap @@ -7,6 +7,7 @@ Primary commands: config Manage tool configuration docs Documentation commands image Manage Azure Linux images + package Manage binary package configuration project Manage Azure Linux projects Meta commands: diff --git a/scenario/__snapshots__/TestSnapshotsContainer_--help_stdout_1.snap b/scenario/__snapshots__/TestSnapshotsContainer_--help_stdout_1.snap index 3fd24b0..99f4df9 100755 --- a/scenario/__snapshots__/TestSnapshotsContainer_--help_stdout_1.snap +++ b/scenario/__snapshots__/TestSnapshotsContainer_--help_stdout_1.snap @@ -14,6 +14,7 @@ Primary commands: config Manage tool configuration docs Documentation commands image Manage Azure Linux images + package Manage binary package configuration project Manage Azure Linux projects Meta commands: diff --git a/scenario/__snapshots__/TestSnapshotsContainer_help_stdout_1.snap b/scenario/__snapshots__/TestSnapshotsContainer_help_stdout_1.snap index 3fd24b0..99f4df9 100755 --- a/scenario/__snapshots__/TestSnapshotsContainer_help_stdout_1.snap +++ b/scenario/__snapshots__/TestSnapshotsContainer_help_stdout_1.snap @@ -14,6 +14,7 @@ Primary commands: config Manage tool configuration docs Documentation commands image Manage Azure Linux images + package Manage binary package configuration project Manage Azure Linux projects Meta commands: diff --git a/scenario/__snapshots__/TestSnapshots_--bogus-flag_stderr_1.snap b/scenario/__snapshots__/TestSnapshots_--bogus-flag_stderr_1.snap index ded02ea..719f467 100755 --- a/scenario/__snapshots__/TestSnapshots_--bogus-flag_stderr_1.snap +++ b/scenario/__snapshots__/TestSnapshots_--bogus-flag_stderr_1.snap @@ -7,6 +7,7 @@ Primary commands: config Manage tool configuration docs Documentation commands image Manage Azure Linux images + package Manage binary package configuration project Manage Azure Linux projects Meta commands: diff --git a/scenario/__snapshots__/TestSnapshots_--help_stdout_1.snap b/scenario/__snapshots__/TestSnapshots_--help_stdout_1.snap index 3fd24b0..99f4df9 100755 --- a/scenario/__snapshots__/TestSnapshots_--help_stdout_1.snap +++ b/scenario/__snapshots__/TestSnapshots_--help_stdout_1.snap @@ -14,6 +14,7 @@ Primary commands: config Manage tool configuration docs Documentation commands image Manage Azure Linux images + package Manage binary package configuration project Manage Azure Linux projects Meta commands: diff --git a/scenario/__snapshots__/TestSnapshots_--help_with_color_stdout_1.snap b/scenario/__snapshots__/TestSnapshots_--help_with_color_stdout_1.snap index 395e845..5ec50a0 100755 --- a/scenario/__snapshots__/TestSnapshots_--help_with_color_stdout_1.snap +++ b/scenario/__snapshots__/TestSnapshots_--help_with_color_stdout_1.snap @@ -14,6 +14,7 @@ Primary commands: config Manage tool configuration docs Documentation commands image Manage Azure Linux images + package Manage binary package configuration project Manage Azure Linux projects Meta commands: diff --git a/scenario/__snapshots__/TestSnapshots_help_stdout_1.snap b/scenario/__snapshots__/TestSnapshots_help_stdout_1.snap index 3fd24b0..99f4df9 100755 --- a/scenario/__snapshots__/TestSnapshots_help_stdout_1.snap +++ b/scenario/__snapshots__/TestSnapshots_help_stdout_1.snap @@ -14,6 +14,7 @@ Primary commands: config Manage tool configuration docs Documentation commands image Manage Azure Linux images + package Manage binary package configuration project Manage Azure Linux projects Meta commands: