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

Groundwork for Custom Timeouts #4475

Merged
merged 7 commits into from
Oct 4, 2019
11 changes: 9 additions & 2 deletions azurerm/internal/common/client_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/Azure/go-autorest/autorest/azure"
"github.com/hashicorp/go-azure-helpers/sender"
"github.com/hashicorp/terraform/httpclient"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/features"
"github.com/terraform-providers/terraform-provider-azurerm/version"
)

Expand All @@ -27,22 +28,28 @@ type ClientOptions struct {
ResourceManagerEndpoint string
StorageAuthorizer autorest.Authorizer

PollingDuration time.Duration
SkipProviderReg bool
DisableCorrelationRequestID bool
Environment azure.Environment

// TODO: remove me in 2.0
PollingDuration time.Duration
}

func (o ClientOptions) ConfigureClient(c *autorest.Client, authorizer autorest.Authorizer) {
setUserAgent(c, o.TerraformVersion, o.PartnerId)

c.Authorizer = authorizer
c.Sender = sender.BuildSender("AzureRM")
c.PollingDuration = o.PollingDuration
c.SkipResourceProviderRegistration = o.SkipProviderReg
if !o.DisableCorrelationRequestID {
c.RequestInspector = WithCorrelationRequestID(CorrelationRequestID())
}

// TODO: remove in 2.0
if !features.SupportsCustomTimeouts() {
c.PollingDuration = o.PollingDuration
}
}

