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