| package schema |
| |
| import ( |
| "fmt" |
| "log" |
| "time" |
| |
| "github.com/hashicorp/terraform/internal/configs/hcl2shim" |
| "github.com/hashicorp/terraform/internal/legacy/terraform" |
| "github.com/mitchellh/copystructure" |
| ) |
| |
| const TimeoutKey = "e2bfb730-ecaa-11e6-8f88-34363bc7c4c0" |
| const TimeoutsConfigKey = "timeouts" |
| |
| 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[TimeoutsConfigKey]; ok { |
| var rawTimeouts []map[string]interface{} |
| switch raw := raw.(type) { |
| case map[string]interface{}: |
| rawTimeouts = append(rawTimeouts, raw) |
| case []map[string]interface{}: |
| rawTimeouts = raw |
| case string: |
| if raw == hcl2shim.UnknownVariableValue { |
| // Timeout is not defined in the config |
| // Defaults will be used instead |
| return nil |
| } else { |
| log.Printf("[ERROR] Invalid timeout value: %q", raw) |
| return fmt.Errorf("Invalid Timeout value found") |
| } |
| case []interface{}: |
| for _, r := range raw { |
| if rMap, ok := r.(map[string]interface{}); ok { |
| rawTimeouts = append(rawTimeouts, rMap) |
| } else { |
| // Go will not allow a fallthrough |
| log.Printf("[ERROR] Invalid timeout structure: %#v", raw) |
| return fmt.Errorf("Invalid Timeout structure found") |
| } |
| } |
| default: |
| log.Printf("[ERROR] Invalid timeout structure: %#v", raw) |
| return fmt.Errorf("Invalid Timeout structure found") |
| } |
| |
| for _, timeoutValues := range rawTimeouts { |
| 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 %q timeout: %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 |
| } |
| } |
| |
| 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 |
| } |