Skip to content
Draft
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
21 changes: 17 additions & 4 deletions cli/azd/pkg/azapi/standard_deployments.go
Original file line number Diff line number Diff line change
Expand Up @@ -579,10 +579,7 @@ func (ds *StandardDeployments) voidSubscriptionDeploymentState(
if has {
var emptyTemplate json.RawMessage = []byte(emptySubscriptionArmTemplate)
emptyDeploymentName := ds.GenerateDeploymentName(*envName)
tags := map[string]*string{
azure.TagKeyAzdEnvName: envName,
"azd-deploy-reason": new("down"),
}
tags := voidDeploymentTags(deployment)

_, err = ds.DeployToSubscription(
ctx,
Expand All @@ -603,6 +600,22 @@ func (ds *StandardDeployments) voidSubscriptionDeploymentState(
return nil
}

func voidDeploymentTags(deployment *ResourceDeployment) map[string]*string {
tags := map[string]*string{
azure.TagKeyAzdEnvName: deployment.Tags[azure.TagKeyAzdEnvName],
"azd-deploy-reason": new("down"),
}

if projectName, has := deployment.Tags[azure.TagKeyAzdProjectName]; has {
tags[azure.TagKeyAzdProjectName] = projectName
}
if layerName, has := deployment.Tags[azure.TagKeyAzdLayerName]; has {
tags[azure.TagKeyAzdLayerName] = layerName
}

return tags
}

func (ds *StandardDeployments) DeleteResourceGroupDeployment(
ctx context.Context,
subscriptionId,
Expand Down
19 changes: 19 additions & 0 deletions cli/azd/pkg/azapi/standard_deployments_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,25 @@ func Test_StandardDeployments_GenerateDeploymentName(t *testing.T) {
}
}

func TestVoidDeploymentTags(t *testing.T) {
deployment := &ResourceDeployment{
Tags: map[string]*string{
azure.TagKeyAzdEnvName: new("dev"),
azure.TagKeyAzdLayerName: new("main"),
azure.TagKeyAzdProjectName: new("project-a"),
"unrelated": new("value"),
},
}

tags := voidDeploymentTags(deployment)

require.Equal(t, "dev", *tags[azure.TagKeyAzdEnvName])
require.Equal(t, "main", *tags[azure.TagKeyAzdLayerName])
require.Equal(t, "project-a", *tags[azure.TagKeyAzdProjectName])
require.Equal(t, "down", *tags["azd-deploy-reason"])
require.NotContains(t, tags, "unrelated")
}

func TestCreatedResourceGroupsFromDeployment(t *testing.T) {
t.Parallel()

Expand Down
3 changes: 3 additions & 0 deletions cli/azd/pkg/azure/tags.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ const (
// TagKeyAzdLayerName is the name of the key in the tags map of a resource
// used to store the azd provisioning layer a resource is associated with.
TagKeyAzdLayerName = "azd-layer-name"
// TagKeyAzdProjectName is the name of the key in the tags map of a deployment
// used to store the azd project a deployment is associated with.
TagKeyAzdProjectName = "azd-project-name"
/* #nosec G101 - Potential hardcoded credentials - false positive */
// TagKeyAzdDeploymentStateParamHashName is the name of the key in the tags map of a deployment
// used to store the parameters hash.
Expand Down
65 changes: 48 additions & 17 deletions cli/azd/pkg/infra/deployment_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"context"
"errors"
"fmt"
"log"
"slices"
"strings"

Expand Down Expand Up @@ -80,6 +79,7 @@ func (dm *DeploymentManager) ResourceGroupDeployment(
func (dm *DeploymentManager) CompletedDeployments(
ctx context.Context,
scope Scope,
projectName string,
envName string,
layerName string,
hint string,
Expand All @@ -102,11 +102,13 @@ func (dm *DeploymentManager) CompletedDeployments(
}
}

// Environment matching strategy
// 1. Deployment with azd tagged env name + layer name
// 2. Exact match on environment name to deployment name (old azd strategy)
// 3. Multiple matching names based specified hint (show user prompt)
// Deployment matching strategy
// 1. Deployment with azd tagged project name + env name + layer name
// 2. Deployment without a project tag that matches the previous env/layer strategy
// 3. Exact match on environment name to deployment name (old azd strategy)
// 4. Multiple matching names based specified hint (show user prompt)
matchingDeployments := []*azapi.ResourceDeployment{}
var legacyDeployment *azapi.ResourceDeployment

for _, deployment := range deployments {
// We only want to consider deployments that are in a terminal state, not any which may be ongoing.
Expand All @@ -115,26 +117,28 @@ func (dm *DeploymentManager) CompletedDeployments(
continue
}

// Match on current azd strategy (tags)
envTag, envTagHas := deployment.Tags[azure.TagKeyAzdEnvName]
layerTag, layerTagHas := deployment.Tags[azure.TagKeyAzdLayerName]
projectTag, projectTagHas := deploymentTag(deployment, azure.TagKeyAzdProjectName)
if projectName != "" && projectTagHas && projectTag != projectName {
// A deployment explicitly owned by another project must never be
// considered by the legacy matching strategies below.
continue
}

if envTagHas && *envTag == envName {
if layerTagHas && *layerTag == layerName {
log.Printf("completedDeployments: matched deployment '%s' using layerName: %s", deployment.Name, layerName)
if deploymentTagsMatch(deployment, envName, layerName) {
if projectName == "" || projectTagHas {
return []*azapi.ResourceDeployment{deployment}, nil
}

// If layerName is empty, we match on the envName alone
if layerName == "" && !layerTagHas {
log.Printf("completedDeployments: matched deployment '%s' using envName", deployment.Name)
return []*azapi.ResourceDeployment{deployment}, nil
if legacyDeployment == nil {
legacyDeployment = deployment
}
continue
}

// LEGACY: match on deployment name
if deployment.Name == envName {
return []*azapi.ResourceDeployment{deployment}, nil
if (projectName == "" || !projectTagHas) && deployment.Name == envName && legacyDeployment == nil {
legacyDeployment = deployment
continue
}

// Fallback: Match on hint
Expand All @@ -143,9 +147,36 @@ func (dm *DeploymentManager) CompletedDeployments(
}
}

if legacyDeployment != nil {
return []*azapi.ResourceDeployment{legacyDeployment}, nil
}

if len(matchingDeployments) == 0 {
return nil, fmt.Errorf("'%s': %w", hint, ErrDeploymentsNotFound)
}

return matchingDeployments, nil
}

func deploymentTag(deployment *azapi.ResourceDeployment, key string) (string, bool) {
value, hasTag := deployment.Tags[key]
if !hasTag || value == nil {
return "", false
}

return *value, true
}

func deploymentTagsMatch(deployment *azapi.ResourceDeployment, envName string, layerName string) bool {
envTag, hasEnvTag := deploymentTag(deployment, azure.TagKeyAzdEnvName)
if !hasEnvTag || envTag != envName {
return false
}

layerTag, hasLayerTag := deploymentTag(deployment, azure.TagKeyAzdLayerName)
if hasLayerTag {
return layerTag == layerName
}

return layerName == ""
}
Loading
Loading