Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow subscription cancellation to be disabled via a feature flag #19936

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
3 changes: 3 additions & 0 deletions internal/features/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,8 @@ func Default() UserFeatures {
RollInstancesWhenRequired: true,
ScaleToZeroOnDelete: true,
},
Subscription: SubscriptionFeatures{
PreventCancellationOnDestroy: false,
},
}
}
5 changes: 5 additions & 0 deletions internal/features/user_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type UserFeatures struct {
LogAnalyticsWorkspace LogAnalyticsWorkspaceFeatures
ResourceGroup ResourceGroupFeatures
ManagedDisk ManagedDiskFeatures
Subscription SubscriptionFeatures
}

type CognitiveAccountFeatures struct {
Expand Down Expand Up @@ -74,3 +75,7 @@ type AppConfigurationFeatures struct {
PurgeSoftDeleteOnDestroy bool
RecoverSoftDeleted bool
}

type SubscriptionFeatures struct {
PreventCancellationOnDestroy bool
}
25 changes: 25 additions & 0 deletions internal/provider/features.go
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,21 @@ func schemaFeatures(supportLegacyTestSuite bool) *pluginsdk.Schema {
},
},
},

"subscription": {
Type: pluginsdk.TypeList,
Optional: true,
MaxItems: 1,
Elem: &pluginsdk.Resource{
Schema: map[string]*pluginsdk.Schema{
"prevent_cancellation_on_destroy": {
Type: pluginsdk.TypeBool,
Optional: true,
Default: false,
},
},
},
},
}

// this is a temporary hack to enable us to gradually add provider blocks to test configurations
Expand Down Expand Up @@ -455,5 +470,15 @@ func expandFeatures(input []interface{}) features.UserFeatures {
}
}

if raw, ok := val["subscription"]; ok {
items := raw.([]interface{})
if len(items) > 0 {
subscriptionRaw := items[0].(map[string]interface{})
if v, ok := subscriptionRaw["prevent_cancellation_on_destroy"]; ok {
featuresMap.Subscription.PreventCancellationOnDestroy = v.(bool)
}
}
}

