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

DevCenter (ADE) components for template source, provision provider & remote environments #2767

Merged
merged 35 commits into from Oct 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
eb32f10
squash
wbreza Oct 4, 2023
70ce400
WIP: unit tests
wbreza Oct 4, 2023
c553de6
Adds prompter unit tests
wbreza Oct 5, 2023
9e421e3
Convert from 'devCenter' to 'platform.config' node
wbreza Oct 6, 2023
469b839
Fixes linting issues
wbreza Oct 6, 2023
a8c1157
Fixes linting issues
wbreza Oct 6, 2023
14ab50d
Fixes prompter bugs
wbreza Oct 9, 2023
ba334f0
Fixes bugs
wbreza Oct 10, 2023
e49e6b1
Adds platform provider concept for platform specific container config…
wbreza Oct 10, 2023
5154a28
WIP: ade provision provider tests
wbreza Oct 11, 2023
c233d8b
ADE provision provider tests
wbreza Oct 11, 2023
1d053d7
Adds more unit tests
wbreza Oct 11, 2023
d16d1a4
Addresses PR feedback
wbreza Oct 12, 2023
f5a134e
Updates prompting to infer devcenter from selected project
wbreza Oct 12, 2023
1dc7a37
Removes 'devcenter' provider kind from provisioning package
wbreza Oct 12, 2023
74a15e7
Rollback pipeline skipping
wbreza Oct 12, 2023
ae596a8
Invalidate environment keys after destroying environment
wbreza Oct 12, 2023
cd85ba9
Removes unused mock catalogs
wbreza Oct 12, 2023
834aace
Rollback path changes
wbreza Oct 12, 2023
6d47b54
Updates devcenter provision provider tests
wbreza Oct 12, 2023
0e38aec
Added DevCenter and projects to supported azure resource types for pr…
wbreza Oct 17, 2023
d0eda1b
Adds platform.type to telemetry property bag
wbreza Oct 18, 2023
61ce6b9
Updates telemetry tests
wbreza Oct 19, 2023
0e526e6
Fixes lint issues
wbreza Oct 19, 2023
bf3baa9
Addresses PR feedback and bug fixes
wbreza Oct 20, 2023
b46f05d
Removes template name completion
wbreza Oct 20, 2023
4d1eefa
Handle additional string conversion use cases
wbreza Oct 25, 2023
71cbc86
Validate platform type is set to a known/valid value
wbreza Oct 25, 2023
f4d282a
Adds better error message for invalid devcenter configuration during …
wbreza Oct 25, 2023
7877be4
Moves project init to repo initializer.
wbreza Oct 26, 2023
24ad023
Adjust how platform configurations are loaded
wbreza Oct 26, 2023
6110378
Adjust how platform configurations are loaded
wbreza Oct 26, 2023
8bcf09d
Adds 'default' platform that is used when platform type is not defined
wbreza Oct 26, 2023
de7d8b3
Refactor platform package, throw error on invalid platform
wbreza Oct 27, 2023
74aae0e
Adds missing azd package
wbreza Oct 27, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .vscode/cspell.global.yaml
Expand Up @@ -58,6 +58,7 @@ ignoreWords:
- containerservice
- databricks
- dedb
- devcenter
- devcontainer
- dnsz
- evgd
Expand Down
7 changes: 7 additions & 0 deletions cli/azd/.vscode/cspell-azd-dictionary.txt
Expand Up @@ -19,6 +19,7 @@ armappconfiguration
armappplatform
armcognitiveservices
asyncmy
armresourcegraph
asyncpg
azapi
AZCLI
Expand Down Expand Up @@ -62,6 +63,9 @@ csharpapp
csharpapptest
cupaloy
deletedservices
devcenter
devcenters
devcentersdk
devel
discarder
docf
Expand Down Expand Up @@ -98,6 +102,7 @@ LASTEXITCODE
ldflags
lechnerc77
libc
mergo
mgmt
mgutz
microsoftonline
Expand Down Expand Up @@ -136,6 +141,8 @@ pyvenv
reauthentication
relogin
remarshal
repourl
resourcegraph
restoreapp
retriable
rzip
Expand Down
1 change: 0 additions & 1 deletion cli/azd/cmd/cobra_builder.go
Expand Up @@ -107,7 +107,6 @@ func (cb *CobraBuilder) configureActionResolver(cmd *cobra.Command, descriptor *
// Registers the following to enable injection into actions that require them
ioc.RegisterInstance(cb.container, cb.runner)
ioc.RegisterInstance(cb.container, middleware.MiddlewareContext(cb.runner))
ioc.RegisterInstance(cb.container, ctx)
wbreza marked this conversation as resolved.
Show resolved Hide resolved
ioc.RegisterInstance(cb.container, cmd)
ioc.RegisterInstance(cb.container, args)

Expand Down
187 changes: 108 additions & 79 deletions cli/azd/cmd/container.go
Expand Up @@ -2,41 +2,43 @@ package cmd

import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"strings"

"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph"
"github.com/azure/azure-dev/cli/azd/cmd/actions"
"github.com/azure/azure-dev/cli/azd/internal"
"github.com/azure/azure-dev/cli/azd/internal/repository"
"github.com/azure/azure-dev/cli/azd/pkg/account"
"github.com/azure/azure-dev/cli/azd/pkg/alpha"
"github.com/azure/azure-dev/cli/azd/pkg/auth"
"github.com/azure/azure-dev/cli/azd/pkg/azapi"
"github.com/azure/azure-dev/cli/azd/pkg/azsdk/storage"
"github.com/azure/azure-dev/cli/azd/pkg/azd"
"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/containerapps"
"github.com/azure/azure-dev/cli/azd/pkg/devcenter"
"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/httputil"
"github.com/azure/azure-dev/cli/azd/pkg/infra"
"github.com/azure/azure-dev/cli/azd/pkg/infra/provisioning"
infraBicep "github.com/azure/azure-dev/cli/azd/pkg/infra/provisioning/bicep"
infraTerraform "github.com/azure/azure-dev/cli/azd/pkg/infra/provisioning/terraform"
"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/pipeline"
"github.com/azure/azure-dev/cli/azd/pkg/platform"
"github.com/azure/azure-dev/cli/azd/pkg/project"
"github.com/azure/azure-dev/cli/azd/pkg/prompt"
"github.com/azure/azure-dev/cli/azd/pkg/state"
"github.com/azure/azure-dev/cli/azd/pkg/templates"
"github.com/azure/azure-dev/cli/azd/pkg/tools/azcli"
"github.com/azure/azure-dev/cli/azd/pkg/tools/bicep"
"github.com/azure/azure-dev/cli/azd/pkg/tools/docker"
"github.com/azure/azure-dev/cli/azd/pkg/tools/dotnet"
"github.com/azure/azure-dev/cli/azd/pkg/tools/git"
Expand All @@ -47,10 +49,10 @@ import (
"github.com/azure/azure-dev/cli/azd/pkg/tools/npm"
"github.com/azure/azure-dev/cli/azd/pkg/tools/python"
"github.com/azure/azure-dev/cli/azd/pkg/tools/swa"
"github.com/azure/azure-dev/cli/azd/pkg/tools/terraform"
"github.com/mattn/go-colorable"
"github.com/mattn/go-isatty"
"github.com/spf13/cobra"
"golang.org/x/exp/slices"
)

// Registers a singleton action initializer for the specified action name
Expand Down Expand Up @@ -227,11 +229,21 @@ func registerCommonDependencies(container *ioc.NestedContainer) {
}
})

