github.com/opsidian/terraform@v0.7.8-0.20161104123224-27c39cdfba5b/terraform/resource.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 "reflect" 6 "sort" 7 "strconv" 8 "strings" 9 10 "github.com/hashicorp/terraform/config" 11 "github.com/mitchellh/copystructure" 12 ) 13 14 // ResourceProvisionerConfig is used to pair a provisioner 15 // with its provided configuration. This allows us to use singleton 16 // instances of each ResourceProvisioner and to keep the relevant 17 // configuration instead of instantiating a new Provisioner for each 18 // resource. 19 type ResourceProvisionerConfig struct { 20 Type string 21 Provisioner ResourceProvisioner 22 Config *ResourceConfig 23 RawConfig *config.RawConfig 24 ConnInfo *config.RawConfig 25 } 26 27 // Resource encapsulates a resource, its configuration, its provider, 28 // its current state, and potentially a desired diff from the state it 29 // wants to reach. 30 type Resource struct { 31 // These are all used by the new EvalNode stuff. 32 Name string 33 Type string 34 CountIndex int 35 36 // These aren't really used anymore anywhere, but we keep them around 37 // since we haven't done a proper cleanup yet. 38 Id string 39 Info *InstanceInfo 40 Config *ResourceConfig 41 Dependencies []string 42 Diff *InstanceDiff 43 Provider ResourceProvider 44 State *InstanceState 45 Provisioners []*ResourceProvisionerConfig 46 Flags ResourceFlag 47 } 48 49 // ResourceKind specifies what kind of instance we're working with, whether 50 // its a primary instance, a tainted instance, or an orphan. 51 type ResourceFlag byte 52 53 // InstanceInfo is used to hold information about the instance and/or 54 // resource being modified. 55 type InstanceInfo struct { 56 // Id is a unique name to represent this instance. This is not related 57 // to InstanceState.ID in any way. 58 Id string 59 60 // ModulePath is the complete path of the module containing this 61 // instance. 62 ModulePath []string 63 64 // Type is the resource type of this instance 65 Type string 66 67 // uniqueExtra is an internal field that can be populated to supply 68 // extra metadata that is used to identify a unique instance in 69 // the graph walk. This will be appended to HumanID when uniqueId 70 // is called. 71 uniqueExtra string 72 } 73 74 // HumanId is a unique Id that is human-friendly and useful for UI elements. 75 func (i *InstanceInfo) HumanId() string { 76 if i == nil { 77 return "<nil>" 78 } 79 80 if len(i.ModulePath) <= 1 { 81 return i.Id 82 } 83 84 return fmt.Sprintf( 85 "module.%s.%s", 86 strings.Join(i.ModulePath[1:], "."), 87 i.Id) 88 } 89 90 func (i *InstanceInfo) uniqueId() string { 91 prefix := i.HumanId() 92 if v := i.uniqueExtra; v != "" { 93 prefix += " " + v 94 } 95 96 return prefix 97 } 98 99 // ResourceConfig holds the configuration given for a resource. This is 100 // done instead of a raw `map[string]interface{}` type so that rich 101 // methods can be added to it to make dealing with it easier. 102 type ResourceConfig struct { 103 ComputedKeys []string 104 Raw map[string]interface{} 105 Config map[string]interface{} 106 107 raw *config.RawConfig 108 } 109 110 // NewResourceConfig creates a new ResourceConfig from a config.RawConfig. 111 func NewResourceConfig(c *config.RawConfig) *ResourceConfig { 112 result := &ResourceConfig{raw: c} 113 result.interpolateForce() 114 return result 115 } 116 117 // DeepCopy performs a deep copy of the configuration. This makes it safe 118 // to modify any of the structures that are part of the resource config without 119 // affecting the original configuration. 120 func (c *ResourceConfig) DeepCopy() *ResourceConfig { 121 // DeepCopying a nil should return a nil to avoid panics 122 if c == nil { 123 return nil 124 } 125 126 // Copy, this will copy all the exported attributes 127 copy, err := copystructure.Config{Lock: true}.Copy(c) 128 if err != nil { 129 panic(err) 130 } 131 132 // Force the type 133 result := copy.(*ResourceConfig) 134 135 // For the raw configuration, we can just use its own copy method 136 result.raw = c.raw.Copy() 137 138 return result 139 } 140 141 // Equal checks the equality of two resource configs. 142 func (c *ResourceConfig) Equal(c2 *ResourceConfig) bool { 143 // If either are nil, then they're only equal if they're both nil 144 if c == nil || c2 == nil { 145 return c == c2 146 } 147 148 // Sort the computed keys so they're deterministic 149 sort.Strings(c.ComputedKeys) 150 sort.Strings(c2.ComputedKeys) 151 152 // Two resource configs if their exported properties are equal. 153 // We don't compare "raw" because it is never used again after 154 // initialization and for all intents and purposes they are equal 155 // if the exported properties are equal. 156 check := [][2]interface{}{ 157 {c.ComputedKeys, c2.ComputedKeys}, 158 {c.Raw, c2.Raw}, 159 {c.Config, c2.Config}, 160 } 161 for _, pair := range check { 162 if !reflect.DeepEqual(pair[0], pair[1]) { 163 return false 164 } 165 } 166 167 return true 168 } 169 170 // CheckSet checks that the given list of configuration keys is 171 // properly set. If not, errors are returned for each unset key. 172 // 173 // This is useful to be called in the Validate method of a ResourceProvider. 174 func (c *ResourceConfig) CheckSet(keys []string) []error { 175 var errs []error 176 177 for _, k := range keys { 178 if !c.IsSet(k) { 179 errs = append(errs, fmt.Errorf("%s must be set", k)) 180 } 181 } 182 183 return errs 184 } 185 186 // Get looks up a configuration value by key and returns the value. 187 // 188 // The second return value is true if the get was successful. Get will 189 // not succeed if the value is being computed. 190 func (c *ResourceConfig) Get(k string) (interface{}, bool) { 191 // First try to get it from c.Config since that has interpolated values 192 result, ok := c.get(k, c.Config) 193 if ok { 194 return result, ok 195 } 196 197 // Otherwise, just get it from the raw config 198 return c.get(k, c.Raw) 199 } 200 201 // GetRaw looks up a configuration value by key and returns the value, 202 // from the raw, uninterpolated config. 203 // 204 // The second return value is true if the get was successful. Get will 205 // not succeed if the value is being computed. 206 func (c *ResourceConfig) GetRaw(k string) (interface{}, bool) { 207 return c.get(k, c.Raw) 208 } 209 210 // IsComputed returns whether the given key is computed or not. 211 func (c *ResourceConfig) IsComputed(k string) bool { 212 _, ok := c.get(k, c.Config) 213 _, okRaw := c.get(k, c.Raw) 214 return !ok && okRaw 215 } 216 217 // IsSet checks if the key in the configuration is set. A key is set if 218 // it has a value or the value is being computed (is unknown currently). 219 // 220 // This function should be used rather than checking the keys of the 221 // raw configuration itself, since a key may be omitted from the raw 222 // configuration if it is being computed. 223 func (c *ResourceConfig) IsSet(k string) bool { 224 if c == nil { 225 return false 226 } 227 228 for _, ck := range c.ComputedKeys { 229 if ck == k { 230 return true 231 } 232 } 233 234 if _, ok := c.Get(k); ok { 235 return true 236 } 237 238 return false 239 } 240 241 func (c *ResourceConfig) get( 242 k string, raw map[string]interface{}) (interface{}, bool) { 243 parts := strings.Split(k, ".") 244 if len(parts) == 1 && parts[0] == "" { 245 parts = nil 246 } 247 248 var current interface{} = raw 249 var previous interface{} = nil 250 for i, part := range parts { 251 if current == nil { 252 return nil, false 253 } 254 255 cv := reflect.ValueOf(current) 256 switch cv.Kind() { 257 case reflect.Map: 258 previous = current 259 v := cv.MapIndex(reflect.ValueOf(part)) 260 if !v.IsValid() { 261 if i > 0 && i != (len(parts)-1) { 262 tryKey := strings.Join(parts[i:], ".") 263 v := cv.MapIndex(reflect.ValueOf(tryKey)) 264 if !v.IsValid() { 265 return nil, false 266 } 267 return v.Interface(), true 268 } 269 270 return nil, false 271 } 272 current = v.Interface() 273 case reflect.Slice: 274 previous = current 275 if part == "#" { 276 current = cv.Len() 277 } else { 278 i, err := strconv.ParseInt(part, 0, 0) 279 if err != nil { 280 return nil, false 281 } 282 if i >= int64(cv.Len()) { 283 return nil, false 284 } 285 current = cv.Index(int(i)).Interface() 286 } 287 case reflect.String: 288 // This happens when map keys contain "." and have a common 289 // prefix so were split as path components above. 290 actualKey := strings.Join(parts[i-1:], ".") 291 if prevMap, ok := previous.(map[string]interface{}); ok { 292 return prevMap[actualKey], true 293 } 294 return nil, false 295 default: 296 panic(fmt.Sprintf("Unknown kind: %s", cv.Kind())) 297 } 298 } 299 300 return current, true 301 } 302 303 // interpolateForce is a temporary thing. We want to get rid of interpolate 304 // above and likewise this, but it can only be done after the f-ast-graph 305 // refactor is complete. 306 func (c *ResourceConfig) interpolateForce() { 307 if c.raw == nil { 308 var err error 309 c.raw, err = config.NewRawConfig(make(map[string]interface{})) 310 if err != nil { 311 panic(err) 312 } 313 } 314 315 c.ComputedKeys = c.raw.UnknownKeys() 316 c.Raw = c.raw.RawMap() 317 c.Config = c.raw.Config() 318 }