Skip to content

feat(repo): add 'azldev repo query' for inspecting and managing RPM repositories#213

Open
liunan-ms wants to merge 2 commits into
microsoft:mainfrom
liunan-ms:liunan/repoquery
Open

feat(repo): add 'azldev repo query' for inspecting and managing RPM repositories#213
liunan-ms wants to merge 2 commits into
microsoft:mainfrom
liunan-ms:liunan/repoquery

Conversation

@liunan-ms
Copy link
Copy Markdown
Contributor

@liunan-ms liunan-ms commented May 28, 2026

This PR adds a new repo command group with a query subcommand that wraps dnf, auto-discovers the RPM sub-repos it should enable, probes them for reachability, then exec's into dnf with the surviving slots wired up via --repofrompath / --enablerepo. --no-debuginfo / --no-srpms are available for dropping sub-repos by their declared kind.

Two selection modes:

  1. URL mode (azldev repo query --repo-prefix URL [--template] …)
    Each URL is expanded against an rpm-repo-set-template (default --template) into one sub-repo per template row, fanned out per --arch where the row's subpath contains $basearch http://, https://, and file:// prefixes are all accepted, so a locally synthesized repo tree (e.g. one produced by a build) can be queried directly without serving it over HTTP

  2. Project-config mode (azldev repo query --version VER [--use-case rpm-build|image-build] ...)
    Resolves the inputs list of the default distro's VER version from [distros..versions..inputs]. GPG keys, per-repo arch allowlists, and metalink-only rejection come from [resources.rpm-repo-sets.] / [resources.rpm-repos.]. --use-case defaults to rpm-build. Mutually exclusive with --repo-prefix / --template.

Example output:

  1. URL mode
