diff --git a/cli/azd/cmd/container.go b/cli/azd/cmd/container.go index 157119278b0..fc8fe02cd8a 100644 --- a/cli/azd/cmd/container.go +++ b/cli/azd/cmd/container.go @@ -177,10 +177,19 @@ func registerCommonDependencies(container *ioc.NestedContainer) { // 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()) + log.Printf("'%s' command asked for envFlag, but envFlag was not included in cmd.Flags().", cmd.CommandPath()) envValue = "" } + envTypeValue, err := cmd.Flags().GetString(internal.EnvironmentTypeFlagName) + if err != nil { + log.Printf( + "'%s' command asked for envTypeFlag, but envTypeFlag was not included in cmd.Flags().", + cmd.CommandPath(), + ) + envTypeValue = "" + } + if envValue == "" { // If no explicit environment flag was set, but one was provided // in the context, use that instead. @@ -190,7 +199,10 @@ func registerCommonDependencies(container *ioc.NestedContainer) { } } - return internal.EnvFlag{EnvironmentName: envValue} + return internal.EnvFlag{ + EnvironmentName: envValue, + EnvironmentType: envTypeValue, + } }) container.MustRegisterSingleton(func(cmd *cobra.Command) CmdAnnotations { @@ -229,9 +241,10 @@ func registerCommonDependencies(container *ioc.NestedContainer) { } environmentName := envFlags.EnvironmentName + environmentType := envFlags.EnvironmentType var err error - env, err := envManager.LoadOrInitInteractive(ctx, environmentName) + env, err := envManager.LoadOrInitInteractiveWithType(ctx, environmentName, environmentType) if err != nil { return nil, fmt.Errorf("loading environment: %w", err) } @@ -382,6 +395,7 @@ func registerCommonDependencies(container *ioc.NestedContainer) { func( ctx context.Context, lazyAzdContext *lazy.Lazy[*azdcontext.AzdContext], + envFlags internal.EnvFlag, ) *lazy.Lazy[*project.ProjectConfig] { return lazy.NewLazy(func() (*project.ProjectConfig, error) { azdCtx, err := lazyAzdContext.GetValue() @@ -389,7 +403,15 @@ func registerCommonDependencies(container *ioc.NestedContainer) { return nil, err } - projectConfig, err := project.Load(ctx, azdCtx.ProjectPath()) + // Use environment-specific project path when an environment is specified + var projectPath string + if envFlags.EnvironmentName != "" { + projectPath = azdCtx.ProjectPathForEnvironment(envFlags.EnvironmentName) + } else { + projectPath = azdCtx.ProjectPath() + } + + projectConfig, err := project.Load(ctx, projectPath) if err != nil { return nil, err } @@ -405,6 +427,7 @@ func registerCommonDependencies(container *ioc.NestedContainer) { lazyProjectConfig *lazy.Lazy[*project.ProjectConfig], lazyAzdContext *lazy.Lazy[*azdcontext.AzdContext], lazyLocalEnvStore *lazy.Lazy[environment.LocalDataStore], + envFlags internal.EnvFlag, ) (*cloud.Cloud, error) { // Precedence for cloud configuration: @@ -424,8 +447,15 @@ func registerCommonDependencies(container *ioc.NestedContainer) { 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 { + var envName string + if envFlags.EnvironmentName != "" { + envName = envFlags.EnvironmentName + } else if defaultEnvName, err := azdCtx.GetDefaultEnvironmentName(); err == nil { + envName = defaultEnvName + } + + if envName != "" { + if env, err := localEnvStore.Get(ctx, envName); err == nil { if cloudConfigurationNode, exists := env.Config.Get(cloud.ConfigPath); exists { if value, err := cloud.ParseCloudConfig(cloudConfigurationNode); err == nil { cloudConfig, err := cloud.NewCloud(value) @@ -438,7 +468,7 @@ func registerCommonDependencies(container *ioc.NestedContainer) { Suggestion: fmt.Sprintf( "Set the cloud configuration by editing the 'cloud' node in the config.json "+ "file for the %s environment\n%s", - defaultEnvName, + envName, validClouds, ), } diff --git a/cli/azd/cmd/container_test.go b/cli/azd/cmd/container_test.go index 1958b94e765..76a9cca4ff7 100644 --- a/cli/azd/cmd/container_test.go +++ b/cli/azd/cmd/container_test.go @@ -11,6 +11,7 @@ import ( "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/spf13/cobra" "github.com/stretchr/testify/require" ) @@ -19,6 +20,13 @@ func Test_Lazy_Project_Config_Resolution(t *testing.T) { container := ioc.NewNestedContainer(nil) ioc.RegisterInstance(container, ctx) + // Register a mock cobra command for testing + mockCmd := &cobra.Command{} + mockCmd.SetContext(ctx) + // Add the environment flag to avoid the "envFlag was not included in cmd.Flags()" error + mockCmd.Flags().StringP("environment", "e", "", "Environment name") + ioc.RegisterInstance(container, mockCmd) + registerCommonDependencies(container) // Register the testing lazy component @@ -88,6 +96,13 @@ func Test_Lazy_AzdContext_Resolution(t *testing.T) { container := ioc.NewNestedContainer(nil) ioc.RegisterInstance(container, ctx) + // Register a mock cobra command for testing + mockCmd := &cobra.Command{} + mockCmd.SetContext(ctx) + // Add the environment flag to avoid the "envFlag was not included in cmd.Flags()" error + mockCmd.Flags().StringP("environment", "e", "", "Environment name") + ioc.RegisterInstance(container, mockCmd) + registerCommonDependencies(container) // Register the testing lazy component diff --git a/cli/azd/cmd/env.go b/cli/azd/cmd/env.go index e61d6f2c760..a1314c6853f 100644 --- a/cli/azd/cmd/env.go +++ b/cli/azd/cmd/env.go @@ -9,6 +9,7 @@ import ( "fmt" "io" "os" + "path/filepath" "slices" "strings" "time" @@ -27,6 +28,7 @@ import ( "github.com/azure/azure-dev/cli/azd/pkg/infra/provisioning/bicep" "github.com/azure/azure-dev/cli/azd/pkg/input" "github.com/azure/azure-dev/cli/azd/pkg/keyvault" + "github.com/azure/azure-dev/cli/azd/pkg/osutil" "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" @@ -808,6 +810,7 @@ func (e *envListAction) Run(ctx context.Context) (*actions.ActionResult, error) type envNewFlags struct { subscription string location string + envType string global *internal.GlobalCommandOptions } @@ -819,6 +822,7 @@ func (f *envNewFlags) Bind(local *pflag.FlagSet, global *internal.GlobalCommandO "Name or ID of an Azure subscription to use for the new environment", ) local.StringVarP(&f.location, "location", "l", "", "Azure location for the new environment") + local.StringVarP(&f.envType, "type", "t", "", "The type of the environment (e.g., dev, test, prod). (Experimental)") f.global = global } @@ -870,10 +874,12 @@ func (en *envNewAction) Run(ctx context.Context) (*actions.ActionResult, error) environmentName = en.args[0] } + envType := en.flags.envType envSpec := environment.Spec{ Name: environmentName, Subscription: en.flags.subscription, Location: en.flags.location, + Type: envType, } env, err := en.envManager.Create(ctx, envSpec) @@ -881,6 +887,56 @@ func (en *envNewAction) Run(ctx context.Context) (*actions.ActionResult, error) return nil, fmt.Errorf("creating new environment: %w", err) } + // Create environment-specific project file if environment type is specified + if envType != "" { + envProjectFileName := azdcontext.ProjectFileNameForEnvironmentType(envType) + envProjectPath := filepath.Join(en.azdCtx.ProjectDirectory(), envProjectFileName) + + if _, err := os.Stat(envProjectPath); os.IsNotExist(err) { + // Check for current project file to copy from + currentProjectFileName := en.azdCtx.ProjectFileName() + currentProjectPath := en.azdCtx.ProjectPath() + shouldCopy := false + + if _, err := os.Stat(currentProjectPath); err == nil { + // Prompt user if they want to copy content from azure.yaml + shouldCopy, err = en.console.Confirm(ctx, input.ConsoleOptions{ + Message: fmt.Sprintf("Copy contents of %s to %s?", currentProjectFileName, envProjectFileName), + DefaultValue: true, + }) + if err != nil { + return nil, fmt.Errorf("prompting to copy azure.yaml content: %w", err) + } + } else if !os.IsNotExist(err) { + return nil, fmt.Errorf("checking for %s: %w", currentProjectFileName, err) + } + + if shouldCopy { + content, err := os.ReadFile(currentProjectPath) + if err != nil { + return nil, fmt.Errorf("reading main project file: %w", err) + } + + if err := os.WriteFile(envProjectPath, content, osutil.PermissionFile); err != nil { + return nil, fmt.Errorf("creating environment project file: %w", err) + } + + en.console.MessageUxItem(ctx, &ux.DoneMessage{ + Message: fmt.Sprintf("Created %s", envProjectFileName), + }) + } else { + _, err = project.New(ctx, envProjectPath, azdcontext.ProjectName(en.azdCtx.ProjectDirectory())) + if err != nil { + return nil, fmt.Errorf("creating empty project file: %w", err) + } + + en.console.MessageUxItem(ctx, &ux.DoneMessage{ + Message: fmt.Sprintf("Created empty %s", envProjectFileName), + }) + } + } + } + envs, err := en.envManager.List(ctx) if err != nil { return nil, fmt.Errorf("listing environments: %w", err) diff --git a/cli/azd/cmd/testdata/TestUsage-azd-deploy.snap b/cli/azd/cmd/testdata/TestUsage-azd-deploy.snap index f56dde06875..bd0dcefae8f 100644 --- a/cli/azd/cmd/testdata/TestUsage-azd-deploy.snap +++ b/cli/azd/cmd/testdata/TestUsage-azd-deploy.snap @@ -10,6 +10,7 @@ Usage Flags --all : Deploys all services that are listed in azure.yaml + --env-type string : The type of the environment (e.g., dev, test, prod). (Experimental) -e, --environment string : The name of the environment to use. --from-package string : Deploys the packaged service located at the provided path. Supports zipped file packages (file path) or container images (image tag). diff --git a/cli/azd/cmd/testdata/TestUsage-azd-down.snap b/cli/azd/cmd/testdata/TestUsage-azd-down.snap index f29b692ce83..0c675b7b8a2 100644 --- a/cli/azd/cmd/testdata/TestUsage-azd-down.snap +++ b/cli/azd/cmd/testdata/TestUsage-azd-down.snap @@ -5,6 +5,7 @@ Usage azd down [flags] Flags + --env-type string : The type of the environment (e.g., dev, test, prod). (Experimental) -e, --environment string : The name of the environment to use. --force : Does not require confirmation before it deletes resources. --purge : Does not require confirmation before it permanently deletes resources that are soft-deleted by default (for example, key vaults). diff --git a/cli/azd/cmd/testdata/TestUsage-azd-env-get-value.snap b/cli/azd/cmd/testdata/TestUsage-azd-env-get-value.snap index fc6d79d8ef2..36e8f1617ab 100644 --- a/cli/azd/cmd/testdata/TestUsage-azd-env-get-value.snap +++ b/cli/azd/cmd/testdata/TestUsage-azd-env-get-value.snap @@ -5,6 +5,7 @@ Usage azd env get-value [flags] Flags + --env-type string : The type of the environment (e.g., dev, test, prod). (Experimental) -e, --environment string : The name of the environment to use. Global Flags diff --git a/cli/azd/cmd/testdata/TestUsage-azd-env-get-values.snap b/cli/azd/cmd/testdata/TestUsage-azd-env-get-values.snap index 7c8296ba56d..39e018afe91 100644 --- a/cli/azd/cmd/testdata/TestUsage-azd-env-get-values.snap +++ b/cli/azd/cmd/testdata/TestUsage-azd-env-get-values.snap @@ -5,6 +5,7 @@ Usage azd env get-values [flags] Flags + --env-type string : The type of the environment (e.g., dev, test, prod). (Experimental) -e, --environment string : The name of the environment to use. Global Flags diff --git a/cli/azd/cmd/testdata/TestUsage-azd-env-new.snap b/cli/azd/cmd/testdata/TestUsage-azd-env-new.snap index 91ae8cdbbd8..1aa5b8cd94d 100644 --- a/cli/azd/cmd/testdata/TestUsage-azd-env-new.snap +++ b/cli/azd/cmd/testdata/TestUsage-azd-env-new.snap @@ -7,6 +7,7 @@ Usage Flags -l, --location string : Azure location for the new environment --subscription string : Name or ID of an Azure subscription to use for the new environment + -t, --type string : The type of the environment (e.g., dev, test, prod). (Experimental) Global Flags -C, --cwd string : Sets the current working directory. diff --git a/cli/azd/cmd/testdata/TestUsage-azd-env-refresh.snap b/cli/azd/cmd/testdata/TestUsage-azd-env-refresh.snap index dcf4cebe0a8..df3c9793852 100644 --- a/cli/azd/cmd/testdata/TestUsage-azd-env-refresh.snap +++ b/cli/azd/cmd/testdata/TestUsage-azd-env-refresh.snap @@ -5,6 +5,7 @@ Usage azd env refresh [flags] Flags + --env-type string : The type of the environment (e.g., dev, test, prod). (Experimental) -e, --environment string : The name of the environment to use. --hint string : Hint to help identify the environment to refresh diff --git a/cli/azd/cmd/testdata/TestUsage-azd-env-set-secret.snap b/cli/azd/cmd/testdata/TestUsage-azd-env-set-secret.snap index fafa1584dd2..fe1e7073388 100644 --- a/cli/azd/cmd/testdata/TestUsage-azd-env-set-secret.snap +++ b/cli/azd/cmd/testdata/TestUsage-azd-env-set-secret.snap @@ -5,6 +5,7 @@ Usage azd env set-secret [flags] Flags + --env-type string : The type of the environment (e.g., dev, test, prod). (Experimental) -e, --environment string : The name of the environment to use. Global Flags diff --git a/cli/azd/cmd/testdata/TestUsage-azd-env-set.snap b/cli/azd/cmd/testdata/TestUsage-azd-env-set.snap index 038d32ce199..023242cbac0 100644 --- a/cli/azd/cmd/testdata/TestUsage-azd-env-set.snap +++ b/cli/azd/cmd/testdata/TestUsage-azd-env-set.snap @@ -5,6 +5,7 @@ Usage azd env set [ ] | [= ...] | [--file ] [flags] Flags + --env-type string : The type of the environment (e.g., dev, test, prod). (Experimental) -e, --environment string : The name of the environment to use. --file string : Path to .env formatted file to load environment values from. diff --git a/cli/azd/cmd/testdata/TestUsage-azd-hooks-run.snap b/cli/azd/cmd/testdata/TestUsage-azd-hooks-run.snap index 323709bfe38..e013a1a9466 100644 --- a/cli/azd/cmd/testdata/TestUsage-azd-hooks-run.snap +++ b/cli/azd/cmd/testdata/TestUsage-azd-hooks-run.snap @@ -5,6 +5,7 @@ Usage azd hooks run [flags] Flags + --env-type string : The type of the environment (e.g., dev, test, prod). (Experimental) -e, --environment string : The name of the environment to use. --platform string : Forces hooks to run for the specified platform. --service string : Only runs hooks for the specified service. diff --git a/cli/azd/cmd/testdata/TestUsage-azd-infra-generate.snap b/cli/azd/cmd/testdata/TestUsage-azd-infra-generate.snap index 8316acc388b..946cafa8fd3 100644 --- a/cli/azd/cmd/testdata/TestUsage-azd-infra-generate.snap +++ b/cli/azd/cmd/testdata/TestUsage-azd-infra-generate.snap @@ -5,6 +5,7 @@ Usage azd infra generate [flags] Flags + --env-type string : The type of the environment (e.g., dev, test, prod). (Experimental) -e, --environment string : The name of the environment to use. --force : Overwrite any existing files without prompting diff --git a/cli/azd/cmd/testdata/TestUsage-azd-init.snap b/cli/azd/cmd/testdata/TestUsage-azd-init.snap index 2b7e020c7b5..9992c4eee97 100644 --- a/cli/azd/cmd/testdata/TestUsage-azd-init.snap +++ b/cli/azd/cmd/testdata/TestUsage-azd-init.snap @@ -9,6 +9,7 @@ Usage Flags -b, --branch string : The template branch to initialize from. Must be used with a template argument (--template or -t). + --env-type string : The type of the environment (e.g., dev, test, prod). (Experimental) -e, --environment string : The name of the environment to use. -f, --filter strings : The tag(s) used to filter template results. Supports comma-separated values. --from-code : Initializes a new application from your existing code. diff --git a/cli/azd/cmd/testdata/TestUsage-azd-monitor.snap b/cli/azd/cmd/testdata/TestUsage-azd-monitor.snap index b3a61421a00..b7862595913 100644 --- a/cli/azd/cmd/testdata/TestUsage-azd-monitor.snap +++ b/cli/azd/cmd/testdata/TestUsage-azd-monitor.snap @@ -5,6 +5,7 @@ Usage azd monitor [flags] Flags + --env-type string : The type of the environment (e.g., dev, test, prod). (Experimental) -e, --environment string : The name of the environment to use. --live : Open a browser to Application Insights Live Metrics. Live Metrics is currently not supported for Python apps. --logs : Open a browser to Application Insights Logs. diff --git a/cli/azd/cmd/testdata/TestUsage-azd-package.snap b/cli/azd/cmd/testdata/TestUsage-azd-package.snap index 46e5ed27b47..aed269c15a3 100644 --- a/cli/azd/cmd/testdata/TestUsage-azd-package.snap +++ b/cli/azd/cmd/testdata/TestUsage-azd-package.snap @@ -10,6 +10,7 @@ Usage Flags --all : Packages all services that are listed in azure.yaml + --env-type string : The type of the environment (e.g., dev, test, prod). (Experimental) -e, --environment string : The name of the environment to use. --output-path string : File or folder path where the generated packages will be saved. diff --git a/cli/azd/cmd/testdata/TestUsage-azd-pipeline-config.snap b/cli/azd/cmd/testdata/TestUsage-azd-pipeline-config.snap index 00371f49934..5f21e5f237e 100644 --- a/cli/azd/cmd/testdata/TestUsage-azd-pipeline-config.snap +++ b/cli/azd/cmd/testdata/TestUsage-azd-pipeline-config.snap @@ -11,6 +11,7 @@ Usage Flags -m, --applicationServiceManagementReference string : Service Management Reference. References application or service contact information from a Service or Asset Management database. This value must be a Universally Unique Identifier (UUID). You can set this value globally by running azd config set pipeline.config.applicationServiceManagementReference . --auth-type string : The authentication type used between the pipeline provider and Azure for deployment (Only valid for GitHub provider). Valid values: federated, client-credentials. + --env-type string : The type of the environment (e.g., dev, test, prod). (Experimental) -e, --environment string : The name of the environment to use. --principal-id string : The client id of the service principal to use to grant access to Azure resources as part of the pipeline. --principal-name string : The name of the service principal to use to grant access to Azure resources as part of the pipeline. diff --git a/cli/azd/cmd/testdata/TestUsage-azd-provision.snap b/cli/azd/cmd/testdata/TestUsage-azd-provision.snap index 33c4f5a05b2..477cb59b5e1 100644 --- a/cli/azd/cmd/testdata/TestUsage-azd-provision.snap +++ b/cli/azd/cmd/testdata/TestUsage-azd-provision.snap @@ -10,6 +10,7 @@ Usage azd provision [flags] Flags + --env-type string : The type of the environment (e.g., dev, test, prod). (Experimental) -e, --environment string : The name of the environment to use. --no-state : (Bicep only) Forces a fresh deployment based on current Bicep template files, ignoring any stored deployment state. --preview : Preview changes to Azure resources. diff --git a/cli/azd/cmd/testdata/TestUsage-azd-restore.snap b/cli/azd/cmd/testdata/TestUsage-azd-restore.snap index 7c571438a97..dd340ab1d4f 100644 --- a/cli/azd/cmd/testdata/TestUsage-azd-restore.snap +++ b/cli/azd/cmd/testdata/TestUsage-azd-restore.snap @@ -9,6 +9,7 @@ Usage Flags --all : Restores all services that are listed in azure.yaml + --env-type string : The type of the environment (e.g., dev, test, prod). (Experimental) -e, --environment string : The name of the environment to use. Global Flags diff --git a/cli/azd/cmd/testdata/TestUsage-azd-show.snap b/cli/azd/cmd/testdata/TestUsage-azd-show.snap index 4fc0230dab4..d473fb1d1f9 100644 --- a/cli/azd/cmd/testdata/TestUsage-azd-show.snap +++ b/cli/azd/cmd/testdata/TestUsage-azd-show.snap @@ -5,6 +5,7 @@ Usage azd show [resource name or ID] [flags] Flags + --env-type string : The type of the environment (e.g., dev, test, prod). (Experimental) -e, --environment string : The name of the environment to use. --show-secrets : Unmask secrets in output. diff --git a/cli/azd/cmd/testdata/TestUsage-azd-up.snap b/cli/azd/cmd/testdata/TestUsage-azd-up.snap index dfccf2941ab..b74126a44b9 100644 --- a/cli/azd/cmd/testdata/TestUsage-azd-up.snap +++ b/cli/azd/cmd/testdata/TestUsage-azd-up.snap @@ -20,6 +20,7 @@ Usage azd up [flags] Flags + --env-type string : The type of the environment (e.g., dev, test, prod). (Experimental) -e, --environment string : The name of the environment to use. Global Flags diff --git a/cli/azd/internal/env_flag.go b/cli/azd/internal/env_flag.go index 2b4ee92482e..72f2510c9cc 100644 --- a/cli/azd/internal/env_flag.go +++ b/cli/azd/internal/env_flag.go @@ -9,21 +9,31 @@ import ( "github.com/spf13/pflag" ) -// EnvFlag is a flag that represents the environment name. Actions which inject an environment should also use this flag -// so the user can control what environment is loaded in a uniform way across all our commands. +// EnvFlag is a flag that represents the environment name and type. Actions which inject an environment +// should also use this flag so the user can control what environment is loaded in a uniform way across all our commands. type EnvFlag struct { - EnvironmentName string - fromEnvVarValue string + EnvironmentName string + EnvironmentType string + fromEnvVarValue string + fromEnvTypeVarValue string } // EnvironmentNameFlagName is the full name of the flag as it appears on the command line. const EnvironmentNameFlagName string = "environment" +// EnvironmentTypeFlagName is the full name of the environment type flag as it appears on the command line. +const EnvironmentTypeFlagName string = "env-type" + // envNameEnvVarName is the same as environment.EnvNameEnvVarName, but duplicated here to prevent an import cycle. const envNameEnvVarName = "AZURE_ENV_NAME" +// envTypeEnvVarName is the environment variable name for environment type. +const envTypeEnvVarName = "AZURE_ENV_TYPE" + func (e *EnvFlag) Bind(local *pflag.FlagSet, global *GlobalCommandOptions) { e.fromEnvVarValue = os.Getenv(envNameEnvVarName) + e.fromEnvTypeVarValue = os.Getenv(envTypeEnvVarName) + local.StringVarP( &e.EnvironmentName, EnvironmentNameFlagName, @@ -31,9 +41,21 @@ func (e *EnvFlag) Bind(local *pflag.FlagSet, global *GlobalCommandOptions) { // Set the default value to AZURE_ENV_NAME value if available e.fromEnvVarValue, "The name of the environment to use.") + + local.StringVar( + &e.EnvironmentType, + EnvironmentTypeFlagName, + // Set the default value to AZURE_ENV_TYPE value if available + e.fromEnvTypeVarValue, + "The type of the environment (e.g., dev, test, prod). (Experimental)") } // checks if the environment name was set from the command line func (e *EnvFlag) FromArg() bool { return e.EnvironmentName != "" && e.EnvironmentName != e.fromEnvVarValue } + +// checks if the environment type was set from the command line or environment variable +func (e *EnvFlag) TypeFromArg() bool { + return e.EnvironmentType != "" && e.EnvironmentType != e.fromEnvTypeVarValue +} diff --git a/cli/azd/pkg/azdo/pipeline.go b/cli/azd/pkg/azdo/pipeline.go index 6273f7d814e..22e00532aa0 100644 --- a/cli/azd/pkg/azdo/pipeline.go +++ b/cli/azd/pkg/azdo/pipeline.go @@ -161,6 +161,10 @@ func getDefinitionVariables( "AZURE_SUBSCRIPTION_ID": createBuildDefinitionVariable(credentials.SubscriptionId, false, false), } + if env.GetEnvironmentType() != "" { + variables[environment.EnvTypeEnvVarName] = createBuildDefinitionVariable(env.GetEnvironmentType(), false, false) + } + if provisioningProvider.Provider == provisioning.Bicep { if rgName, has := env.LookupEnv(environment.ResourceGroupEnvVarName); has { variables[environment.ResourceGroupEnvVarName] = createBuildDefinitionVariable(rgName, false, false) diff --git a/cli/azd/pkg/environment/azdcontext/azdcontext.go b/cli/azd/pkg/environment/azdcontext/azdcontext.go index 1b8f46a8e97..00653142873 100644 --- a/cli/azd/pkg/environment/azdcontext/azdcontext.go +++ b/cli/azd/pkg/environment/azdcontext/azdcontext.go @@ -7,11 +7,15 @@ import ( "encoding/json" "errors" "fmt" + "log" "os" "path/filepath" + "regexp" + "strings" "github.com/azure/azure-dev/cli/azd/internal/names" "github.com/azure/azure-dev/cli/azd/pkg/osutil" + "github.com/joho/godotenv" ) const ProjectFileName = "azure.yaml" @@ -20,10 +24,41 @@ const DotEnvFileName = ".env" const ConfigFileName = "config.json" const ConfigFileVersion = 1 +// Environment types should only contain alphanumeric characters +var environmentTypeRegexp = regexp.MustCompile(`^[a-zA-Z0-9]{1,32}$`) + type AzdContext struct { projectDirectory string } +func (c *AzdContext) ProjectFileName() string { + return c.ProjectFileNameForEnvironment("") +} + +// ProjectFileNameForEnvironment returns the project file name for a specific environment. +// If environmentName is empty, uses the default environment. +func (c *AzdContext) ProjectFileNameForEnvironment(environmentName string) string { + // Get environment type for the specified environment (or default if empty) + envType, err := c.GetEnvironmentType(environmentName) + if err != nil { + log.Printf("getting env type: %v", err) + envType = "" + } + + return ProjectFileNameForEnvironmentType(envType) +} + +// ProjectFileNameForEnvironmentType returns the project file name for a specific environment type. +// If environmentType is empty, returns the default project file name. +func ProjectFileNameForEnvironmentType(environmentType string) string { + if environmentType == "" { + return "azure.yaml" + } + + projectFileName := fmt.Sprintf("azure.%s.yaml", environmentType) + return projectFileName +} + func (c *AzdContext) ProjectDirectory() string { return c.projectDirectory } @@ -33,7 +68,17 @@ func (c *AzdContext) SetProjectDirectory(dir string) { } func (c *AzdContext) ProjectPath() string { - return filepath.Join(c.ProjectDirectory(), ProjectFileName) + fileName := c.ProjectFileName() + projectPath := filepath.Join(c.ProjectDirectory(), fileName) + return projectPath +} + +// ProjectPathForEnvironment returns the project path for a specific environment. +// If environmentName is empty, uses the default environment. +func (c *AzdContext) ProjectPathForEnvironment(environmentName string) string { + fileName := c.ProjectFileNameForEnvironment(environmentName) + projectPath := filepath.Join(c.ProjectDirectory(), fileName) + return projectPath } func (c *AzdContext) EnvironmentDirectory() string { @@ -49,10 +94,6 @@ func (c *AzdContext) EnvironmentRoot(name string) string { return filepath.Join(c.EnvironmentDirectory(), name) } -func (c *AzdContext) GetEnvironmentWorkDirectory(name string) string { - return filepath.Join(c.EnvironmentRoot(name), "wd") -} - // GetDefaultEnvironmentName returns the name of the default environment. Returns // an empty string if a default environment has not been set. func (c *AzdContext) GetDefaultEnvironmentName() (string, error) { @@ -73,6 +114,42 @@ func (c *AzdContext) GetDefaultEnvironmentName() (string, error) { return config.DefaultEnvironment, nil } +// GetEnvironmentType returns the environment type for a specific environment. +// If environmentName is empty, returns the type for the default environment. +func (c *AzdContext) GetEnvironmentType(environmentName string) (string, error) { + targetEnvName := environmentName + + if environmentName == "" { + defaultEnvName, err := c.GetDefaultEnvironmentName() + if err != nil { + return "", fmt.Errorf("getting default environment name: %w", err) + } + + if defaultEnvName == "" { + return "", nil + } + + targetEnvName = defaultEnvName + } + + // Read the environment's .env file + envFilePath := filepath.Join(c.EnvironmentRoot(targetEnvName), DotEnvFileName) + envVars, err := godotenv.Read(envFilePath) + + switch { + case errors.Is(err, os.ErrNotExist): + return "", nil + case err != nil: + return "", fmt.Errorf("reading environment .env file: %w", err) + } + + if envType, exists := envVars["AZURE_ENV_TYPE"]; exists { + return envType, nil + } + + return "", nil +} + // ProjectState represents the state of the project. type ProjectState struct { DefaultEnvironment string @@ -81,6 +158,7 @@ type ProjectState struct { // SetProjectState persists the state of the project to the file system, like the default environment. func (c *AzdContext) SetProjectState(state ProjectState) error { path := filepath.Join(c.EnvironmentDirectory(), ConfigFileName) + config := configFile{ Version: ConfigFileVersion, DefaultEnvironment: state.DefaultEnvironment, @@ -117,10 +195,52 @@ func NewAzdContext() (*AzdContext, error) { return NewAzdContextFromWd(wd) } +// hasValidProjectFile checks if a valid project file exists in the given directory. +// It looks for azure.yaml first, then checks for azure.{envType}.yaml files where envType is valid. +// Returns true if a valid project file is found, false otherwise. +func hasValidProjectFile(searchDir string) bool { + // First check for the default azure.yaml (matching original logic exactly) + defaultProjectPath := filepath.Join(searchDir, ProjectFileName) + stat, err := os.Stat(defaultProjectPath) + if err == nil && !stat.IsDir() { + // Found azure.yaml and it's a file + return true + } + if err != nil && !os.IsNotExist(err) { + return false + } + + // Then check for azure.{envType}.yaml files + entries, err := os.ReadDir(searchDir) + if err != nil { + return false + } + + for _, entry := range entries { + if entry.IsDir() { + continue + } + + filename := entry.Name() + // Check if filename matches azure.{envType}.yaml pattern + if strings.HasPrefix(filename, "azure.") && strings.HasSuffix(filename, ".yaml") { + envType := strings.TrimPrefix(filename, "azure.") + envType = strings.TrimSuffix(envType, ".yaml") + + if envType != "" && environmentTypeRegexp.MatchString(envType) { + return true + } + } + } + + return false +} + // Creates context with project directory set to the nearest project file found. // // The project file is first searched for in the working directory, if not found, the parent directory is searched -// recursively up to root. If no project file is found, errNoProject is returned. +// recursively up to root. The search looks for azure.yaml first, then for azure.{envType}.yaml files where envType +// is a valid environment type. If no project file is found, errNoProject is returned. func NewAzdContextFromWd(wd string) (*AzdContext, error) { // Walk up from the wd to the root, looking for a project file. If we find one, that's // the root project directory. @@ -130,26 +250,18 @@ func NewAzdContextFromWd(wd string) (*AzdContext, error) { } for { - projectFilePath := filepath.Join(searchDir, ProjectFileName) - stat, err := os.Stat(projectFilePath) - if os.IsNotExist(err) || (err == nil && stat.IsDir()) { - parent := filepath.Dir(searchDir) - if parent == searchDir { - return nil, ErrNoProject - } - searchDir = parent - } else if err == nil { - // We found our azure.yaml file, and searchDir is the directory - // that contains it. - break - } else { - return nil, fmt.Errorf("searching for project file: %w", err) + if hasValidProjectFile(searchDir) { + return &AzdContext{ + projectDirectory: searchDir, + }, nil } - } - return &AzdContext{ - projectDirectory: searchDir, - }, nil + parent := filepath.Dir(searchDir) + if parent == searchDir { + return nil, ErrNoProject + } + searchDir = parent + } } type configFile struct { diff --git a/cli/azd/pkg/environment/azdcontext/azdcontext_test.go b/cli/azd/pkg/environment/azdcontext/azdcontext_test.go new file mode 100644 index 00000000000..bdeebde39b3 --- /dev/null +++ b/cli/azd/pkg/environment/azdcontext/azdcontext_test.go @@ -0,0 +1,222 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package azdcontext + +import ( + "os" + "path/filepath" + "testing" + + "github.com/azure/azure-dev/cli/azd/pkg/osutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// setupTestEnv creates a test environment with the given name and environment type +func setupTestEnv(t *testing.T, azdDir, envName, envType string) { + t.Helper() + + envDir := filepath.Join(azdDir, envName) + require.NoError(t, os.MkdirAll(envDir, osutil.PermissionDirectory)) + + envPath := filepath.Join(envDir, DotEnvFileName) + content := "AZURE_ENV_NAME=" + envName + "\n" + if envType != "" { + content += "AZURE_ENV_TYPE=" + envType + "\n" + } + require.NoError(t, os.WriteFile(envPath, []byte(content), osutil.PermissionFileOwnerOnly)) +} + +// setupDefaultEnv sets up the default environment configuration +func setupDefaultEnv(t *testing.T, azdDir, defaultEnvName string) { + t.Helper() + + configPath := filepath.Join(azdDir, ConfigFileName) + config := `{"version": 1, "defaultEnvironment": "` + defaultEnvName + `"}` + require.NoError(t, os.WriteFile(configPath, []byte(config), osutil.PermissionFileOwnerOnly)) +} + +func TestProjectFileNameForEnvironment(t *testing.T) { + tests := []struct { + name string + envName string + envType string + defaultEnv string + expected string + }{ + { + name: "no default environment", + envName: "", + expected: "azure.yaml", + }, + { + name: "dev type", + envName: "TESTENV", + envType: "dev", + expected: "azure.dev.yaml", + }, + { + name: "prod type", + envName: "TESTENV", + envType: "prod", + expected: "azure.prod.yaml", + }, + { + name: "non-existent environment", + envName: "nonexistent", + expected: "azure.yaml", + }, + { + name: "existing environment without type", + envName: "TESTENV", + envType: "", // explicitly no type + expected: "azure.yaml", + }, + { + name: "default environment set", + envName: "", + envType: "dev", + defaultEnv: "TESTENV1", + expected: "azure.dev.yaml", + }, + { + name: "default environment without type", + envName: "", + envType: "", // default env has no type + defaultEnv: "TESTENV", + expected: "azure.yaml", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + azdCtx := NewAzdContextWithDirectory(t.TempDir()) + azdDir := filepath.Join(azdCtx.ProjectDirectory(), EnvironmentDirectoryName) + require.NoError(t, os.MkdirAll(azdDir, osutil.PermissionDirectory)) + + // Setup test environment if specified + if tt.envName != "" { + setupTestEnv(t, azdDir, tt.envName, tt.envType) + } + if tt.defaultEnv != "" && tt.envName == "" { + setupTestEnv(t, azdDir, tt.defaultEnv, tt.envType) + setupDefaultEnv(t, azdDir, tt.defaultEnv) + } + + fileName := azdCtx.ProjectFileNameForEnvironment(tt.envName) + assert.Equal(t, tt.expected, fileName) + }) + } +} + +func TestProjectPathForEnvironment(t *testing.T) { + tests := []struct { + name string + envName string + envType string + expected string + }{ + { + name: "environment with type", + envName: "TESTENV1", + envType: "prod", + expected: "azure.prod.yaml", + }, + { + name: "default project path", + envName: "", + expected: "azure.yaml", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + azdCtx := NewAzdContextWithDirectory(t.TempDir()) + azdDir := filepath.Join(azdCtx.ProjectDirectory(), EnvironmentDirectoryName) + require.NoError(t, os.MkdirAll(azdDir, osutil.PermissionDirectory)) + + if tt.envType != "" { + setupTestEnv(t, azdDir, tt.envName, tt.envType) + } + + var projectPath string + if tt.envName == "" { + projectPath = azdCtx.ProjectPath() + } else { + projectPath = azdCtx.ProjectPathForEnvironment(tt.envName) + } + + expectedPath := filepath.Join(azdCtx.ProjectDirectory(), tt.expected) + assert.Equal(t, expectedPath, projectPath) + }) + } +} + +func TestGetEnvironmentType(t *testing.T) { + tests := []struct { + name string + envName string + envType string + setupDefault bool + defaultEnvName string + expected string + }{ + { + name: "non-existent environment", + envName: "nonexistent", + expected: "", + }, + { + name: "existing environment with type", + envName: "TESTENV", + envType: "dev", + expected: "dev", + }, + { + name: "existing environment without type", + envName: "TESTENV", + envType: "", + expected: "", + }, + { + name: "default environment with type", + envName: "", + envType: "dev", + setupDefault: true, + defaultEnvName: "TESTENV", + expected: "dev", + }, + { + name: "no default environment", + envName: "", + expected: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + azdCtx := NewAzdContextWithDirectory(t.TempDir()) + azdDir := filepath.Join(azdCtx.ProjectDirectory(), EnvironmentDirectoryName) + require.NoError(t, os.MkdirAll(azdDir, osutil.PermissionDirectory)) + + // Setup environment if needed + if tt.envName != "" || tt.defaultEnvName != "" { + envName := tt.envName + if envName == "" { + envName = tt.defaultEnvName + } + setupTestEnv(t, azdDir, envName, tt.envType) + } + + // Setup default environment if needed + if tt.setupDefault { + setupDefaultEnv(t, azdDir, tt.defaultEnvName) + } + + envType, err := azdCtx.GetEnvironmentType(tt.envName) + require.NoError(t, err) + assert.Equal(t, tt.expected, envType) + }) + } +} diff --git a/cli/azd/pkg/environment/environment.go b/cli/azd/pkg/environment/environment.go index dfa3f7dbd73..5f47e245337 100644 --- a/cli/azd/pkg/environment/environment.go +++ b/cli/azd/pkg/environment/environment.go @@ -21,6 +21,9 @@ import ( // EnvNameEnvVarName is the name of the key used to store the envname property in the environment. const EnvNameEnvVarName = "AZURE_ENV_NAME" +// EnvTypeEnvVarName is the name of the key used to store the environment type (e.g., dev, prod) in the .env file +const EnvTypeEnvVarName = "AZURE_ENV_TYPE" + // LocationEnvVarName is the name of the key used to store the location property in the environment. const LocationEnvVarName = "AZURE_LOCATION" @@ -129,6 +132,9 @@ type EnvironmentResolver func(ctx context.Context) (*Environment, error) // https://docs.microsoft.com/azure/azure-resource-manager/management/resource-name-rules#microsoftresources) var EnvironmentNameRegexp = regexp.MustCompile(`^[a-zA-Z0-9-\(\)_\.]{1,64}$`) +// Environment types should only contain alphanumeric characters +var EnvironmentTypeRegexp = regexp.MustCompile(`^[a-zA-Z0-9]{1,32}$`) + // The maximum length of an environment name. var EnvironmentNameMaxLength = 64 @@ -136,6 +142,10 @@ func IsValidEnvironmentName(name string) bool { return EnvironmentNameRegexp.MatchString(name) } +func IsValidEnvironmentType(envType string) bool { + return EnvironmentTypeRegexp.MatchString(envType) +} + // CleanName returns a version of [name] where all characters not allowed in an environment name have been replaced // with hyphens func CleanName(name string) string { @@ -233,6 +243,17 @@ func (e *Environment) SetLocation(location string) { e.DotenvSet(LocationEnvVarName, location) } +// GetEnvironmentType returns the environment type (e.g., dev, prod) from the +// .env file, or an empty string if not set. +func (e *Environment) GetEnvironmentType() string { + return e.Getenv(EnvTypeEnvVarName) +} + +// SetEnvironmentType sets the environment type (e.g., dev, prod) in the .env file. +func (e *Environment) SetEnvironmentType(envType string) { + e.DotenvSet(EnvTypeEnvVarName, envType) +} + // Key returns the environment key name for the given name. func Key(name string) string { return strings.ReplaceAll(strings.ToUpper(name), "-", "_") diff --git a/cli/azd/pkg/environment/environment_test.go b/cli/azd/pkg/environment/environment_test.go index fc1f90df283..7a27193f65f 100644 --- a/cli/azd/pkg/environment/environment_test.go +++ b/cli/azd/pkg/environment/environment_test.go @@ -273,3 +273,67 @@ func createEnvManager(mockContext *mocks.MockContext, root string) (Manager, *az return newManagerForTest(azdCtx, mockContext.Console, localDataStore, nil), azdCtx } + +func TestEnvironmentType(t *testing.T) { + t.Parallel() + + t.Run("GetEnvironmentType_FromDotEnv", func(t *testing.T) { + env := NewWithValues("test", map[string]string{ + "AZURE_ENV_NAME": "test", + "AZURE_ENV_TYPE": "development", + }) + + envType := env.GetEnvironmentType() + assert.Equal(t, "development", envType) + }) + + t.Run("GetEnvironmentType_Empty", func(t *testing.T) { + env := New("test") + + envType := env.GetEnvironmentType() + assert.Equal(t, "", envType) + }) + + t.Run("SetEnvironmentType", func(t *testing.T) { + env := New("test") + + env.SetEnvironmentType("production") + envType := env.GetEnvironmentType() + assert.Equal(t, "production", envType) + + // Verify it's also in the dotenv map + dotenv := env.Dotenv() + assert.Equal(t, "production", dotenv["AZURE_ENV_TYPE"]) + }) + + t.Run("SetEnvironmentType_Override", func(t *testing.T) { + env := NewWithValues("test", map[string]string{ + "AZURE_ENV_NAME": "test", + "AZURE_ENV_TYPE": "development", + }) + + // Change the environment type + env.SetEnvironmentType("staging") + envType := env.GetEnvironmentType() + assert.Equal(t, "staging", envType) + }) +} + +func TestIsValidEnvironmentType(t *testing.T) { + t.Parallel() + + assert.True(t, IsValidEnvironmentType("dev")) + assert.True(t, IsValidEnvironmentType("development")) + assert.True(t, IsValidEnvironmentType("prod")) + assert.True(t, IsValidEnvironmentType("production")) + assert.True(t, IsValidEnvironmentType("staging")) + assert.True(t, IsValidEnvironmentType("test123")) + assert.True(t, IsValidEnvironmentType("TEST")) + + assert.False(t, IsValidEnvironmentType("")) + assert.False(t, IsValidEnvironmentType("dev-env")) // hyphen not allowed + assert.False(t, IsValidEnvironmentType("dev_env")) // underscore not allowed + assert.False(t, IsValidEnvironmentType("dev env")) // space not allowed + assert.False(t, IsValidEnvironmentType("dev.env")) // dot not allowed + assert.False(t, IsValidEnvironmentType("123456789012345678901234567890123")) // too long +} diff --git a/cli/azd/pkg/environment/manager.go b/cli/azd/pkg/environment/manager.go index 3f0413adb06..b25100d60e6 100644 --- a/cli/azd/pkg/environment/manager.go +++ b/cli/azd/pkg/environment/manager.go @@ -37,6 +37,7 @@ type Spec struct { Name string Subscription string Location string + Type string // suggest is the name that is offered as a suggestion if we need to prompt the user for an environment name. Examples []string } @@ -66,6 +67,11 @@ type Manager interface { // If the name is empty, the user is prompted to select or create an environment. // If the environment does not exist, the user is prompted to create it. LoadOrInitInteractive(ctx context.Context, name string) (*Environment, error) + + // Loads the environment with the given name, with optional environment type for creation. + // If the name is empty, the user is prompted to select or create an environment. + // If the environment does not exist, the user is prompted to create it with the specified type. + LoadOrInitInteractiveWithType(ctx context.Context, name, envType string) (*Environment, error) List(ctx context.Context) ([]*Description, error) // Get returns the existing environment with the given name. @@ -137,6 +143,10 @@ func (m *manager) Create(ctx context.Context, spec Spec) (*Environment, error) { return nil, err } + if err := m.ensureValidEnvironmentType(ctx, spec.Type); err != nil { + return nil, err + } + // Ensure the environment does not already exist: _, err := m.Get(ctx, spec.Name) switch { @@ -157,6 +167,10 @@ func (m *manager) Create(ctx context.Context, spec Spec) (*Environment, error) { env.SetLocation(spec.Location) } + if spec.Type != "" { + env.SetEnvironmentType(spec.Type) + } + if err := m.SaveWithOptions(ctx, env, &SaveOptions{IsNew: true}); err != nil { return nil, err } @@ -165,7 +179,11 @@ func (m *manager) Create(ctx context.Context, spec Spec) (*Environment, error) { } func (m *manager) LoadOrInitInteractive(ctx context.Context, environmentName string) (*Environment, error) { - env, isNew, err := m.loadOrInitEnvironment(ctx, environmentName) + return m.LoadOrInitInteractiveWithType(ctx, environmentName, "") +} + +func (m *manager) LoadOrInitInteractiveWithType(ctx context.Context, environmentName, envType string) (*Environment, error) { + env, isNew, err := m.loadOrInitEnvironmentWithType(ctx, environmentName, envType) switch { case errors.Is(err, ErrNotFound): return nil, fmt.Errorf("environment %s does not exist", environmentName) @@ -224,7 +242,11 @@ func (m *manager) LoadOrInitInteractive(ctx context.Context, environmentName str return env, nil } -func (m *manager) loadOrInitEnvironment(ctx context.Context, environmentName string) (*Environment, bool, error) { +func (m *manager) loadOrInitEnvironmentWithType( + ctx context.Context, + environmentName, + envType string, +) (*Environment, bool, error) { // If there's a default environment, use that if environmentName == "" { var err error @@ -313,13 +335,23 @@ func (m *manager) loadOrInitEnvironment(ctx context.Context, environmentName str // Create the environment spec := &Spec{ Name: environmentName, + Type: envType, } if err := m.ensureValidEnvironmentName(ctx, spec); err != nil { return nil, false, err } - return New(spec.Name), true, nil + if err := m.ensureValidEnvironmentType(ctx, envType); err != nil { + return nil, false, err + } + + env := New(spec.Name) + if envType != "" { + env.SetEnvironmentType(envType) + } + + return env, true, nil } // ConfigPath returns the path to the environment config file @@ -519,7 +551,29 @@ func (m *manager) ensureValidEnvironmentName(ctx context.Context, spec *Spec) er func invalidEnvironmentNameMsg(environmentName string) string { return fmt.Sprintf( - "environment name '%s' is invalid (it should contain only alphanumeric characters and hyphens)\n", + "environment name '%s' is invalid (it should contain only alphanumeric characters and hyphens)", environmentName, ) } + +func invalidEnvironmentTypeMsg(environmentType string) string { + return fmt.Sprintf( + "environment type '%s' is invalid (it should contain only alphanumeric characters)", + environmentType, + ) +} + +// ensureValidEnvironmentType validates environment type and outputs error message to console if invalid +func (m *manager) ensureValidEnvironmentType(ctx context.Context, envType string) error { + if envType == "" { + return nil // Empty environment type is allowed + } + + if !IsValidEnvironmentType(envType) { + errMsg := invalidEnvironmentTypeMsg(envType) + m.console.Message(ctx, errMsg) + return errors.New(errMsg) + } + + return nil +} diff --git a/cli/azd/pkg/environment/manager_test.go b/cli/azd/pkg/environment/manager_test.go index 624652d5a26..8acd0e5007f 100644 --- a/cli/azd/pkg/environment/manager_test.go +++ b/cli/azd/pkg/environment/manager_test.go @@ -364,6 +364,143 @@ func Test_EnvManager_CreateFromContainer(t *testing.T) { }) } +func Test_EnvManager_CreateWithType(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockContext := mocks.NewMockContext(context.Background()) + envManager := createEnvManagerForManagerTest(t, mockContext) + + spec := Spec{ + Name: "test-env", + Subscription: "test-subscription", + Location: "eastus", + Type: "development", + } + + env, err := envManager.Create(*mockContext.Context, spec) + require.NoError(t, err) + require.NotNil(t, env) + require.Equal(t, "test-env", env.Name()) + require.Equal(t, "test-subscription", env.GetSubscriptionId()) + require.Equal(t, "eastus", env.GetLocation()) + require.Equal(t, "development", env.GetEnvironmentType()) + + // Verify it's in the dotenv + dotenv := env.Dotenv() + require.Equal(t, "development", dotenv["AZURE_ENV_TYPE"]) + }) +} + +func Test_EnvManager_LoadOrInitInteractiveWithType(t *testing.T) { + t.Run("creates new environment with type when not exists", func(t *testing.T) { + mockContext := mocks.NewMockContext(context.Background()) + mockContext.Console.WhenConfirm(func(options input.ConsoleOptions) bool { + return strings.Contains(options.Message, "would you like to create it?") + }).Respond(true) + + envManager := createEnvManagerForManagerTest(t, mockContext) + + // Test creating new environment with type + envName := "new-test-env" + envType := "production" + + env, err := envManager.LoadOrInitInteractiveWithType(*mockContext.Context, envName, envType) + require.NoError(t, err) + require.NotNil(t, env) + require.Equal(t, envName, env.Name()) + require.Equal(t, envType, env.GetEnvironmentType()) + + // Verify it's in the dotenv + dotenv := env.Dotenv() + require.Equal(t, envType, dotenv["AZURE_ENV_TYPE"]) + }) + + t.Run("loads existing environment ignoring type parameter", func(t *testing.T) { + mockContext := mocks.NewMockContext(context.Background()) + envManager := createEnvManagerForManagerTest(t, mockContext) + + // First create an environment with one type + originalType := "development" + envName := "existing-env" + + mockContext.Console.WhenConfirm(func(options input.ConsoleOptions) bool { + return strings.Contains(options.Message, "would you like to create it?") + }).Respond(true) + + env1, err := envManager.LoadOrInitInteractiveWithType(*mockContext.Context, envName, originalType) + require.NoError(t, err) + require.Equal(t, originalType, env1.GetEnvironmentType()) + + // Now try to load the same environment with a different type - should ignore the new type + differentType := "production" + env2, err := envManager.LoadOrInitInteractiveWithType(*mockContext.Context, envName, differentType) + require.NoError(t, err) + require.Equal(t, envName, env2.Name()) + // Should still have the original type, not the new one + require.Equal(t, originalType, env2.GetEnvironmentType()) + }) + + t.Run("creates new environment without type when empty type provided", func(t *testing.T) { + mockContext := mocks.NewMockContext(context.Background()) + mockContext.Console.WhenConfirm(func(options input.ConsoleOptions) bool { + return strings.Contains(options.Message, "would you like to create it?") + }).Respond(true) + + envManager := createEnvManagerForManagerTest(t, mockContext) + + envName := "no-type-env" + + env, err := envManager.LoadOrInitInteractiveWithType(*mockContext.Context, envName, "") + require.NoError(t, err) + require.NotNil(t, env) + require.Equal(t, envName, env.Name()) + require.Equal(t, "", env.GetEnvironmentType()) + + // Verify AZURE_ENV_TYPE is not set in dotenv when no type specified + dotenv := env.Dotenv() + require.NotContains(t, dotenv, "AZURE_ENV_TYPE") + }) + + t.Run("delegates to LoadOrInitInteractive when no type specified", func(t *testing.T) { + mockContext := mocks.NewMockContext(context.Background()) + mockContext.Console.WhenConfirm(func(options input.ConsoleOptions) bool { + return strings.Contains(options.Message, "would you like to create it?") + }).Respond(true) + + envManager := createEnvManagerForManagerTest(t, mockContext) + + envName := "delegate-test-env" + + // Both methods should produce the same result when no type is specified + env1, err1 := envManager.LoadOrInitInteractive(*mockContext.Context, envName) + require.NoError(t, err1) + + // Create another environment with the new method but empty type + envName2 := "delegate-test-env-2" + env2, err2 := envManager.LoadOrInitInteractiveWithType(*mockContext.Context, envName2, "") + require.NoError(t, err2) + + // Both should have empty environment type + require.Equal(t, "", env1.GetEnvironmentType()) + require.Equal(t, "", env2.GetEnvironmentType()) + }) + + t.Run("validates environment type and returns error for invalid type", func(t *testing.T) { + mockContext := mocks.NewMockContext(context.Background()) + mockContext.Console.WhenConfirm(func(options input.ConsoleOptions) bool { + return strings.Contains(options.Message, "would you like to create it?") + }).Respond(true) + + envManager := createEnvManagerForManagerTest(t, mockContext) + + envName := "test-env" + invalidEnvType := "invalid-type-with-hyphens" // hyphens not allowed in environment type + + _, err := envManager.LoadOrInitInteractiveWithType(*mockContext.Context, envName, invalidEnvType) + require.Error(t, err) + require.Contains(t, err.Error(), "environment type 'invalid-type-with-hyphens' is invalid") + }) +} + func registerContainerComponents(t *testing.T, mockContext *mocks.MockContext) { mockContext.Container.MustRegisterSingleton(func() context.Context { return *mockContext.Context diff --git a/cli/azd/pkg/pipeline/github_provider.go b/cli/azd/pkg/pipeline/github_provider.go index 7a156471659..4c2a9086b6c 100644 --- a/cli/azd/pkg/pipeline/github_provider.go +++ b/cli/azd/pkg/pipeline/github_provider.go @@ -458,13 +458,22 @@ func (p *GitHubCiProvider) setPipelineVariables( infraOptions provisioning.Options, tenantId, clientId string, ) error { - for name, value := range map[string]string{ + vars := map[string]string{ environment.EnvNameEnvVarName: p.env.Name(), environment.LocationEnvVarName: p.env.GetLocation(), environment.SubscriptionIdEnvVarName: p.env.GetSubscriptionId(), environment.TenantIdEnvVarName: tenantId, "AZURE_CLIENT_ID": clientId, - } { + } + + if envType := p.env.GetEnvironmentType(); envType != "" { + vars[environment.EnvTypeEnvVarName] = envType + } + + for name, value := range vars { + if value == "" { + continue + } if err := p.ghCli.SetVariable(ctx, repoSlug, name, value); err != nil { return fmt.Errorf("failed setting %s variable: %w", name, err) } diff --git a/cli/azd/pkg/pipeline/pipeline.go b/cli/azd/pkg/pipeline/pipeline.go index fd6cd8fb23b..8b3265e93c5 100644 --- a/cli/azd/pkg/pipeline/pipeline.go +++ b/cli/azd/pkg/pipeline/pipeline.go @@ -330,6 +330,7 @@ type projectProperties struct { Secrets []string RequiredAlphaFeatures []string providerParameters []provisioning.Parameter + EnvType string } type authConfiguration struct { diff --git a/cli/azd/pkg/pipeline/pipeline_manager.go b/cli/azd/pkg/pipeline/pipeline_manager.go index 471efa2dd90..7b8a6664d64 100644 --- a/cli/azd/pkg/pipeline/pipeline_manager.go +++ b/cli/azd/pkg/pipeline/pipeline_manager.go @@ -1276,6 +1276,11 @@ func generatePipelineDefinition(path string, props projectProperties) error { } } + // Determines which azure..yaml file azd uses to provision in pipeline + if props.EnvType != "" && !slices.Contains(tmplContext.Variables, "AZURE_ENV_TYPE") { + tmplContext.Variables = append(tmplContext.Variables, "AZURE_ENV_TYPE") + } + if props.InfraProvider == infraProviderTerraform { // terraform provider does not resolve this variables automatically, AZD needs to define them tmplContext.Variables = append(tmplContext.Variables, "AZURE_LOCATION") @@ -1449,6 +1454,7 @@ func (pm *PipelineManager) ensurePipelineDefinition(ctx context.Context) error { Secrets: pm.prjConfig.Pipeline.Secrets, RequiredAlphaFeatures: requiredAlphaFeatures, providerParameters: pm.configOptions.providerParameters, + EnvType: pm.env.GetEnvironmentType(), }) if err != nil { return err diff --git a/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles-no_files_-_azdo_selected_-_App_host_-_fed_Cred.snap b/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles-no_files_-_azdo_selected_-_App_host_-_fed_Cred.snap index b026ef1049d..d6e6e7cb15a 100644 --- a/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles-no_files_-_azdo_selected_-_App_host_-_fed_Cred.snap +++ b/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles-no_files_-_azdo_selected_-_App_host_-_fed_Cred.snap @@ -9,16 +9,16 @@ steps: # setup-azd@1 needs to be manually installed in your organization # if you can't install it, you can use the below bash script to install azd # and remove this step - - task: setup-azd@1 - displayName: Install azd + # - task: setup-azd@1 + # displayName: Install azd # If you can't install above task in your organization, you can comment it and uncomment below task to install azd - # - task: Bash@3 - # displayName: Install azd - # inputs: - # targetType: 'inline' - # script: | - # curl -fsSL https://aka.ms/install-azd.sh | bash + - task: Bash@3 + displayName: Install azd + inputs: + targetType: 'inline' + script: | + curl -fsSL https://azuresdkartifacts.z5.web.core.windows.net/azd/standalone/pr/5412/install-azd.sh | bash -s -- --base-url https://azuresdkartifacts.z5.web.core.windows.net/azd/standalone/pr/5412 --version '' --verbose --skip-verify # azd delegate auth to az to use service connection with AzureCLI@2 - pwsh: | diff --git a/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles-no_files_-_azdo_selected_-_branch_name.snap b/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles-no_files_-_azdo_selected_-_branch_name.snap index 931f10f8d02..3f7c6797c85 100644 --- a/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles-no_files_-_azdo_selected_-_branch_name.snap +++ b/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles-no_files_-_azdo_selected_-_branch_name.snap @@ -9,16 +9,16 @@ steps: # setup-azd@1 needs to be manually installed in your organization # if you can't install it, you can use the below bash script to install azd # and remove this step - - task: setup-azd@1 - displayName: Install azd + # - task: setup-azd@1 + # displayName: Install azd # If you can't install above task in your organization, you can comment it and uncomment below task to install azd - # - task: Bash@3 - # displayName: Install azd - # inputs: - # targetType: 'inline' - # script: | - # curl -fsSL https://aka.ms/install-azd.sh | bash + - task: Bash@3 + displayName: Install azd + inputs: + targetType: 'inline' + script: | + curl -fsSL https://azuresdkartifacts.z5.web.core.windows.net/azd/standalone/pr/5412/install-azd.sh | bash -s -- --base-url https://azuresdkartifacts.z5.web.core.windows.net/azd/standalone/pr/5412 --version '' --verbose --skip-verify # azd delegate auth to az to use service connection with AzureCLI@2 - pwsh: | diff --git a/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles-no_files_-_azdo_selected_-_no_app_host_-_client_cred.snap b/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles-no_files_-_azdo_selected_-_no_app_host_-_client_cred.snap index 5b9b227c950..b6f956baa82 100644 --- a/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles-no_files_-_azdo_selected_-_no_app_host_-_client_cred.snap +++ b/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles-no_files_-_azdo_selected_-_no_app_host_-_client_cred.snap @@ -9,16 +9,16 @@ steps: # setup-azd@1 needs to be manually installed in your organization # if you can't install it, you can use the below bash script to install azd # and remove this step - - task: setup-azd@1 - displayName: Install azd + # - task: setup-azd@1 + # displayName: Install azd # If you can't install above task in your organization, you can comment it and uncomment below task to install azd - # - task: Bash@3 - # displayName: Install azd - # inputs: - # targetType: 'inline' - # script: | - # curl -fsSL https://aka.ms/install-azd.sh | bash + - task: Bash@3 + displayName: Install azd + inputs: + targetType: 'inline' + script: | + curl -fsSL https://azuresdkartifacts.z5.web.core.windows.net/azd/standalone/pr/5412/install-azd.sh | bash -s -- --base-url https://azuresdkartifacts.z5.web.core.windows.net/azd/standalone/pr/5412 --version '' --verbose --skip-verify # azd delegate auth to az to use service connection with AzureCLI@2 - pwsh: | diff --git a/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles-no_files_-_azdo_selected_-_no_app_host_-_fed_Cred.snap b/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles-no_files_-_azdo_selected_-_no_app_host_-_fed_Cred.snap index 5b9b227c950..b6f956baa82 100644 --- a/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles-no_files_-_azdo_selected_-_no_app_host_-_fed_Cred.snap +++ b/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles-no_files_-_azdo_selected_-_no_app_host_-_fed_Cred.snap @@ -9,16 +9,16 @@ steps: # setup-azd@1 needs to be manually installed in your organization # if you can't install it, you can use the below bash script to install azd # and remove this step - - task: setup-azd@1 - displayName: Install azd + # - task: setup-azd@1 + # displayName: Install azd # If you can't install above task in your organization, you can comment it and uncomment below task to install azd - # - task: Bash@3 - # displayName: Install azd - # inputs: - # targetType: 'inline' - # script: | - # curl -fsSL https://aka.ms/install-azd.sh | bash + - task: Bash@3 + displayName: Install azd + inputs: + targetType: 'inline' + script: | + curl -fsSL https://azuresdkartifacts.z5.web.core.windows.net/azd/standalone/pr/5412/install-azd.sh | bash -s -- --base-url https://azuresdkartifacts.z5.web.core.windows.net/azd/standalone/pr/5412 --version '' --verbose --skip-verify # azd delegate auth to az to use service connection with AzureCLI@2 - pwsh: | diff --git a/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles-no_files_-_azdo_selected_-_no_app_host_-_variables_and_secrets.snap b/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles-no_files_-_azdo_selected_-_no_app_host_-_variables_and_secrets.snap index 7912264554c..91ee8f6b969 100644 --- a/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles-no_files_-_azdo_selected_-_no_app_host_-_variables_and_secrets.snap +++ b/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles-no_files_-_azdo_selected_-_no_app_host_-_variables_and_secrets.snap @@ -9,16 +9,16 @@ steps: # setup-azd@1 needs to be manually installed in your organization # if you can't install it, you can use the below bash script to install azd # and remove this step - - task: setup-azd@1 - displayName: Install azd + # - task: setup-azd@1 + # displayName: Install azd # If you can't install above task in your organization, you can comment it and uncomment below task to install azd - # - task: Bash@3 - # displayName: Install azd - # inputs: - # targetType: 'inline' - # script: | - # curl -fsSL https://aka.ms/install-azd.sh | bash + - task: Bash@3 + displayName: Install azd + inputs: + targetType: 'inline' + script: | + curl -fsSL https://azuresdkartifacts.z5.web.core.windows.net/azd/standalone/pr/5412/install-azd.sh | bash -s -- --base-url https://azuresdkartifacts.z5.web.core.windows.net/azd/standalone/pr/5412 --version '' --verbose --skip-verify # azd delegate auth to az to use service connection with AzureCLI@2 - pwsh: | diff --git a/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles-no_files_-_github_selected_-_App_host_-_fed_Cred.snap b/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles-no_files_-_github_selected_-_App_host_-_fed_Cred.snap index 40bfa7c920b..b87dad4556e 100644 --- a/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles-no_files_-_github_selected_-_App_host_-_fed_Cred.snap +++ b/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles-no_files_-_github_selected_-_App_host_-_fed_Cred.snap @@ -24,8 +24,10 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + # - name: Install azd + # uses: Azure/setup-azd@v2 - name: Install azd - uses: Azure/setup-azd@v2 + run: curl -fsSL https://azuresdkartifacts.z5.web.core.windows.net/azd/standalone/pr/5412/install-azd.sh | bash -s -- --base-url https://azuresdkartifacts.z5.web.core.windows.net/azd/standalone/pr/5412 --version '' --verbose --skip-verify - name: Setup .NET uses: actions/setup-dotnet@v4 with: diff --git a/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles-no_files_-_github_selected_-_Variables_and_Secrets.snap b/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles-no_files_-_github_selected_-_Variables_and_Secrets.snap index 2e57caaf9fe..9c1402d3596 100644 --- a/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles-no_files_-_github_selected_-_Variables_and_Secrets.snap +++ b/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles-no_files_-_github_selected_-_Variables_and_Secrets.snap @@ -26,8 +26,10 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + # - name: Install azd + # uses: Azure/setup-azd@v2 - name: Install azd - uses: Azure/setup-azd@v2 + run: curl -fsSL https://azuresdkartifacts.z5.web.core.windows.net/azd/standalone/pr/5412/install-azd.sh | bash -s -- --base-url https://azuresdkartifacts.z5.web.core.windows.net/azd/standalone/pr/5412 --version '' --verbose --skip-verify - name: Setup .NET uses: actions/setup-dotnet@v4 with: diff --git a/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles-no_files_-_github_selected_-_branch_name.snap b/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles-no_files_-_github_selected_-_branch_name.snap index 5f299008abf..1a03f934292 100644 --- a/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles-no_files_-_github_selected_-_branch_name.snap +++ b/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles-no_files_-_github_selected_-_branch_name.snap @@ -24,8 +24,10 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + # - name: Install azd + # uses: Azure/setup-azd@v2 - name: Install azd - uses: Azure/setup-azd@v2 + run: curl -fsSL https://azuresdkartifacts.z5.web.core.windows.net/azd/standalone/pr/5412/install-azd.sh | bash -s -- --base-url https://azuresdkartifacts.z5.web.core.windows.net/azd/standalone/pr/5412 --version '' --verbose --skip-verify - name: Log in with Azure (Federated Credentials) run: | azd auth login ` diff --git a/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles-no_files_-_github_selected_-_no_app_host_-_client_cred.snap b/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles-no_files_-_github_selected_-_no_app_host_-_client_cred.snap index 63eac357512..44273280d70 100644 --- a/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles-no_files_-_github_selected_-_no_app_host_-_client_cred.snap +++ b/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles-no_files_-_github_selected_-_no_app_host_-_client_cred.snap @@ -19,8 +19,10 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + # - name: Install azd + # uses: Azure/setup-azd@v2 - name: Install azd - uses: Azure/setup-azd@v2 + run: curl -fsSL https://azuresdkartifacts.z5.web.core.windows.net/azd/standalone/pr/5412/install-azd.sh | bash -s -- --base-url https://azuresdkartifacts.z5.web.core.windows.net/azd/standalone/pr/5412 --version '' --verbose --skip-verify - name: Log in with Azure (Client Credentials) run: | $info = $Env:AZURE_CREDENTIALS | ConvertFrom-Json -AsHashtable; diff --git a/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles-no_files_-_github_selected_-_no_app_host_-_fed_Cred.snap b/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles-no_files_-_github_selected_-_no_app_host_-_fed_Cred.snap index 25732faa1be..4eb93bff6dd 100644 --- a/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles-no_files_-_github_selected_-_no_app_host_-_fed_Cred.snap +++ b/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles-no_files_-_github_selected_-_no_app_host_-_fed_Cred.snap @@ -24,8 +24,10 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + # - name: Install azd + # uses: Azure/setup-azd@v2 - name: Install azd - uses: Azure/setup-azd@v2 + run: curl -fsSL https://azuresdkartifacts.z5.web.core.windows.net/azd/standalone/pr/5412/install-azd.sh | bash -s -- --base-url https://azuresdkartifacts.z5.web.core.windows.net/azd/standalone/pr/5412 --version '' --verbose --skip-verify - name: Log in with Azure (Federated Credentials) run: | azd auth login ` diff --git a/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles_azureDevOpsDirectory-no_files_-_azdo_selected_-_azuredevops_dir_-_client_cred.snap b/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles_azureDevOpsDirectory-no_files_-_azdo_selected_-_azuredevops_dir_-_client_cred.snap index 5b9b227c950..b6f956baa82 100644 --- a/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles_azureDevOpsDirectory-no_files_-_azdo_selected_-_azuredevops_dir_-_client_cred.snap +++ b/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles_azureDevOpsDirectory-no_files_-_azdo_selected_-_azuredevops_dir_-_client_cred.snap @@ -9,16 +9,16 @@ steps: # setup-azd@1 needs to be manually installed in your organization # if you can't install it, you can use the below bash script to install azd # and remove this step - - task: setup-azd@1 - displayName: Install azd + # - task: setup-azd@1 + # displayName: Install azd # If you can't install above task in your organization, you can comment it and uncomment below task to install azd - # - task: Bash@3 - # displayName: Install azd - # inputs: - # targetType: 'inline' - # script: | - # curl -fsSL https://aka.ms/install-azd.sh | bash + - task: Bash@3 + displayName: Install azd + inputs: + targetType: 'inline' + script: | + curl -fsSL https://azuresdkartifacts.z5.web.core.windows.net/azd/standalone/pr/5412/install-azd.sh | bash -s -- --base-url https://azuresdkartifacts.z5.web.core.windows.net/azd/standalone/pr/5412 --version '' --verbose --skip-verify # azd delegate auth to az to use service connection with AzureCLI@2 - pwsh: | diff --git a/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles_azureDevOpsDirectory-no_files_-_azdo_selected_-_azuredevops_dir_-_fed_Cred.snap b/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles_azureDevOpsDirectory-no_files_-_azdo_selected_-_azuredevops_dir_-_fed_Cred.snap index 5b9b227c950..b6f956baa82 100644 --- a/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles_azureDevOpsDirectory-no_files_-_azdo_selected_-_azuredevops_dir_-_fed_Cred.snap +++ b/cli/azd/pkg/pipeline/testdata/Test_promptForCiFiles_azureDevOpsDirectory-no_files_-_azdo_selected_-_azuredevops_dir_-_fed_Cred.snap @@ -9,16 +9,16 @@ steps: # setup-azd@1 needs to be manually installed in your organization # if you can't install it, you can use the below bash script to install azd # and remove this step - - task: setup-azd@1 - displayName: Install azd + # - task: setup-azd@1 + # displayName: Install azd # If you can't install above task in your organization, you can comment it and uncomment below task to install azd - # - task: Bash@3 - # displayName: Install azd - # inputs: - # targetType: 'inline' - # script: | - # curl -fsSL https://aka.ms/install-azd.sh | bash + - task: Bash@3 + displayName: Install azd + inputs: + targetType: 'inline' + script: | + curl -fsSL https://azuresdkartifacts.z5.web.core.windows.net/azd/standalone/pr/5412/install-azd.sh | bash -s -- --base-url https://azuresdkartifacts.z5.web.core.windows.net/azd/standalone/pr/5412 --version '' --verbose --skip-verify # azd delegate auth to az to use service connection with AzureCLI@2 - pwsh: | diff --git a/cli/azd/resources/pipeline/.azdo/azure-dev.ymlt b/cli/azd/resources/pipeline/.azdo/azure-dev.ymlt index be177aadf92..3df56d8a68a 100644 --- a/cli/azd/resources/pipeline/.azdo/azure-dev.ymlt +++ b/cli/azd/resources/pipeline/.azdo/azure-dev.ymlt @@ -10,16 +10,16 @@ steps: # setup-azd@1 needs to be manually installed in your organization # if you can't install it, you can use the below bash script to install azd # and remove this step - - task: setup-azd@1 - displayName: Install azd + # - task: setup-azd@1 + # displayName: Install azd # If you can't install above task in your organization, you can comment it and uncomment below task to install azd - # - task: Bash@3 - # displayName: Install azd - # inputs: - # targetType: 'inline' - # script: | - # curl -fsSL https://aka.ms/install-azd.sh | bash + - task: Bash@3 + displayName: Install azd + inputs: + targetType: 'inline' + script: | + curl -fsSL https://azuresdkartifacts.z5.web.core.windows.net/azd/standalone/pr/5412/install-azd.sh | bash -s -- --base-url https://azuresdkartifacts.z5.web.core.windows.net/azd/standalone/pr/5412 --version '' --verbose --skip-verify # azd delegate auth to az to use service connection with AzureCLI@2 - pwsh: | diff --git a/cli/azd/resources/pipeline/.github/azure-dev.ymlt b/cli/azd/resources/pipeline/.github/azure-dev.ymlt index a79661e3c64..cdb072134c2 100644 --- a/cli/azd/resources/pipeline/.github/azure-dev.ymlt +++ b/cli/azd/resources/pipeline/.github/azure-dev.ymlt @@ -40,8 +40,10 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + # - name: Install azd + # uses: Azure/setup-azd@v2 - name: Install azd - uses: Azure/setup-azd@v2 + run: curl -fsSL https://azuresdkartifacts.z5.web.core.windows.net/azd/standalone/pr/5412/install-azd.sh | bash -s -- --base-url https://azuresdkartifacts.z5.web.core.windows.net/azd/standalone/pr/5412 --version '' --verbose --skip-verify {{- if .IsTerraform}} - name: Install Terraform uses: hashicorp/setup-terraform@v3 diff --git a/cli/azd/test/mocks/mockenv/mock_manager.go b/cli/azd/test/mocks/mockenv/mock_manager.go index b3db301cfc7..f9948eb1d41 100644 --- a/cli/azd/test/mocks/mockenv/mock_manager.go +++ b/cli/azd/test/mocks/mockenv/mock_manager.go @@ -24,6 +24,15 @@ func (m *MockEnvManager) LoadOrInitInteractive(ctx context.Context, name string) return args.Get(0).(*environment.Environment), args.Error(1) } +func (m *MockEnvManager) LoadOrInitInteractiveWithType( + ctx context.Context, + name, + envType string, +) (*environment.Environment, error) { + args := m.Called(ctx, name, envType) + return args.Get(0).(*environment.Environment), args.Error(1) +} + func (m *MockEnvManager) List(ctx context.Context) ([]*environment.Description, error) { args := m.Called(ctx) return args.Get(0).([]*environment.Description), args.Error(1)