Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lazy POC to help with updating singleton components in composite commands or middleware usage #1

Draft
wants to merge 2 commits into
base: hooks-middleware
Choose a base branch
from
Draft
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
83 changes: 55 additions & 28 deletions cli/azd/cmd/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/azure/azure-dev/cli/azd/pkg/exec"
"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"
"github.com/azure/azure-dev/cli/azd/pkg/project"
"github.com/azure/azure-dev/cli/azd/pkg/templates"
Expand Down Expand Up @@ -96,8 +97,6 @@ func registerCommonDependencies(container *ioc.NestedContainer) {
})
})

container.RegisterSingleton(azdcontext.NewAzdContext)

container.RegisterSingleton(func(ctx context.Context, authManager *auth.Manager) (azcore.TokenCredential, error) {
credential, err := authManager.CredentialForCurrentUser(ctx, nil)
if err != nil {
Expand Down Expand Up @@ -142,47 +141,75 @@ func registerCommonDependencies(container *ioc.NestedContainer) {
return flagsWithEnv
})

container.RegisterSingleton(
func(azdContext *azdcontext.AzdContext, envFlags flagsWithEnv) (*environment.Environment, error) {
if azdContext == nil {
return nil, azdcontext.ErrNoProject
}
// AzdContext
container.RegisterSingleton(azdcontext.NewAzdContext)
container.RegisterSingleton(func() *lazy.Lazy[*azdcontext.AzdContext] {
return lazy.NewLazy(azdcontext.NewAzdContext)
})

environmentName := envFlags.EnvironmentName()
var err error
// Environment
initEnvironment := func(azdContext *azdcontext.AzdContext, envFlags flagsWithEnv) (*environment.Environment, error) {
if azdContext == nil {
return nil, azdcontext.ErrNoProject
}

if environmentName == "" {
defaultEnvName, err := azdContext.GetDefaultEnvironmentName()
if err != nil {
return nil, err
}
environmentName := envFlags.EnvironmentName()
var err error

environmentName = defaultEnvName
if environmentName == "" {
defaultEnvName, err := azdContext.GetDefaultEnvironmentName()
if err != nil {
return nil, err
}

env, err := environment.GetEnvironment(azdContext, environmentName)
environmentName = defaultEnvName
}

env, err := environment.GetEnvironment(azdContext, environmentName)
if err != nil {
return nil, err
}

return env, nil
}

container.RegisterSingleton(initEnvironment)
container.RegisterSingleton(func(lazyAzdContext *lazy.Lazy[*azdcontext.AzdContext], envFlags flagsWithEnv) *lazy.Lazy[*environment.Environment] {
return lazy.NewLazy(func() (*environment.Environment, error) {
azdCtx, err := lazyAzdContext.GetValue()
if err != nil {
return nil, err
}

return env, nil
},
)
return initEnvironment(azdCtx, envFlags)
})
})

container.RegisterSingleton(
func(azdContext *azdcontext.AzdContext) (*project.ProjectConfig, error) {
if azdContext == nil {
return nil, azdcontext.ErrNoProject
}
// ProjectConfig
initProjectConfig := func(azdContext *azdcontext.AzdContext) (*project.ProjectConfig, error) {
if azdContext == nil {
return nil, azdcontext.ErrNoProject
}

projectConfig, err := project.LoadProjectConfig(azdContext.ProjectPath())
projectConfig, err := project.LoadProjectConfig(azdContext.ProjectPath())
if err != nil {
return nil, err
}

return projectConfig, nil
}

container.RegisterSingleton(initProjectConfig)
container.RegisterSingleton(func(lazyAzdContext *lazy.Lazy[*azdcontext.AzdContext]) *lazy.Lazy[*project.ProjectConfig] {
return lazy.NewLazy(func() (*project.ProjectConfig, error) {
azdCtx, err := lazyAzdContext.GetValue()
if err != nil {
return nil, err
}

return projectConfig, nil
},
)
return initProjectConfig(azdCtx)
})
})

container.RegisterSingleton(repository.NewInitializer)
container.RegisterSingleton(config.NewUserConfigManager)
Expand Down
25 changes: 16 additions & 9 deletions cli/azd/cmd/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ import (

"github.com/azure/azure-dev/cli/azd/cmd/actions"
"github.com/azure/azure-dev/cli/azd/internal"
"github.com/azure/azure-dev/cli/azd/pkg/environment"
"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/input"
"github.com/azure/azure-dev/cli/azd/pkg/lazy"
"github.com/azure/azure-dev/cli/azd/pkg/output"
"github.com/azure/azure-dev/cli/azd/pkg/output/ux"
"github.com/azure/azure-dev/cli/azd/pkg/project"
Expand Down Expand Up @@ -84,6 +86,9 @@ After the deployment is complete, the endpoint is printed. To start the service,

type deployAction struct {
flags *deployFlags
azdCtx *lazy.Lazy[*azdcontext.AzdContext]
env *lazy.Lazy[*environment.Environment]
projConfig *lazy.Lazy[*project.ProjectConfig]
azCli azcli.AzCli
formatter output.Formatter
writer io.Writer
Expand All @@ -93,6 +98,9 @@ type deployAction struct {

func newDeployAction(
flags *deployFlags,
azdCtx *lazy.Lazy[*azdcontext.AzdContext],
env *lazy.Lazy[*environment.Environment],
projConfig *lazy.Lazy[*project.ProjectConfig],
azCli azcli.AzCli,
commandRunner exec.CommandRunner,
console input.Console,
Expand All @@ -101,6 +109,9 @@ func newDeployAction(
) actions.Action {
return &deployAction{
flags: flags,
azdCtx: azdCtx,
env: env,
projConfig: projConfig,
azCli: azCli,
formatter: formatter,
writer: writer,
Expand All @@ -115,23 +126,19 @@ type DeploymentResult struct {
}

func (d *deployAction) Run(ctx context.Context) (*actions.ActionResult, error) {
// We call `NewAzdContext` here instead of having the value injected because we want to delay the
// walk for the context until this command has started to execute (for example, in the case of `up`,
// the context is not created until the init action actually runs, which is after the infraCreateAction
// object is created.
azdCtx, err := azdcontext.NewAzdContext()
azdCtx, err := d.azdCtx.GetValue()
if err != nil {
return nil, err
}

env, err := loadOrInitEnvironment(ctx, &d.flags.environmentName, azdCtx, d.console, d.azCli)
env, err := d.env.GetValue()
if err != nil {
return nil, fmt.Errorf("loading environment: %w", err)
return nil, err
}

projConfig, err := project.GetCurrent()
projConfig, err := d.projConfig.GetValue()
if err != nil {
return nil, fmt.Errorf("loading project: %w", err)
return nil, err
}

if d.flags.serviceName != "" && !projConfig.HasService(d.flags.serviceName) {
Expand Down
40 changes: 19 additions & 21 deletions cli/azd/cmd/infra_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ import (

"github.com/azure/azure-dev/cli/azd/cmd/actions"
"github.com/azure/azure-dev/cli/azd/internal"
"github.com/azure/azure-dev/cli/azd/pkg/environment/azdcontext"
"github.com/azure/azure-dev/cli/azd/pkg/environment"
"github.com/azure/azure-dev/cli/azd/pkg/exec"
"github.com/azure/azure-dev/cli/azd/pkg/infra"
"github.com/azure/azure-dev/cli/azd/pkg/infra/provisioning"
"github.com/azure/azure-dev/cli/azd/pkg/input"
"github.com/azure/azure-dev/cli/azd/pkg/lazy"
"github.com/azure/azure-dev/cli/azd/pkg/output"
"github.com/azure/azure-dev/cli/azd/pkg/output/ux"
"github.com/azure/azure-dev/cli/azd/pkg/project"
Expand Down Expand Up @@ -64,6 +65,8 @@ func newInfraCreateCmd() *cobra.Command {

type infraCreateAction struct {
flags *infraCreateFlags
env *lazy.Lazy[*environment.Environment]
projConfig *lazy.Lazy[*project.ProjectConfig]
azCli azcli.AzCli
formatter output.Formatter
writer io.Writer
Expand All @@ -73,6 +76,8 @@ type infraCreateAction struct {

func newInfraCreateAction(
flags *infraCreateFlags,
env *lazy.Lazy[*environment.Environment],
projConfig *lazy.Lazy[*project.ProjectConfig],
azCli azcli.AzCli,
console input.Console,
formatter output.Formatter,
Expand All @@ -81,6 +86,8 @@ func newInfraCreateAction(
) actions.Action {
return &infraCreateAction{
flags: flags,
env: env,
projConfig: projConfig,
azCli: azCli,
formatter: formatter,
writer: writer,
Expand All @@ -90,11 +97,12 @@ func newInfraCreateAction(
}

func (i *infraCreateAction) Run(ctx context.Context) (*actions.ActionResult, error) {
// We call `NewAzdContext` here instead of having the value injected because we want to delay the
// walk for the context until this command has started to execute (for example, in the case of `up`,
// the context is not created until the init action actually runs, which is after the infraCreateAction
// object is created.
azdCtx, err := azdcontext.NewAzdContext()
env, err := i.env.GetValue()
if err != nil {
return nil, err
}

projConfig, err := i.projConfig.GetValue()
if err != nil {
return nil, err
}
Expand All @@ -105,22 +113,12 @@ func (i *infraCreateAction) Run(ctx context.Context) (*actions.ActionResult, err
TitleNote: "Provisioning Azure resources can take some time"},
)

env, err := loadOrInitEnvironment(ctx, &i.flags.environmentName, azdCtx, i.console, i.azCli)
if err != nil {
return nil, fmt.Errorf("loading environment: %w", err)
}

prj, err := project.GetCurrent()
if err != nil {
return nil, fmt.Errorf("loading project: %w", err)
}

if err = prj.Initialize(ctx, env, i.commandRunner); err != nil {
if err := projConfig.Initialize(ctx, env, i.commandRunner); err != nil {
return nil, err
}

infraManager, err := provisioning.NewManager(
ctx, env, prj.Path, prj.Infra, i.console.IsUnformatted(), i.azCli, i.console, i.commandRunner,
ctx, env, projConfig.Path, projConfig.Infra, i.console.IsUnformatted(), i.azCli, i.console, i.commandRunner,
)
if err != nil {
return nil, fmt.Errorf("creating provisioning manager: %w", err)
Expand Down Expand Up @@ -158,9 +156,9 @@ func (i *infraCreateAction) Run(ctx context.Context) (*actions.ActionResult, err
return nil, fmt.Errorf("deployment failed: %w", err)
}

for _, svc := range prj.Services {
for _, svc := range projConfig.Services {
eventArgs := project.ServiceLifecycleEventArgs{
Project: prj,
Project: projConfig,
Service: svc,
Args: map[string]any{
"bicepOutput": deployResult.Deployment.Outputs,
Expand Down Expand Up @@ -193,7 +191,7 @@ func (i *infraCreateAction) Run(ctx context.Context) (*actions.ActionResult, err
return &actions.ActionResult{
Message: &actions.ResultMessage{
Header: "Your project has been provisioned!",
FollowUp: getResourceGroupFollowUp(ctx, i.formatter, i.azCli, prj, env),
FollowUp: getResourceGroupFollowUp(ctx, i.formatter, i.azCli, projConfig, env),
},
}, nil
}
49 changes: 37 additions & 12 deletions cli/azd/cmd/middleware/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,22 @@ import (
"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/input"
"github.com/azure/azure-dev/cli/azd/pkg/lazy"
"github.com/azure/azure-dev/cli/azd/pkg/project"
)

type HooksMiddleware struct {
env *environment.Environment
projectConfig *project.ProjectConfig
env *lazy.Lazy[*environment.Environment]
projectConfig *lazy.Lazy[*project.ProjectConfig]
commandRunner exec.CommandRunner
console input.Console
options *Options
}

// Creates a new instance of the Hooks middleware
func NewHooksMiddleware(
env *environment.Environment,
projectConfig *project.ProjectConfig,
env *lazy.Lazy[*environment.Environment],
projectConfig *lazy.Lazy[*project.ProjectConfig],
commandRunner exec.CommandRunner,
console input.Console,
options *Options,
Expand All @@ -50,27 +51,39 @@ func (m *HooksMiddleware) Run(ctx context.Context, next NextFn) (*actions.Action
// Register command level hooks for the executing cobra command & action
// Invokes the middleware next function
func (m *HooksMiddleware) registerCommandHooks(ctx context.Context, next NextFn) (*actions.ActionResult, error) {
if m.projectConfig.Hooks == nil || len(m.projectConfig.Hooks) == 0 {
projectConfig, err := m.projectConfig.GetValue()
if err != nil {
log.Printf("failed loading project configuration, %s\n", err.Error())
return next(ctx)
}

env, err := m.env.GetValue()
if err != nil {
log.Printf("failed loading environment, %s\n", err.Error())
return next(ctx)
}

if projectConfig.Hooks == nil || len(projectConfig.Hooks) == 0 {
log.Println("project does not contain any command hooks.")
return next(ctx)
}

hooksManager := ext.NewHooksManager(m.projectConfig.Path)
hooksManager := ext.NewHooksManager(projectConfig.Path)
hooksRunner := ext.NewHooksRunner(
hooksManager,
m.commandRunner,
m.console,
m.projectConfig.Path,
m.projectConfig.Hooks,
m.env.Environ(),
projectConfig.Path,
projectConfig.Hooks,
env.Environ(),
)

var actionResult *actions.ActionResult

commandNames := []string{m.options.Name}
commandNames = append(commandNames, m.options.Aliases...)

err := hooksRunner.Invoke(ctx, commandNames, func() error {
err = hooksRunner.Invoke(ctx, commandNames, func() error {
result, err := next(ctx)
if err != nil {
return err
Expand All @@ -90,7 +103,19 @@ func (m *HooksMiddleware) registerCommandHooks(ctx context.Context, next NextFn)
// 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 {
for serviceName, service := range m.projectConfig.Services {
projectConfig, err := m.projectConfig.GetValue()
if err != nil {
log.Printf("failed loading project configuration, %s\n", err.Error())
return nil
}

env, err := m.env.GetValue()
if err != nil {
log.Printf("failed loading environment, %s\n", err.Error())
return nil
}

for serviceName, service := range projectConfig.Services {
// If the service hasn't configured any hooks we can continue on.
if service.Hooks == nil || len(service.Hooks) == 0 {
log.Printf("service '%s' does not require any command hooks.\n", serviceName)
Expand All @@ -104,7 +129,7 @@ func (m *HooksMiddleware) registerServiceHooks(ctx context.Context) error {
m.console,
service.Path(),
service.Hooks,
m.env.Environ(),
env.Environ(),
)

for hookName, hookConfig := range service.Hooks {
Expand Down
Loading