feat(extensions): dependency-aware uninstall and ownership tracking - #9866
feat(extensions): dependency-aware uninstall and ownership tracking#9866JeffreyCA wants to merge 17 commits into
Conversation
|
Azure Pipelines: Successfully started running 1 pipeline(s). 21 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
2c4b36c to
61ead71
Compare
8fab975 to
6c8aaba
Compare
|
/azp run azure-dev - cli |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
Installed records now store the installed version's dependency list and whether the extension was installed as a dependency. Installs by name mark a record explicit; updates preserve the flag and backfill older records. azd extension uninstall plans the removal from those records: it fails when other installed extensions require a target (--force overrides), removes dependencies that are no longer required (--no-dependencies keeps them), and lists each removed or kept dependency with the reason. azd extension show gains Dependencies and Required By sections, compatibility and update annotations, an installed-record fallback, and camelCase JSON keys. Adds the ext.uninstall telemetry event and updates docs and snapshots.
- Promote project-required extensions during auto-install discovery, where already-installed requirements and installed providers are skipped, so a record a pack pulled in survives the pack's removal. - Backfill a legacy child's dependency snapshot before the unconstrained dependency check, so parents declaring dependencies without a version still protect the tree after one update. - Compute the orphan closure before evaluating dependents, so a dependency-installed extension in a cycle with a target leaves with it instead of blocking it. - Assert the show command's JSON keys on the raw object, since decoding into the tagged struct accepts the old PascalCase names. - Advertise multiple ids in the uninstall usage string and mark `[name...]` arguments variadic in the fig spec instead of leaving the dots in the name. - Use errors.AsType in the backfill test.
Fail fast when the installed extension configuration cannot be read, and narrow the legacy dependency backfill contract to directly reconciled records. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Render orphaned dependencies as a bulleted operation preview and use a direct counted confirmation prompt. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Refer directly to the dependency preview in the uninstall confirmation prompt. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Align the event comment and telemetry references with the span, which records both successful and failed extension uninstall attempts. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Ignore only missing installed records during dependency snapshot backfill and return malformed configuration errors to the caller. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
3531ee7 to
a4a4ca3
Compare
|
Azure Pipelines: Successfully started running 1 pipeline(s). 21 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
Case-variant explicit installs fail instead of promoting dependency-owned records.
Review tier: Balanced
Findings: 1
Pre-existing issues (1)
| Severity | Finding |
|---|---|
cli/azd/cmd/extension.go — Installed-only show still depends on every registry lookup succeeding. FindExtensions returns… View comment |
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
cli/azd/cmd/extension.go:1306
- This promotion path is unreachable when the user varies the extension ID casing. Installed and registry filters are case-insensitive, but
alreadyInstalledis computed with the exact map lookupallInstalled[extensionId]; for example, installingAZURE.AI.PROJECTSover dependency-installedazure.ai.projectsfalls into the fresh-install path and fails withErrExtensionInstalledinstead of clearing ownership. Resolve the existing record case-insensitively (and use its canonical ID) before branching, and add a casing test.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
Variadic completion handling and retained-dependency reporting remain incorrect, and the required privacy review is unconfirmed.
Review tier: Balanced
Findings: None
Issues resolved since last review (1)
| Severity | Finding |
|---|---|
cli/azd/cmd/extension.go — Installed-only show still depends on every registry lookup succeeding. FindExtensions returns… View resolved comment |
Suppressed comments (3)
Previously missed (2) — in code that hasn't changed since the last review.
cli/azd/internal/figspec/spec_builder.go:318
- This marks every
...token variadic without considering later positional arguments. Forexec [command] [args...] [-- script-args...], the regenerated spec now has two variadic entries; the first consumes subsequent positions, so thescript-argsentry cannot be selected correctly. Handle Cobra's--argument group specially, or avoid marking a non-final argument variadic, and add anexec-shaped test.
cli/azd/pkg/extensions/uninstall.go:180 - Traversal stops whenever a reachable dependency is retained. For
pack -> explicit A -> dependency B, the plan reports only A; B is never added toRetained, despite issue #8258 and the new docs saying normal cleanup lists each kept dependency with its reason. Continue traversing retained dependencies for reporting with cycle protection, while keeping them out of the removal set, and add this graph shape to the tests.
cli/azd/internal/tracing/events/events.go:40
ExtensionUninstallEventis a new event, so the repository privacy checklist requires a privacy review; the PR checklist still leaves that review unconfirmed. Complete and record the required review before merge.
// ExtensionUninstallEvent tracks a single extension uninstall attempt by
// `azd extension uninstall`, including attempts for dependencies removed alongside it.
ExtensionUninstallEvent = "ext.uninstall"
Azure Dev CLI Install InstructionsInstall scriptsMacOS/Linux
bash: pwsh: WindowsPowerShell install MSI install Standalone Binary
MSI
Documentationlearn.microsoft.com documentationtitle: Azure Developer CLI reference
|

Fixes #8258
This PR makes
azd extension uninstalldependency-aware, records why each extension was installed, and reorganizesazd extension showso it can explain an extension's dependencies and dependents. It closes the gap where uninstalling themicrosoft.foundryextension pack stranded its seven dependencies and uninstalling one of those dependencies silently brokeazure.ai.agents.Ownership tracking
Each installed record now stores the installed version's dependency list and an
installedAsDependencyflag, so uninstall planning never needs registry access.requiredVersions.extensionsentries clear the dependency flag. Updates and reuse of inferred providers preserve ownership.Dependency-aware uninstall
The command plans the whole removal from the installed records before removing anything.
--forceis set.--no-promptaccepts removal. Declining or using--no-dependencieskeeps dependencies without bypassing protection of required extensions.--allruns through the same path.Uninstalling microsoft.foundry meta-package and dependencies:

Uninstalling microsoft.foundry meta-package only interactively:

Uninstalling microsoft.foundry meta-package only with

--no-dependencies:Uninstalling azure.ai.projects when other extensions depend on it:

Force-uninstalling azure.ai.projects when other extensions depend on it:

azd extension showThe layout follows
azd tool show, with compatibility, ownership, dependencies, and installed dependents. Empty rows are omitted.It prefers the installed source and uses local metadata when no registry lists the extension. Legacy dependency details never come from another source's release.
JSON output changes from PascalCase to camelCase keys and omits empty fields. This breaks the previous schema; the command group is beta and no in-repo consumer depends on it.
Telemetry
Adds one
ext.uninstallspan per attempted removal, including dependencies, using existing id, version, and source-category fields. Internal uninstalls during updates do not emit it.Testing
--force,--no-dependencies, legacy records, and blank ids.ext.uninstallspan, and show resolution, JSON shape, and display layout.azdregistry covering every scenario above withmicrosoft.foundry.Telemetry Change Checklist
New Fields
ext.uninstallspan reusesextension.id,extension.version, andextension.source.category)New Events
events/events.goconstwhose Go identifier containsEvent(end it withPrefixfor a prefix-match group) so the GDPR classifier discovers itdocs/specs/metrics-audit/telemetry-schema.mdprefix.noun.verb)Privacy
CustomerContentemitted in telemetryTesting
Downstream
Documentation
docs/specs/metrics-audit/feature-telemetry-matrix.md)docs/specs/metrics-audit/telemetry-schema.md)privacy-review-checklist.mdunchanged (nothing new is hashed)