Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions cli/azd/cmd/auto_install.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/azure/azure-dev/cli/azd/internal"
"github.com/azure/azure-dev/cli/azd/internal/runcontext/agentdetect"
"github.com/azure/azure-dev/cli/azd/internal/tracing/resource"
"github.com/azure/azure-dev/cli/azd/pkg/environment"
"github.com/azure/azure-dev/cli/azd/pkg/extensions"
"github.com/azure/azure-dev/cli/azd/pkg/input"
"github.com/azure/azure-dev/cli/azd/pkg/ioc"
Expand Down Expand Up @@ -616,6 +617,7 @@ func CreateGlobalFlagSet() *pflag.FlagSet {
"no-prompt",
false,
"Accepts the default value instead of prompting, or it fails if there is no default.")
globalFlags.StringP("environment", "e", "", "The name of the environment to use.")

// The telemetry system is responsible for reading these flags value and using it to configure the telemetry
// system, but we still need to add it to our flag set so that when we parse the command line with Cobra we
Expand Down Expand Up @@ -669,6 +671,17 @@ func ParseGlobalFlags(args []string, opts *internal.GlobalCommandOptions) error
opts.NoPrompt = boolVal
}

// Parse -e/--environment with strict validation.
// Invalid environment names are rejected with an error so users get clear feedback on typos.
// Extensions have been migrated off -e (see reserved flags registry), so any -e value
// should be a valid environment name.
if strVal, err := globalFlagSet.GetString("environment"); err == nil && strVal != "" {
if !environment.IsValidEnvironmentName(strVal) {
return environment.InvalidEnvironmentNameError(strVal)
}
opts.EnvironmentName = strVal
}

// Agent Detection: If --no-prompt was not explicitly set and we detect an AI coding agent
// as the caller, automatically enable no-prompt mode for non-interactive execution.
noPromptFlag := globalFlagSet.Lookup("no-prompt")
Expand Down
99 changes: 99 additions & 0 deletions cli/azd/cmd/auto_install_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -421,3 +421,102 @@ func TestParseGlobalFlags_AgentDetection(t *testing.T) {
})
}
}

func TestParseGlobalFlags_EnvironmentName(t *testing.T) {
tests := []struct {
name string
args []string
expectedEnvName string
}{
{
name: "valid env name with -e",
args: []string{"-e", "dev", "up"},
expectedEnvName: "dev",
},
{
name: "valid env name with --environment",
args: []string{"--environment", "production", "deploy"},
expectedEnvName: "production",
},
{
name: "valid env name with equals syntax",
args: []string{"--environment=staging", "deploy"},
expectedEnvName: "staging",
},
{
name: "env name with dots and hyphens",
args: []string{"-e", "my-env.v2", "up"},
expectedEnvName: "my-env.v2",
},
{
name: "empty value",
args: []string{"up"},
expectedEnvName: "",
},
{
name: "env name alongside other global flags",
args: []string{"--debug", "-e", "myenv", "--no-prompt", "deploy"},
expectedEnvName: "myenv",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Clear agent detection to avoid NoPrompt side effects
clearAgentEnvVarsForTest(t)
agentdetect.ResetDetection()

opts := &internal.GlobalCommandOptions{}
err := ParseGlobalFlags(tt.args, opts)
require.NoError(t, err)

assert.Equal(t, tt.expectedEnvName, opts.EnvironmentName,
"EnvironmentName should be %q for test case: %s", tt.expectedEnvName, tt.name)

agentdetect.ResetDetection()
})
}
}

