Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
1 change: 0 additions & 1 deletion cli/azd/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ require (
github.com/google/uuid v1.6.0
github.com/gorilla/websocket v1.5.3
github.com/joho/godotenv v1.5.1
github.com/magefile/mage v1.15.0
github.com/mark3labs/mcp-go v0.41.1
github.com/mattn/go-colorable v0.1.14
github.com/mattn/go-isatty v0.0.20
Expand Down
2 changes: 0 additions & 2 deletions cli/azd/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -284,8 +284,6 @@ github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kUL
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8=
github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/mark3labs/mcp-go v0.41.1 h1:w78eWfiQam2i8ICL7AL0WFiq7KHNJQ6UB53ZVtH4KGA=
Expand Down
18 changes: 15 additions & 3 deletions cli/azd/internal/cmd/provision.go
Original file line number Diff line number Diff line change
Expand Up @@ -447,10 +447,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 @@ -24,6 +24,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 @@ -737,13 +738,22 @@ func (p *BicepProvider) Preview(ctx context.Context) (*provisioning.DeployPrevie
for _, change := range deployPreviewResult.Properties.Changes {
resourceAfter := change.After.(map[string]interface{})

// 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: resourceAfter["type"].(string),
Name: resourceAfter["name"].(string),
Before: change.Before,
After: change.After,
Delta: delta,
})
}

Expand All @@ -757,6 +767,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
87 changes: 81 additions & 6 deletions cli/azd/pkg/output/ux/preview_provision.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,18 @@ 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 {
Expand Down Expand Up @@ -76,7 +85,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 +111,81 @@ 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)

// Add property-level changes if available
if len(op.PropertyDeltas) > 0 {
for _, delta := range op.PropertyDeltas {
propertyLine := formatPropertyChange(currentIndentation+" ", 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 := output.WithGrayFormat

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

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
40 changes: 40 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,43 @@ 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)
}
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"
114 changes: 114 additions & 0 deletions cli/azd/test/functional/testdata/preview-enhancement/TESTING.md
Comment thread
vhvb1989 marked this conversation as resolved.
Outdated
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# Manual Testing Guide for Property-Level Preview Changes

This guide explains how to manually test the enhanced `azd provision --preview` functionality.

## Test Setup

1. Navigate to this test directory:
```bash
cd /tmp/test-azd-preview
```

2. Ensure you have the modified azd binary:
```bash
cd /home/runner/work/azure-dev/azure-dev/cli/azd
go build -o /tmp/azd
```

3. Use the test azd binary:
```bash
export PATH=/tmp:$PATH
```

## Test Scenarios

### Scenario 1: Initial Deployment (Create Resources)

1. Initialize the environment:
```bash
azd env new test-env
```

2. Set required environment variables (you'll be prompted if not set):
```bash
azd env set AZURE_LOCATION eastus
```

3. Run preview (should show CREATE operations with property details):
```bash
azd provision --preview
```

**Expected Output:**
- Should show the storage account being created
- Should display property values that will be set (e.g., sku.name, minimumTlsVersion)
- Property changes should be prefixed with `+` symbol in gray/white color

### Scenario 2: Modify Existing Resources

1. After first deployment, modify main.bicep to change the SKU:
```bicep
sku: {
name: 'Standard_GRS' // Changed from Standard_LRS
}
```

2. Run preview again:
```bash
azd provision --preview
```

**Expected Output:**
- Should show MODIFY operation for the storage account
- Should display property changes with:
- `~` symbol in yellow for modified properties
- Before and after values: `"Standard_LRS" => "Standard_GRS"`

### Scenario 3: Add New Properties

1. Modify main.bicep to add a new property:
```bicep
properties: {
minimumTlsVersion: 'TLS1_2'
supportsHttpsTrafficOnly: true
allowBlobPublicAccess: false // New property
}
```

2. Run preview:
```bash
azd provision --preview
```

**Expected Output:**
- Should show MODIFY operation
- New property should appear with `+` symbol
- Existing properties that changed should show `~` symbol

## Verification Checklist

- [ ] Property changes are displayed under each resource
- [ ] Create operations show `+` symbol with property values
- [ ] Modify operations show `~` symbol with before/after values
- [ ] Delete operations show `-` symbol (if applicable)
- [ ] Colors are correctly applied (gray for create, yellow for modify, red for delete)
- [ ] Complex values (objects, arrays) are formatted as JSON
- [ ] Multiple property changes per resource are all displayed
- [ ] Output is properly indented and aligned

## Comparison with Azure Bicep what-if

To compare the output with native Bicep what-if:

```bash
cd infra
az deployment group what-if --resource-group <your-rg> --template-file main.bicep --parameters main.parameters.json
```

The azd output should now provide similar detail to the native what-if command.

## Notes

- Property-level details are only available with Bicep provider
- Terraform provider already shows plan details via `terraform plan`
- This feature requires Azure credentials and an active subscription
Loading
Loading