container.RegisterSingleton(storage.NewBlobClient)
container.RegisterSingleton(storage.NewBlobSdkClient)
container.RegisterSingleton(environment.NewLocalFileDataStore)
container.RegisterSingleton(environment.NewManager)

container.RegisterSingleton(func() *lazy.Lazy[environment.LocalDataStore] {
return lazy.NewLazy(func() (environment.LocalDataStore, error) {
var localDataStore environment.LocalDataStore
err := container.Resolve(&localDataStore)
if err != nil {
return nil, err
}

return localDataStore, nil
})
})

// Environment manager depends on azd context
container.RegisterSingleton(func(azdContext *lazy.Lazy[*azdcontext.AzdContext]) *lazy.Lazy[environment.Manager] {
return lazy.NewLazy(func() (environment.Manager, error) {
Expand All @@ -253,23 +265,17 @@ func registerCommonDependencies(container *ioc.NestedContainer) {
})
})

// Remote Environment State Providers
remoteStateProviderMap := map[environment.RemoteKind]any{
environment.RemoteKindAzureBlobStorage: environment.NewStorageBlobDataStore,
}

for remoteKind, constructor := range remoteStateProviderMap {
if err := container.RegisterNamedSingleton(string(remoteKind), constructor); err != nil {
panic(fmt.Errorf("registering remote state provider %s: %w", remoteKind, err))
}
}

container.RegisterSingleton(func(
lazyProjectConfig *lazy.Lazy[*project.ProjectConfig],
userConfigManager config.UserConfigManager,
) (*state.RemoteConfig, error) {
var remoteStateConfig *state.RemoteConfig

userConfig, err := userConfigManager.Load()
if err != nil {
return nil, fmt.Errorf("loading user config: %w", err)
}

// The project config may not be available yet
// Ex) Within init phase of fingerprinting
projectConfig, _ := lazyProjectConfig.GetValue()
Expand All @@ -280,56 +286,14 @@ func registerCommonDependencies(container *ioc.NestedContainer) {
if projectConfig != nil && projectConfig.State != nil && projectConfig.State.Remote != nil {
remoteStateConfig = projectConfig.State.Remote
} else {
userConfig, err := userConfigManager.Load()
if err != nil {
return nil, fmt.Errorf("loading user config: %w", err)
}

remoteState, ok := userConfig.Get("state.remote")
if ok {
jsonBytes, err := json.Marshal(remoteState)
if err != nil {
return nil, fmt.Errorf("marshalling remote state: %w", err)
}

if err := json.Unmarshal(jsonBytes, &remoteStateConfig); err != nil {
return nil, fmt.Errorf("unmarshalling remote state: %w", err)
}
if _, err := userConfig.GetSection("state.remote", &remoteStateConfig); err != nil {
return nil, fmt.Errorf("getting remote state config: %w", err)
}
}

return remoteStateConfig, nil
})

container.RegisterSingleton(func(
remoteStateConfig *state.RemoteConfig,
projectConfig *project.ProjectConfig,
) (*storage.AccountConfig, error) {
if remoteStateConfig == nil {
return nil, nil
}

var storageAccountConfig *storage.AccountConfig
jsonBytes, err := json.Marshal(remoteStateConfig.Config)
if err != nil {
return nil, fmt.Errorf("marshalling remote state config: %w", err)
}

if err := json.Unmarshal(jsonBytes, &storageAccountConfig); err != nil {
return nil, fmt.Errorf("unmarshalling remote state config: %w", err)
}

// If a container name has not been explicitly configured
// Default to use the project name as the container name
if storageAccountConfig.ContainerName == "" {
// Azure blob storage containers must be lowercase and can only container alphanumeric characters and hyphens
// We will do our best to preserve the original project name by forcing to lowercase.
storageAccountConfig.ContainerName = strings.ToLower(projectConfig.Name)
}

return storageAccountConfig, nil
})

// Lazy loads an existing environment, erroring out if not available
// One can repeatedly call GetValue to wait until the environment is available.
container.RegisterSingleton(
Expand Down Expand Up @@ -399,6 +363,20 @@ func registerCommonDependencies(container *ioc.NestedContainer) {
})
})

container.RegisterSingleton(func(
ctx context.Context,
credential azcore.TokenCredential,
httpClient httputil.HttpClient,
) (*armresourcegraph.Client, error) {
options := azsdk.
DefaultClientOptionsBuilder(ctx, httpClient, "azd").
BuildArmClientOptions()

return armresourcegraph.NewClient(credential, options)
})

container.RegisterSingleton(templates.NewTemplateManager)
container.RegisterSingleton(templates.NewSourceManager)
container.RegisterSingleton(project.NewResourceManager)
container.RegisterSingleton(project.NewProjectManager)
container.RegisterSingleton(project.NewServiceManager)
Expand All @@ -407,8 +385,6 @@ func registerCommonDependencies(container *ioc.NestedContainer) {
container.RegisterSingleton(config.NewUserConfigManager)
container.RegisterSingleton(config.NewManager)
container.RegisterSingleton(config.NewFileConfigManager)
container.RegisterSingleton(templates.NewTemplateManager)
container.RegisterSingleton(templates.NewSourceManager)
container.RegisterSingleton(auth.NewManager)
container.RegisterSingleton(azcli.NewUserProfileService)
container.RegisterSingleton(account.NewSubscriptionsService)
Expand Down Expand Up @@ -446,7 +422,6 @@ func registerCommonDependencies(container *ioc.NestedContainer) {
})
container.RegisterSingleton(azapi.NewDeployments)
container.RegisterSingleton(azapi.NewDeploymentOperations)
container.RegisterSingleton(bicep.NewBicepCli)
container.RegisterSingleton(docker.NewDocker)
container.RegisterSingleton(dotnet.NewDotNetCli)
container.RegisterSingleton(git.NewGitCli)
Expand All @@ -457,25 +432,13 @@ func registerCommonDependencies(container *ioc.NestedContainer) {
container.RegisterSingleton(npm.NewNpmCli)
container.RegisterSingleton(python.NewPythonCli)
container.RegisterSingleton(swa.NewSwaCli)
container.RegisterSingleton(terraform.NewTerraformCli)

// Provisioning
container.RegisterSingleton(infra.NewAzureResourceManager)
container.RegisterTransient(provisioning.NewManager)
container.RegisterSingleton(provisioning.NewPrincipalIdProvider)
container.RegisterSingleton(prompt.NewDefaultPrompter)

// Provisioning Providers
provisionProviderMap := map[provisioning.ProviderKind]any{
provisioning.Bicep: infraBicep.NewBicepProvider,
provisioning.Terraform: infraTerraform.NewTerraformProvider,
}

for provider, constructor := range provisionProviderMap {
if err := container.RegisterNamedTransient(string(provider), constructor); err != nil {
panic(fmt.Errorf("registering IaC provider %s: %w", provider, err))
}
}

// Other
container.RegisterSingleton(createClock)

Expand Down Expand Up @@ -534,6 +497,72 @@ func registerCommonDependencies(container *ioc.NestedContainer) {
}
}

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

return platformConfig, err
})
})