func TestParseGlobalFlags_InvalidEnvironmentName(t *testing.T) {
tests := []struct {
name string
args []string
}{
{
name: "URL value",
args: []string{"-e", "https://foo.services.ai.azure.com/api/projects/bar", "model", "custom", "create"},
},
{
name: "value with colons",
args: []string{"-e", "host:port", "model", "custom", "create"},
},
{
name: "value with slashes",
args: []string{"-e", "path/to/thing", "model", "custom", "create"},
},
{
name: "value with spaces",
args: []string{"-e", "env name with spaces"},
},
{
name: "special characters",
args: []string{"-e", "env@#$%"},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
clearAgentEnvVarsForTest(t)
agentdetect.ResetDetection()

opts := &internal.GlobalCommandOptions{}
err := ParseGlobalFlags(tt.args, opts)
require.Error(t, err)
assert.Contains(t, err.Error(), "is invalid")
assert.Empty(t, opts.EnvironmentName)

agentdetect.ResetDetection()
})
}
}
22 changes: 19 additions & 3 deletions cli/azd/cmd/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,12 @@ func registerCommonDependencies(container *ioc.NestedContainer) {
return writer
})

container.MustRegisterScoped(func(ctx context.Context, cmd *cobra.Command) internal.EnvFlag {
// The env flag `-e, --environment` is available on most azd commands but not all
container.MustRegisterScoped(func(
ctx context.Context,
cmd *cobra.Command,
globalOptions *internal.GlobalCommandOptions,
) 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
Expand All @@ -201,6 +205,13 @@ func registerCommonDependencies(container *ioc.NestedContainer) {
envValue = ""
}

// For extension commands (DisableFlagParsing=true), cobra never parses -e so
// cmd.Flags().GetString always returns "". Fall back to the value that was
// pre-parsed in ParseGlobalFlags before the command tree was built.
if envValue == "" && globalOptions.EnvironmentName != "" {
envValue = globalOptions.EnvironmentName
}

if envValue == "" {
// If no explicit environment flag was set, but one was provided
// in the context, use that instead.
Expand Down Expand Up @@ -981,6 +992,11 @@ func (w *workflowCmdAdapter) ExecuteContext(ctx context.Context, args []string)
// extractGlobalArgs extracts global flag arguments from the process command line.
// It parses os.Args against the global flag set and returns only the flags that were
// explicitly set by the user, formatted as command-line arguments.
//
// The "environment" flag is intentionally excluded: workflow steps may define their own
// -e/--environment (e.g. `azd: env set KEY VALUE -e env1`), and appending the parent's
// --environment would override the step-level value. Environment propagation to workflow
// steps is handled by the globalOptions DI fallback in the EnvFlag resolver instead.
func extractGlobalArgs() []string {
globalFlagSet := CreateGlobalFlagSet()
globalFlagSet.SetOutput(io.Discard)
Expand All @@ -989,7 +1005,7 @@ func extractGlobalArgs() []string {

var result []string
globalFlagSet.VisitAll(func(f *pflag.Flag) {
if f.Changed {
if f.Changed && f.Name != internal.EnvironmentNameFlagName {
// Use --flag=value syntax to avoid ambiguity. The two-arg form (--flag value)
// doesn't work for boolean flags, where the value is treated as a positional arg.
result = append(result, fmt.Sprintf("--%s=%s", f.Name, f.Value.String()))
Expand Down
16 changes: 8 additions & 8 deletions cli/azd/cmd/extensions.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,22 +244,22 @@ func (a *extensionAction) Run(ctx context.Context) (*actions.ActionResult, error
allEnv = append(allEnv, traceEnv...)
}

// Read global flags for propagation via InvokeOptions
debugEnabled, _ := a.cmd.Flags().GetBool("debug")
cwd, _ := a.cmd.Flags().GetString("cwd")
envName, _ := a.cmd.Flags().GetString("environment")

// Use globalOptions for flag propagation instead of cmd.Flags().
// Extension commands use DisableFlagParsing=true, so cobra never parses
// global flags like --debug, --cwd, or -e. The globalOptions were populated
// by ParseGlobalFlags() before command tree construction and are the only
// reliable source for these values.
options := &extensions.InvokeOptions{
Args: a.args,
Env: allEnv,
// cmd extensions are always interactive (connected to terminal)
Interactive: true,
Debug: debugEnabled,
Debug: a.globalOptions.EnableDebugLogging,
// Use globalOptions.NoPrompt which includes agent detection,
// not just the --no-prompt CLI flag
NoPrompt: a.globalOptions.NoPrompt,
Cwd: cwd,
Environment: envName,
Cwd: a.globalOptions.Cwd,
Environment: a.globalOptions.EnvironmentName,
}

_, invokeErr := a.extensionRunner.Invoke(ctx, extension, options)
Expand Down
37 changes: 10 additions & 27 deletions cli/azd/cmd/testdata/TestFigSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,15 +226,6 @@ const completionSpec: Fig.Spec = {
name: ['init'],
description: 'Initialize a new AI agent project. (Preview)',
options: [
{
name: ['--environment', '-e'],
description: 'The name of the azd environment to use.',
args: [
{
name: 'environment',
},
],
},
{
name: ['--host'],
description: 'For container based agents, can override the default host to target a container app instead. Accepted values: \'containerapp\'',
Expand Down Expand Up @@ -435,15 +426,6 @@ const completionSpec: Fig.Spec = {
name: ['init'],
description: 'Initialize a new AI Fine-tuning project. (Preview)',
options: [
{
name: ['--environment', '-n'],
description: 'The name of the azd environment to use.',
args: [
{
name: 'environment',
},
],
},
{
name: ['--from-job', '-j'],
description: 'Clone configuration from an existing job ID',
Expand Down Expand Up @@ -1158,15 +1140,6 @@ const completionSpec: Fig.Spec = {
name: ['init'],
description: 'Initialize a new AI models project. (Preview)',
options: [
{
name: ['--environment', '-n'],
description: 'The name of the azd environment to use',
args: [
{
name: 'environment',
},
],
},
{
name: ['--project-endpoint', '-e'],
description: 'Azure AI Foundry project endpoint URL (e.g., https://account.services.ai.azure.com/api/projects/project-name)',
Expand Down Expand Up @@ -3698,6 +3671,16 @@ const completionSpec: Fig.Spec = {
description: 'Enables debugging and diagnostics logging.',
isPersistent: true,
},
{
name: ['--environment', '-e'],
description: 'The name of the environment to use.',
isPersistent: true,
args: [
{
name: 'environment',
},
],
},
{
name: ['--no-prompt'],
description: 'Accepts the default value instead of prompting, or it fails if there is no default.',
Expand Down
11 changes: 6 additions & 5 deletions cli/azd/cmd/testdata/TestUsage-azd-add.snap
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ Usage
azd add [flags]

Global Flags
-C, --cwd string : Sets the current working directory.
--debug : Enables debugging and diagnostics logging.
--docs : Opens the documentation for azd add in your web browser.
-h, --help : Gets help for add.
--no-prompt : Accepts the default value instead of prompting, or it fails if there is no default.
-C, --cwd string : Sets the current working directory.
--debug : Enables debugging and diagnostics logging.
--docs : Opens the documentation for azd add in your web browser.
-e, --environment string : The name of the environment to use.
-h, --help : Gets help for add.
--no-prompt : Accepts the default value instead of prompting, or it fails if there is no default.

Find a bug? Want to let us know how we're doing? Fill out this brief survey: https://aka.ms/azure-dev/hats.

Expand Down
11 changes: 6 additions & 5 deletions cli/azd/cmd/testdata/TestUsage-azd-ai-agent.snap
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ Usage
azd ai agent [flags]

Global Flags
-C, --cwd string : Sets the current working directory.
--debug : Enables debugging and diagnostics logging.
--docs : Opens the documentation for azd ai agent in your web browser.
-h, --help : Gets help for agent.
--no-prompt : Accepts the default value instead of prompting, or it fails if there is no default.
-C, --cwd string : Sets the current working directory.
--debug : Enables debugging and diagnostics logging.
--docs : Opens the documentation for azd ai agent in your web browser.
-e, --environment string : The name of the environment to use.
-h, --help : Gets help for agent.
--no-prompt : Accepts the default value instead of prompting, or it fails if there is no default.

Find a bug? Want to let us know how we're doing? Fill out this brief survey: https://aka.ms/azure-dev/hats.

Expand Down
11 changes: 6 additions & 5 deletions cli/azd/cmd/testdata/TestUsage-azd-ai-finetuning.snap
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ Usage
azd ai finetuning [flags]

Global Flags
-C, --cwd string : Sets the current working directory.
--debug : Enables debugging and diagnostics logging.
--docs : Opens the documentation for azd ai finetuning in your web browser.
-h, --help : Gets help for finetuning.
--no-prompt : Accepts the default value instead of prompting, or it fails if there is no default.
-C, --cwd string : Sets the current working directory.
--debug : Enables debugging and diagnostics logging.
--docs : Opens the documentation for azd ai finetuning in your web browser.
-e, --environment string : The name of the environment to use.
-h, --help : Gets help for finetuning.
--no-prompt : Accepts the default value instead of prompting, or it fails if there is no default.

Find a bug? Want to let us know how we're doing? Fill out this brief survey: https://aka.ms/azure-dev/hats.

Expand Down
11 changes: 6 additions & 5 deletions cli/azd/cmd/testdata/TestUsage-azd-ai-models.snap
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ Usage
azd ai models [flags]

Global Flags
-C, --cwd string : Sets the current working directory.
--debug : Enables debugging and diagnostics logging.
--docs : Opens the documentation for azd ai models in your web browser.
-h, --help : Gets help for models.
--no-prompt : Accepts the default value instead of prompting, or it fails if there is no default.
-C, --cwd string : Sets the current working directory.
--debug : Enables debugging and diagnostics logging.
--docs : Opens the documentation for azd ai models in your web browser.
-e, --environment string : The name of the environment to use.
-h, --help : Gets help for models.
--no-prompt : Accepts the default value instead of prompting, or it fails if there is no default.

Find a bug? Want to let us know how we're doing? Fill out this brief survey: https://aka.ms/azure-dev/hats.

Expand Down
11 changes: 6 additions & 5 deletions cli/azd/cmd/testdata/TestUsage-azd-ai.snap
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ Available Commands
models : Extension for managing custom models in Azure AI Foundry. (Preview)

Global Flags
-C, --cwd string : Sets the current working directory.
--debug : Enables debugging and diagnostics logging.
--docs : Opens the documentation for azd ai in your web browser.
-h, --help : Gets help for ai.
--no-prompt : Accepts the default value instead of prompting, or it fails if there is no default.
-C, --cwd string : Sets the current working directory.
--debug : Enables debugging and diagnostics logging.
--docs : Opens the documentation for azd ai in your web browser.
-e, --environment string : The name of the environment to use.
-h, --help : Gets help for ai.
--no-prompt : Accepts the default value instead of prompting, or it fails if there is no default.

Use azd ai [command] --help to view examples and more information about a specific command.

Expand Down
11 changes: 6 additions & 5 deletions cli/azd/cmd/testdata/TestUsage-azd-appservice.snap
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ Usage
azd appservice [flags]

Global Flags
-C, --cwd string : Sets the current working directory.
--debug : Enables debugging and diagnostics logging.
--docs : Opens the documentation for azd appservice in your web browser.
-h, --help : Gets help for appservice.
--no-prompt : Accepts the default value instead of prompting, or it fails if there is no default.
-C, --cwd string : Sets the current working directory.
--debug : Enables debugging and diagnostics logging.
--docs : Opens the documentation for azd appservice in your web browser.
-e, --environment string : The name of the environment to use.
-h, --help : Gets help for appservice.
--no-prompt : Accepts the default value instead of prompting, or it fails if there is no default.

Find a bug? Want to let us know how we're doing? Fill out this brief survey: https://aka.ms/azure-dev/hats.

Expand Down
Loading
Loading