func setUserAgent(client *autorest.Client, tfVersion, partnerID string) {
Expand Down
22 changes: 22 additions & 0 deletions azurerm/internal/features/custom_timeouts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package features

import (
"os"
"strings"
)

// SupportsCustomTimeouts returns whether Custom Timeouts are supported
//
// This feature allows Resources to define Custom Timeouts for Creation, Updating and Deletion
// which helps work with Azure resources that take longer to provision/delete.
// When this feature is disabled, all resources have a hard-coded timeout of 3 hours.
//
// This feature-toggle defaults to off in 1.x versions of the Azure Provider, however this will
// become the default behaviour in version 2.0 of the Azure Provider. As outlined in the announcement
// for v2.0 of the Azure Provider: https://github.com/terraform-providers/terraform-provider-azurerm/issues/2807
//
// Operators wishing to adopt this behaviour can opt-into this behaviour in 1.x versions of the
// Azure Provider by setting the Environment Variable 'ARM_PROVIDER_CUSTOM_TIMEOUTS' to 'true'
func SupportsCustomTimeouts() bool {
return strings.EqualFold(os.Getenv("ARM_PROVIDER_CUSTOM_TIMEOUTS"), "true")
}
55 changes: 55 additions & 0 deletions azurerm/internal/features/custom_timeouts_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package features

import (
"os"
"testing"
)

func TestCustomTimeouts(t *testing.T) {
testData := []struct {
name string
value string
expected bool
}{
{
name: "unset",
value: "",
expected: false,
},
{
name: "disabled lower-case",
value: "false",
expected: false,
},
{
name: "disabled upper-case",
value: "FALSE",
expected: false,
},
{
name: "enabled lower-case",
value: "true",
expected: true,
},
{
name: "enabled upper-case",
value: "TRUE",
expected: true,
},
{
name: "invalid",
value: "pandas",
expected: false,
},
}

for _, v := range testData {
t.Logf("[DEBUG] Test %q..", v.name)

os.Setenv("ARM_PROVIDER_CUSTOM_TIMEOUTS", v.value)
actual := SupportsCustomTimeouts()
if v.expected != actual {
t.Fatalf("Expected %t but got %t", v.expected, actual)
}
}
}
66 changes: 66 additions & 0 deletions azurerm/internal/timeouts/determine.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package timeouts

import (
"context"
"time"

"github.com/hashicorp/terraform/helper/schema"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/features"
)

// TODO: tests for this

// ForCreate returns the context wrapped with the timeout for an Create operation
//
// If the 'SupportsCustomTimeouts' feature toggle is enabled - this is wrapped with a context
// Otherwise this returns the default context
func ForCreate(ctx context.Context, d *schema.ResourceData) (context.Context, context.CancelFunc) {
return buildWithTimeout(ctx, d.Timeout(schema.TimeoutCreate))
}

// ForCreateUpdate returns the context wrapped with the timeout for an combined Create/Update operation
//
// If the 'SupportsCustomTimeouts' feature toggle is enabled - this is wrapped with a context
// Otherwise this returns the default context
func ForCreateUpdate(ctx context.Context, d *schema.ResourceData) (context.Context, context.CancelFunc) {
if d.IsNewResource() {
return ForCreate(ctx, d)
}

return ForUpdate(ctx, d)
}

// ForDelete returns the context wrapped with the timeout for an Delete operation
//
// If the 'SupportsCustomTimeouts' feature toggle is enabled - this is wrapped with a context
// Otherwise this returns the default context
func ForDelete(ctx context.Context, d *schema.ResourceData) (context.Context, context.CancelFunc) {
return buildWithTimeout(ctx, d.Timeout(schema.TimeoutDelete))
}

// ForRead returns the context wrapped with the timeout for an Read operation
//
// If the 'SupportsCustomTimeouts' feature toggle is enabled - this is wrapped with a context
// Otherwise this returns the default context
func ForRead(ctx context.Context, d *schema.ResourceData) (context.Context, context.CancelFunc) {
return buildWithTimeout(ctx, d.Timeout(schema.TimeoutRead))
}

// ForUpdate returns the context wrapped with the timeout for an Update operation
//
// If the 'SupportsCustomTimeouts' feature toggle is enabled - this is wrapped with a context
// Otherwise this returns the default context
func ForUpdate(ctx context.Context, d *schema.ResourceData) (context.Context, context.CancelFunc) {
return buildWithTimeout(ctx, d.Timeout(schema.TimeoutUpdate))
}

func buildWithTimeout(ctx context.Context, timeout time.Duration) (context.Context, context.CancelFunc) {
if features.SupportsCustomTimeouts() {
return context.WithTimeout(ctx, timeout)
}

nullFunc := func() {
// do nothing on cancel since timeouts aren't enabled
}
return ctx, nullFunc
}
25 changes: 25 additions & 0 deletions azurerm/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import (
"log"
"os"
"strings"
"time"

"github.com/hashicorp/go-azure-helpers/authentication"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/features"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/common"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/compute"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils"
Expand Down Expand Up @@ -476,6 +478,29 @@ func Provider() terraform.ResourceProvider {
}
}

// TODO: remove all of this in 2.0 once Custom Timeouts are supported
if features.SupportsCustomTimeouts() {
// default everything to 3 hours for now
for _, v := range resources {
if v.Timeouts == nil {
v.Timeouts = &schema.ResourceTimeout{
Create: schema.DefaultTimeout(3 * time.Hour),
Update: schema.DefaultTimeout(3 * time.Hour),
Delete: schema.DefaultTimeout(3 * time.Hour),
Default: schema.DefaultTimeout(3 * time.Hour),

// Read is the only exception, since if it's taken more than 5 minutes something's seriously wrong
Read: schema.DefaultTimeout(5 * time.Minute),
}
}
}
} else {
// ensure any timeouts configured on the resources are removed until 2.0
for _, v := range resources {
v.Timeouts = nil
}
}

p := &schema.Provider{
Schema: map[string]*schema.Schema{
"subscription_id": {
Expand Down
10 changes: 7 additions & 3 deletions azurerm/resource_arm_resource_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/features"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tags"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts"

"github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2018-05-01/resources"
"github.com/hashicorp/terraform/helper/schema"
Expand Down Expand Up @@ -37,7 +38,8 @@ func resourceArmResourceGroup() *schema.Resource {

func resourceArmResourceGroupCreateUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient).resource.GroupsClient
ctx := meta.(*ArmClient).StopContext
ctx, cancel := timeouts.ForCreateUpdate(meta.(*ArmClient).StopContext, d)
defer cancel()

name := d.Get("name").(string)
location := azure.NormalizeLocation(d.Get("location").(string))
Expand Down Expand Up @@ -77,7 +79,8 @@ func resourceArmResourceGroupCreateUpdate(d *schema.ResourceData, meta interface

func resourceArmResourceGroupRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient).resource.GroupsClient
ctx := meta.(*ArmClient).StopContext
ctx, cancel := timeouts.ForRead(meta.(*ArmClient).StopContext, d)
defer cancel()

id, err := azure.ParseAzureResourceID(d.Id())
if err != nil {
Expand Down Expand Up @@ -106,7 +109,8 @@ func resourceArmResourceGroupRead(d *schema.ResourceData, meta interface{}) erro

func resourceArmResourceGroupDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient).resource.GroupsClient
ctx := meta.(*ArmClient).StopContext
ctx, cancel := timeouts.ForDelete(meta.(*ArmClient).StopContext, d)
defer cancel()

id, err := azure.ParseAzureResourceID(d.Id())
if err != nil {
Expand Down