Skip to content
Open
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
75 changes: 75 additions & 0 deletions pkg/infrastructure/azure/azure.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package azure

import (
"context"
"errors"
"fmt"
"math/rand"
"net/http"
Expand Down Expand Up @@ -828,6 +829,15 @@ func (p *Provider) PostDestroy(ctx context.Context, in clusterapi.PostDestroyerI
return fmt.Errorf("failed to delete konnectivity security rule: %w", err)
}
}

// Clean up bootstrap storage account (ignition container and optionally
// the entire account if no image gallery containers remain).
if in.Metadata.Azure.CloudName != aztypes.StackCloud {
if err := deleteBootstrapIgnition(ctx, session, opts, resourceGroupName, in.Metadata.InfraID); err != nil {
logrus.Warnf("Failed to clean up bootstrap storage: %v", err)
}
}

return nil
}

Expand Down Expand Up @@ -884,6 +894,71 @@ func randomString(length int) string {
return string(s)
}

// deleteBootstrapIgnition removes the ignition container and, if no other
// containers remain, deletes the storage account created for bootstrap.
func deleteBootstrapIgnition(ctx context.Context, session *azconfig.Session, opts *arm.ClientOptions, resourceGroupName, infraID string) error {
storageAccountName := aztypes.GetStorageAccountName(infraID)

storageClientFactory, err := armstorage.NewClientFactory(
session.Credentials.SubscriptionID,
session.TokenCreds,
opts,
)
if err != nil {
return fmt.Errorf("failed to create storage client factory: %w", err)
}

blobContainersClient := storageClientFactory.NewBlobContainersClient()
_, err = blobContainersClient.Delete(ctx, resourceGroupName, storageAccountName, "ignition", nil)
if err != nil {
if !isNotFoundError(err) {
return fmt.Errorf("failed to delete ignition container: %w", err)
}
logrus.Debugf("Ignition container already deleted from storage account %s", storageAccountName)
} else {
logrus.Infof("Deleted bootstrap ignition container from storage account %s", storageAccountName)
}

pager := blobContainersClient.NewListPager(resourceGroupName, storageAccountName, nil)
hasContainers := false
for pager.More() {
page, err := pager.NextPage(ctx)
if err != nil {
if isNotFoundError(err) {
break
}
logrus.Warnf("Failed to list containers in storage account %s, skipping account deletion: %v", storageAccountName, err)
return nil
}
Comment on lines +931 to +932

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Propagate the error when listing containers fails.

Returning nil here swallows the error and falsely reports success to the caller. Since PostDestroy safely traps errors from this helper to perform a best-effort cleanup without failing the overall process, returning the error directly ensures it is properly propagated and logged by the caller. As per path instructions, never ignore error returns.

♻️ Proposed fix to propagate the error
-			logrus.Warnf("Failed to list containers in storage account %s, skipping account deletion: %v", storageAccountName, err)
-			return nil
+			return fmt.Errorf("failed to list containers in storage account %s: %w", storageAccountName, err)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return nil
}
return fmt.Errorf("failed to list containers in storage account %s: %w", storageAccountName, err)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/infrastructure/azure/azure.go` around lines 931 - 932, Update the
container-listing error branch in the helper used by PostDestroy to return the
encountered error instead of nil, preserving the existing successful return
path. Ensure PostDestroy continues to handle the propagated error for
best-effort cleanup.

Source: Path instructions

if len(page.Value) > 0 {
hasContainers = true
break
}
}

accountsClient := storageClientFactory.NewAccountsClient()
if !hasContainers {
_, err = accountsClient.Delete(ctx, resourceGroupName, storageAccountName, nil)
if err != nil {
if isNotFoundError(err) {
logrus.Debugf("Storage account %s already deleted", storageAccountName)
return nil
}
return fmt.Errorf("failed to delete storage account %s: %w", storageAccountName, err)
}
logrus.Infof("Deleted bootstrap storage account %s", storageAccountName)
} else {
logrus.Infof("Storage account %s has remaining containers, skipping deletion (will be removed with resource group)", storageAccountName)
}

return nil
}

func isNotFoundError(err error) bool {
var respErr *azcore.ResponseError
return errors.As(err, &respErr) && respErr.StatusCode == http.StatusNotFound
}

// Ignition provisions the Azure container that holds the bootstrap ignition
// file.
func (p Provider) Ignition(ctx context.Context, in clusterapi.IgnitionInput) ([]*corev1.Secret, error) {
Expand Down