Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions cli/azd/cmd/auto_install.go
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,7 @@ type extensionAutoInstallManager interface {
opts extensions.InstallOptions,
) (*extensions.ExtensionVersion, error)
ListInstalled() (map[string]*extensions.Extension, error)
MarkExplicitlyInstalled(id string) error
}

func tryAutoInstallExtensionVersion(
Expand All @@ -456,6 +457,13 @@ func tryAutoInstallExtensionVersion(
if err := validateInstalledExtensionVersion(installedExtension, versionPreference); err != nil {
return false, err
}
// The project requires this extension in its own right, so a record that only a
// pack pulled in becomes explicit and survives when that pack is uninstalled.
if installedExtension.InstalledAsDependency {
if err := extensionManager.MarkExplicitlyInstalled(extension.Id); err != nil {
Comment thread
JeffreyCA marked this conversation as resolved.
return false, fmt.Errorf("marking extension %s as explicitly installed: %w", extension.Id, err)
}
}
return false, nil
}

Expand Down
70 changes: 70 additions & 0 deletions cli/azd/cmd/auto_install_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,15 @@ func (m *fakeExtensionAutoInstallManager) ListInstalled() (map[string]*extension
return m.installed, nil
}

func (m *fakeExtensionAutoInstallManager) MarkExplicitlyInstalled(id string) error {
installed, ok := m.installed[id]
if !ok {
return extensions.ErrInstalledExtensionNotFound
}
installed.InstalledAsDependency = false
return nil
}

func TestMissingProjectExtensions(t *testing.T) {
versionConstraint := ">=1.0.0-beta.4"
manager := &fakeExtensionAutoInstallManager{
Expand Down Expand Up @@ -2468,3 +2477,64 @@ func TestProjectExtensionErrorsCarrySuggestions(t *testing.T) {
assert.Contains(t, suggestErr.Suggestion, "azd extension source list")
})
}

func TestTryAutoInstallExtensionVersionPromotesDependencyInstalledExtension(t *testing.T) {
t.Parallel()

// The project requires an extension that a pack pulled in earlier; it must survive the
// pack's removal from now on.
manager := &fakeExtensionAutoInstallManager{
installed: map[string]*extensions.Extension{
"azure.ai.agents": {Id: "azure.ai.agents", Version: "1.0.0", InstalledAsDependency: true},
},
}

installed, err := tryAutoInstallExtensionVersion(
t.Context(),
mockinput.NewMockConsole(),
manager,
extensions.ExtensionMetadata{Id: "azure.ai.agents"},
"",
false,
)
require.NoError(t, err)
require.False(t, installed, "already installed, so nothing is downloaded")
require.False(t, manager.installed["azure.ai.agents"].InstalledAsDependency)
}

func TestMissingProjectExtensionsPromotesOnlyExplicitRequirements(t *testing.T) {
t.Parallel()

// Only the named requiredVersions entry becomes explicit. Reusing an inferred provider
// does not change its ownership, including on repeated project commands.
manager := &fakeExtensionAutoInstallManager{
installed: map[string]*extensions.Extension{
"azure.ai.projects": {
Id: "azure.ai.projects", Version: "1.0.0", InstalledAsDependency: true,
},
"azure.ai.agents": {
Id: "azure.ai.agents", Version: "1.0.0", InstalledAsDependency: true,
Capabilities: []extensions.CapabilityType{extensions.ServiceTargetProviderCapability},
Providers: []extensions.Provider{
{Name: "azure.ai.agent", Type: extensions.ServiceTargetProviderType},
},
},
},
}
projectConfig := &project.ProjectConfig{
RequiredVersions: &project.RequiredVersions{
Extensions: map[string]*string{"azure.ai.projects": nil},
},
Services: map[string]*project.ServiceConfig{
"agent": {Host: "azure.ai.agent"},
},
}

for range 2 {
requirements, err := missingProjectExtensions(t.Context(), mockinput.NewMockConsole(), manager, projectConfig)
require.NoError(t, err)
require.Empty(t, requirements, "everything the project needs is already installed")
require.False(t, manager.installed["azure.ai.projects"].InstalledAsDependency)
require.True(t, manager.installed["azure.ai.agents"].InstalledAsDependency)
}
}
Loading
Loading