diff --git a/cli/azd/cmd/middleware/hooks.go b/cli/azd/cmd/middleware/hooks.go index d43794fbeb4..fdcaa9257d1 100644 --- a/cli/azd/cmd/middleware/hooks.go +++ b/cli/azd/cmd/middleware/hooks.go @@ -14,26 +14,27 @@ import ( "github.com/azure/azure-dev/cli/azd/pkg/ext" "github.com/azure/azure-dev/cli/azd/pkg/input" "github.com/azure/azure-dev/cli/azd/pkg/ioc" + "github.com/azure/azure-dev/cli/azd/pkg/lazy" "github.com/azure/azure-dev/cli/azd/pkg/output/ux" "github.com/azure/azure-dev/cli/azd/pkg/project" ) type HooksMiddleware struct { - envManager environment.Manager - env *environment.Environment - projectConfig *project.ProjectConfig - importManager *project.ImportManager - commandRunner exec.CommandRunner - console input.Console - options *Options - serviceLocator ioc.ServiceLocator + envManager environment.Manager + env *environment.Environment + lazyProjectConfig *lazy.Lazy[*project.ProjectConfig] + importManager *project.ImportManager + commandRunner exec.CommandRunner + console input.Console + options *Options + serviceLocator ioc.ServiceLocator } // Creates a new instance of the Hooks middleware func NewHooksMiddleware( envManager environment.Manager, env *environment.Environment, - projectConfig *project.ProjectConfig, + lazyProjectConfig *lazy.Lazy[*project.ProjectConfig], importManager *project.ImportManager, commandRunner exec.CommandRunner, console input.Console, @@ -41,54 +42,62 @@ func NewHooksMiddleware( serviceLocator ioc.ServiceLocator, ) Middleware { return &HooksMiddleware{ - envManager: envManager, - env: env, - projectConfig: projectConfig, - importManager: importManager, - commandRunner: commandRunner, - console: console, - options: options, - serviceLocator: serviceLocator, + envManager: envManager, + env: env, + lazyProjectConfig: lazyProjectConfig, + importManager: importManager, + commandRunner: commandRunner, + console: console, + options: options, + serviceLocator: serviceLocator, } } // Runs the Hooks middleware func (m *HooksMiddleware) Run(ctx context.Context, next NextFn) (*actions.ActionResult, error) { + projectConfig, err := m.lazyProjectConfig.GetValue() + if err != nil || projectConfig == nil { + log.Println("azd project is not available, skipping all hook registrations.") + return next(ctx) + } + // Validate hooks and display any warnings if !m.options.IsChildAction(ctx) { - if err := m.validateHooks(ctx, m.projectConfig); err != nil { + if err := m.validateHooks(ctx, projectConfig); err != nil { return nil, fmt.Errorf("failed validating hooks, %w", err) } } - if err := m.registerServiceHooks(ctx); err != nil { + if err := m.registerServiceHooks(ctx, projectConfig); err != nil { return nil, fmt.Errorf("failed registering service hooks, %w", err) } - return m.registerCommandHooks(ctx, next) + return m.registerCommandHooks(ctx, projectConfig, next) } // Register command level hooks for the executing cobra command & action // Invokes the middleware next function + func (m *HooksMiddleware) registerCommandHooks( ctx context.Context, + projectConfig *project.ProjectConfig, next NextFn, ) (*actions.ActionResult, error) { - if len(m.projectConfig.Hooks) == 0 { + if len(projectConfig.Hooks) == 0 { log.Println( "azd project does not contain any command hooks, skipping command hook registrations.", ) return next(ctx) } - hooksManager := ext.NewHooksManager(m.projectConfig.Path, m.commandRunner) + hooksManager := ext.NewHooksManager(projectConfig.Path, m.commandRunner) hooksRunner := ext.NewHooksRunner( hooksManager, m.commandRunner, m.envManager, m.console, - m.projectConfig.Path, - m.projectConfig.Hooks, + projectConfig.Path, + projectConfig.Hooks, m.env, m.serviceLocator, ) @@ -117,8 +126,11 @@ func (m *HooksMiddleware) registerCommandHooks( // Registers event handlers for all services within the project configuration // Runs hooks for each matching event handler -func (m *HooksMiddleware) registerServiceHooks(ctx context.Context) error { - stableServices, err := m.importManager.ServiceStable(ctx, m.projectConfig) +func (m *HooksMiddleware) registerServiceHooks( + ctx context.Context, + projectConfig *project.ProjectConfig, +) error { + stableServices, err := m.importManager.ServiceStable(ctx, projectConfig) if err != nil { return fmt.Errorf("failed getting services: %w", err) } diff --git a/cli/azd/cmd/middleware/hooks_test.go b/cli/azd/cmd/middleware/hooks_test.go index f33d531d9dc..5849ac039ab 100644 --- a/cli/azd/cmd/middleware/hooks_test.go +++ b/cli/azd/cmd/middleware/hooks_test.go @@ -15,6 +15,7 @@ import ( "github.com/azure/azure-dev/cli/azd/pkg/environment/azdcontext" "github.com/azure/azure-dev/cli/azd/pkg/exec" "github.com/azure/azure-dev/cli/azd/pkg/ext" + "github.com/azure/azure-dev/cli/azd/pkg/lazy" "github.com/azure/azure-dev/cli/azd/pkg/project" "github.com/azure/azure-dev/cli/azd/test/mocks" "github.com/azure/azure-dev/cli/azd/test/mocks/mockenv" @@ -288,6 +289,34 @@ func Test_ServiceHooks_Registered(t *testing.T) { require.Equal(t, 1, preDeployCount) } +func Test_HooksMiddleware_SkipsWhenProjectUnavailable(t *testing.T) { + mockContext := mocks.NewMockContext(context.Background()) + envManager := &mockenv.MockEnvManager{} + env := environment.New("test") + + lazyProjectConfig := lazy.NewLazy(func() (*project.ProjectConfig, error) { + return nil, errors.New("failed to load project") + }) + + middleware := NewHooksMiddleware( + envManager, + env, + lazyProjectConfig, + project.NewImportManager(nil), + mockContext.CommandRunner, + mockContext.Console, + &Options{CommandPath: "command"}, + mockContext.Container, + ) + + nextFn, actionRan := createNextFn() + result, err := middleware.Run(*mockContext.Context, nextFn) + + require.NoError(t, err) + require.NotNil(t, result) + require.True(t, *actionRan) +} + func createAzdContext(t *testing.T) *azdcontext.AzdContext { tempDir := t.TempDir() ostest.Chdir(t, tempDir) @@ -343,10 +372,12 @@ func runMiddleware( envManager.On("Save", mock.Anything, mock.Anything).Return(nil) envManager.On("Reload", mock.Anything, mock.Anything).Return(nil) + lazyProjectConfig := lazy.From(projectConfig) + middleware := NewHooksMiddleware( envManager, env, - projectConfig, + lazyProjectConfig, project.NewImportManager(nil), mockContext.CommandRunner, mockContext.Console,