return featuresMap
}
67 changes: 67 additions & 0 deletions internal/provider/features_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ func TestExpandFeatures(t *testing.T) {
ResourceGroup: features.ResourceGroupFeatures{
PreventDeletionIfContainsResources: true,
},
Subscription: features.SubscriptionFeatures{
PreventCancellationOnDestroy: false,
},
},
},
{
Expand Down Expand Up @@ -124,6 +127,11 @@ func TestExpandFeatures(t *testing.T) {
"prevent_deletion_if_contains_resources": true,
},
},
"subscription": []interface{}{
map[string]interface{}{
"prevent_cancellation_on_destroy": true,
},
},
"template_deployment": []interface{}{
map[string]interface{}{
"delete_nested_items_during_deletion": true,
Expand Down Expand Up @@ -180,6 +188,9 @@ func TestExpandFeatures(t *testing.T) {
ResourceGroup: features.ResourceGroupFeatures{
PreventDeletionIfContainsResources: true,
},
Subscription: features.SubscriptionFeatures{
PreventCancellationOnDestroy: true,
},
TemplateDeployment: features.TemplateDeploymentFeatures{
DeleteNestedItemsDuringDeletion: true,
},
Expand Down Expand Up @@ -249,6 +260,11 @@ func TestExpandFeatures(t *testing.T) {
"prevent_deletion_if_contains_resources": false,
},
},
"subscription": []interface{}{
map[string]interface{}{
"prevent_cancellation_on_destroy": false,
},
},
"template_deployment": []interface{}{
map[string]interface{}{
"delete_nested_items_during_deletion": false,
Expand Down Expand Up @@ -305,6 +321,9 @@ func TestExpandFeatures(t *testing.T) {
ResourceGroup: features.ResourceGroupFeatures{
PreventDeletionIfContainsResources: false,
},
Subscription: features.SubscriptionFeatures{
PreventCancellationOnDestroy: false,
},
TemplateDeployment: features.TemplateDeploymentFeatures{
DeleteNestedItemsDuringDeletion: false,
},
Expand Down Expand Up @@ -1196,3 +1215,51 @@ func TestExpandFeaturesManagedDisk(t *testing.T) {
}
}
}

func TestExpandFeaturesSubscription(t *testing.T) {
testData := []struct {
Name string
Input []interface{}
EnvVars map[string]interface{}
Expected features.UserFeatures
}{
{
Name: "Empty Block",
Input: []interface{}{
map[string]interface{}{
"subscription": []interface{}{},
},
},
Expected: features.UserFeatures{
Subscription: features.SubscriptionFeatures{
PreventCancellationOnDestroy: false,
},
},
},
{
Name: "No Downtime Resize Enabled",
Input: []interface{}{
map[string]interface{}{
"subscription": []interface{}{
map[string]interface{}{
"prevent_cancellation_on_destroy": true,
},
},
},
},
Expected: features.UserFeatures{
Subscription: features.SubscriptionFeatures{
PreventCancellationOnDestroy: true,
},
},
},
}

for _, testCase := range testData {
t.Logf("[DEBUG] Test Case: %q", testCase.Name)
result := expandFeatures(testCase.Input)
if !reflect.DeepEqual(result.Subscription, testCase.Expected.Subscription) {
t.Fatalf("Expected %+v but got %+v", result.Subscription, testCase.Expected.Subscription)
}
}
}
20 changes: 13 additions & 7 deletions internal/services/subscription/subscription_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -408,15 +408,21 @@ func resourceSubscriptionDelete(d *pluginsdk.ResourceData, meta interface{}) err
}

// Cancel the Subscription
if _, err := subscriptionClient.Cancel(ctx, subscriptionId); err != nil {
return fmt.Errorf("failed to cancel Subscription: %+v", err)
}
if !meta.(*clients.Client).Features.Subscription.PreventCancellationOnDestroy {
log.Printf("[DEBUG] Cancelling subscription %s", subscriptionId)

deadline, _ := ctx.Deadline()
deleteDeadline := time.Until(deadline)
if _, err := subscriptionClient.Cancel(ctx, subscriptionId); err != nil {
return fmt.Errorf("failed to cancel Subscription: %+v", err)
}

if err := waitForSubscriptionStateToSettle(ctx, meta.(*clients.Client), subscriptionId, "Cancelled", deleteDeadline); err != nil {
return fmt.Errorf("failed to cancel Subscription %q (Alias %q): %+v", subscriptionId, id.AliasName, err)
deadline, _ := ctx.Deadline()
deleteDeadline := time.Until(deadline)

if err := waitForSubscriptionStateToSettle(ctx, meta.(*clients.Client), subscriptionId, "Cancelled", deleteDeadline); err != nil {
return fmt.Errorf("failed to cancel Subscription %q (Alias %q): %+v", subscriptionId, id.AliasName, err)
}
} else {
log.Printf("[DEBUG] Skipping subscription %s cancellation due to feature flag.", *id)
}

return nil
Expand Down
10 changes: 10 additions & 0 deletions website/docs/guides/features-block.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ provider "azurerm" {
prevent_deletion_if_contains_resources = true
}

subscription {
prevent_cancellation_on_destroy = false
}

template_deployment {
delete_nested_items_during_deletion = true
}
Expand Down Expand Up @@ -185,6 +189,12 @@ The `resource_group` block supports the following:

---

The `subscription` block supports the following:

* `prevent_cancellation_on_destroy` - (Optional) Should the `azurerm_subscription` resource prevent a subscription to be cancelled on destroy? Defaults to `false`.

---

The `template_deployment` block supports the following:

* `delete_nested_items_during_deletion` - (Optional) Should the `azurerm_resource_group_template_deployment` resource attempt to delete resources that have been provisioned by the ARM Template, when the Resource Group Template Deployment is deleted? Defaults to `true`.
Expand Down
2 changes: 2 additions & 0 deletions website/docs/r/subscription.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ Manages an Alias for a Subscription - which adds an Alias to an existing Subscri

~> **NOTE:** It is not possible to destroy (cancel) a subscription if it contains resources. If resources are present that are not managed by Terraform then these will need to be removed before the Subscription can be destroyed.

~> **Note:** This resource will automatically attempt to cancel a subscription when it is deleted. This behavior can be disabled in the provider `features` block by setting the `prevent_cancellation_on_destroy` field to `true` within the `subscription` block.

~> **NOTE:** Azure supports Multiple Aliases per Subscription, however, to reliably manage this resource in Terraform only a single Alias is supported.

~> **NOTE:** When using this resource across tenants the `client_id` and `tenant_id` of the `provider` config block should be for the home tenant details for the SPN / User or a permissions error will likely be encountered. See [the official documentation](https://learn.microsoft.com/en-us/azure/cost-management-billing/manage/programmatically-create-subscription) for more details.
Expand Down
Loading