Skip to content
Merged
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
2 changes: 1 addition & 1 deletion cli/azd/cmd/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -896,7 +896,7 @@ func (f *envNewFlags) Bind(local *pflag.FlagSet, global *internal.GlobalCommandO
&f.subscription,
"subscription",
"",
"Name or ID of an Azure subscription to use for the new environment",
"ID of an Azure subscription to use for the new environment",
)
local.StringVarP(&f.location, "location", "l", "", "Azure location for the new environment")

Expand Down
2 changes: 1 addition & 1 deletion cli/azd/cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ func (i *initFlags) Bind(local *pflag.FlagSet, global *internal.GlobalCommandOpt
"subscription",
"s",
"",
"Name or ID of an Azure subscription to use for the new environment",
"ID of an Azure subscription to use for the new environment",
)
local.BoolVarP(
&i.fromCode,
Expand Down
40 changes: 38 additions & 2 deletions cli/azd/cmd/testdata/TestFigSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1221,7 +1221,7 @@ const completionSpec: Fig.Spec = {
},
{
name: ['--subscription'],
description: 'Name or ID of an Azure subscription to use for the new environment',
description: 'ID of an Azure subscription to use for the new environment',
args: [
{
name: 'subscription',
Expand Down Expand Up @@ -1678,7 +1678,7 @@ const completionSpec: Fig.Spec = {
},
{
name: ['--subscription', '-s'],
description: 'Name or ID of an Azure subscription to use for the new environment',
description: 'ID of an Azure subscription to use for the new environment',
args: [
{
name: 'subscription',
Expand Down Expand Up @@ -2054,6 +2054,15 @@ const completionSpec: Fig.Spec = {
},
],
},
{
name: ['--location', '-l'],
description: 'Azure location for the new environment',
args: [
{
name: 'location',
},
],
},
{
name: ['--no-state'],
description: '(Bicep only) Forces a fresh deployment based on current Bicep template files, ignoring any stored deployment state.',
Expand All @@ -2062,6 +2071,15 @@ const completionSpec: Fig.Spec = {
name: ['--preview'],
description: 'Preview changes to Azure resources.',
},
{
name: ['--subscription'],
description: 'ID of an Azure subscription to use for the new environment',
args: [
{
name: 'subscription',
},
],
},
],
args: {
name: 'layer',
Expand Down Expand Up @@ -2262,6 +2280,24 @@ const completionSpec: Fig.Spec = {
},
],
},
{
name: ['--location', '-l'],
description: 'Azure location for the new environment',
args: [
{
name: 'location',
},
],
},
{
name: ['--subscription'],
description: 'ID of an Azure subscription to use for the new environment',
args: [
{
name: 'subscription',
},
],
},
],
},
{
Expand Down
2 changes: 1 addition & 1 deletion cli/azd/cmd/testdata/TestUsage-azd-env-new.snap
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,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
--subscription string : ID of an Azure subscription to use for the new environment

Global Flags
-C, --cwd string : Sets the current working directory.
Expand Down
2 changes: 1 addition & 1 deletion cli/azd/cmd/testdata/TestUsage-azd-init.snap
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Flags
--from-code : Initializes a new application from your existing code.
-l, --location string : Azure location for the new environment
-m, --minimal : Initializes a minimal project.
-s, --subscription string : Name or ID of an Azure subscription to use for the new environment
-s, --subscription string : ID of an Azure subscription to use for the new environment
-t, --template string : Initializes a new application from a template. You can use Full URI, <owner>/<repository>, or <repository> if it's part of the azure-samples organization.
--up : Provision and deploy to Azure after initializing the project from a template.

Expand Down
8 changes: 5 additions & 3 deletions cli/azd/cmd/testdata/TestUsage-azd-provision.snap
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ Usage
azd provision [<layer>] [flags]

Flags
-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.
-e, --environment string : The name of the environment to use.
-l, --location string : Azure location for the new environment
--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.
--subscription string : ID of an Azure subscription to use for the new environment

Global Flags
-C, --cwd string : Sets the current working directory.
Expand Down
4 changes: 3 additions & 1 deletion cli/azd/cmd/testdata/TestUsage-azd-up.snap
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ Usage
azd up [flags]

Flags
-e, --environment string : The name of the environment to use.
-e, --environment string : The name of the environment to use.
-l, --location string : Azure location for the new environment
--subscription string : ID of an Azure subscription to use for the new environment

Global Flags
-C, --cwd string : Sets the current working directory.
Expand Down
28 changes: 28 additions & 0 deletions cli/azd/cmd/up.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,34 @@ func newUpAction(
}

func (u *upAction) Run(ctx context.Context) (*actions.ActionResult, error) {
// Apply --subscription and --location flags to the environment before provisioning
updatedEnv := false
if flagSub := u.flags.ProvisionFlags.Subscription(); flagSub != "" {
if existing := u.env.GetSubscriptionId(); existing != "" && existing != flagSub {
return nil, fmt.Errorf(
"cannot change subscription for existing environment '%s' (current: %s, requested: %s). "+
"Create a new environment with 'azd env new' instead",
u.env.Name(), existing, flagSub)
}
u.env.SetSubscriptionId(flagSub)
updatedEnv = true
}
if flagLoc := u.flags.ProvisionFlags.Location(); flagLoc != "" {
if existing := u.env.GetLocation(); existing != "" && existing != flagLoc {
return nil, fmt.Errorf(
"cannot change location for existing environment '%s' (current: %s, requested: %s). "+
"Create a new environment with 'azd env new' instead",
u.env.Name(), existing, flagLoc)
}
u.env.SetLocation(flagLoc)
updatedEnv = true
}
if updatedEnv {
if err := u.envManager.Save(ctx, u.env); err != nil {
return nil, fmt.Errorf("saving environment: %w", err)
}
}
Comment thread
rajeshkamal5050 marked this conversation as resolved.

infra, err := u.importManager.ProjectInfrastructure(ctx, u.projectConfig)
if err != nil {
return nil, err
Expand Down
47 changes: 47 additions & 0 deletions cli/azd/internal/cmd/provision.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ type ProvisionFlags struct {
noProgress bool
preview bool
ignoreDeploymentState bool
subscription string
location string
global *internal.GlobalCommandOptions
*internal.EnvFlag
}
Expand All @@ -55,9 +57,26 @@ func (i *ProvisionFlags) BindNonCommon(local *pflag.FlagSet, global *internal.Gl
local.BoolVar(&i.noProgress, "no-progress", false, "Suppresses progress information.")
//deprecate:Flag hide --no-progress
_ = local.MarkHidden("no-progress")
local.StringVar(
&i.subscription,
"subscription",
"",
"ID of an Azure subscription to use for the new environment",
)
local.StringVarP(&i.location, "location", "l", "", "Azure location for the new environment")
i.global = global
}

// Subscription returns the value of the --subscription flag.
func (i *ProvisionFlags) Subscription() string {
return i.subscription
}

// Location returns the value of the --location flag.
func (i *ProvisionFlags) Location() string {
return i.location
}

func (i *ProvisionFlags) bindCommon(local *pflag.FlagSet, global *internal.GlobalCommandOptions) {
local.BoolVar(&i.preview, "preview", false, "Preview changes to Azure resources.")
local.BoolVar(
Expand Down Expand Up @@ -199,6 +218,34 @@ func (p *ProvisionAction) Run(ctx context.Context) (*actions.ActionResult, error
return nil, err
}

// Apply --subscription and --location flags to the environment before provisioning
envChanged := false
if p.flags.subscription != "" {
if existing := p.env.GetSubscriptionId(); existing != "" && existing != p.flags.subscription {
return nil, fmt.Errorf(
"cannot change subscription for existing environment '%s' (current: %s, requested: %s). "+
"Create a new environment with 'azd env new' instead",
p.env.Name(), existing, p.flags.subscription)
}
p.env.SetSubscriptionId(p.flags.subscription)
envChanged = true
}
if p.flags.location != "" {
if existing := p.env.GetLocation(); existing != "" && existing != p.flags.location {
return nil, fmt.Errorf(
"cannot change location for existing environment '%s' (current: %s, requested: %s). "+
"Create a new environment with 'azd env new' instead",
p.env.Name(), existing, p.flags.location)
}
p.env.SetLocation(p.flags.location)
envChanged = true
}
if envChanged {
if err := p.envManager.Save(ctx, p.env); err != nil {
return nil, fmt.Errorf("saving environment: %w", err)
}
}

infra, err := p.importManager.ProjectInfrastructure(ctx, p.projectConfig)
if err != nil {
return nil, err
Expand Down
120 changes: 120 additions & 0 deletions cli/azd/internal/cmd/provision_guard_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package cmd

import (
"fmt"
"testing"

"github.com/azure/azure-dev/cli/azd/pkg/environment"
"github.com/stretchr/testify/require"
)

func Test_EnvFlagGuard(t *testing.T) {
tests := []struct {
name string
existingSub string
existingLoc string
flagSub string
flagLoc string
wantSubErr bool
wantLocErr bool
wantSubErrMsg string
wantLocErrMsg string
}{
{
name: "EmptyEnv_NewFlags_Allowed",
existingSub: "",
existingLoc: "",
flagSub: "new-sub-id",
flagLoc: "eastus",
},
{
name: "SameValues_NoOp",
existingSub: "sub-123",
existingLoc: "westus2",
flagSub: "sub-123",
flagLoc: "westus2",
},
{
name: "DifferentSub_Error",
existingSub: "sub-123",
existingLoc: "westus2",
flagSub: "sub-456",
flagLoc: "",
wantSubErr: true,
wantSubErrMsg: "cannot change subscription",
},
{
name: "DifferentLoc_Error",
existingSub: "sub-123",
existingLoc: "westus2",
flagSub: "",
flagLoc: "eastus",
wantLocErr: true,
wantLocErrMsg: "cannot change location",
},
{
name: "DifferentBoth_SubErrorFirst",
existingSub: "sub-123",
existingLoc: "westus2",
flagSub: "sub-456",
flagLoc: "eastus",
wantSubErr: true,
wantSubErrMsg: "cannot change subscription",
},
{
name: "NoFlags_NoChange",
existingSub: "sub-123",
existingLoc: "westus2",
flagSub: "",
flagLoc: "",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
env := environment.New("test-env")
if tt.existingSub != "" {
env.SetSubscriptionId(tt.existingSub)
}
if tt.existingLoc != "" {
env.SetLocation(tt.existingLoc)
}

// Simulate the guard logic from provision.go
var subErr, locErr error
if tt.flagSub != "" {
if existing := env.GetSubscriptionId(); existing != "" && existing != tt.flagSub {
subErr = fmt.Errorf(
"cannot change subscription for existing environment '%s' (current: %s, requested: %s). "+
"Create a new environment with 'azd env new' instead",
env.Name(), existing, tt.flagSub)
}
}
if subErr == nil && tt.flagLoc != "" {
if existing := env.GetLocation(); existing != "" && existing != tt.flagLoc {
locErr = fmt.Errorf(
"cannot change location for existing environment '%s' (current: %s, requested: %s). "+
"Create a new environment with 'azd env new' instead",
env.Name(), existing, tt.flagLoc)
}
}

if tt.wantSubErr {
require.Error(t, subErr)
require.Contains(t, subErr.Error(), tt.wantSubErrMsg)
} else {
require.NoError(t, subErr)
}

if tt.wantLocErr {
require.Error(t, locErr)
require.Contains(t, locErr.Error(), tt.wantLocErrMsg)
} else {
require.NoError(t, locErr)
}
})
}
}
Loading