Skip to content

Commit

Permalink
feat: allow subscription cancellation to be disabled via a feature flag
Browse files Browse the repository at this point in the history
  • Loading branch information
josh-barker committed Jul 5, 2023
1 parent c90b2fb commit ad8f45f
Show file tree
Hide file tree
Showing 7 changed files with 120 additions and 5 deletions.
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)
}
}
}
13 changes: 8 additions & 5 deletions internal/services/subscription/subscription_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -408,15 +408,18 @@ 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)

if _, err := subscriptionClient.Cancel(ctx, subscriptionId); err != nil {
return fmt.Errorf("failed to cancel Subscription: %+v", 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

0 comments on commit ad8f45f

Please sign in to comment.