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
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package azure

import "regexp"

var ConnectionStringJSONRegex = regexp.MustCompile(`("[\w]*(?:CONNECTION_STRING|ConnectionString)":\s*)"[^"]*"`)
18 changes: 15 additions & 3 deletions cli/azd/internal/cmd/provision.go
Original file line number Diff line number Diff line change
Expand Up @@ -445,10 +445,22 @@ func (p *ProvisionAction) Run(ctx context.Context) (*actions.ActionResult, error
func deployResultToUx(previewResult *provisioning.DeployPreviewResult) ux.UxItem {
var operations []*ux.Resource
for _, change := range previewResult.Preview.Properties.Changes {
// Convert property deltas to UX format
var propertyDeltas []ux.PropertyDelta
for _, delta := range change.Delta {
propertyDeltas = append(propertyDeltas, ux.PropertyDelta{
Path: delta.Path,
ChangeType: string(delta.ChangeType),
Before: delta.Before,
After: delta.After,
})
}

operations = append(operations, &ux.Resource{
Operation: ux.OperationType(change.ChangeType),
Type: change.ResourceType,
Name: change.Name,
Operation: ux.OperationType(change.ChangeType),
Type: change.ResourceType,
Name: change.Name,
PropertyDeltas: propertyDeltas,
})
}
return &ux.PreviewProvision{
Expand Down
44 changes: 44 additions & 0 deletions cli/azd/pkg/infra/provisioning/bicep/bicep_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cognitiveservices/armcognitiveservices"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources"
"github.com/azure/azure-dev/cli/azd/pkg/account"
"github.com/azure/azure-dev/cli/azd/pkg/async"
"github.com/azure/azure-dev/cli/azd/pkg/azapi"
Expand Down Expand Up @@ -811,13 +812,22 @@ func (p *BicepProvider) Preview(ctx context.Context) (*provisioning.DeployPrevie
resourceType, _ := resourceState["type"].(string)
resourceName, _ := resourceState["name"].(string)

// Convert Delta (property-level changes) from Azure SDK format to our format
var delta []provisioning.DeploymentPreviewPropertyChange
if change.Delta != nil {
delta = convertPropertyChanges(change.Delta)
}

changes = append(changes, &provisioning.DeploymentPreviewChange{
ChangeType: provisioning.ChangeType(*change.ChangeType),
ResourceId: provisioning.Resource{
Id: *change.ResourceID,
},
ResourceType: resourceType,
Name: resourceName,
Before: change.Before,
After: change.After,
Delta: delta,
})
}

Expand All @@ -831,6 +841,40 @@ func (p *BicepProvider) Preview(ctx context.Context) (*provisioning.DeployPrevie
}, nil
}

// convertPropertyChanges converts Azure SDK's WhatIfPropertyChange to our DeploymentPreviewPropertyChange
func convertPropertyChanges(changes []*armresources.WhatIfPropertyChange) []provisioning.DeploymentPreviewPropertyChange {
if changes == nil {
return nil
}

result := make([]provisioning.DeploymentPreviewPropertyChange, 0, len(changes))
for _, change := range changes {
if change == nil {
continue
}

propertyChange := provisioning.DeploymentPreviewPropertyChange{
Path: convert.ToValueWithDefault(change.Path, ""),
Before: change.Before,
After: change.After,
}

// Convert PropertyChangeType
if change.PropertyChangeType != nil {
propertyChange.ChangeType = provisioning.PropertyChangeType(*change.PropertyChangeType)
}

// Recursively convert children if present
if change.Children != nil {
propertyChange.Children = convertPropertyChanges(change.Children)
}

result = append(result, propertyChange)
}

return result
}

type itemToPurge struct {
resourceType string
count int
Expand Down
99 changes: 91 additions & 8 deletions cli/azd/pkg/output/ux/preview_provision.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,26 @@ func (op OperationType) String() (displayName string) {

// Resource provides a basic structure for an Azure resource.
type Resource struct {
Operation OperationType
Name string
Type string
Operation OperationType
Name string
Type string
PropertyDeltas []PropertyDelta
}

// PropertyDelta represents a property-level change in a resource
type PropertyDelta struct {
Path string
ChangeType string
Before interface{}
After interface{}
}

func colorType(opType OperationType) func(string, ...interface{}) string {
var final func(format string, a ...interface{}) string
switch opType {
case OperationTypeCreate,
OperationTypeNoChange,
case OperationTypeCreate:
final = color.GreenString
case OperationTypeNoChange,
OperationTypeIgnore:
final = output.WithGrayFormat
case OperationTypeDelete:
Expand All @@ -76,7 +86,7 @@ func (pp *PreviewProvision) ToString(currentIndentation string) string {

title := currentIndentation + "Resources:"

changes := make([]string, len(pp.Operations))
var output []string
actions := make([]string, len(pp.Operations))
resources := make([]string, len(pp.Operations))

Expand All @@ -102,15 +112,88 @@ func (pp *PreviewProvision) ToString(currentIndentation string) string {
}

for index, op := range pp.Operations {
changes[index] = fmt.Sprintf("%s%s %s %s",
resourceLine := fmt.Sprintf("%s%s %s %s",
currentIndentation,
colorType(op.Operation)(actions[index]),
resources[index],
op.Name,
)
output = append(output, resourceLine)

// Only show property-level changes for resources that are being created, modified, or deleted
// Skip showing properties for NoChange/Ignore operations
if len(op.PropertyDeltas) > 0 &&
op.Operation != OperationTypeNoChange &&
op.Operation != OperationTypeIgnore {
// Calculate indentation to align with resource name (after the second colon)
// Find the position of the second colon in the resource line
propertyIndent := currentIndentation + " "

for _, delta := range op.PropertyDeltas {
propertyLine := formatPropertyChange(propertyIndent, delta)
output = append(output, propertyLine)
}
}
}

return fmt.Sprintf("%s\n\n%s", title, strings.Join(output, "\n"))
}

// formatPropertyChange formats a single property change for display
func formatPropertyChange(indent string, delta PropertyDelta) string {
changeSymbol := ""
changeColor := func(format string, a ...interface{}) string { return fmt.Sprintf(format, a...) }

switch delta.ChangeType {
case "Create":
changeSymbol = "+"
changeColor = color.GreenString
case "Delete":
changeSymbol = "-"
changeColor = color.RedString
case "Modify":
changeSymbol = "~"
changeColor = color.YellowString
case "Array":
changeSymbol = "*"
changeColor = color.CyanString
}

return fmt.Sprintf("%s\n\n%s", title, strings.Join(changes, "\n"))
// Format values for display
beforeStr := formatValue(delta.Before)
afterStr := formatValue(delta.After)

if delta.ChangeType == "Modify" {
return changeColor("%s%s %s: %s => %s", indent, changeSymbol, delta.Path, beforeStr, afterStr)
} else if delta.ChangeType == "Create" {
return changeColor("%s%s %s: %s", indent, changeSymbol, delta.Path, afterStr)
} else if delta.ChangeType == "Delete" {
return changeColor("%s%s %s", indent, changeSymbol, delta.Path)
} else {
// Array or other types
return changeColor("%s%s %s", indent, changeSymbol, delta.Path)
}
}

// formatValue formats a value for display (handling various types)
func formatValue(value interface{}) string {
if value == nil {
return "(null)"
}

switch v := value.(type) {
case string:
return fmt.Sprintf("\"%s\"", v)
case map[string]interface{}, []interface{}:
// For complex types, use a JSON-like representation
data, err := json.Marshal(v)
if err != nil {
return fmt.Sprintf("%v", v)
}
return string(data)
default:
return fmt.Sprintf("%v", v)
}
}

func (pp *PreviewProvision) MarshalJSON() ([]byte, error) {
Expand Down
89 changes: 89 additions & 0 deletions cli/azd/pkg/output/ux/preview_provision_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,92 @@ func TestPreviewProvisionNoChanges(t *testing.T) {
output := pp.ToString(" ")
require.Equal(t, "", output)
}

func TestPreviewProvisionWithPropertyChanges(t *testing.T) {
pp := &PreviewProvision{
Operations: []*Resource{
{
Type: "Microsoft.Storage/storageAccounts",
Name: "mystorageaccount",
Operation: OperationTypeModify,
PropertyDeltas: []PropertyDelta{
{
Path: "properties.sku.name",
ChangeType: "Modify",
Before: "Standard_LRS",
After: "Premium_LRS",
},
{
Path: "properties.minimumTlsVersion",
ChangeType: "Create",
After: "TLS1_2",
},
},
},
{
Type: "Microsoft.KeyVault/vaults",
Name: "mykeyvault",
Operation: OperationTypeCreate,
PropertyDeltas: []PropertyDelta{
{
Path: "properties.sku.name",
ChangeType: "Create",
After: "standard",
},
},
},
},
}

output := pp.ToString(" ")
snapshot.SnapshotT(t, output)
}

func TestPreviewProvisionSkipHidesProperties(t *testing.T) {
pp := &PreviewProvision{
Operations: []*Resource{
{
Type: "Microsoft.Storage/storageAccounts",
Name: "mystorageaccount",
Operation: OperationTypeModify,
PropertyDeltas: []PropertyDelta{
{
Path: "properties.sku.name",
ChangeType: "Modify",
Before: "Standard_LRS",
After: "Premium_LRS",
},
},
},
{
Type: "Microsoft.KeyVault/vaults",
Name: "skippedvault",
Operation: OperationTypeIgnore,
PropertyDeltas: []PropertyDelta{
{
Path: "properties.sku.name",
ChangeType: "NoEffect",
Before: "standard",
After: "standard",
},
},
},
{
Type: "Microsoft.Network/virtualNetworks",
Name: "unchangedvnet",
Operation: OperationTypeNoChange,
PropertyDeltas: []PropertyDelta{
{
Path: "properties.addressSpace",
ChangeType: "NoEffect",
Before: "10.0.0.0/16",
After: "10.0.0.0/16",
},
},
},
},
}

output := pp.ToString(" ")
snapshot.SnapshotT(t, output)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Resources:

Modify : Microsoft.Storage/storageAccounts : mystorageaccount
~ properties.sku.name: "Standard_LRS" => "Premium_LRS"
Skip : Microsoft.KeyVault/vaults : skippedvault
Skip : Microsoft.Network/virtualNetworks : unchangedvnet
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Resources:

Modify : Microsoft.Storage/storageAccounts : mystorageaccount
~ properties.sku.name: "Standard_LRS" => "Premium_LRS"
+ properties.minimumTlsVersion: "TLS1_2"
Create : Microsoft.KeyVault/vaults : mykeyvault
+ properties.sku.name: "standard"
Loading
Loading