cli/azd/cmd/container.go (671 lines of code) (raw):
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package cmd
import (
"context"
"fmt"
"io"
"log"
"net/http"
"net/url"
"os"
"slices"
"strings"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/MakeNowJust/heredoc/v2"
"github.com/azure/azure-dev/cli/azd/cmd/actions"
"github.com/azure/azure-dev/cli/azd/cmd/middleware"
"github.com/azure/azure-dev/cli/azd/internal"
"github.com/azure/azure-dev/cli/azd/internal/cmd"
"github.com/azure/azure-dev/cli/azd/internal/grpcserver"
"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/ai"
"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/azd"
"github.com/azure/azure-dev/cli/azd/pkg/azsdk"
"github.com/azure/azure-dev/cli/azd/pkg/azsdk/storage"
"github.com/azure/azure-dev/cli/azd/pkg/cloud"
"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/containerregistry"
"github.com/azure/azure-dev/cli/azd/pkg/devcenter"
"github.com/azure/azure-dev/cli/azd/pkg/entraid"
"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/extensions"
"github.com/azure/azure-dev/cli/azd/pkg/helm"
"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"
"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/keyvault"
"github.com/azure/azure-dev/cli/azd/pkg/kubelogin"
"github.com/azure/azure-dev/cli/azd/pkg/kustomize"
"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/docker"
"github.com/azure/azure-dev/cli/azd/pkg/tools/dotnet"
"github.com/azure/azure-dev/cli/azd/pkg/tools/git"
"github.com/azure/azure-dev/cli/azd/pkg/tools/github"
"github.com/azure/azure-dev/cli/azd/pkg/tools/javac"
"github.com/azure/azure-dev/cli/azd/pkg/tools/kubectl"
"github.com/azure/azure-dev/cli/azd/pkg/tools/maven"
"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/workflow"
"github.com/mattn/go-colorable"
"github.com/spf13/cobra"
)
// Registers a transient action initializer for the specified action name
// This returns a function that when called resolves the action
// This is to ensure pre-conditions are met for composite actions like 'up'
// This finds the action for a named instance and casts it to the correct type for injection
func registerAction[T actions.Action](container *ioc.NestedContainer, actionName string) {
container.MustRegisterTransient(func(serviceLocator ioc.ServiceLocator) (T, error) {
return resolveAction[T](serviceLocator, actionName)
})
}
// Resolves the action instance for the specified action name
// This finds the action for a named instance and casts it to the correct type for injection
func resolveAction[T actions.Action](serviceLocator ioc.ServiceLocator, actionName string) (T, error) {
var zero T
var action actions.Action
err := serviceLocator.ResolveNamed(actionName, &action)
if err != nil {
return zero, err
}
instance, ok := action.(T)
if !ok {
return zero, fmt.Errorf("failed converting action to '%T'", zero)
}
return instance, nil
}
// Registers common Azd dependencies
func registerCommonDependencies(container *ioc.NestedContainer) {
// Core bootstrapping registrations
ioc.RegisterInstance(container, container)
container.MustRegisterSingleton(NewCobraBuilder)
// Standard Registrations
container.MustRegisterTransient(output.GetCommandFormatter)
container.MustRegisterScoped(func(
rootOptions *internal.GlobalCommandOptions,
formatter output.Formatter,
cmd *cobra.Command) input.Console {
writer := cmd.OutOrStdout()
// When using JSON formatting, we want to ensure we always write messages from the console to stderr.
if formatter != nil && formatter.Kind() == output.JsonFormat {
writer = cmd.ErrOrStderr()
}
if os.Getenv("NO_COLOR") != "" {
writer = colorable.NewNonColorable(writer)
}
isTerminal := cmd.OutOrStdout() == os.Stdout &&
cmd.InOrStdin() == os.Stdin && input.IsTerminal(os.Stdout.Fd(), os.Stdin.Fd())
return input.NewConsole(rootOptions.NoPrompt, isTerminal, input.Writers{Output: writer}, input.ConsoleHandles{
Stdin: cmd.InOrStdin(),
Stdout: cmd.OutOrStdout(),
Stderr: cmd.ErrOrStderr(),
}, formatter, nil)
})
container.MustRegisterSingleton(
func(console input.Console, rootOptions *internal.GlobalCommandOptions) exec.CommandRunner {
return exec.NewCommandRunner(
&exec.RunnerOptions{
Stdin: console.Handles().Stdin,
Stdout: console.Handles().Stdout,
Stderr: console.Handles().Stderr,
DebugLogging: rootOptions.EnableDebugLogging,
})
},
)
client := createHttpClient()
ioc.RegisterInstance[policy.Transporter](container, client)
ioc.RegisterInstance[auth.HttpClient](container, client)
// Auth
container.MustRegisterSingleton(auth.NewLoggedInGuard)
container.MustRegisterSingleton(auth.NewMultiTenantCredentialProvider)
container.MustRegisterSingleton(func(mgr *auth.Manager) CredentialProviderFn {
return mgr.CredentialForCurrentUser
})
container.MustRegisterSingleton(func(console input.Console) io.Writer {
writer := console.Handles().Stdout
if os.Getenv("NO_COLOR") != "" {
writer = colorable.NewNonColorable(writer)
}
return writer
})
container.MustRegisterScoped(func(cmd *cobra.Command) internal.EnvFlag {
// The env flag `-e, --environment` is available on most azd commands but not all
// This is typically used to override the default environment and is used for bootstrapping other components
// such as the azd environment.
// If the flag is not available, don't panic, just return an empty string which will then allow for our default
// semantics to follow.
envValue, err := cmd.Flags().GetString(internal.EnvironmentNameFlagName)
if err != nil {
log.Printf("'%s'command asked for envFlag, but envFlag was not included in cmd.Flags().", cmd.CommandPath())
envValue = ""
}
if envValue == "" {
// If no explicit environment flag was set, but one was provided
// in the context, use that instead.
// This is used in workflow execution (in `up`) to influence the environment used.
if envFlag, ok := cmd.Context().Value(envFlagCtxKey).(internal.EnvFlag); ok {
return envFlag
}
}
return internal.EnvFlag{EnvironmentName: envValue}
})
container.MustRegisterSingleton(func(cmd *cobra.Command) CmdAnnotations {
return cmd.Annotations
})
// Azd Context
// Scoped registration is required since the value of the azd context can change through the lifetime of a command
// Example: Within extensions multiple workflows can be dispatched which can cause the azd context to be updated.
// A specific example is within AI builder. It invokes `init` command when project is not found.
container.MustRegisterScoped(func(lazyAzdContext *lazy.Lazy[*azdcontext.AzdContext]) (*azdcontext.AzdContext, error) {
return lazyAzdContext.GetValue()
})
// Lazy loads the Azd context after the azure.yaml file becomes available
container.MustRegisterScoped(func() *lazy.Lazy[*azdcontext.AzdContext] {
return lazy.NewLazy(azdcontext.NewAzdContext)
})
// Register an initialized environment based on the specified environment flag, or the default environment.
// Note that referencing an *environment.Environment in a command automatically triggers a UI prompt if the
// environment is uninitialized or a default environment doesn't yet exist.
container.MustRegisterScoped(
func(ctx context.Context,
azdContext *azdcontext.AzdContext,
envManager environment.Manager,
lazyEnv *lazy.Lazy[*environment.Environment],
envFlags internal.EnvFlag,
) (*environment.Environment, error) {
if azdContext == nil {
return nil, azdcontext.ErrNoProject
}
environmentName := envFlags.EnvironmentName
var err error
env, err := envManager.LoadOrInitInteractive(ctx, environmentName)
if err != nil {
return nil, fmt.Errorf("loading environment: %w", err)
}
// Reset lazy env value after loading or creating environment
// This allows any previous lazy instances (such as hooks) to now point to the same instance
lazyEnv.SetValue(env)
return env, nil
},
)
container.MustRegisterScoped(func(lazyEnvManager *lazy.Lazy[environment.Manager]) environment.EnvironmentResolver {
return func(ctx context.Context) (*environment.Environment, error) {
azdCtx, err := azdcontext.NewAzdContext()
if err != nil {
return nil, err
}
defaultEnv, err := azdCtx.GetDefaultEnvironmentName()
if err != nil {
return nil, err
}
// We need to lazy load the environment manager since it depends on azd context
envManager, err := lazyEnvManager.GetValue()
if err != nil {
return nil, err
}
return envManager.Get(ctx, defaultEnv)
}
})
container.MustRegisterSingleton(environment.NewLocalFileDataStore)
container.MustRegisterSingleton(environment.NewManager)
container.MustRegisterSingleton(func(serviceLocator ioc.ServiceLocator) *lazy.Lazy[environment.LocalDataStore] {
return lazy.NewLazy(func() (environment.LocalDataStore, error) {
var localDataStore environment.LocalDataStore
err := serviceLocator.Resolve(&localDataStore)
if err != nil {
return nil, err
}
return localDataStore, nil
})
})
// Environment manager depends on azd context
container.MustRegisterSingleton(
func(serviceLocator ioc.ServiceLocator,
azdContext *lazy.Lazy[*azdcontext.AzdContext]) *lazy.Lazy[environment.Manager] {
return lazy.NewLazy(func() (environment.Manager, error) {
azdCtx, err := azdContext.GetValue()
if err != nil {
return nil, err
}
// Register the Azd context instance as a singleton in the container if now available
ioc.RegisterInstance(container, azdCtx)
var envManager environment.Manager
err = serviceLocator.Resolve(&envManager)
if err != nil {
return nil, err
}
return envManager, nil
})
},
)
container.MustRegisterSingleton(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()
// Lookup remote state config in the following precedence:
// 1. Project azure.yaml
// 2. User configuration
if projectConfig != nil && projectConfig.State != nil && projectConfig.State.Remote != nil {
remoteStateConfig = projectConfig.State.Remote
} else {
if _, err := userConfig.GetSection("state.remote", &remoteStateConfig); err != nil {
return nil, fmt.Errorf("getting remote state config: %w", err)
}
}
return remoteStateConfig, nil
})
// Lazy loads an existing environment, erroring out if not available
// One can repeatedly call GetValue to wait until the environment is available.
container.MustRegisterScoped(
func(
ctx context.Context,
lazyEnvManager *lazy.Lazy[environment.Manager],
lazyAzdContext *lazy.Lazy[*azdcontext.AzdContext],
envFlags internal.EnvFlag,
) *lazy.Lazy[*environment.Environment] {
return lazy.NewLazy(func() (*environment.Environment, error) {
azdCtx, err := lazyAzdContext.GetValue()
if err != nil {
return nil, err
}
environmentName := envFlags.EnvironmentName
if environmentName == "" {
environmentName, err = azdCtx.GetDefaultEnvironmentName()
if err != nil {
return nil, err
}
}
envManager, err := lazyEnvManager.GetValue()
if err != nil {
return nil, err
}
env, err := envManager.Get(ctx, environmentName)
if err != nil {
return nil, err
}
return env, err
})
},
)
// Project Config
container.MustRegisterScoped(
func(lazyConfig *lazy.Lazy[*project.ProjectConfig]) (*project.ProjectConfig, error) {
return lazyConfig.GetValue()
},
)
// Lazy loads the project config from the Azd Context when it becomes available
container.MustRegisterScoped(
func(
ctx context.Context,
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
}
projectConfig, err := project.Load(ctx, azdCtx.ProjectPath())
if err != nil {
return nil, err
}
return projectConfig, nil
})
},
)
container.MustRegisterSingleton(func(
ctx context.Context,
userConfigManager config.UserConfigManager,
lazyProjectConfig *lazy.Lazy[*project.ProjectConfig],
lazyAzdContext *lazy.Lazy[*azdcontext.AzdContext],
lazyLocalEnvStore *lazy.Lazy[environment.LocalDataStore],
) (*cloud.Cloud, error) {
// Precedence for cloud configuration:
// 1. Local environment config (.azure/<environment>/config.json)
// 2. Project config (azure.yaml)
// 3. User config (~/.azure/config.json)
// Default if no cloud configured: Azure Public Cloud
validClouds := fmt.Sprintf(
"Valid cloud names are '%s', '%s', '%s'.",
cloud.AzurePublicName,
cloud.AzureChinaCloudName,
cloud.AzureUSGovernmentName,
)
// Local Environment Configuration (.azure/<environment>/config.json)
localEnvStore, _ := lazyLocalEnvStore.GetValue()
if azdCtx, err := lazyAzdContext.GetValue(); err == nil {
if azdCtx != nil && localEnvStore != nil {
if defaultEnvName, err := azdCtx.GetDefaultEnvironmentName(); err == nil {
if env, err := localEnvStore.Get(ctx, defaultEnvName); err == nil {
if cloudConfigurationNode, exists := env.Config.Get(cloud.ConfigPath); exists {
if value, err := cloud.ParseCloudConfig(cloudConfigurationNode); err == nil {
cloudConfig, err := cloud.NewCloud(value)
if err == nil {
return cloudConfig, nil
}
return nil, &internal.ErrorWithSuggestion{
Err: err,
Suggestion: fmt.Sprintf(
"Set the cloud configuration by editing the 'cloud' node in the config.json "+
"file for the %s environment\n%s",
defaultEnvName,
validClouds,
),
}
}
}
}
}
}
}
// Project Configuration (azure.yaml)
projConfig, err := lazyProjectConfig.GetValue()
if err == nil && projConfig != nil && projConfig.Cloud != nil {
if value, err := cloud.ParseCloudConfig(projConfig.Cloud); err == nil {
if cloudConfig, err := cloud.ParseCloudConfig(value); err == nil {
if cloud, err := cloud.NewCloud(cloudConfig); err == nil {
return cloud, nil
} else {
return nil, &internal.ErrorWithSuggestion{
Err: err,
//nolint:lll
Suggestion: fmt.Sprintf("Set the cloud configuration by editing the 'cloud' node in the project YAML file\n%s", validClouds),
}
}
}
}
}
// User Configuration (~/.azure/config.json)
if azdConfig, err := userConfigManager.Load(); err == nil {
if cloudConfigNode, exists := azdConfig.Get(cloud.ConfigPath); exists {
if value, err := cloud.ParseCloudConfig(cloudConfigNode); err == nil {
if cloud, err := cloud.NewCloud(value); err == nil {
return cloud, nil
} else {
return nil, &internal.ErrorWithSuggestion{
Err: err,
Suggestion: fmt.Sprintf(
"Set the cloud configuration using 'azd config set cloud.name <name>'.\n%s", validClouds),
}
}
}
}
}
return cloud.NewCloud(&cloud.Config{Name: cloud.AzurePublicName})
})
container.MustRegisterSingleton(func(transport policy.Transporter, cloud *cloud.Cloud) *azcore.ClientOptions {
return &azcore.ClientOptions{
Cloud: cloud.Configuration,
PerCallPolicies: []policy.Policy{
azsdk.NewMsCorrelationPolicy(),
azsdk.NewUserAgentPolicy(internal.UserAgent()),
},
Transport: transport,
}
})
container.MustRegisterSingleton(func(transport policy.Transporter, cloud *cloud.Cloud) *arm.ClientOptions {
return &arm.ClientOptions{
ClientOptions: azcore.ClientOptions{
Cloud: cloud.Configuration,
Logging: policy.LogOptions{
AllowedHeaders: []string{azsdk.MsCorrelationIdHeader},
},
PerCallPolicies: []policy.Policy{
azsdk.NewMsCorrelationPolicy(),
azsdk.NewUserAgentPolicy(internal.UserAgent()),
},
Transport: transport,
},
}
})
container.MustRegisterSingleton(templates.NewTemplateManager)
container.MustRegisterSingleton(templates.NewSourceManager)
container.MustRegisterScoped(project.NewResourceManager)
container.MustRegisterScoped(func(serviceLocator ioc.ServiceLocator) *lazy.Lazy[project.ResourceManager] {
return lazy.NewLazy(func() (project.ResourceManager, error) {
var resourceManager project.ResourceManager
err := serviceLocator.Resolve(&resourceManager)
return resourceManager, err
})
})
container.MustRegisterScoped(project.NewProjectManager)
// Currently caches manifest across command executions
container.MustRegisterSingleton(project.NewDotNetImporter)
container.MustRegisterScoped(project.NewImportManager)
container.MustRegisterScoped(project.NewServiceManager)
// Even though the service manager is scoped based on its use of environment we can still
// register its internal cache as a singleton to ensure operation caching is consistent across all instances
container.MustRegisterSingleton(func() project.ServiceOperationCache {
return project.ServiceOperationCache{}
})
container.MustRegisterScoped(func(serviceLocator ioc.ServiceLocator) *lazy.Lazy[project.ServiceManager] {
return lazy.NewLazy(func() (project.ServiceManager, error) {
var serviceManager project.ServiceManager
err := serviceLocator.Resolve(&serviceManager)
return serviceManager, err
})
})
container.MustRegisterSingleton(repository.NewInitializer)
container.MustRegisterSingleton(alpha.NewFeaturesManager)
container.MustRegisterSingleton(config.NewUserConfigManager)
container.MustRegisterSingleton(config.NewManager)
container.MustRegisterSingleton(config.NewFileConfigManager)
container.MustRegisterScoped(func() (auth.ExternalAuthConfiguration, error) {
cert := os.Getenv("AZD_AUTH_CERT")
endpoint := os.Getenv("AZD_AUTH_ENDPOINT")
key := os.Getenv("AZD_AUTH_KEY")
client := &http.Client{}
if len(cert) > 0 {
transport, err := httputil.TlsEnabledTransport(cert)
if err != nil {
return auth.ExternalAuthConfiguration{},
fmt.Errorf("parsing AZD_AUTH_CERT: %w", err)
}
client.Transport = transport
endpointUrl, err := url.Parse(endpoint)
if err != nil {
return auth.ExternalAuthConfiguration{},
fmt.Errorf("invalid AZD_AUTH_ENDPOINT value '%s': %w", endpoint, err)
}
if endpointUrl.Scheme != "https" {
return auth.ExternalAuthConfiguration{},
fmt.Errorf("invalid AZD_AUTH_ENDPOINT value '%s': scheme must be 'https' when certificate is provided",
endpoint)
}
}
return auth.ExternalAuthConfiguration{
Endpoint: endpoint,
Transporter: client,
Key: key,
}, nil
})
container.MustRegisterScoped(auth.NewManager)
container.MustRegisterSingleton(azapi.NewUserProfileService)
container.MustRegisterSingleton(account.NewSubscriptionsService)
container.MustRegisterSingleton(account.NewManager)
container.MustRegisterSingleton(account.NewSubscriptionsManager)
container.MustRegisterSingleton(account.NewSubscriptionCredentialProvider)
container.MustRegisterSingleton(azapi.NewManagedClustersService)
container.MustRegisterSingleton(entraid.NewEntraIdService)
container.MustRegisterSingleton(azapi.NewContainerRegistryService)
container.MustRegisterSingleton(containerapps.NewContainerAppService)
container.MustRegisterSingleton(containerregistry.NewRemoteBuildManager)
container.MustRegisterSingleton(keyvault.NewKeyVaultService)
container.MustRegisterSingleton(storage.NewFileShareService)
container.MustRegisterScoped(project.NewContainerHelper)
container.MustRegisterSingleton(azapi.NewSpringService)
container.MustRegisterSingleton(func(subManager *account.SubscriptionsManager) account.SubscriptionTenantResolver {
return subManager
})
// Tools
container.MustRegisterSingleton(azapi.NewAzureClient)
// Tools
container.MustRegisterSingleton(azapi.NewResourceService)
container.MustRegisterSingleton(docker.NewCli)
container.MustRegisterSingleton(dotnet.NewCli)
container.MustRegisterSingleton(git.NewCli)
container.MustRegisterSingleton(github.NewGitHubCli)
container.MustRegisterSingleton(javac.NewCli)
container.MustRegisterSingleton(kubectl.NewCli)
container.MustRegisterSingleton(maven.NewCli)
container.MustRegisterSingleton(kubelogin.NewCli)
container.MustRegisterSingleton(helm.NewCli)
container.MustRegisterSingleton(kustomize.NewCli)
container.MustRegisterSingleton(npm.NewCli)
container.MustRegisterSingleton(python.NewCli)
container.MustRegisterSingleton(swa.NewCli)
container.MustRegisterScoped(ai.NewPythonBridge)
container.MustRegisterScoped(project.NewAiHelper)
// Provisioning
container.MustRegisterSingleton(func(
serviceLocator ioc.ServiceLocator,
featureManager *alpha.FeatureManager,
) (azapi.DeploymentService, error) {
deploymentsType := azapi.DeploymentTypeStandard
if featureManager.IsEnabled(azapi.FeatureDeploymentStacks) {
deploymentsType = azapi.DeploymentTypeStacks
}
var deployments azapi.DeploymentService
if err := serviceLocator.ResolveNamed(string(deploymentsType), &deployments); err != nil {
return nil, err
}
return deployments, nil
})
container.MustRegisterSingleton(azapi.NewResourceService)
// Register Deployment Services
deploymentServiceTypes := map[azapi.DeploymentType]any{
azapi.DeploymentTypeStandard: func(deploymentService *azapi.StandardDeployments) azapi.DeploymentService {
return deploymentService
},
azapi.DeploymentTypeStacks: func(deploymentService *azapi.StackDeployments) azapi.DeploymentService {
return deploymentService
},
}
for deploymentType, constructor := range deploymentServiceTypes {
container.MustRegisterNamedSingleton(string(deploymentType), constructor)
}
container.MustRegisterSingleton(azapi.NewStandardDeployments)
container.MustRegisterSingleton(azapi.NewStackDeployments)
container.MustRegisterScoped(infra.NewDeploymentManager)
container.MustRegisterSingleton(infra.NewAzureResourceManager)
container.MustRegisterScoped(provisioning.NewManager)
container.MustRegisterScoped(provisioning.NewPrincipalIdProvider)
container.MustRegisterScoped(prompt.NewDefaultPrompter)
// Other
container.MustRegisterSingleton(createClock)
// Service Targets
serviceTargetMap := map[project.ServiceTargetKind]any{
project.NonSpecifiedTarget: project.NewAppServiceTarget,
project.AppServiceTarget: project.NewAppServiceTarget,
project.AzureFunctionTarget: project.NewFunctionAppTarget,
project.ContainerAppTarget: project.NewContainerAppTarget,
project.StaticWebAppTarget: project.NewStaticWebAppTarget,
project.AksTarget: project.NewAksTarget,
project.SpringAppTarget: project.NewSpringAppTarget,
project.DotNetContainerAppTarget: project.NewDotNetContainerAppTarget,
project.AiEndpointTarget: project.NewAiEndpointTarget,
}
for target, constructor := range serviceTargetMap {
container.MustRegisterNamedScoped(string(target), constructor)
}
// Languages
frameworkServiceMap := map[project.ServiceLanguageKind]any{
project.ServiceLanguageNone: project.NewNoOpProject,
project.ServiceLanguageDotNet: project.NewDotNetProject,
project.ServiceLanguageCsharp: project.NewDotNetProject,
project.ServiceLanguageFsharp: project.NewDotNetProject,
project.ServiceLanguagePython: project.NewPythonProject,
project.ServiceLanguageJavaScript: project.NewNpmProject,
project.ServiceLanguageTypeScript: project.NewNpmProject,
project.ServiceLanguageJava: project.NewMavenProject,
project.ServiceLanguageDocker: project.NewDockerProject,
project.ServiceLanguageSwa: project.NewSwaProject,
}
for language, constructor := range frameworkServiceMap {
container.MustRegisterNamedScoped(string(language), constructor)
}
container.MustRegisterNamedScoped(string(project.ServiceLanguageDocker), project.NewDockerProjectAsFrameworkService)
// Pipelines
container.MustRegisterScoped(pipeline.NewPipelineManager)
container.MustRegisterSingleton(func(flags *pipelineConfigFlags) *pipeline.PipelineManagerArgs {
return &flags.PipelineManagerArgs
})
pipelineProviderMap := map[string]any{
"github-ci": pipeline.NewGitHubCiProvider,
"github-scm": pipeline.NewGitHubScmProvider,
"azdo-ci": pipeline.NewAzdoCiProvider,
"azdo-scm": pipeline.NewAzdoScmProvider,
}
for provider, constructor := range pipelineProviderMap {
container.MustRegisterNamedScoped(string(provider), constructor)
}
// Platform configuration
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,
) *lazy.Lazy[*platform.Config] {
return lazy.NewLazy(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)
}
var platformConfig *platform.Config
_, err = config.GetSection("platform", &platformConfig)
if err != nil {
return nil, fmt.Errorf("getting platform config: %w", err)
}
// If we still don't have a platform configuration, check the OS environment
// We check the OS environment instead of AZD environment because the global platform configuration
// cannot be known at this time in the azd bootstrapping process.
if platformConfig == nil {
if envPlatformType, has := os.LookupEnv(environment.PlatformTypeEnvVarName); has {
platformConfig = &platform.Config{
Type: platform.PlatformKind(envPlatformType),
}
}
}
if platformConfig == nil || 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'.
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,
)
}
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)
container.MustRegisterNamedSingleton(platformName, constructor)
}
container.MustRegisterSingleton(func(s ioc.ServiceLocator) (workflow.AzdCommandRunner, error) {
var rootCmd *cobra.Command
if err := s.ResolveNamed("root-cmd", &rootCmd); err != nil {
return nil, err
}
return &workflowCmdAdapter{cmd: rootCmd}, nil
})
container.MustRegisterSingleton(workflow.NewRunner)
container.MustRegisterScoped(func(authManager *auth.Manager) prompt.AuthManager {
return authManager
})
container.MustRegisterSingleton(func(subscriptionManager *account.SubscriptionsManager) prompt.SubscriptionManager {
return subscriptionManager
})
container.MustRegisterSingleton(func(resourceService *azapi.ResourceService) prompt.ResourceService {
return resourceService
})
container.MustRegisterScoped(prompt.NewPromptService)
// Extensions
container.MustRegisterSingleton(extensions.NewManager)
container.MustRegisterSingleton(extensions.NewRunner)
container.MustRegisterSingleton(extensions.NewSourceManager)
// gRPC Server
container.MustRegisterScoped(grpcserver.NewServer)
container.MustRegisterScoped(grpcserver.NewProjectService)
container.MustRegisterScoped(grpcserver.NewEnvironmentService)
container.MustRegisterScoped(grpcserver.NewPromptService)
container.MustRegisterScoped(grpcserver.NewDeploymentService)
container.MustRegisterScoped(grpcserver.NewEventService)
container.MustRegisterSingleton(grpcserver.NewUserConfigService)
container.MustRegisterSingleton(grpcserver.NewComposeService)
container.MustRegisterSingleton(grpcserver.NewWorkflowService)
// Required for nested actions called from composite actions like 'up'
registerAction[*cmd.ProvisionAction](container, "azd-provision-action")
registerAction[*downAction](container, "azd-down-action")
registerAction[*configShowAction](container, "azd-config-show-action")
}
// workflowCmdAdapter adapts a cobra command to the workflow.AzdCommandRunner interface
type workflowCmdAdapter struct {
cmd *cobra.Command
}
func (w *workflowCmdAdapter) SetArgs(args []string) {
w.cmd.SetArgs(args)
}
// ExecuteContext implements workflow.AzdCommandRunner
func (w *workflowCmdAdapter) ExecuteContext(ctx context.Context) error {
childCtx := middleware.WithChildAction(ctx)
return w.cmd.ExecuteContext(childCtx)
}
// ArmClientInitializer is a function definition for all Azure SDK ARM Client
type ArmClientInitializer[T comparable] func(
subscriptionId string,
credentials azcore.TokenCredential,
armClientOptions *arm.ClientOptions,
) (T, error)