Screenshot 2026-06-01 140551 2. Project-config mode using the distro version inputs in [azurelinux.repos.distro.toml](https://github.com/microsoft/azurelinux/pull/17597) image

Copilot AI review requested due to automatic review settings May 28, 2026 03:14
Copy link
Copy Markdown
Member

@reubeno reubeno left a comment

Choose a reason for hiding this comment

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

Thanks for putting this together! A few questions -- happy to chat synchronously to go over some of it.

Comment thread defaultconfigs/defaultconfigs.go Outdated

// ReadRootConfig returns the bytes of the embedded root default configuration
// file (defaults.toml). Callers that need to inspect a single value from the
// defaults without paying for the full project-config loader can decode the
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this just a performance optimization? I don't know that we want to set the precedent for directly inspecting the configs without going through validation, include loading, group inheritance, etc.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed this helper. Refactored the code to use project-config with full validation.

Comment thread internal/app/azldev/cmds/repo/repo.go Outdated
func OnAppInit(app *azldev.App) {
cmd := &cobra.Command{
Use: "repo",
Short: "Inspect and synthesize Azure Linux RPM repositories",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is relatively specifc (i.e., "inspect and synthesize"). I think the command tree could be more broadly described as inspecting and managing RPM repositories. They need not even be Azure Linux, as we could use it to query, say, Fedora too.

@@ -0,0 +1,329 @@
// Copyright (c) Microsoft Corporation.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Meta-question: for querying, is there a reason we can't just wrap dnf? The value is in being able to wire up all the required repo flags, gpg options, etc. -- but there doesn't seem like there's much value in doing the heavy lifting of parsing and inspecting rpm repo metadata.

The current dnf wrapper script is something I use often -- and it lets me directly use all dnf commands (e.g., repoclosure, repoquery, list, etc.) as well as all their standard options.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Refactored the code to make repo query command a dnf wrapper.

Comment thread internal/app/azldev/cmds/repo/query.go Outdated
cmd.Flags().BoolVar(&options.Synthesize, "synthesize", false,
"reserved for a future release")

if err := cmd.MarkFlagRequired("repo-prefix"); err != nil {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why would this be required if the user wants to point to an RPM set?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

"--repo-prefix" now is not a required flag, repeatable repo prefixes are allowed for URL mode.

Comment thread internal/app/azldev/cmds/repo/query.go Outdated
"comma-separated arches to expand $basearch over")
cmd.Flags().StringVar(&options.OutputDir, "output-dir", "",
"with --output json, write per-slot packages.json files under <dir>/<template>/<subrepo>/[<arch>/]")
cmd.Flags().BoolVar(&options.Synthesize, "synthesize", false,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Let's leave out future options for latest PRs?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

synthesize support is removed from the current PR and will be added in a separate PR.


cmd.Flags().StringArrayVar(&options.RepoPrefixes, "repo-prefix", nil,
"layout prefix; expanded under the chosen --template; may be repeated")
cmd.Flags().StringVar(&options.Template, "template", repolayout.DefaultTemplateName,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is it possible to have reasonable defaults that leverage the default RPM inputs in the currently selected distro? My thought is that we'll have distro "versions" for "koji" vs. "dev" vs. "prod" -- and distro version selector may allow simplifying the querying?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, this's added into the new --version mode in this PR. It pulls the repo list straight from [distros..versions..inputs] of the project's selected distro, only version is needed.

… probing

Adds a new 'repo' command group with a 'query' subcommand that auto-discovers Azure Linux RPM sub-repos under one or more --repo-prefix URLs and exec's dnf against the reachable slots. Includes the shared repolayout package (template expansion, prefix normalization, dedup) used by the resolver.

- bounded-parallel HEAD/Stat probes via parmap.Map + IOBoundConcurrency

- aggregates all probe failures before aborting; per-prefix kept/dropped/failed logging

- passes --disablerepo=* --refresh to dnf; one --repofrompath/--enablerepo pair per kept slot

- regenerated CLI docs
Resolve the dnf repo set from the default distro's
[distros.<d>.versions.<VER>.inputs] list instead of requiring
--repo-prefix. --use-case selects rpm-build (default) or image-build
inputs. Per-repo arch allowlists, GPG keys, and metalink-only rejection
are honored; source repos with no $basearch are deduped across the
per-arch fan-out. --repo-prefix/--template stay mutually exclusive with
--version.

Adds InputRepo.RepoID / InputRepo.GPGKey and a SubstituteBasearch helper
in repolayout so the version-mode resolver can carry canonical ids and
forward gpgkey/gpgcheck setopts to dnf. Includes internal unit tests for
the new resolver and regenerated CLI docs.
@liunan-ms liunan-ms changed the title feat(repo): add 'azldev repo query' for standard AZL repo layouts feat(repo): add 'azldev repo query' for inspecting and managing RPM repositories Jun 1, 2026

// DefaultTemplateName is the name of the built-in standard Azure Linux layout
// template defined in `defaultconfigs/content/defaults.toml`.
const DefaultTemplateName = "azl-standard"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Since this is defined by defaultconfigs, could we define this const in the defaultconfigs package too? That would leave them close together.

Arch string
// URL is the fully-resolved repo base URL.
URL string
// PrefixIndex is the 1-based position of the originating --repo-prefix among
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why are these prefix indices/counts needed? It seems like a bit of knowledge from above leaking down into this layer.

SubrepoName: sub.Name,
Kind: kind,
Arch: arch,
URL: base + "/" + strings.ReplaceAll(sub.Subpath, basearchPlaceholder, arch),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we use URL.JoinPath instead here?

TemplateName: templateName,
SubrepoName: sub.Name,
Kind: kind,
URL: base + "/" + sub.Subpath,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same as above.

}

// DedupInputRepos drops duplicate entries by URL while preserving order.
func DedupInputRepos(repos []InputRepo) []InputRepo {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Does the lo package offer any helpers that can do this for us? Perhaps lo.Uniq or perhaps they have a disjoint set data structure we could use?

workerEnv, cancel := env.WithCancel()
defer cancel()

results := probeAll(workerEnv, env.IOBoundConcurrency(), repos)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why do we need to probe the repos? dnf will end up doing the same anyhow. Let's see if we can simplify the code that we need to maintain here.

// surviving slots wired up via --repofrompath / --enablerepo. It does not
// return on success — control is transferred to dnf via [syscall.Exec].
func RunQuery(env *azldev.Env, options *QueryOptions, dnfArgs []string) error {
if !env.CommandInSearchPath(DnfBinary) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

There's a prereqs package in azldev that we should try to use here.

}

// Hand control to dnf — does not return on success.
if err := syscall.Exec(dnfPath, argv, os.Environ()); err != nil {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why exec? Can we use the externalcmd package and retain control after it exits?


argv := buildDNFArgv(kept, dnfArgs)

dnfPath, err := exec.LookPath(DnfBinary)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this required? Shouldn't execution do the right searching through paths.

cfg := env.Config()
version := options.Version

distroName := cfg.Project.DefaultDistro.Name
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could you file a follow-up GitHub issue for us to allow overriding the "default distro" on the azldev command line. Right now I think it's only configurable via TOML files.

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.

2 participants