helper/schema: Add configurable Timeouts (#12311)
* helper/schema: Add custom Timeout block for resources * refactor DefaultTimeout to suuport multiple types. Load meta in Refresh from Instance State * update vpc but it probably wont last anyway * refactor test into table test for more cases * rename constant keys * refactor configdecode * remove VPC demo * remove comments * remove more comments * refactor some * rename timeKeys to timeoutKeys * remove note * documentation/resources: Document the Timeout block * document timeouts * have a test case that covers 'hours' * restore a System default timeout of 20 minutes, instead of 0 * restore system default timeout of 20 minutes, refactor tests, add test method to handle system default * rename timeout key constants * test applying timeout to state * refactor test * Add resource Diff test * clarify docs * update to use constants
This commit is contained in:
parent
e5e37b0025
commit
2fe5976aec
|
@ -25,6 +25,12 @@ func resourceAwsDbInstance() *schema.Resource {
|
|||
State: resourceAwsDbInstanceImport,
|
||||
},
|
||||
|
||||
Timeouts: &schema.ResourceTimeout{
|
||||
Create: schema.DefaultTimeout(40 * time.Minute),
|
||||
Update: schema.DefaultTimeout(80 * time.Minute),
|
||||
Delete: schema.DefaultTimeout(40 * time.Minute),
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
"name": {
|
||||
Type: schema.TypeString,
|
||||
|
@ -480,7 +486,7 @@ func resourceAwsDbInstanceCreate(d *schema.ResourceData, meta interface{}) error
|
|||
"maintenance", "renaming", "rebooting", "upgrading"},
|
||||
Target: []string{"available"},
|
||||
Refresh: resourceAwsDbInstanceStateRefreshFunc(d, meta),
|
||||
Timeout: 40 * time.Minute,
|
||||
Timeout: d.Timeout(schema.TimeoutCreate),
|
||||
MinTimeout: 10 * time.Second,
|
||||
Delay: 30 * time.Second, // Wait 30 secs before starting
|
||||
}
|
||||
|
@ -638,7 +644,7 @@ func resourceAwsDbInstanceCreate(d *schema.ResourceData, meta interface{}) error
|
|||
"maintenance", "renaming", "rebooting", "upgrading", "configuring-enhanced-monitoring"},
|
||||
Target: []string{"available"},
|
||||
Refresh: resourceAwsDbInstanceStateRefreshFunc(d, meta),
|
||||
Timeout: 40 * time.Minute,
|
||||
Timeout: d.Timeout(schema.TimeoutCreate),
|
||||
MinTimeout: 10 * time.Second,
|
||||
Delay: 30 * time.Second, // Wait 30 secs before starting
|
||||
}
|
||||
|
@ -811,7 +817,7 @@ func resourceAwsDbInstanceDelete(d *schema.ResourceData, meta interface{}) error
|
|||
"modifying", "deleting", "available"},
|
||||
Target: []string{},
|
||||
Refresh: resourceAwsDbInstanceStateRefreshFunc(d, meta),
|
||||
Timeout: 40 * time.Minute,
|
||||
Timeout: d.Timeout(schema.TimeoutDelete),
|
||||
MinTimeout: 10 * time.Second,
|
||||
Delay: 30 * time.Second, // Wait 30 secs before starting
|
||||
}
|
||||
|
@ -978,7 +984,7 @@ func resourceAwsDbInstanceUpdate(d *schema.ResourceData, meta interface{}) error
|
|||
"maintenance", "renaming", "rebooting", "upgrading", "configuring-enhanced-monitoring", "moving-to-vpc"},
|
||||
Target: []string{"available"},
|
||||
Refresh: resourceAwsDbInstanceStateRefreshFunc(d, meta),
|
||||
Timeout: 80 * time.Minute,
|
||||
Timeout: d.Timeout(schema.TimeoutUpdate),
|
||||
MinTimeout: 10 * time.Second,
|
||||
Delay: 30 * time.Second, // Wait 30 secs before starting
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package schema
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
|
@ -94,6 +95,15 @@ type Resource struct {
|
|||
// This is a private interface for now, for use by DataSourceResourceShim,
|
||||
// and not for general use. (But maybe later...)
|
||||
deprecationMessage string
|
||||
|
||||
// Timeouts allow users to specify specific time durations in which an
|
||||
// operation should time out, to allow them to extend an action to suit their
|
||||
// usage. For example, a user may specify a large Creation timeout for their
|
||||
// AWS RDS Instance due to it's size, or restoring from a snapshot.
|
||||
// Resource implementors must enable Timeout support by adding the allowed
|
||||
// actions (Create, Read, Update, Delete, Default) to the Resource struct, and
|
||||
// accessing them in the matching methods.
|
||||
Timeouts *ResourceTimeout
|
||||
}
|
||||
|
||||
// See Resource documentation.
|
||||
|
@ -125,6 +135,18 @@ func (r *Resource) Apply(
|
|||
return s, err
|
||||
}
|
||||
|
||||
// Instance Diff shoould have the timeout info, need to copy it over to the
|
||||
// ResourceData meta
|
||||
rt := ResourceTimeout{}
|
||||
if _, ok := d.Meta[TimeoutKey]; ok {
|
||||
if err := rt.DiffDecode(d); err != nil {
|
||||
log.Printf("[ERR] Error decoding ResourceTimeout: %s", err)
|
||||
}
|
||||
} else {
|
||||
log.Printf("[DEBUG] No meta timeoutkey found in Apply()")
|
||||
}
|
||||
data.timeouts = &rt
|
||||
|
||||
if s == nil {
|
||||
// The Terraform API dictates that this should never happen, but
|
||||
// it doesn't hurt to be safe in this case.
|
||||
|
@ -150,6 +172,8 @@ func (r *Resource) Apply(
|
|||
|
||||
// Reset the data to be stateless since we just destroyed
|
||||
data, err = schemaMap(r.Schema).Data(nil, d)
|
||||
// data was reset, need to re-apply the parsed timeouts
|
||||
data.timeouts = &rt
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -176,7 +200,28 @@ func (r *Resource) Apply(
|
|||
func (r *Resource) Diff(
|
||||
s *terraform.InstanceState,
|
||||
c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) {
|
||||
return schemaMap(r.Schema).Diff(s, c)
|
||||
|
||||
t := &ResourceTimeout{}
|
||||
err := t.ConfigDecode(r, c)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[ERR] Error decoding timeout: %s", err)
|
||||
}
|
||||
|
||||
instanceDiff, err := schemaMap(r.Schema).Diff(s, c)
|
||||
if err != nil {
|
||||
return instanceDiff, err
|
||||
}
|
||||
|
||||
if instanceDiff != nil {
|
||||
if err := t.DiffEncode(instanceDiff); err != nil {
|
||||
log.Printf("[ERR] Error encoding timeout to instance diff: %s", err)
|
||||
}
|
||||
} else {
|
||||
log.Printf("[DEBUG] Instance Diff is nil in Diff()")
|
||||
}
|
||||
|
||||
return instanceDiff, err
|
||||
}
|
||||
|
||||
// Validate validates the resource configuration against the schema.
|
||||
|
@ -226,10 +271,19 @@ func (r *Resource) Refresh(
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
rt := ResourceTimeout{}
|
||||
if _, ok := s.Meta[TimeoutKey]; ok {
|
||||
if err := rt.StateDecode(s); err != nil {
|
||||
log.Printf("[ERR] Error decoding ResourceTimeout: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if r.Exists != nil {
|
||||
// Make a copy of data so that if it is modified it doesn't
|
||||
// affect our Read later.
|
||||
data, err := schemaMap(r.Schema).Data(s, nil)
|
||||
data.timeouts = &rt
|
||||
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
@ -252,6 +306,7 @@ func (r *Resource) Refresh(
|
|||
}
|
||||
|
||||
data, err := schemaMap(r.Schema).Data(s, nil)
|
||||
data.timeouts = &rt
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
@ -19,11 +20,12 @@ import (
|
|||
// The most relevant methods to take a look at are Get, Set, and Partial.
|
||||
type ResourceData struct {
|
||||
// Settable (internally)
|
||||
schema map[string]*Schema
|
||||
config *terraform.ResourceConfig
|
||||
state *terraform.InstanceState
|
||||
diff *terraform.InstanceDiff
|
||||
meta map[string]interface{}
|
||||
schema map[string]*Schema
|
||||
config *terraform.ResourceConfig
|
||||
state *terraform.InstanceState
|
||||
diff *terraform.InstanceDiff
|
||||
meta map[string]interface{}
|
||||
timeouts *ResourceTimeout
|
||||
|
||||
// Don't set
|
||||
multiReader *MultiLevelFieldReader
|
||||
|
@ -250,6 +252,12 @@ func (d *ResourceData) State() *terraform.InstanceState {
|
|||
return nil
|
||||
}
|
||||
|
||||
if d.timeouts != nil {
|
||||
if err := d.timeouts.StateEncode(&result); err != nil {
|
||||
log.Printf("[ERR] Error encoding Timeout meta to Instance State: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Look for a magic key in the schema that determines we skip the
|
||||
// integrity check of fields existing in the schema, allowing dynamic
|
||||
// keys to be created.
|
||||
|
@ -331,6 +339,35 @@ func (d *ResourceData) State() *terraform.InstanceState {
|
|||
return &result
|
||||
}
|
||||
|
||||
// Timeout returns the data for the given timeout key
|
||||
// Returns a duration of 20 minutes for any key not found, or not found and no default.
|
||||
func (d *ResourceData) Timeout(key string) time.Duration {
|
||||
key = strings.ToLower(key)
|
||||
|
||||
var timeout *time.Duration
|
||||
switch key {
|
||||
case TimeoutCreate:
|
||||
timeout = d.timeouts.Create
|
||||
case TimeoutRead:
|
||||
timeout = d.timeouts.Read
|
||||
case TimeoutUpdate:
|
||||
timeout = d.timeouts.Update
|
||||
case TimeoutDelete:
|
||||
timeout = d.timeouts.Delete
|
||||
}
|
||||
|
||||
if timeout != nil {
|
||||
return *timeout
|
||||
}
|
||||
|
||||
if d.timeouts.Default != nil {
|
||||
return *d.timeouts.Default
|
||||
}
|
||||
|
||||
// Return system default of 20 minutes
|
||||
return 20 * time.Minute
|
||||
}
|
||||
|
||||
func (d *ResourceData) init() {
|
||||
// Initialize the field that will store our new state
|
||||
var copyState terraform.InstanceState
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
package schema
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
@ -1080,6 +1082,78 @@ func TestResourceDataGetOk(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestResourceDataTimeout(t *testing.T) {
|
||||
cases := []struct {
|
||||
Name string
|
||||
Rd *ResourceData
|
||||
Expected *ResourceTimeout
|
||||
}{
|
||||
{
|
||||
Name: "Basic example default",
|
||||
Rd: &ResourceData{timeouts: timeoutForValues(10, 3, 0, 15, 0)},
|
||||
Expected: expectedTimeoutForValues(10, 3, 0, 15, 0),
|
||||
},
|
||||
{
|
||||
Name: "Resource and config match update, create",
|
||||
Rd: &ResourceData{timeouts: timeoutForValues(10, 0, 3, 0, 0)},
|
||||
Expected: expectedTimeoutForValues(10, 0, 3, 0, 0),
|
||||
},
|
||||
{
|
||||
Name: "Resource provides default",
|
||||
Rd: &ResourceData{timeouts: timeoutForValues(10, 0, 0, 0, 7)},
|
||||
Expected: expectedTimeoutForValues(10, 7, 7, 7, 7),
|
||||
},
|
||||
{
|
||||
Name: "Resource provides default and delete",
|
||||
Rd: &ResourceData{timeouts: timeoutForValues(10, 0, 0, 15, 7)},
|
||||
Expected: expectedTimeoutForValues(10, 7, 7, 15, 7),
|
||||
},
|
||||
{
|
||||
Name: "Resource provides default, config overwrites other values",
|
||||
Rd: &ResourceData{timeouts: timeoutForValues(10, 3, 0, 0, 13)},
|
||||
Expected: expectedTimeoutForValues(10, 3, 13, 13, 13),
|
||||
},
|
||||
}
|
||||
|
||||
keys := timeoutKeys()
|
||||
for i, c := range cases {
|
||||
t.Run(fmt.Sprintf("%d-%s", i, c.Name), func(t *testing.T) {
|
||||
|
||||
for _, k := range keys {
|
||||
got := c.Rd.Timeout(k)
|
||||
var ex *time.Duration
|
||||
switch k {
|
||||
case TimeoutCreate:
|
||||
ex = c.Expected.Create
|
||||
case TimeoutRead:
|
||||
ex = c.Expected.Read
|
||||
case TimeoutUpdate:
|
||||
ex = c.Expected.Update
|
||||
case TimeoutDelete:
|
||||
ex = c.Expected.Delete
|
||||
case TimeoutDefault:
|
||||
ex = c.Expected.Default
|
||||
}
|
||||
|
||||
if got > 0 && ex == nil {
|
||||
t.Fatalf("Unexpected value in (%s), case %d check 1:\n\texpected: %#v\n\tgot: %#v", k, i, ex, got)
|
||||
}
|
||||
if got == 0 && ex != nil {
|
||||
t.Fatalf("Unexpected value in (%s), case %d check 2:\n\texpected: %#v\n\tgot: %#v", k, i, *ex, got)
|
||||
}
|
||||
|
||||
// confirm values
|
||||
if ex != nil {
|
||||
if got != *ex {
|
||||
t.Fatalf("Timeout %s case (%d) expected (%#v), got (%#v)", k, i, *ex, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceDataHasChange(t *testing.T) {
|
||||
cases := []struct {
|
||||
Schema map[string]*Schema
|
||||
|
@ -3081,6 +3155,24 @@ func TestResourceDataSetConnInfo(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestResourceDataSetMeta_Timeouts(t *testing.T) {
|
||||
d := &ResourceData{}
|
||||
d.SetId("foo")
|
||||
|
||||
rt := ResourceTimeout{
|
||||
Create: DefaultTimeout(7 * time.Minute),
|
||||
}
|
||||
|
||||
d.timeouts = &rt
|
||||
|
||||
expected := expectedForValues(7, 0, 0, 0, 0)
|
||||
|
||||
actual := d.State()
|
||||
if !reflect.DeepEqual(actual.Meta[TimeoutKey], expected) {
|
||||
t.Fatalf("Bad Meta_timeout match:\n\texpected: %#v\n\tgot: %#v", expected, actual.Meta[TimeoutKey])
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceDataSetId(t *testing.T) {
|
||||
d := &ResourceData{}
|
||||
d.SetId("foo")
|
||||
|
|
|
@ -5,7 +5,9 @@ import (
|
|||
"reflect"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
|
@ -62,6 +64,138 @@ func TestResourceApply_create(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestResourceApply_Timeout_state(t *testing.T) {
|
||||
r := &Resource{
|
||||
SchemaVersion: 2,
|
||||
Schema: map[string]*Schema{
|
||||
"foo": &Schema{
|
||||
Type: TypeInt,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
Timeouts: &ResourceTimeout{
|
||||
Create: DefaultTimeout(40 * time.Minute),
|
||||
Update: DefaultTimeout(80 * time.Minute),
|
||||
Delete: DefaultTimeout(40 * time.Minute),
|
||||
},
|
||||
}
|
||||
|
||||
called := false
|
||||
r.Create = func(d *ResourceData, m interface{}) error {
|
||||
called = true
|
||||
d.SetId("foo")
|
||||
return nil
|
||||
}
|
||||
|
||||
var s *terraform.InstanceState = nil
|
||||
|
||||
d := &terraform.InstanceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"foo": &terraform.ResourceAttrDiff{
|
||||
New: "42",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
diffTimeout := &ResourceTimeout{
|
||||
Create: DefaultTimeout(40 * time.Minute),
|
||||
Update: DefaultTimeout(80 * time.Minute),
|
||||
Delete: DefaultTimeout(40 * time.Minute),
|
||||
}
|
||||
|
||||
if err := diffTimeout.DiffEncode(d); err != nil {
|
||||
t.Fatalf("Error encoding timeout to diff: %s", err)
|
||||
}
|
||||
|
||||
actual, err := r.Apply(s, d, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if !called {
|
||||
t.Fatal("not called")
|
||||
}
|
||||
|
||||
expected := &terraform.InstanceState{
|
||||
ID: "foo",
|
||||
Attributes: map[string]string{
|
||||
"id": "foo",
|
||||
"foo": "42",
|
||||
},
|
||||
Meta: map[string]interface{}{
|
||||
"schema_version": "2",
|
||||
TimeoutKey: expectedForValues(40, 0, 80, 40, 0),
|
||||
},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("Not equal in Timeout State:\n\texpected: %#v\n\tactual: %#v", expected.Meta, actual.Meta)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceDiff_Timeout_diff(t *testing.T) {
|
||||
r := &Resource{
|
||||
Schema: map[string]*Schema{
|
||||
"foo": &Schema{
|
||||
Type: TypeInt,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
Timeouts: &ResourceTimeout{
|
||||
Create: DefaultTimeout(40 * time.Minute),
|
||||
Update: DefaultTimeout(80 * time.Minute),
|
||||
Delete: DefaultTimeout(40 * time.Minute),
|
||||
},
|
||||
}
|
||||
|
||||
r.Create = func(d *ResourceData, m interface{}) error {
|
||||
d.SetId("foo")
|
||||
return nil
|
||||
}
|
||||
|
||||
raw, err := config.NewRawConfig(
|
||||
map[string]interface{}{
|
||||
"foo": 42,
|
||||
"timeout": []map[string]interface{}{
|
||||
map[string]interface{}{
|
||||
"create": "2h",
|
||||
}},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
var s *terraform.InstanceState = nil
|
||||
conf := terraform.NewResourceConfig(raw)
|
||||
|
||||
actual, err := r.Diff(s, conf)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
expected := &terraform.InstanceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"foo": &terraform.ResourceAttrDiff{
|
||||
New: "42",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
diffTimeout := &ResourceTimeout{
|
||||
Create: DefaultTimeout(120 * time.Minute),
|
||||
Update: DefaultTimeout(80 * time.Minute),
|
||||
Delete: DefaultTimeout(40 * time.Minute),
|
||||
}
|
||||
|
||||
if err := diffTimeout.DiffEncode(expected); err != nil {
|
||||
t.Fatalf("Error encoding timeout to diff: %s", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("Not equal in Timeout Diff:\n\texpected: %#v\n\tactual: %#v", expected.Meta, actual.Meta)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceApply_destroy(t *testing.T) {
|
||||
r := &Resource{
|
||||
Schema: map[string]*Schema{
|
||||
|
|
|
@ -0,0 +1,233 @@
|
|||
package schema
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/mitchellh/copystructure"
|
||||
)
|
||||
|
||||
const TimeoutKey = "e2bfb730-ecaa-11e6-8f88-34363bc7c4c0"
|
||||
|
||||
const (
|
||||
TimeoutCreate = "create"
|
||||
TimeoutRead = "read"
|
||||
TimeoutUpdate = "update"
|
||||
TimeoutDelete = "delete"
|
||||
TimeoutDefault = "default"
|
||||
)
|
||||
|
||||
func timeoutKeys() []string {
|
||||
return []string{
|
||||
TimeoutCreate,
|
||||
TimeoutRead,
|
||||
TimeoutUpdate,
|
||||
TimeoutDelete,
|
||||
TimeoutDefault,
|
||||
}
|
||||
}
|
||||
|
||||
// could be time.Duration, int64 or float64
|
||||
func DefaultTimeout(tx interface{}) *time.Duration {
|
||||
var td time.Duration
|
||||
switch raw := tx.(type) {
|
||||
case time.Duration:
|
||||
return &raw
|
||||
case int64:
|
||||
td = time.Duration(raw)
|
||||
case float64:
|
||||
td = time.Duration(int64(raw))
|
||||
default:
|
||||
log.Printf("[WARN] Unknown type in DefaultTimeout: %#v", tx)
|
||||
}
|
||||
return &td
|
||||
}
|
||||
|
||||
type ResourceTimeout struct {
|
||||
Create, Read, Update, Delete, Default *time.Duration
|
||||
}
|
||||
|
||||
// ConfigDecode takes a schema and the configuration (available in Diff) and
|
||||
// validates, parses the timeouts into `t`
|
||||
func (t *ResourceTimeout) ConfigDecode(s *Resource, c *terraform.ResourceConfig) error {
|
||||
if s.Timeouts != nil {
|
||||
raw, err := copystructure.Copy(s.Timeouts)
|
||||
if err != nil {
|
||||
log.Printf("[DEBUG] Error with deep copy: %s", err)
|
||||
}
|
||||
*t = *raw.(*ResourceTimeout)
|
||||
}
|
||||
|
||||
if raw, ok := c.Config["timeout"]; ok {
|
||||
configTimeouts := raw.([]map[string]interface{})
|
||||
for _, timeoutValues := range configTimeouts {
|
||||
// loop through each Timeout given in the configuration and validate they
|
||||
// the Timeout defined in the resource
|
||||
for timeKey, timeValue := range timeoutValues {
|
||||
// validate that we're dealing with the normal CRUD actions
|
||||
var found bool
|
||||
for _, key := range timeoutKeys() {
|
||||
if timeKey == key {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return fmt.Errorf("Unsupported Timeout configuration key found (%s)", timeKey)
|
||||
}
|
||||
|
||||
// Get timeout
|
||||
rt, err := time.ParseDuration(timeValue.(string))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error parsing Timeout for (%s): %s", timeKey, err)
|
||||
}
|
||||
|
||||
var timeout *time.Duration
|
||||
switch timeKey {
|
||||
case TimeoutCreate:
|
||||
timeout = t.Create
|
||||
case TimeoutUpdate:
|
||||
timeout = t.Update
|
||||
case TimeoutRead:
|
||||
timeout = t.Read
|
||||
case TimeoutDelete:
|
||||
timeout = t.Delete
|
||||
case TimeoutDefault:
|
||||
timeout = t.Default
|
||||
}
|
||||
|
||||
// If the resource has not delcared this in the definition, then error
|
||||
// with an unsupported message
|
||||
if timeout == nil {
|
||||
return unsupportedTimeoutKeyError(timeKey)
|
||||
}
|
||||
|
||||
*timeout = rt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func unsupportedTimeoutKeyError(key string) error {
|
||||
return fmt.Errorf("Timeout Key (%s) is not supported", key)
|
||||
}
|
||||
|
||||
// DiffEncode, StateEncode, and MetaDecode are analogous to the Go stdlib JSONEncoder
|
||||
// interface: they encode/decode a timeouts struct from an instance diff, which is
|
||||
// where the timeout data is stored after a diff to pass into Apply.
|
||||
//
|
||||
// StateEncode encodes the timeout into the ResourceData's InstanceState for
|
||||
// saving to state
|
||||
//
|
||||
func (t *ResourceTimeout) DiffEncode(id *terraform.InstanceDiff) error {
|
||||
return t.metaEncode(id)
|
||||
}
|
||||
|
||||
func (t *ResourceTimeout) StateEncode(is *terraform.InstanceState) error {
|
||||
return t.metaEncode(is)
|
||||
}
|
||||
|
||||
// metaEncode encodes the ResourceTimeout into a map[string]interface{} format
|
||||
// and stores it in the Meta field of the interface it's given.
|
||||
// Assumes the interface is either *terraform.InstanceState or
|
||||
// *terraform.InstanceDiff, returns an error otherwise
|
||||
func (t *ResourceTimeout) metaEncode(ids interface{}) error {
|
||||
m := make(map[string]interface{})
|
||||
|
||||
if t.Create != nil {
|
||||
m[TimeoutCreate] = t.Create.Nanoseconds()
|
||||
}
|
||||
if t.Read != nil {
|
||||
m[TimeoutRead] = t.Read.Nanoseconds()
|
||||
}
|
||||
if t.Update != nil {
|
||||
m[TimeoutUpdate] = t.Update.Nanoseconds()
|
||||
}
|
||||
if t.Delete != nil {
|
||||
m[TimeoutDelete] = t.Delete.Nanoseconds()
|
||||
}
|
||||
if t.Default != nil {
|
||||
m[TimeoutDefault] = t.Default.Nanoseconds()
|
||||
// for any key above that is nil, if default is specified, we need to
|
||||
// populate it with the default
|
||||
for _, k := range timeoutKeys() {
|
||||
if _, ok := m[k]; !ok {
|
||||
m[k] = t.Default.Nanoseconds()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// only add the Timeout to the Meta if we have values
|
||||
if len(m) > 0 {
|
||||
switch instance := ids.(type) {
|
||||
case *terraform.InstanceDiff:
|
||||
if instance.Meta == nil {
|
||||
instance.Meta = make(map[string]interface{})
|
||||
}
|
||||
instance.Meta[TimeoutKey] = m
|
||||
case *terraform.InstanceState:
|
||||
if instance.Meta == nil {
|
||||
instance.Meta = make(map[string]interface{})
|
||||
}
|
||||
instance.Meta[TimeoutKey] = m
|
||||
default:
|
||||
return fmt.Errorf("Error matching type for Diff Encode")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *ResourceTimeout) StateDecode(id *terraform.InstanceState) error {
|
||||
return t.metaDecode(id)
|
||||
}
|
||||
func (t *ResourceTimeout) DiffDecode(is *terraform.InstanceDiff) error {
|
||||
return t.metaDecode(is)
|
||||
}
|
||||
|
||||
func (t *ResourceTimeout) metaDecode(ids interface{}) error {
|
||||
var rawMeta interface{}
|
||||
var ok bool
|
||||
switch rawInstance := ids.(type) {
|
||||
case *terraform.InstanceDiff:
|
||||
rawMeta, ok = rawInstance.Meta[TimeoutKey]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
case *terraform.InstanceState:
|
||||
rawMeta, ok = rawInstance.Meta[TimeoutKey]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("Unknown or unsupported type in metaDecode: %#v", ids)
|
||||
}
|
||||
|
||||
times := rawMeta.(map[string]interface{})
|
||||
if len(times) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if v, ok := times[TimeoutCreate]; ok {
|
||||
t.Create = DefaultTimeout(v)
|
||||
}
|
||||
if v, ok := times[TimeoutRead]; ok {
|
||||
t.Read = DefaultTimeout(v)
|
||||
}
|
||||
if v, ok := times[TimeoutUpdate]; ok {
|
||||
t.Update = DefaultTimeout(v)
|
||||
}
|
||||
if v, ok := times[TimeoutDelete]; ok {
|
||||
t.Delete = DefaultTimeout(v)
|
||||
}
|
||||
if v, ok := times[TimeoutDefault]; ok {
|
||||
t.Default = DefaultTimeout(v)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,352 @@
|
|||
package schema
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
func TestResourceTimeout_ConfigDecode_badkey(t *testing.T) {
|
||||
cases := []struct {
|
||||
Name string
|
||||
// what the resource has defined in source
|
||||
ResourceDefaultTimeout *ResourceTimeout
|
||||
// configuration provider by user in tf file
|
||||
Config []map[string]interface{}
|
||||
// what we expect the parsed ResourceTimeout to be
|
||||
Expected *ResourceTimeout
|
||||
// Should we have an error (key not defined in source)
|
||||
ShouldErr bool
|
||||
}{
|
||||
{
|
||||
Name: "Source does not define 'delete' key",
|
||||
ResourceDefaultTimeout: timeoutForValues(10, 0, 5, 0, 0),
|
||||
Config: expectedConfigForValues(2, 0, 0, 1, 0),
|
||||
Expected: timeoutForValues(10, 0, 5, 0, 0),
|
||||
ShouldErr: true,
|
||||
},
|
||||
{
|
||||
Name: "Config overrides create",
|
||||
ResourceDefaultTimeout: timeoutForValues(10, 0, 5, 0, 0),
|
||||
Config: expectedConfigForValues(2, 0, 7, 0, 0),
|
||||
Expected: timeoutForValues(2, 0, 7, 0, 0),
|
||||
ShouldErr: false,
|
||||
},
|
||||
{
|
||||
Name: "Config overrides create, default provided. Should still have zero values",
|
||||
ResourceDefaultTimeout: timeoutForValues(10, 0, 5, 0, 3),
|
||||
Config: expectedConfigForValues(2, 0, 7, 0, 0),
|
||||
Expected: timeoutForValues(2, 0, 7, 0, 3),
|
||||
ShouldErr: false,
|
||||
},
|
||||
{
|
||||
Name: "Use something besides 'minutes'",
|
||||
ResourceDefaultTimeout: timeoutForValues(10, 0, 5, 0, 3),
|
||||
Config: []map[string]interface{}{
|
||||
map[string]interface{}{
|
||||
"create": "2h",
|
||||
}},
|
||||
Expected: timeoutForValues(120, 0, 5, 0, 3),
|
||||
ShouldErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, c := range cases {
|
||||
t.Run(fmt.Sprintf("%d-%s", i, c.Name), func(t *testing.T) {
|
||||
r := &Resource{
|
||||
Timeouts: c.ResourceDefaultTimeout,
|
||||
}
|
||||
|
||||
raw, err := config.NewRawConfig(
|
||||
map[string]interface{}{
|
||||
"foo": "bar",
|
||||
"timeout": c.Config,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
conf := terraform.NewResourceConfig(raw)
|
||||
|
||||
timeout := &ResourceTimeout{}
|
||||
decodeErr := timeout.ConfigDecode(r, conf)
|
||||
if c.ShouldErr {
|
||||
if decodeErr == nil {
|
||||
t.Fatalf("ConfigDecode case (%d): Expected bad timeout key: %s", i, decodeErr)
|
||||
}
|
||||
// should error, err was not nil, continue
|
||||
return
|
||||
} else {
|
||||
if decodeErr != nil {
|
||||
// should not error, error was not nil, fatal
|
||||
t.Fatalf("decodeError was not nil: %s", decodeErr)
|
||||
}
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(c.Expected, timeout) {
|
||||
t.Fatalf("ConfigDecode match error case (%d), expected:\n%#v\ngot:\n%#v", i, c.Expected, timeout)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceTimeout_ConfigDecode(t *testing.T) {
|
||||
r := &Resource{
|
||||
Timeouts: &ResourceTimeout{
|
||||
Create: DefaultTimeout(10 * time.Minute),
|
||||
Update: DefaultTimeout(5 * time.Minute),
|
||||
},
|
||||
}
|
||||
|
||||
raw, err := config.NewRawConfig(
|
||||
map[string]interface{}{
|
||||
"foo": "bar",
|
||||
"timeout": []map[string]interface{}{
|
||||
map[string]interface{}{
|
||||
"create": "2m",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"update": "1m",
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
c := terraform.NewResourceConfig(raw)
|
||||
|
||||
timeout := &ResourceTimeout{}
|
||||
err = timeout.ConfigDecode(r, c)
|
||||
if err != nil {
|
||||
t.Fatalf("Expected good timeout returned:, %s", err)
|
||||
}
|
||||
|
||||
expected := &ResourceTimeout{
|
||||
Create: DefaultTimeout(2 * time.Minute),
|
||||
Update: DefaultTimeout(1 * time.Minute),
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(timeout, expected) {
|
||||
t.Fatalf("bad timeout decode, expected (%#v), got (%#v)", expected, timeout)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceTimeout_DiffEncode_basic(t *testing.T) {
|
||||
cases := []struct {
|
||||
Timeout *ResourceTimeout
|
||||
Expected map[string]interface{}
|
||||
// Not immediately clear when an error would hit
|
||||
ShouldErr bool
|
||||
}{
|
||||
// Two fields
|
||||
{
|
||||
Timeout: timeoutForValues(10, 0, 5, 0, 0),
|
||||
Expected: map[string]interface{}{TimeoutKey: expectedForValues(10, 0, 5, 0, 0)},
|
||||
ShouldErr: false,
|
||||
},
|
||||
// Two fields, one is Default
|
||||
{
|
||||
Timeout: timeoutForValues(10, 0, 0, 0, 7),
|
||||
Expected: map[string]interface{}{TimeoutKey: expectedForValues(10, 0, 0, 0, 7)},
|
||||
ShouldErr: false,
|
||||
},
|
||||
// All fields
|
||||
{
|
||||
Timeout: timeoutForValues(10, 3, 4, 1, 7),
|
||||
Expected: map[string]interface{}{TimeoutKey: expectedForValues(10, 3, 4, 1, 7)},
|
||||
ShouldErr: false,
|
||||
},
|
||||
// No fields
|
||||
{
|
||||
Timeout: &ResourceTimeout{},
|
||||
Expected: nil,
|
||||
ShouldErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
state := &terraform.InstanceDiff{}
|
||||
err := c.Timeout.DiffEncode(state)
|
||||
if err != nil && !c.ShouldErr {
|
||||
t.Fatalf("Error, expected:\n%#v\n got:\n%#v\n", c.Expected, state.Meta)
|
||||
}
|
||||
|
||||
// should maybe just compare [TimeoutKey] but for now we're assuming only
|
||||
// that in Meta
|
||||
if !reflect.DeepEqual(state.Meta, c.Expected) {
|
||||
t.Fatalf("Encode not equal, expected:\n%#v\n\ngot:\n%#v\n", c.Expected, state.Meta)
|
||||
}
|
||||
}
|
||||
// same test cases but for InstanceState
|
||||
for _, c := range cases {
|
||||
state := &terraform.InstanceState{}
|
||||
err := c.Timeout.StateEncode(state)
|
||||
if err != nil && !c.ShouldErr {
|
||||
t.Fatalf("Error, expected:\n%#v\n got:\n%#v\n", c.Expected, state.Meta)
|
||||
}
|
||||
|
||||
// should maybe just compare [TimeoutKey] but for now we're assuming only
|
||||
// that in Meta
|
||||
if !reflect.DeepEqual(state.Meta, c.Expected) {
|
||||
t.Fatalf("Encode not equal, expected:\n%#v\n\ngot:\n%#v\n", c.Expected, state.Meta)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceTimeout_MetaDecode_basic(t *testing.T) {
|
||||
cases := []struct {
|
||||
State *terraform.InstanceDiff
|
||||
Expected *ResourceTimeout
|
||||
// Not immediately clear when an error would hit
|
||||
ShouldErr bool
|
||||
}{
|
||||
// Two fields
|
||||
{
|
||||
State: &terraform.InstanceDiff{Meta: map[string]interface{}{TimeoutKey: expectedForValues(10, 0, 5, 0, 0)}},
|
||||
Expected: timeoutForValues(10, 0, 5, 0, 0),
|
||||
ShouldErr: false,
|
||||
},
|
||||
// Two fields, one is Default
|
||||
{
|
||||
State: &terraform.InstanceDiff{Meta: map[string]interface{}{TimeoutKey: expectedForValues(10, 0, 0, 0, 7)}},
|
||||
Expected: timeoutForValues(10, 7, 7, 7, 7),
|
||||
ShouldErr: false,
|
||||
},
|
||||
// All fields
|
||||
{
|
||||
State: &terraform.InstanceDiff{Meta: map[string]interface{}{TimeoutKey: expectedForValues(10, 3, 4, 1, 7)}},
|
||||
Expected: timeoutForValues(10, 3, 4, 1, 7),
|
||||
ShouldErr: false,
|
||||
},
|
||||
// No fields
|
||||
{
|
||||
State: &terraform.InstanceDiff{},
|
||||
Expected: &ResourceTimeout{},
|
||||
ShouldErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
rt := &ResourceTimeout{}
|
||||
err := rt.DiffDecode(c.State)
|
||||
if err != nil && !c.ShouldErr {
|
||||
t.Fatalf("Error, expected:\n%#v\n got:\n%#v\n", c.Expected, rt)
|
||||
}
|
||||
|
||||
// should maybe just compare [TimeoutKey] but for now we're assuming only
|
||||
// that in Meta
|
||||
if !reflect.DeepEqual(rt, c.Expected) {
|
||||
t.Fatalf("Encode not equal, expected:\n%#v\n\ngot:\n%#v\n", c.Expected, rt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func timeoutForValues(create, read, update, del, def int) *ResourceTimeout {
|
||||
rt := ResourceTimeout{}
|
||||
|
||||
if create != 0 {
|
||||
rt.Create = DefaultTimeout(time.Duration(create) * time.Minute)
|
||||
}
|
||||
if read != 0 {
|
||||
rt.Read = DefaultTimeout(time.Duration(read) * time.Minute)
|
||||
}
|
||||
if update != 0 {
|
||||
rt.Update = DefaultTimeout(time.Duration(update) * time.Minute)
|
||||
}
|
||||
if del != 0 {
|
||||
rt.Delete = DefaultTimeout(time.Duration(del) * time.Minute)
|
||||
}
|
||||
|
||||
if def != 0 {
|
||||
rt.Default = DefaultTimeout(time.Duration(def) * time.Minute)
|
||||
}
|
||||
|
||||
return &rt
|
||||
}
|
||||
|
||||
// Generates a ResourceTimeout struct that should reflect the
|
||||
// d.Timeout("key") results
|
||||
func expectedTimeoutForValues(create, read, update, del, def int) *ResourceTimeout {
|
||||
rt := ResourceTimeout{}
|
||||
|
||||
defaultValues := []*int{&create, &read, &update, &del, &def}
|
||||
for _, v := range defaultValues {
|
||||
if *v == 0 {
|
||||
*v = 20
|
||||
}
|
||||
}
|
||||
|
||||
if create != 0 {
|
||||
rt.Create = DefaultTimeout(time.Duration(create) * time.Minute)
|
||||
}
|
||||
if read != 0 {
|
||||
rt.Read = DefaultTimeout(time.Duration(read) * time.Minute)
|
||||
}
|
||||
if update != 0 {
|
||||
rt.Update = DefaultTimeout(time.Duration(update) * time.Minute)
|
||||
}
|
||||
if del != 0 {
|
||||
rt.Delete = DefaultTimeout(time.Duration(del) * time.Minute)
|
||||
}
|
||||
|
||||
if def != 0 {
|
||||
rt.Default = DefaultTimeout(time.Duration(def) * time.Minute)
|
||||
}
|
||||
|
||||
return &rt
|
||||
}
|
||||
|
||||
func expectedForValues(create, read, update, del, def int) map[string]interface{} {
|
||||
ex := make(map[string]interface{})
|
||||
|
||||
if create != 0 {
|
||||
ex["create"] = DefaultTimeout(time.Duration(create) * time.Minute).Nanoseconds()
|
||||
}
|
||||
if read != 0 {
|
||||
ex["read"] = DefaultTimeout(time.Duration(read) * time.Minute).Nanoseconds()
|
||||
}
|
||||
if update != 0 {
|
||||
ex["update"] = DefaultTimeout(time.Duration(update) * time.Minute).Nanoseconds()
|
||||
}
|
||||
if del != 0 {
|
||||
ex["delete"] = DefaultTimeout(time.Duration(del) * time.Minute).Nanoseconds()
|
||||
}
|
||||
|
||||
if def != 0 {
|
||||
defNano := DefaultTimeout(time.Duration(def) * time.Minute).Nanoseconds()
|
||||
ex["default"] = defNano
|
||||
|
||||
for _, k := range timeoutKeys() {
|
||||
if _, ok := ex[k]; !ok {
|
||||
ex[k] = defNano
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ex
|
||||
}
|
||||
|
||||
func expectedConfigForValues(create, read, update, delete, def int) []map[string]interface{} {
|
||||
ex := make([]map[string]interface{}, 0)
|
||||
|
||||
if create != 0 {
|
||||
ex = append(ex, map[string]interface{}{"create": fmt.Sprintf("%dm", create)})
|
||||
}
|
||||
if read != 0 {
|
||||
ex = append(ex, map[string]interface{}{"read": fmt.Sprintf("%dm", read)})
|
||||
}
|
||||
if update != 0 {
|
||||
ex = append(ex, map[string]interface{}{"update": fmt.Sprintf("%dm", update)})
|
||||
}
|
||||
if delete != 0 {
|
||||
ex = append(ex, map[string]interface{}{"delete": fmt.Sprintf("%dm", delete)})
|
||||
}
|
||||
|
||||
if def != 0 {
|
||||
ex = append(ex, map[string]interface{}{"default": fmt.Sprintf("%dm", def)})
|
||||
}
|
||||
return ex
|
||||
}
|
|
@ -1327,6 +1327,9 @@ func (m schemaMap) validateObject(
|
|||
if m, ok := raw.(map[string]interface{}); ok {
|
||||
for subk, _ := range m {
|
||||
if _, ok := schema[subk]; !ok {
|
||||
if subk == "timeout" {
|
||||
continue
|
||||
}
|
||||
es = append(es, fmt.Errorf(
|
||||
"%s: invalid or unknown key: %s", k, subk))
|
||||
}
|
||||
|
|
|
@ -4773,6 +4773,23 @@ func TestSchemaMap_Validate(t *testing.T) {
|
|||
|
||||
Err: false,
|
||||
},
|
||||
|
||||
"special timeout field": {
|
||||
Schema: map[string]*Schema{
|
||||
"availability_zone": &Schema{
|
||||
Type: TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
},
|
||||
|
||||
Config: map[string]interface{}{
|
||||
"timeout": "bar",
|
||||
},
|
||||
|
||||
Err: false,
|
||||
},
|
||||
}
|
||||
|
||||
for tn, tc := range cases {
|
||||
|
|
|
@ -97,6 +97,43 @@ Additionally you can also use a single entry with a wildcard (e.g. `"*"`)
|
|||
which will match all attribute names. Using a partial string together with a
|
||||
wildcard (e.g. `"rout*"`) is **not** supported.
|
||||
|
||||
|
||||
<a id="timeouts"></a>
|
||||
|
||||
### Timeouts
|
||||
|
||||
Individual Resources may provide a `timeout` block to enable users to configure the
|
||||
amount of time a specific operation is allowed to take before being considered
|
||||
an error. For example, the
|
||||
[aws_db_instance](/docs/providers/aws/r/db_instance.html#timeouts)
|
||||
resource provides configurable timeouts for the
|
||||
`create`, `update`, and `delete` operations. Any Resource that provies Timeouts
|
||||
will document the default values for that operation, and users can overwrite
|
||||
them in their configuration.
|
||||
|
||||
Example overwriting the `create` and `delete` timeouts:
|
||||
|
||||
```
|
||||
resource "aws_db_instance" "timeout_example" {
|
||||
allocated_storage = 10
|
||||
engine = "mysql"
|
||||
engine_version = "5.6.17"
|
||||
instance_class = "db.t1.micro"
|
||||
name = "mydb"
|
||||
[...]
|
||||
|
||||
timeout {
|
||||
create = "60m"
|
||||
delete = "2h"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Individual Resources must opt-in to providing configurable Timeouts, and
|
||||
attempting to configure the timeout for a Resource that does not support
|
||||
Timeouts, or overwriting a specific action that the Resource does not specify as
|
||||
an option, will result in an error. Valid units of time are `s`, `m`, `h`.
|
||||
|
||||
<a id="explicit-dependencies"></a>
|
||||
|
||||
### Explicit Dependencies
|
||||
|
|
|
@ -144,6 +144,19 @@ On Oracle instances the following is exported additionally:
|
|||
|
||||
* `character_set_name` - The character set used on Oracle instances.
|
||||
|
||||
|
||||
<a id="timeouts"></a>
|
||||
## Timeouts
|
||||
|
||||
`aws_db_instance` provides the following
|
||||
[Timeouts](/docs/configuration/resources.html#timeouts) configuration options:
|
||||
|
||||
- `create` - (Default `40 minutes`) Used for Creating Instances, Replicas, and
|
||||
restoring from Snapshots
|
||||
- `update` - (Default `80 minutes`) Used for Database modifications
|
||||
- `delete` - (Default `40 minutes`) Used for destroying databases. This includes
|
||||
the time required to take snapshots
|
||||
|
||||
[1]: https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Overview.Replication.html
|
||||
[2]: https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_UpgradeDBInstance.Maintenance.html
|
||||
|
||||
|
|
Loading…
Reference in New Issue