github.com/hashicorp/terraform-plugin-sdk@v1.17.2/helper/schema/resource_timeout.go (about) 1 package schema 2 3 import ( 4 "fmt" 5 "log" 6 "time" 7 8 "github.com/hashicorp/terraform-plugin-sdk/internal/configs/hcl2shim" 9 "github.com/hashicorp/terraform-plugin-sdk/terraform" 10 "github.com/mitchellh/copystructure" 11 ) 12 13 const TimeoutKey = "e2bfb730-ecaa-11e6-8f88-34363bc7c4c0" 14 const TimeoutsConfigKey = "timeouts" 15 16 const ( 17 TimeoutCreate = "create" 18 TimeoutRead = "read" 19 TimeoutUpdate = "update" 20 TimeoutDelete = "delete" 21 TimeoutDefault = "default" 22 ) 23 24 func timeoutKeys() []string { 25 return []string{ 26 TimeoutCreate, 27 TimeoutRead, 28 TimeoutUpdate, 29 TimeoutDelete, 30 TimeoutDefault, 31 } 32 } 33 34 // could be time.Duration, int64 or float64 35 func DefaultTimeout(tx interface{}) *time.Duration { 36 var td time.Duration 37 switch raw := tx.(type) { 38 case time.Duration: 39 return &raw 40 case int64: 41 td = time.Duration(raw) 42 case float64: 43 td = time.Duration(int64(raw)) 44 default: 45 log.Printf("[WARN] Unknown type in DefaultTimeout: %#v", tx) 46 } 47 return &td 48 } 49 50 type ResourceTimeout struct { 51 Create, Read, Update, Delete, Default *time.Duration 52 } 53 54 // ConfigDecode takes a schema and the configuration (available in Diff) and 55 // validates, parses the timeouts into `t` 56 func (t *ResourceTimeout) ConfigDecode(s *Resource, c *terraform.ResourceConfig) error { 57 if s.Timeouts != nil { 58 raw, err := copystructure.Copy(s.Timeouts) 59 if err != nil { 60 log.Printf("[DEBUG] Error with deep copy: %s", err) 61 } 62 *t = *raw.(*ResourceTimeout) 63 } 64 65 if raw, ok := c.Config[TimeoutsConfigKey]; ok { 66 var rawTimeouts []map[string]interface{} 67 switch raw := raw.(type) { 68 case map[string]interface{}: 69 rawTimeouts = append(rawTimeouts, raw) 70 case []map[string]interface{}: 71 rawTimeouts = raw 72 case string: 73 if raw == hcl2shim.UnknownVariableValue { 74 // Timeout is not defined in the config 75 // Defaults will be used instead 76 return nil 77 } else { 78 log.Printf("[ERROR] Invalid timeout value: %q", raw) 79 return fmt.Errorf("Invalid Timeout value found") 80 } 81 case []interface{}: 82 for _, r := range raw { 83 if rMap, ok := r.(map[string]interface{}); ok { 84 rawTimeouts = append(rawTimeouts, rMap) 85 } else { 86 // Go will not allow a fallthrough 87 log.Printf("[ERROR] Invalid timeout structure: %#v", raw) 88 return fmt.Errorf("Invalid Timeout structure found") 89 } 90 } 91 default: 92 log.Printf("[ERROR] Invalid timeout structure: %#v", raw) 93 return fmt.Errorf("Invalid Timeout structure found") 94 } 95 96 for _, timeoutValues := range rawTimeouts { 97 for timeKey, timeValue := range timeoutValues { 98 // validate that we're dealing with the normal CRUD actions 99 var found bool 100 for _, key := range timeoutKeys() { 101 if timeKey == key { 102 found = true 103 break 104 } 105 } 106 107 if !found { 108 return fmt.Errorf("Unsupported Timeout configuration key found (%s)", timeKey) 109 } 110 111 // Get timeout 112 rt, err := time.ParseDuration(timeValue.(string)) 113 if err != nil { 114 return fmt.Errorf("Error parsing %q timeout: %s", timeKey, err) 115 } 116 117 var timeout *time.Duration 118 switch timeKey { 119 case TimeoutCreate: 120 timeout = t.Create 121 case TimeoutUpdate: 122 timeout = t.Update 123 case TimeoutRead: 124 timeout = t.Read 125 case TimeoutDelete: 126 timeout = t.Delete 127 case TimeoutDefault: 128 timeout = t.Default 129 } 130 131 // If the resource has not delcared this in the definition, then error 132 // with an unsupported message 133 if timeout == nil { 134 return unsupportedTimeoutKeyError(timeKey) 135 } 136 137 *timeout = rt 138 } 139 return nil 140 } 141 } 142 143 return nil 144 } 145 146 func unsupportedTimeoutKeyError(key string) error { 147 return fmt.Errorf("Timeout Key (%s) is not supported", key) 148 } 149 150 // DiffEncode, StateEncode, and MetaDecode are analogous to the Go stdlib JSONEncoder 151 // interface: they encode/decode a timeouts struct from an instance diff, which is 152 // where the timeout data is stored after a diff to pass into Apply. 153 // 154 // StateEncode encodes the timeout into the ResourceData's InstanceState for 155 // saving to state 156 // 157 func (t *ResourceTimeout) DiffEncode(id *terraform.InstanceDiff) error { 158 return t.metaEncode(id) 159 } 160 161 func (t *ResourceTimeout) StateEncode(is *terraform.InstanceState) error { 162 return t.metaEncode(is) 163 } 164 165 // metaEncode encodes the ResourceTimeout into a map[string]interface{} format 166 // and stores it in the Meta field of the interface it's given. 167 // Assumes the interface is either *terraform.InstanceState or 168 // *terraform.InstanceDiff, returns an error otherwise 169 func (t *ResourceTimeout) metaEncode(ids interface{}) error { 170 m := make(map[string]interface{}) 171 172 if t.Create != nil { 173 m[TimeoutCreate] = t.Create.Nanoseconds() 174 } 175 if t.Read != nil { 176 m[TimeoutRead] = t.Read.Nanoseconds() 177 } 178 if t.Update != nil { 179 m[TimeoutUpdate] = t.Update.Nanoseconds() 180 } 181 if t.Delete != nil { 182 m[TimeoutDelete] = t.Delete.Nanoseconds() 183 } 184 if t.Default != nil { 185 m[TimeoutDefault] = t.Default.Nanoseconds() 186 // for any key above that is nil, if default is specified, we need to 187 // populate it with the default 188 for _, k := range timeoutKeys() { 189 if _, ok := m[k]; !ok { 190 m[k] = t.Default.Nanoseconds() 191 } 192 } 193 } 194 195 // only add the Timeout to the Meta if we have values 196 if len(m) > 0 { 197 switch instance := ids.(type) { 198 case *terraform.InstanceDiff: 199 if instance.Meta == nil { 200 instance.Meta = make(map[string]interface{}) 201 } 202 instance.Meta[TimeoutKey] = m 203 case *terraform.InstanceState: 204 if instance.Meta == nil { 205 instance.Meta = make(map[string]interface{}) 206 } 207 instance.Meta[TimeoutKey] = m 208 default: 209 return fmt.Errorf("Error matching type for Diff Encode") 210 } 211 } 212 213 return nil 214 } 215 216 func (t *ResourceTimeout) StateDecode(id *terraform.InstanceState) error { 217 return t.metaDecode(id) 218 } 219 func (t *ResourceTimeout) DiffDecode(is *terraform.InstanceDiff) error { 220 return t.metaDecode(is) 221 } 222 223 func (t *ResourceTimeout) metaDecode(ids interface{}) error { 224 var rawMeta interface{} 225 var ok bool 226 switch rawInstance := ids.(type) { 227 case *terraform.InstanceDiff: 228 rawMeta, ok = rawInstance.Meta[TimeoutKey] 229 if !ok { 230 return nil 231 } 232 case *terraform.InstanceState: 233 rawMeta, ok = rawInstance.Meta[TimeoutKey] 234 if !ok { 235 return nil 236 } 237 default: 238 return fmt.Errorf("Unknown or unsupported type in metaDecode: %#v", ids) 239 } 240 241 times := rawMeta.(map[string]interface{}) 242 if len(times) == 0 { 243 return nil 244 } 245 246 if v, ok := times[TimeoutCreate]; ok { 247 t.Create = DefaultTimeout(v) 248 } 249 if v, ok := times[TimeoutRead]; ok { 250 t.Read = DefaultTimeout(v) 251 } 252 if v, ok := times[TimeoutUpdate]; ok { 253 t.Update = DefaultTimeout(v) 254 } 255 if v, ok := times[TimeoutDelete]; ok { 256 t.Delete = DefaultTimeout(v) 257 } 258 if v, ok := times[TimeoutDefault]; ok { 259 t.Default = DefaultTimeout(v) 260 } 261 262 return nil 263 }