Skip to content

feat(cli): avocado login as a shortcut for connect auth login - #242

Closed
mobileoverlord wants to merge 1 commit into
mainfrom
jschneck/cli-login
Closed

mobileoverlord wants to merge 1 commit into
mainfrom
jschneck/cli-login

Conversation

@mobileoverlord

@mobileoverlord mobileoverlord commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Owner: the secure-feeds series (241 → 242 → 243 → 244). Both sessions push as mobileoverlord, so GitHub cannot tell you which one wrote a PR — this line can.

Adds avocado login as a top-level shortcut for avocado connect auth login.

Logging in is what raises the package-feed rate limit and unlocks private feeds, so it deserves a top-level verb rather than being three words deep.

Both spellings share one LoginArgs struct and run the same command, so flags, help text and behaviour cannot drift; the nested variant becomes a tuple variant over that struct. -p and the other short flags are untouched.

Neither spelling routes to the avocado-vm: they are pure Connect API calls, and routing could auto-start a virtual machine for a login.

How it was tested

A parse test asserts that avocado login and avocado connect auth login produce the same arguments from the same flags, and that VM routing is off for both. That is the regression that matters, since the whole point is that the two cannot diverge.

Stacked on #241, which it does not depend on functionally — it is based there only to keep the branch history linear. Review it on its own; the diff is one file.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

LoginArgs::run awaits an async method that borrows a temporary ConnectAuthLoginCommand, which will fail to compile due to a temporary dropped while borrowed across .await.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds a top-level avocado login command as a shortcut for avocado connect auth login, implemented by sharing a single LoginArgs struct and dispatch path so the two spellings can’t drift.

Changes:

  • Introduces Commands::Login(LoginArgs) as a top-level CLI verb with help text explaining why login matters for feed access.
  • Refactors connect auth login to reuse the same LoginArgs and execution path (LoginArgs::run()).
  • Adds a parse/routing regression test ensuring both spellings parse equivalently and neither triggers VM routing.
File summaries
File Description
src/main.rs Adds the new top-level login command, shares args with connect auth login, and adds a regression test for parsing + VM routing.
Review details
  • Files reviewed: 1/1 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/main.rs Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

The new regression test doesn’t fully assert equivalence for all parsed LoginArgs fields (e.g., url and output), weakening the stated guarantee that the two spellings cannot diverge.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 1/1 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread src/main.rs

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

The new regression test does not fully enforce “no drift” between the two spellings (it should compare all shared parsed fields), and there’s a small return-type consistency issue in LoginArgs::run.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (1)

