Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
19 changes: 17 additions & 2 deletions cli/azd/cmd/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -1151,12 +1151,19 @@ func (ef *envRefreshAction) Run(ctx context.Context) (*actions.ActionResult, err
Title: fmt.Sprintf("Refreshing environment %s (azd env refresh)", ef.env.Name()),
})

// Initialize services, skipping any with unsupported hosts (e.g., extension-provided hosts
// that are not currently loaded). Env refresh only needs infrastructure outputs from Azure;
// services with unsupported hosts simply won't receive the environment-updated event.
if err := ef.projectManager.Initialize(ctx, ef.projectConfig); err != nil {
return nil, err
if !hasUnsupportedHostError(err) {
return nil, err
}
}

if err := ef.projectManager.EnsureAllTools(ctx, ef.projectConfig, nil); err != nil {
return nil, err
if !hasUnsupportedHostError(err) {
return nil, err
}
}
Comment thread
jongio marked this conversation as resolved.

infra, err := ef.importManager.ProjectInfrastructure(ctx, ef.projectConfig)
Expand Down Expand Up @@ -1253,6 +1260,14 @@ func (ef *envRefreshAction) Run(ctx context.Context) (*actions.ActionResult, err
}, nil
}

// hasUnsupportedHostError reports whether err (or any error in its chain) is caused by
// an unsupported service host. This lets env refresh continue when extension-provided hosts
// (e.g., azure.ai.agent) are not loaded.
func hasUnsupportedHostError(err error) bool {
_, ok := errors.AsType[*project.UnsupportedServiceHostError](err)
return ok
}

func newEnvGetValuesFlags(cmd *cobra.Command, global *internal.GlobalCommandOptions) *envGetValuesFlags {
flags := &envGetValuesFlags{}
flags.Bind(cmd.Flags(), global)
Expand Down
74 changes: 74 additions & 0 deletions cli/azd/cmd/env_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@
package cmd

import (
"errors"
"fmt"
"testing"

"github.com/azure/azure-dev/cli/azd/internal"
"github.com/azure/azure-dev/cli/azd/pkg/project"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -170,3 +174,73 @@ func TestNewEnvConfigUnsetCmd(t *testing.T) {
require.Equal(t, "unset <path>", cmd.Use)
require.NotEmpty(t, cmd.Short)
}

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

tests := []struct {
name string
err error
expected bool
}{
{
name: "nil error",
err: nil,
expected: false,
},
{
name: "unrelated error",
err: assert.AnError,
expected: false,
},
{
name: "direct UnsupportedServiceHostError",
err: &project.UnsupportedServiceHostError{
Host: "azure.ai.agent",
ServiceName: "agent",
},
expected: true,
},
{
name: "wrapped UnsupportedServiceHostError",
err: fmt.Errorf("initializing service 'agent', %w", &project.UnsupportedServiceHostError{
Host: "azure.ai.agent",
ServiceName: "agent",
}),
expected: true,
},
{
name: "wrapped in ErrorWithSuggestion",
err: fmt.Errorf("getting service target: %w", &internal.ErrorWithSuggestion{
Err: &project.UnsupportedServiceHostError{
Host: "azure.ai.agent",
ServiceName: "agent",
},
Suggestion: "install an extension",
}),
expected: true,
},
{
name: "errors.Join with UnsupportedServiceHostError",
err: errors.Join(
fmt.Errorf("initializing service 'agent', %w", &project.UnsupportedServiceHostError{
Host: "azure.ai.agent",
ServiceName: "agent",
}),
fmt.Errorf("initializing service 'other', %w", &project.UnsupportedServiceHostError{
Host: "azure.ai.other",
ServiceName: "other",
}),
),
expected: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := hasUnsupportedHostError(tt.err)
assert.Equal(t, tt.expected, got)
})
}
}
25 changes: 23 additions & 2 deletions cli/azd/pkg/project/project_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,9 @@ func NewProjectManager(
}
}

// Initializes the project and all child services defined within the project configuration
// Initializes the project and all child services defined within the project configuration.
// Services with unsupported hosts (e.g., extension-provided hosts that are not loaded) are skipped,
// and the resulting UnsupportedServiceHostError is returned after all other services are initialized.
func (pm *projectManager) Initialize(ctx context.Context, projectConfig *ProjectConfig) error {
servicesStable, err := pm.importManager.ServiceStable(ctx, projectConfig)
if err != nil {
Expand All @@ -106,12 +108,22 @@ func (pm *projectManager) Initialize(ctx context.Context, projectConfig *Project

tracing.SetUsageAttributes(fields.ProjectServiceTargetsKey.StringSlice(serviceTargets))

var unsupportedHostErrors []error
for _, svc := range servicesStable {
if err := pm.serviceManager.Initialize(ctx, svc); err != nil {
return fmt.Errorf("initializing service '%s', %w", svc.Name, err)
initErr := fmt.Errorf("initializing service '%s', %w", svc.Name, err)
if _, ok := errors.AsType[*UnsupportedServiceHostError](err); ok {
unsupportedHostErrors = append(unsupportedHostErrors, initErr)
continue
}
return initErr
}
}

if len(unsupportedHostErrors) > 0 {
return errors.Join(unsupportedHostErrors...)
}

return nil
}

Expand Down Expand Up @@ -155,13 +167,18 @@ func (pm *projectManager) EnsureAllTools(
return err
}

var unsupportedHostErrors []error
for _, svc := range servicesStable {
if serviceFilterFn != nil && !serviceFilterFn(svc) {
continue
}

svcTools, err := pm.serviceManager.GetRequiredTools(ctx, svc)
if err != nil {
if _, ok := errors.AsType[*UnsupportedServiceHostError](err); ok {
unsupportedHostErrors = append(unsupportedHostErrors, fmt.Errorf("getting service required tools: %w", err))
continue
}
return fmt.Errorf("getting service required tools: %w", err)
}

Expand All @@ -172,6 +189,10 @@ func (pm *projectManager) EnsureAllTools(
return err
}

if len(unsupportedHostErrors) > 0 {
return errors.Join(unsupportedHostErrors...)
}

return nil
}

Expand Down
Loading