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
21 changes: 18 additions & 3 deletions cli/azd/pkg/infra/provisioning/bicep/bicep_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -735,15 +735,30 @@ func (p *BicepProvider) Preview(ctx context.Context) (*provisioning.DeployPrevie

var changes []*provisioning.DeploymentPreviewChange
for _, change := range deployPreviewResult.Properties.Changes {
resourceAfter := change.After.(map[string]interface{})
// Use After state if available (e.g., Create, Modify), otherwise use Before state (e.g., Delete).
// ARM returns nil for After when a resource is being deleted and nil for Before when created.
var resourceState map[string]interface{}
if change.After != nil {
resourceState, _ = change.After.(map[string]interface{})
}
if resourceState == nil && change.Before != nil {
resourceState, _ = change.Before.(map[string]interface{})
}
if resourceState == nil {
// Skip changes with no resource state information
continue
}

resourceType, _ := resourceState["type"].(string)
resourceName, _ := resourceState["name"].(string)

changes = append(changes, &provisioning.DeploymentPreviewChange{
ChangeType: provisioning.ChangeType(*change.ChangeType),
ResourceId: provisioning.Resource{
Id: *change.ResourceID,
},
ResourceType: resourceAfter["type"].(string),
Name: resourceAfter["name"].(string),
ResourceType: resourceType,
Name: resourceName,
})
}

Expand Down
97 changes: 97 additions & 0 deletions cli/azd/pkg/infra/provisioning/bicep/bicep_provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1462,3 +1462,100 @@ func TestDefaultLocationToSelectFn(t *testing.T) {
require.Nil(t, result)
})
}

func TestPreviewWithNilResourceState(t *testing.T) {
mockContext := mocks.NewMockContext(context.Background())
prepareBicepMocks(mockContext)

// Setup the WhatIf endpoint mock to return changes with nil After (simulating Delete)
// and nil Before (simulating Create)
mockContext.HttpClient.When(func(request *http.Request) bool {
return request.Method == http.MethodPost &&
strings.Contains(request.URL.Path, "/providers/Microsoft.Resources/deployments/") &&
strings.HasSuffix(request.URL.Path, "/whatIf")
}).RespondFn(func(request *http.Request) (*http.Response, error) {
// Return a WhatIfOperationResult with various scenarios
whatIfResult := armresources.WhatIfOperationResult{
Status: to.Ptr("Succeeded"),
Properties: &armresources.WhatIfOperationProperties{
Changes: []*armresources.WhatIfChange{
// Create scenario: Before is nil, After has value
{
ChangeType: to.Ptr(armresources.ChangeTypeCreate),
ResourceID: to.Ptr("/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Web/sites/app1"),
Before: nil,
After: map[string]interface{}{
"type": "Microsoft.Web/sites",
"name": "app1",
},
},
// Delete scenario: After is nil, Before has value
{
ChangeType: to.Ptr(armresources.ChangeTypeDelete),
ResourceID: to.Ptr("/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Web/sites/app2"),
Before: map[string]interface{}{
"type": "Microsoft.Web/sites",
"name": "app2",
},
After: nil,
},
// Modify scenario: Both Before and After have values
{
ChangeType: to.Ptr(armresources.ChangeTypeModify),
ResourceID: to.Ptr("/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Web/sites/app3"),
Before: map[string]interface{}{
"type": "Microsoft.Web/sites",
"name": "app3",
},
After: map[string]interface{}{
"type": "Microsoft.Web/sites",
"name": "app3",
},
},
// Edge case: Both Before and After are nil (should be skipped)
{
ChangeType: to.Ptr(armresources.ChangeTypeUnsupported),
ResourceID: to.Ptr("/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Unknown/unknown"),
Before: nil,
After: nil,
},
},
},
}

bodyBytes, _ := json.Marshal(whatIfResult)
return &http.Response{
Request: request,
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewBuffer(bodyBytes)),
}, nil
})

infraProvider := createBicepProvider(t, mockContext)

result, err := infraProvider.Preview(*mockContext.Context)

require.NoError(t, err)
require.NotNil(t, result)
require.NotNil(t, result.Preview)
require.NotNil(t, result.Preview.Properties)

// We expect 3 changes (the edge case with both nil should be skipped)
changes := result.Preview.Properties.Changes
require.Len(t, changes, 3)

// Verify Create change (uses After)
assert.Equal(t, provisioning.ChangeTypeCreate, changes[0].ChangeType)
assert.Equal(t, "Microsoft.Web/sites", changes[0].ResourceType)
assert.Equal(t, "app1", changes[0].Name)

// Verify Delete change (uses Before since After is nil)
assert.Equal(t, provisioning.ChangeTypeDelete, changes[1].ChangeType)
assert.Equal(t, "Microsoft.Web/sites", changes[1].ResourceType)
assert.Equal(t, "app2", changes[1].Name)

// Verify Modify change
assert.Equal(t, provisioning.ChangeTypeModify, changes[2].ChangeType)
assert.Equal(t, "Microsoft.Web/sites", changes[2].ResourceType)
assert.Equal(t, "app3", changes[2].Name)
}
Loading