1package schema 2 3import ( 4 "fmt" 5 "log" 6 "time" 7 8 "github.com/hashicorp/terraform/config" 9 "github.com/hashicorp/terraform/terraform" 10 "github.com/mitchellh/copystructure" 11) 12 13const TimeoutKey = "e2bfb730-ecaa-11e6-8f88-34363bc7c4c0" 14const TimeoutsConfigKey = "timeouts" 15 16const ( 17 TimeoutCreate = "create" 18 TimeoutRead = "read" 19 TimeoutUpdate = "update" 20 TimeoutDelete = "delete" 21 TimeoutDefault = "default" 22) 23 24func 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 35func 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 50type 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` 56func (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 == config.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 default: 82 log.Printf("[ERROR] Invalid timeout structure: %#v", raw) 83 return fmt.Errorf("Invalid Timeout structure found") 84 } 85 86 for _, timeoutValues := range rawTimeouts { 87 for timeKey, timeValue := range timeoutValues { 88 // validate that we're dealing with the normal CRUD actions 89 var found bool 90 for _, key := range timeoutKeys() { 91 if timeKey == key { 92 found = true 93 break 94 } 95 } 96 97 if !found { 98 return fmt.Errorf("Unsupported Timeout configuration key found (%s)", timeKey) 99 } 100 101 // Get timeout 102 rt, err := time.ParseDuration(timeValue.(string)) 103 if err != nil { 104 return fmt.Errorf("Error parsing %q timeout: %s", timeKey, err) 105 } 106 107 var timeout *time.Duration 108 switch timeKey { 109 case TimeoutCreate: 110 timeout = t.Create 111 case TimeoutUpdate: 112 timeout = t.Update 113 case TimeoutRead: 114 timeout = t.Read 115 case TimeoutDelete: 116 timeout = t.Delete 117 case TimeoutDefault: 118 timeout = t.Default 119 } 120 121 // If the resource has not delcared this in the definition, then error 122 // with an unsupported message 123 if timeout == nil { 124 return unsupportedTimeoutKeyError(timeKey) 125 } 126 127 *timeout = rt 128 } 129 return nil 130 } 131 } 132 133 return nil 134} 135 136func unsupportedTimeoutKeyError(key string) error { 137 return fmt.Errorf("Timeout Key (%s) is not supported", key) 138} 139 140// DiffEncode, StateEncode, and MetaDecode are analogous to the Go stdlib JSONEncoder 141// interface: they encode/decode a timeouts struct from an instance diff, which is 142// where the timeout data is stored after a diff to pass into Apply. 143// 144// StateEncode encodes the timeout into the ResourceData's InstanceState for 145// saving to state 146// 147func (t *ResourceTimeout) DiffEncode(id *terraform.InstanceDiff) error { 148 return t.metaEncode(id) 149} 150 151func (t *ResourceTimeout) StateEncode(is *terraform.InstanceState) error { 152 return t.metaEncode(is) 153} 154 155// metaEncode encodes the ResourceTimeout into a map[string]interface{} format 156// and stores it in the Meta field of the interface it's given. 157// Assumes the interface is either *terraform.InstanceState or 158// *terraform.InstanceDiff, returns an error otherwise 159func (t *ResourceTimeout) metaEncode(ids interface{}) error { 160 m := make(map[string]interface{}) 161 162 if t.Create != nil { 163 m[TimeoutCreate] = t.Create.Nanoseconds() 164 } 165 if t.Read != nil { 166 m[TimeoutRead] = t.Read.Nanoseconds() 167 } 168 if t.Update != nil { 169 m[TimeoutUpdate] = t.Update.Nanoseconds() 170 } 171 if t.Delete != nil { 172 m[TimeoutDelete] = t.Delete.Nanoseconds() 173 } 174 if t.Default != nil { 175 m[TimeoutDefault] = t.Default.Nanoseconds() 176 // for any key above that is nil, if default is specified, we need to 177 // populate it with the default 178 for _, k := range timeoutKeys() { 179 if _, ok := m[k]; !ok { 180 m[k] = t.Default.Nanoseconds() 181 } 182 } 183 } 184 185 // only add the Timeout to the Meta if we have values 186 if len(m) > 0 { 187 switch instance := ids.(type) { 188 case *terraform.InstanceDiff: 189 if instance.Meta == nil { 190 instance.Meta = make(map[string]interface{}) 191 } 192 instance.Meta[TimeoutKey] = m 193 case *terraform.InstanceState: 194 if instance.Meta == nil { 195 instance.Meta = make(map[string]interface{}) 196 } 197 instance.Meta[TimeoutKey] = m 198 default: 199 return fmt.Errorf("Error matching type for Diff Encode") 200 } 201 } 202 203 return nil 204} 205 206func (t *ResourceTimeout) StateDecode(id *terraform.InstanceState) error { 207 return t.metaDecode(id) 208} 209func (t *ResourceTimeout) DiffDecode(is *terraform.InstanceDiff) error { 210 return t.metaDecode(is) 211} 212 213func (t *ResourceTimeout) metaDecode(ids interface{}) error { 214 var rawMeta interface{} 215 var ok bool 216 switch rawInstance := ids.(type) { 217 case *terraform.InstanceDiff: 218 rawMeta, ok = rawInstance.Meta[TimeoutKey] 219 if !ok { 220 return nil 221 } 222 case *terraform.InstanceState: 223 rawMeta, ok = rawInstance.Meta[TimeoutKey] 224 if !ok { 225 return nil 226 } 227 default: 228 return fmt.Errorf("Unknown or unsupported type in metaDecode: %#v", ids) 229 } 230 231 times := rawMeta.(map[string]interface{}) 232 if len(times) == 0 { 233 return nil 234 } 235 236 if v, ok := times[TimeoutCreate]; ok { 237 t.Create = DefaultTimeout(v) 238 } 239 if v, ok := times[TimeoutRead]; ok { 240 t.Read = DefaultTimeout(v) 241 } 242 if v, ok := times[TimeoutUpdate]; ok { 243 t.Update = DefaultTimeout(v) 244 } 245 if v, ok := times[TimeoutDelete]; ok { 246 t.Delete = DefaultTimeout(v) 247 } 248 if v, ok := times[TimeoutDefault]; ok { 249 t.Default = DefaultTimeout(v) 250 } 251 252 return nil 253} 254