pkg/extensions: Manager.Install and Manager.Upgrade should be transactional
- Dominant language
- Go
- Stars
- 569
- Forks
- 364
- Avg merge
- 2d 19h
- Merged PRs (30d)
- 136
Description
Both lifecycles can leave broken state on failure:
- **Upgrade** ([manager.go](https://github.com/Azure/azure-dev/blob/main/cli/azd/pkg/extensions/manager.go#L608-L628)): `Uninstall` → `Install`. If install fails, the original entry is gone from `azd extension list` with no rollback.
- **Install of a pack** ([manager.go](https://github.com/Azure/azure-dev/blob/main/cli/azd/pkg/extensions/manager.go#L393-L416)): if child N fails, children 1..N-1 installed by this call stay on disk and the bundle row is never written.
**Possible approach:** Two design options worth weighing:
1. **Snapshot-and-rollback** — track work done in this call (deps actually installed in this loop — not those that hit `ErrExtensionInstalled`); on failure, undo in reverse. For `Upgrade`, capture the pre-existing entry and restore it.
2. **Install-then-swap** — stage the new install in a sibling location, atomically swap on success.
(1) is simpler; (2) is more robust and matches what modern package managers do. Either way, the property we want is: after a failed install/upgrade, state is observably identical to before the command ran. Pre-existing installs (those the user already had) should never be touched.
**Acceptance:**
- [ ] After a failed leaf upgrade, the original entry is still present at the original version
- [ ] After a failed pack install, zero children installed *by this call* remain; pre-existing installs untouched
- [ ] After a failed pack upgrade, original bundle row + original children remain
- [ ] Failure error message indicates what was rolled back
- [ ] `manager_test.go` injects a failure mid-dep-loop and asserts state
Contributor guide
Assessment
This issue has not been assessed yet.