src/main.rs:5294

  • This test aims to ensure avocado login and connect auth login cannot drift, but it only validates token, profile, and org against constants. To actually guard against drift, compare all shared parsed fields between the two (url, token, profile, org, output), and then assert the expected values on just one side.
        for args in [a, b] {
            assert_eq!(
                (
                    args.token.as_deref(),
                    args.profile.as_deref(),
                    args.org.as_deref()
                ),
                (Some("t"), Some("p"), Some("o"))
            );
  • Files reviewed: 1/1 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread src/main.rs Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟢 Approval recommended

The change is a small, well-scoped CLI refactor with a targeted regression test ensuring the shortcut and nested command stay equivalent and remain non-routed.

Review details
  • Files reviewed: 1/1 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

The new regression test does not yet assert all shared LoginArgs fields (e.g., url and output), so the “cannot diverge” guarantee described in the PR can still regress without failing tests.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (1)

src/main.rs:5290

  • This test claims to ensure avocado login and connect auth login cannot diverge, but it only checks token/profile/org. It should also assert the remaining shared fields (url and output) to match expectations, otherwise drift in those flags or defaults won’t be caught.
        for args in [a, b] {
            assert_eq!(
                (
                    args.token.as_deref(),
                    args.profile.as_deref(),
  • Files reviewed: 1/1 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread src/main.rs Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟢 Approval recommended

The change is small, behavior-preserving (shared args + shared execution), and includes a focused regression test confirming equivalence and VM-routing expectations.

Review details

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

src/main.rs:1449

  • Use "Log in" (verb) rather than "Login" (noun/adjective) for consistency with the new top-level login help text and standard grammar.
  • Files reviewed: 1/1 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟢 Approval recommended

The shortcut reuses a single args struct and execution path, and the added test covers both CLI spellings plus the VM-routing invariant.

Review details
  • Files reviewed: 1/1 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟢 Approval recommended

The change is narrowly scoped, keeps behavior unified via a shared args struct + shared execution path, and includes a targeted regression test for equivalence and VM-routing invariants.

Review details
  • Files reviewed: 1/1 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟢 Approval recommended

The change is small and well-contained, reuses a shared args struct to prevent drift, and includes a targeted regression test for the key behavior and VM-routing constraint.

Review details
  • Files reviewed: 1/1 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@mobileoverlord
mobileoverlord force-pushed the jschneck/cli-login branch 2 times, most recently from bee387d to f51e935 Compare September 7, 2026 18:26
nicksinas
nicksinas previously approved these changes Sep 15, 2026
Base automatically changed from jschneck/feeds-named to main September 15, 2026 04:24
@mobileoverlord
mobileoverlord dismissed nicksinas’s stale review September 15, 2026 04:24

The merge-base changed after approval.

Logging in is what raises the package-feed rate limit and gives access to private
feeds, so it deserves a top-level verb rather than being three words deep.

Both spellings share one `LoginArgs` struct and run the same command, so flags,
help text and behaviour cannot drift; the nested variant becomes a tuple variant
over that struct. Neither routes to the avocado-vm: they are pure Connect API
calls, and routing could auto-start a virtual machine for a login.

The regression test asserts all five shared fields rather than a sample — the
whole point is that the two cannot diverge, and a test covering three of five
would not notice the other two diverging.

Review also noted that this file aliases `anyhow::Result` as `Result` everywhere
else including `main`, so the one `anyhow::Result` spelling invited the reader to
look for a difference that was not there.

One review finding was answered rather than changed: the claim that borrowing a
temporary across the `.await` cannot compile. A temporary lives to the end of its
enclosing statement, so it does; the build and the full suite confirm it.
@mobileoverlord

Copy link
Copy Markdown
Contributor Author

Superseded by a fresh PR from this same branch (head fd6ee04, CI green): GitHub's stack handling blocks both the admin merge and the async merge once the parent landed and the approval was dismissed.

mobileoverlord added a commit that referenced this pull request Sep 15, 2026
Supersedes #242 (same branch, head fd6ee04, CI green on that sha);
recreated because GitHub's stack handling blocked merging the original
after #241 landed.

> **Owner: the secure-feeds series** (241 → 242 → 243 → 244). Both
sessions push as `mobileoverlord`, so GitHub cannot tell you which one
wrote a PR — this line can.

Adds `avocado login` as a top-level shortcut for `avocado connect auth
login`.

Logging in is what raises the package-feed rate limit and unlocks
private feeds, so it deserves a top-level verb rather than being three
words deep.

Both spellings share one `LoginArgs` struct and run the same command, so
flags, help text and behaviour cannot drift; the nested variant becomes
a tuple variant over that struct. `-p` and the other short flags are
untouched.

Neither spelling routes to the avocado-vm: they are pure Connect API
calls, and routing could auto-start a virtual machine for a login.

## How it was tested

A parse test asserts that `avocado login` and `avocado connect auth
login` produce the same arguments from the same flags, and that VM
routing is off for both. That is the regression that matters, since the
whole point is that the two cannot diverge.

Stacked on #241, which it does not depend on functionally — it is based
there only to keep the branch history linear. Review it on its own; the
diff is one file.
@mobileoverlord
mobileoverlord deleted the jschneck/cli-login branch September 15, 2026 04:31
mobileoverlord added a commit that referenced this pull request Sep 15, 2026
#262)

Supersedes #243 (same branch, GitHub-rebased head 5be014d); recreated
because GitHub's stack handling blocked merging the original after its
parent landed.

> **Owner: the secure-feeds series** (241 → 242 → 243 → 244). Both
sessions push as `mobileoverlord`, so GitHub cannot tell you which one
wrote a PR — this line can.

Makes `org:` feeds work, and stops re-resolving feeds for every
container.

## Private organization feeds

```yaml
repos:
  acme:
    org: acme          # release/channel optional; branch defaults to main
distro:
  feeds: [acme]
```

The CLI exchanges its Connect credential for a short-lived feed token,
injects it into the generated repository file, and dnf reads a feed that
refuses anonymous access. Previously `org:` parsed and was refused.

**The token never becomes a build input.** Resolution records only what
is stable — the organization as the credential identity, and a
`connect://<org>/<path>` placeholder as the URL — and the recorded
document is written before anything is minted. A per-build token in the
stamp hash would invalidate every cached sysroot once per build, and a
server-side host change should not either, because the organization is
the input and the host is a detail of how it was served.

The private tree mirrors the public one, so an organization feed is a
distro-shaped feed whose release version is
`<release>/orgs/<org>/<branch>`, and no new path construction was
needed. The minted URL also goes through the loopback rewrite, which URL
feeds get at resolution time and this had to get at mint time, or a
locally hosted Connect resolves to nothing inside the container.

Errors name the remedy: not logged in points at `avocado login`, 403
says the account is not entitled, 404 says the deployment does not serve
feed tokens yet.

## Feeds are materialized once per invocation

Previously every container run re-resolved, re-minted and wrote a fresh
temporary directory. Two consequences:

- A single `avocado build` minted **five** feed tokens. It is one per
invocation now, and the end-to-end rig asserts it, so a regression fails
a test rather than surfacing later as a rate limit refusing an ordinary
build.
- The mount list is part of a container's shape, so a per-run directory
gave every step a unique shape and prevented sharing a container between
steps.

Each stage writes into its own subdirectory and `AVOCADO_FEEDS_DIR`
selects it, so the mount is identical across steps while dnf still sees
only its stage's feeds. Credentials now live as long as the invocation
rather than one step; nothing reaches the SDK volume or the project
directory, and the directory is removed when the process ends. The
condition that would invalidate that trade, untrusted code running in
the shared container, is named at the function.

## Test infrastructure included

`scripts/local-feeds/edge.py` implements the observable contract of
three pieces that do not exist yet — the token mint, the ES256 verifier,
and the rate limiter — so this could be built and tested before any of
them. `contract-tests.sh` asserts that contract against a base URL,
naming no implementation, so the same file can later run against a real
deployment. `org-feed-rig.sh` proves the whole path: mint, inject,
authenticate, install, and no token in the recorded document.

Two behaviours worth calling out, both found by review against the
server design. A missing credential is answered with **401 and a Basic
challenge**, not 403, because dnf authenticates only when challenged and
only to Basic; 403 fails every fetch while working perfectly under `curl
-u`. And the organization is a path **segment**, not a prefix, because
the release precedes it — a prefix check would silently degrade into any
authenticated organization being able to read any other.

## Status

The server side of this is several phases out, so `org:` currently fails
closed with a clear message rather than working. Merging it early is
what lets the client be finished and tested; landing it later is also
fine. Say which you prefer.

Stacked on #242.
mobileoverlord added a commit that referenced this pull request Sep 15, 2026
## What

Cut `1.0.0-rc.4`: the `[Unreleased]` changelog section becomes `##
[1.0.0-rc.4] - 2026-09-15`, and the crate version moves from
`1.0.0-rc.3` to `1.0.0-rc.4` in `Cargo.toml` and `Cargo.lock`.

## Release notes

The rc.4 section of `CHANGELOG.md` is the release notes. It covers
everything merged since rc.3, grouped Added / Changed / Fixed /
Security:

- **Added:** named package feeds (`repos:` / `distro.feeds`, #241),
private organization feeds (#243), feed set and package origin in the
lock (#244), `avocado login` (#242), `kernel.cmdline` / `cmdline_extra`
(#252), the managed `avocado hitl` NFS server (#258), the `avocado
install` up-to-date skip (#260), per-runtime and per-extension SBOMs,
and the `build` / `provision` var-image split (breaking: `build` no
longer produces the var image).
- **Changed:** installs no longer prompt and `--force` means reinstall
only; `--output json` no longer implies `--force`; session containers
(exec reuse, `atexit` teardown, Ctrl-C reaping, no async-runtime
blocking); stamp coverage and output digests (`STAMP_VERSION` 2 → 3 →
4); `ext build` / `ext image` / rootfs / initramfs image skips; no more
image copying and re-hashing in runtime builds.
- **Fixed:** the `depends_on` closure is built (#253), package-sourced
overlays ship (#254), the overlay staging dir is cleared every build
(#255), the SDK entrypoint reads one `VERSION_CODENAME` (#240), kernel
changes invalidate extension stamps and clean the sysroot (#267,
`STAMP_VERSION` 4 → 5), plus the stamp-system hardening from the dogfood
runs.
- **Security:** `rustls` 0.23.45 for RUSTSEC-2026-0285.

Every bullet that was under `[Unreleased]` on `main` is present; entries
for the PRs that landed without a changelog line were added. An empty
`[Unreleased]` heading remains for the next cycle.

## After merge

Tag the squash commit `1.0.0-rc.4` to run the release workflow. The
first `avocado build` after upgrading reports every stamp as "stamp
format changed (v4 → v5)" and rebuilds once.

## Verification

fmt, clippy (`-D warnings`) and the full test suite pass on this branch;
the version guard test reads `1.0.0-rc.4`.
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.

3 participants