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