diff --git a/cli/azd/pkg/prompt/prompt_service.go b/cli/azd/pkg/prompt/prompt_service.go index 4527ff0ab5f..f8d4fc0ad97 100644 --- a/cli/azd/pkg/prompt/prompt_service.go +++ b/cli/azd/pkg/prompt/prompt_service.go @@ -7,7 +7,9 @@ import ( "context" "fmt" "io" + "os" "slices" + "strconv" "strings" "dario.cat/mergo" @@ -28,6 +30,28 @@ var ( ErrNoResourceSelected = fmt.Errorf("no resource selected") ) +func isDemoModeEnabled() bool { + v, err := strconv.ParseBool(os.Getenv("AZD_DEMO_MODE")) + return err == nil && v +} + +func formatSubscriptionDisplayName(subscription *account.Subscription, hideId bool) string { + if hideId { + return subscription.Name + } + + return fmt.Sprintf("%s %s", subscription.Name, output.WithGrayFormat("(%s)", subscription.Id)) +} + +func formatAutoSelectedSubscriptionMessage(subscription *account.Subscription, hideId bool) string { + message := fmt.Sprintf("Auto-selected subscription: %s", subscription.Name) + if hideId { + return message + } + + return fmt.Sprintf("%s (%s)", message, subscription.Id) +} + // ResourceOptions contains options for prompting the user to select a resource. type ResourceOptions struct { // ResourceType is the type of resource to select. @@ -222,6 +246,8 @@ func (ps *promptService) PromptSubscription( } } + hideId := isDemoModeEnabled() + // Handle --no-prompt mode if ps.globalOptions.NoPrompt { // Load subscriptions for both default lookup and auto-selection @@ -238,6 +264,13 @@ func (ps *promptService) PromptSubscription( } } + if hideId { + return nil, fmt.Errorf( + "default subscription not found. " + + "Update your default subscription using " + + "'azd config set defaults.subscription '") + } + return nil, fmt.Errorf( "default subscription '%s' not found. "+ "Update your default subscription using "+ @@ -254,9 +287,7 @@ func (ps *promptService) PromptSubscription( "and that your account has one or more active subscriptions. " + "If needed, run 'azd auth login' to sign in.") case 1: - ps.console.Message(ctx, fmt.Sprintf( - "Auto-selected subscription: %s (%s)", - subscriptionList[0].Name, subscriptionList[0].Id)) + ps.console.Message(ctx, formatAutoSelectedSubscriptionMessage(&subscriptionList[0], hideId)) return &subscriptionList[0], nil default: return nil, fmt.Errorf( @@ -281,7 +312,7 @@ func (ps *promptService) PromptSubscription( return subscriptions, nil }, DisplayResource: func(subscription *account.Subscription) (string, error) { - return fmt.Sprintf("%s %s", subscription.Name, output.WithGrayFormat("(%s)", subscription.Id)), nil + return formatSubscriptionDisplayName(subscription, hideId), nil }, Selected: func(subscription *account.Subscription) bool { return strings.EqualFold(subscription.Id, defaultSubscriptionId) diff --git a/cli/azd/pkg/prompt/prompt_service_test.go b/cli/azd/pkg/prompt/prompt_service_test.go index c16fe277523..01def1c4399 100644 --- a/cli/azd/pkg/prompt/prompt_service_test.go +++ b/cli/azd/pkg/prompt/prompt_service_test.go @@ -5,6 +5,7 @@ package prompt import ( "context" + "strings" "testing" "github.com/azure/azure-dev/cli/azd/internal" @@ -166,3 +167,79 @@ func Test_PromptService_PromptSubscription_NoPrompt_AutoSelect(t *testing.T) { }) } } + +func TestFormatSubscriptionDisplayName_DemoModeHidesId(t *testing.T) { + displayName := formatSubscriptionDisplayName(&account.Subscription{ + Id: "/subscriptions/sub-1", + Name: "Subscription 1", + }, true) + + require.Equal(t, "Subscription 1", displayName) +} + +func TestPromptSubscription_NoPrompt_AutoSelect_DemoModeRedactsOutput(t *testing.T) { + t.Setenv("AZD_DEMO_MODE", "true") + + ucm := newInMemoryUserConfigManager(nil) + authManager := &mockauth.MockAuthManager{} + subscriptionManager := &mockaccount.MockSubscriptionManager{} + resourceService := &mockazapi.MockResourceService{} + mockConsole := mockinput.NewMockConsole() + mockConsole.SetNoPromptMode(true) + + subscriptionManager. + On("GetSubscriptions", mock.Anything). + Return([]account.Subscription{ + {Id: "sub-1", TenantId: "tenant-1", Name: "My Only Sub"}, + }, nil) + + ps := NewPromptService( + authManager, + mockConsole, + ucm, + subscriptionManager, + resourceService, + &internal.GlobalCommandOptions{NoPrompt: true}, + ) + + result, err := ps.PromptSubscription(context.Background(), nil) + require.NoError(t, err) + require.Equal(t, "sub-1", result.Id) + require.Len(t, mockConsole.Output(), 1) + require.Equal(t, "Auto-selected subscription: My Only Sub", mockConsole.Output()[0]) +} + +func TestPromptSubscription_NoPrompt_DefaultNotFound_DemoModeRedactsId(t *testing.T) { + t.Setenv("AZD_DEMO_MODE", "true") + + cfg := config.NewEmptyConfig() + err := cfg.Set("defaults.subscription", "sub-secret") + require.NoError(t, err) + + ucm := newInMemoryUserConfigManager(cfg) + authManager := &mockauth.MockAuthManager{} + subscriptionManager := &mockaccount.MockSubscriptionManager{} + resourceService := &mockazapi.MockResourceService{} + mockConsole := mockinput.NewMockConsole() + mockConsole.SetNoPromptMode(true) + + subscriptionManager. + On("GetSubscriptions", mock.Anything). + Return([]account.Subscription{ + {Id: "sub-1", TenantId: "tenant-1", Name: "Sub 1"}, + }, nil) + + ps := NewPromptService( + authManager, + mockConsole, + ucm, + subscriptionManager, + resourceService, + &internal.GlobalCommandOptions{NoPrompt: true}, + ) + + _, err = ps.PromptSubscription(context.Background(), nil) + require.Error(t, err) + require.ErrorContains(t, err, "default subscription not found") + require.False(t, strings.Contains(err.Error(), "sub-secret")) +}