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

Deventer context / config fix #3390

Merged
merged 9 commits into from
Feb 28, 2024
Merged
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
115 changes: 53 additions & 62 deletions cli/azd/cmd/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,13 +180,13 @@ func registerCommonDependencies(container *ioc.NestedContainer) {
})

// Azd Context
container.MustRegisterSingleton(azdcontext.NewAzdContext)
container.MustRegisterSingleton(func(lazyAzdContext *lazy.Lazy[*azdcontext.AzdContext]) (*azdcontext.AzdContext, error) {
wbreza marked this conversation as resolved.
Show resolved Hide resolved
return lazyAzdContext.GetValue()
})

// Lazy loads the Azd context after the azure.yaml file becomes available
container.MustRegisterSingleton(func() *lazy.Lazy[*azdcontext.AzdContext] {
return lazy.NewLazy(func() (*azdcontext.AzdContext, error) {
return azdcontext.NewAzdContext()
})
return lazy.NewLazy(azdcontext.NewAzdContext)
})

// Register an initialized environment based on the specified environment flag, or the default environment.
Expand Down Expand Up @@ -348,17 +348,8 @@ func registerCommonDependencies(container *ioc.NestedContainer) {
// Required to be singleton (shared) because the project/service holds important event handlers
// from both hooks and internal that are used during azd lifecycle calls.
container.MustRegisterSingleton(
func(ctx context.Context, azdContext *azdcontext.AzdContext) (*project.ProjectConfig, error) {
if azdContext == nil {
return nil, azdcontext.ErrNoProject
}

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

return projectConfig, nil
func(lazyConfig *lazy.Lazy[*project.ProjectConfig]) (*project.ProjectConfig, error) {
return lazyConfig.GetValue()
},
)

Expand All @@ -367,19 +358,21 @@ func registerCommonDependencies(container *ioc.NestedContainer) {
// from both hooks and internal that are used during azd lifecycle calls.
container.MustRegisterSingleton(
func(
serviceLocator ioc.ServiceLocator,
ctx context.Context,
lazyAzdContext *lazy.Lazy[*azdcontext.AzdContext],
) *lazy.Lazy[*project.ProjectConfig] {
return lazy.NewLazy(func() (*project.ProjectConfig, error) {
_, err := lazyAzdContext.GetValue()
azdCtx, err := lazyAzdContext.GetValue()
if err != nil {
return nil, err
}

var projectConfig *project.ProjectConfig
err = serviceLocator.Resolve(&projectConfig)
projectConfig, err := project.Load(ctx, azdCtx.ProjectPath())
if err != nil {
return nil, err
}

return projectConfig, err
return projectConfig, nil
})
},
)
Expand Down Expand Up @@ -543,59 +536,57 @@ func registerCommonDependencies(container *ioc.NestedContainer) {
}

// Platform configuration
container.MustRegisterSingleton(func(serviceLocator ioc.ServiceLocator) *lazy.Lazy[*platform.Config] {
return lazy.NewLazy(func() (*platform.Config, error) {
var platformConfig *platform.Config
err := serviceLocator.Resolve(&platformConfig)

return platformConfig, err
})
container.MustRegisterSingleton(func(lazyConfig *lazy.Lazy[*platform.Config]) (*platform.Config, error) {
return lazyConfig.GetValue()
})

container.MustRegisterSingleton(func(
lazyProjectConfig *lazy.Lazy[*project.ProjectConfig],
userConfigManager config.UserConfigManager,
) (*platform.Config, error) {
// First check `azure.yaml` for platform configuration section
projectConfig, err := lazyProjectConfig.GetValue()
if err == nil && projectConfig != nil && projectConfig.Platform != nil {
return projectConfig.Platform, nil
}
) *lazy.Lazy[*platform.Config] {
return lazy.NewLazy[*platform.Config](func() (*platform.Config, error) {
// First check `azure.yaml` for platform configuration section
projectConfig, err := lazyProjectConfig.GetValue()
if err == nil && projectConfig != nil && projectConfig.Platform != nil {
return projectConfig.Platform, nil
}

// Fallback to global user configuration
config, err := userConfigManager.Load()
if err != nil {
return nil, fmt.Errorf("loading user config: %w", err)
}
// Fallback to global user configuration
config, err := userConfigManager.Load()
if err != nil {
return nil, fmt.Errorf("loading user config: %w", err)
}

var platformConfig *platform.Config
ok, err := config.GetSection("platform", &platformConfig)
if err != nil {
return nil, fmt.Errorf("getting platform config: %w", err)
}
var platformConfig *platform.Config
ok, err := config.GetSection("platform", &platformConfig)
if err != nil {
return nil, fmt.Errorf("getting platform config: %w", err)
}

if !ok || platformConfig.Type == "" {
return nil, platform.ErrPlatformConfigNotFound
}
if !ok || platformConfig.Type == "" {
return nil, platform.ErrPlatformConfigNotFound
}

// Validate platform type
supportedPlatformKinds := []string{
string(devcenter.PlatformKindDevCenter),
string(azd.PlatformKindDefault),
}
if !slices.Contains(supportedPlatformKinds, string(platformConfig.Type)) {
return nil, fmt.Errorf(
heredoc.Doc(`platform type '%s' is not supported. Valid values are '%s'.
// Validate platform type
supportedPlatformKinds := []string{
string(devcenter.PlatformKindDevCenter),
string(azd.PlatformKindDefault),
}
if !slices.Contains(supportedPlatformKinds, string(platformConfig.Type)) {
return nil, fmt.Errorf(
heredoc.Doc(`platform type '%s' is not supported. Valid values are '%s'.
Run %s to set or %s to reset. (%w)`),
platformConfig.Type,
strings.Join(supportedPlatformKinds, ","),
output.WithBackticks("azd config set platform.type <type>"),
output.WithBackticks("azd config unset platform.type"),
platform.ErrPlatformNotSupported,
)
}
platformConfig.Type,
strings.Join(supportedPlatformKinds, ","),
output.WithBackticks("azd config set platform.type <type>"),
output.WithBackticks("azd config unset platform.type"),
platform.ErrPlatformNotSupported,
)
}

return platformConfig, nil
return platformConfig, nil

})
})

// Platform Providers
Expand Down
88 changes: 88 additions & 0 deletions cli/azd/cmd/container_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package cmd

import (
"context"
"testing"

"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/project"
"github.com/stretchr/testify/require"
)

func Test_Lazy_Project_Config_Resolution(t *testing.T) {
ctx := context.Background()
container := ioc.NewNestedContainer(nil)
ioc.RegisterInstance(container, ctx)

registerCommonDependencies(container)

// Register the testing lazy component
wbreza marked this conversation as resolved.
Show resolved Hide resolved
container.MustRegisterTransient(
func(lazyProjectConfig *lazy.Lazy[*project.ProjectConfig]) *testLazyComponent[*project.ProjectConfig] {
return &testLazyComponent[*project.ProjectConfig]{
lazy: lazyProjectConfig,
}
},
)

// Register the testing concrete component
container.MustRegisterTransient(
func(projectConfig *project.ProjectConfig) *testConcreteComponent[*project.ProjectConfig] {
return &testConcreteComponent[*project.ProjectConfig]{
concrete: projectConfig,
}
},
)

// The lazy components depends on the lazy project config.
// The lazy instance itself should never be nil
var lazyComponent *testLazyComponent[*project.ProjectConfig]
err := container.Resolve(&lazyComponent)
require.NoError(t, err)
require.NotNil(t, lazyComponent.lazy)

// Get the lazy project config instance itself to use for comparison
var lazyProjectConfig *lazy.Lazy[*project.ProjectConfig]
err = container.Resolve(&lazyProjectConfig)
require.NoError(t, err)
require.NotNil(t, lazyProjectConfig)

// At this point a project config is not available, so we should get an error
projectConfig, err := lazyProjectConfig.GetValue()
require.Nil(t, projectConfig)
require.Error(t, err)

// Set a project config on the lazy instance
projectConfig = &project.ProjectConfig{
Name: "test",
}

lazyProjectConfig.SetValue(projectConfig)

// Now lets resolve a type that depends on a concrete project config
// The project config should be be available not that the lazy has been set above
var staticComponent *testConcreteComponent[*project.ProjectConfig]
err = container.Resolve(&staticComponent)
require.NoError(t, err)
require.NotNil(t, staticComponent.concrete)

// Now we validate that the instance returned by the lazy instance is the same as the one resolved directly
lazyValue, err := lazyComponent.lazy.GetValue()
require.NoError(t, err)
directValue, err := lazyProjectConfig.GetValue()
require.NoError(t, err)

// Finally we validate that the return project config across all resolutions point to the same project config pointer
require.Same(t, lazyProjectConfig, lazyComponent.lazy)
require.Same(t, lazyValue, directValue)
require.Same(t, directValue, staticComponent.concrete)
}

type testLazyComponent[T comparable] struct {
lazy *lazy.Lazy[T]
}

type testConcreteComponent[T comparable] struct {
concrete T
}
5 changes: 4 additions & 1 deletion cli/azd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/azure/azure-dev/cli/azd/internal/telemetry"
"github.com/azure/azure-dev/cli/azd/pkg/config"
"github.com/azure/azure-dev/cli/azd/pkg/installer"
"github.com/azure/azure-dev/cli/azd/pkg/ioc"
"github.com/azure/azure-dev/cli/azd/pkg/osutil"
"github.com/azure/azure-dev/cli/azd/pkg/output"
"github.com/blang/semver/v4"
Expand Down Expand Up @@ -58,7 +59,9 @@ func main() {
latest := make(chan semver.Version)
go fetchLatestVersion(latest)

cmdErr := cmd.NewRootCmd(false, nil, nil).ExecuteContext(ctx)
rootContainer := ioc.NewNestedContainer(nil)
ioc.RegisterInstance(rootContainer, ctx)
cmdErr := cmd.NewRootCmd(false, nil, rootContainer).ExecuteContext(ctx)

if !isJsonOutput() {
if firstNotice := telemetry.FirstNotice(); firstNotice != "" {
Expand Down
17 changes: 16 additions & 1 deletion cli/azd/pkg/devcenter/environment_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type EnvironmentStore struct {
devCenterClient devcentersdk.DevCenterClient
prompter *Prompter
manager Manager
local environment.LocalDataStore
}

// NewEnvironmentStore creates a new devcenter environment store
Expand All @@ -29,12 +30,14 @@ func NewEnvironmentStore(
devCenterClient devcentersdk.DevCenterClient,
prompter *Prompter,
manager Manager,
local environment.LocalDataStore,
) environment.RemoteDataStore {
return &EnvironmentStore{
config: config,
devCenterClient: devCenterClient,
prompter: prompter,
manager: manager,
local: local,
}
}

Expand Down Expand Up @@ -174,7 +177,19 @@ func (s *EnvironmentStore) Reload(ctx context.Context, env *environment.Environm
// DevCenter doesn't implement any APIs for saving environment configuration / metadata
// outside of the environment definition itself or the ARM deployment outputs
func (s *EnvironmentStore) Save(ctx context.Context, env *environment.Environment) error {
return nil
if s.config.Project != "" {
if err := env.Config.Set(DevCenterProjectPath, s.config.Project); err != nil {
return err
}
}

if s.config.EnvironmentType != "" {
if err := env.Config.Set(DevCenterEnvTypePath, s.config.EnvironmentType); err != nil {
return err
}
}

return s.local.Save(ctx, env)
wbreza marked this conversation as resolved.
Show resolved Hide resolved
}

// matchingEnvironments returns a list of environments matching the configured environment definition
Expand Down
12 changes: 9 additions & 3 deletions cli/azd/pkg/devcenter/environment_store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import (

"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph"
"github.com/azure/azure-dev/cli/azd/pkg/azsdk"
"github.com/azure/azure-dev/cli/azd/pkg/config"
"github.com/azure/azure-dev/cli/azd/pkg/devcentersdk"
"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/infra/provisioning"
"github.com/azure/azure-dev/cli/azd/test/mocks"
"github.com/azure/azure-dev/cli/azd/test/mocks/mockdevcentersdk"
Expand Down Expand Up @@ -229,7 +231,7 @@ func Test_EnvironmentStore_Save(t *testing.T) {
func newEnvironmentStoreForTest(
t *testing.T,
mockContext *mocks.MockContext,
config *Config,
devCenterConfig *Config,
manager Manager,
) environment.RemoteDataStore {
coreOptions := azsdk.
Expand All @@ -254,7 +256,11 @@ func newEnvironmentStoreForTest(
if manager == nil {
manager = &mockDevCenterManager{}
}
prompter := NewPrompter(config, mockContext.Console, manager, devCenterClient)
prompter := NewPrompter(devCenterConfig, mockContext.Console, manager, devCenterClient)

return NewEnvironmentStore(config, devCenterClient, prompter, manager)
azdContext := azdcontext.NewAzdContextWithDirectory(t.TempDir())
fileConfigManager := config.NewFileConfigManager(config.NewManager())
dataStore := environment.NewLocalFileDataStore(azdContext, fileConfigManager)

return NewEnvironmentStore(devCenterConfig, devCenterClient, prompter, manager, dataStore)
}
12 changes: 6 additions & 6 deletions cli/azd/pkg/devcenter/platform.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func (p *Platform) IsEnabled() bool {
// ConfigureContainer configures the IoC container for the devcenter platform components
func (p *Platform) ConfigureContainer(container *ioc.NestedContainer) error {
// DevCenter Config
container.MustRegisterSingleton(func(
container.MustRegisterTransient(func(
wbreza marked this conversation as resolved.
Show resolved Hide resolved
ctx context.Context,
lazyAzdCtx *lazy.Lazy[*azdcontext.AzdContext],
userConfigManager config.UserConfigManager,
Expand Down Expand Up @@ -152,16 +152,16 @@ func (p *Platform) ConfigureContainer(container *ioc.NestedContainer) error {
})

// Provision Provider
container.MustRegisterNamedScoped(string(ProvisionKindDevCenter), NewProvisionProvider)
container.MustRegisterNamedTransient(string(ProvisionKindDevCenter), NewProvisionProvider)

// Remote Environment Storage
container.MustRegisterNamedScoped(string(RemoteKindDevCenter), NewEnvironmentStore)
container.MustRegisterNamedTransient(string(RemoteKindDevCenter), NewEnvironmentStore)

// Template Sources
container.MustRegisterNamedScoped(string(SourceKindDevCenter), NewTemplateSource)
container.MustRegisterNamedTransient(string(SourceKindDevCenter), NewTemplateSource)

container.MustRegisterSingleton(NewManager)
container.MustRegisterSingleton(NewPrompter)
container.MustRegisterTransient(NewManager)
container.MustRegisterTransient(NewPrompter)

// Other devcenter components
container.MustRegisterSingleton(func(
Expand Down
2 changes: 2 additions & 0 deletions cli/azd/pkg/devcenter/provision_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,8 @@ func (p *ProvisionProvider) EnsureEnv(ctx context.Context) error {
}
}

p.config = updatedConfig

return nil
}

Expand Down
Loading
Loading