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