container.RegisterSingleton(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
}

// 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)
}

if !ok {
return nil, errors.New("platform config not found")
}

// Validate platform type
supportedPlatformKinds := []string{
string(devcenter.PlatformKindDevCenter),
string(azd.PlatformKindDefault),
}
if !slices.Contains(supportedPlatformKinds, string(platformConfig.Type)) {
return nil, fmt.Errorf(
"platform kind '%s' is not supported. Valid values are '%s', %w",
platformConfig.Type,
strings.Join(supportedPlatformKinds, ","),
platform.ErrPlatformNotSupported,
)
}

return platformConfig, nil
})

// Platform Providers
platformProviderMap := map[platform.PlatformKind]any{
azd.PlatformKindDefault: azd.NewDefaultPlatform,
devcenter.PlatformKindDevCenter: devcenter.NewPlatform,
}

for provider, constructor := range platformProviderMap {
platformName := fmt.Sprintf("%s-platform", provider)
if err := container.RegisterNamedSingleton(platformName, constructor); err != nil {
panic(fmt.Errorf("registering platform provider %s: %w", provider, err))
}
}

// Required for nested actions called from composite actions like 'up'
registerActionInitializer[*initAction](container, "azd-init-action")
registerActionInitializer[*provisionAction](container, "azd-provision-action")
Expand Down