Skip to content

Commit eef92c9

Browse files
Copilotvhvb1989
andauthored
Fix nil panic in provision preview when ARM returns nil After field (#6282)
* Initial plan * Fix nil panic in Preview when ARM returns nil After field Co-authored-by: vhvb1989 <24213737+vhvb1989@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: vhvb1989 <24213737+vhvb1989@users.noreply.github.com>
1 parent e2b9ddf commit eef92c9

File tree

2 files changed

+115
-3
lines changed

2 files changed

+115
-3
lines changed

cli/azd/pkg/infra/provisioning/bicep/bicep_provider.go

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -749,15 +749,30 @@ func (p *BicepProvider) Preview(ctx context.Context) (*provisioning.DeployPrevie
749749

750750
var changes []*provisioning.DeploymentPreviewChange
751751
for _, change := range deployPreviewResult.Properties.Changes {
752-
resourceAfter := change.After.(map[string]interface{})
752+
// Use After state if available (e.g., Create, Modify), otherwise use Before state (e.g., Delete).
753+
// ARM returns nil for After when a resource is being deleted and nil for Before when created.
754+
var resourceState map[string]interface{}
755+
if change.After != nil {
756+
resourceState, _ = change.After.(map[string]interface{})
757+
}
758+
if resourceState == nil && change.Before != nil {
759+
resourceState, _ = change.Before.(map[string]interface{})
760+
}
761+
if resourceState == nil {
762+
// Skip changes with no resource state information
763+
continue
764+
}
765+
766+
resourceType, _ := resourceState["type"].(string)
767+
resourceName, _ := resourceState["name"].(string)
753768

754769
changes = append(changes, &provisioning.DeploymentPreviewChange{
755770
ChangeType: provisioning.ChangeType(*change.ChangeType),
756771
ResourceId: provisioning.Resource{
757772
Id: *change.ResourceID,
758773
},
759-
ResourceType: resourceAfter["type"].(string),
760-
Name: resourceAfter["name"].(string),
774+
ResourceType: resourceType,
775+
Name: resourceName,
761776
})
762777
}
763778

cli/azd/pkg/infra/provisioning/bicep/bicep_provider_test.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1462,3 +1462,100 @@ func TestDefaultLocationToSelectFn(t *testing.T) {
14621462
require.Nil(t, result)
14631463
})
14641464
}
1465+
1466+
func TestPreviewWithNilResourceState(t *testing.T) {
1467+
mockContext := mocks.NewMockContext(context.Background())
1468+
prepareBicepMocks(mockContext)
1469+
1470+
// Setup the WhatIf endpoint mock to return changes with nil After (simulating Delete)
1471+
// and nil Before (simulating Create)
1472+
mockContext.HttpClient.When(func(request *http.Request) bool {
1473+
return request.Method == http.MethodPost &&
1474+
strings.Contains(request.URL.Path, "/providers/Microsoft.Resources/deployments/") &&
1475+
strings.HasSuffix(request.URL.Path, "/whatIf")
1476+
}).RespondFn(func(request *http.Request) (*http.Response, error) {
1477+
// Return a WhatIfOperationResult with various scenarios
1478+
whatIfResult := armresources.WhatIfOperationResult{
1479+
Status: to.Ptr("Succeeded"),
1480+
Properties: &armresources.WhatIfOperationProperties{
1481+
Changes: []*armresources.WhatIfChange{
1482+
// Create scenario: Before is nil, After has value
1483+
{
1484+
ChangeType: to.Ptr(armresources.ChangeTypeCreate),
1485+
ResourceID: to.Ptr("/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Web/sites/app1"),
1486+
Before: nil,
1487+
After: map[string]interface{}{
1488+
"type": "Microsoft.Web/sites",
1489+
"name": "app1",
1490+
},
1491+
},
1492+
// Delete scenario: After is nil, Before has value
1493+
{
1494+
ChangeType: to.Ptr(armresources.ChangeTypeDelete),
1495+
ResourceID: to.Ptr("/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Web/sites/app2"),
1496+
Before: map[string]interface{}{
1497+
"type": "Microsoft.Web/sites",
1498+
"name": "app2",
1499+
},
1500+
After: nil,
1501+
},
1502+
// Modify scenario: Both Before and After have values
1503+
{
1504+
ChangeType: to.Ptr(armresources.ChangeTypeModify),
1505+
ResourceID: to.Ptr("/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Web/sites/app3"),
1506+
Before: map[string]interface{}{
1507+
"type": "Microsoft.Web/sites",
1508+
"name": "app3",
1509+
},
1510+
After: map[string]interface{}{
1511+
"type": "Microsoft.Web/sites",
1512+
"name": "app3",
1513+
},
1514+
},
1515+
// Edge case: Both Before and After are nil (should be skipped)
1516+
{
1517+
ChangeType: to.Ptr(armresources.ChangeTypeUnsupported),
1518+
ResourceID: to.Ptr("/subscriptions/sub/resourceGroups/rg/providers/Microsoft.Unknown/unknown"),
1519+
Before: nil,
1520+
After: nil,
1521+
},
1522+
},
1523+
},
1524+
}
1525+
1526+
bodyBytes, _ := json.Marshal(whatIfResult)
1527+
return &http.Response{
1528+
Request: request,
1529+
StatusCode: http.StatusOK,
1530+
Body: io.NopCloser(bytes.NewBuffer(bodyBytes)),
1531+
}, nil
1532+
})
1533+
1534+
infraProvider := createBicepProvider(t, mockContext)
1535+
1536+
result, err := infraProvider.Preview(*mockContext.Context)
1537+
1538+
require.NoError(t, err)
1539+
require.NotNil(t, result)
1540+
require.NotNil(t, result.Preview)
1541+
require.NotNil(t, result.Preview.Properties)
1542+
1543+
// We expect 3 changes (the edge case with both nil should be skipped)
1544+
changes := result.Preview.Properties.Changes
1545+
require.Len(t, changes, 3)
1546+
1547+
// Verify Create change (uses After)
1548+
assert.Equal(t, provisioning.ChangeTypeCreate, changes[0].ChangeType)
1549+
assert.Equal(t, "Microsoft.Web/sites", changes[0].ResourceType)
1550+
assert.Equal(t, "app1", changes[0].Name)
1551+
1552+
// Verify Delete change (uses Before since After is nil)
1553+
assert.Equal(t, provisioning.ChangeTypeDelete, changes[1].ChangeType)
1554+
assert.Equal(t, "Microsoft.Web/sites", changes[1].ResourceType)
1555+
assert.Equal(t, "app2", changes[1].Name)
1556+
1557+
// Verify Modify change
1558+
assert.Equal(t, provisioning.ChangeTypeModify, changes[2].ChangeType)
1559+
assert.Equal(t, "Microsoft.Web/sites", changes[2].ResourceType)
1560+
assert.Equal(t, "app3", changes[2].Name)
1561+
}

0 commit comments

Comments
 (0)