github.com/ndarilek/terraform@v0.3.8-0.20150320140257-d3135c1b2bac/config/raw_config.go (about) 1 package config 2 3 import ( 4 "bytes" 5 "encoding/gob" 6 7 "github.com/hashicorp/terraform/config/lang" 8 "github.com/hashicorp/terraform/config/lang/ast" 9 "github.com/mitchellh/copystructure" 10 "github.com/mitchellh/reflectwalk" 11 ) 12 13 // UnknownVariableValue is a sentinel value that can be used 14 // to denote that the value of a variable is unknown at this time. 15 // RawConfig uses this information to build up data about 16 // unknown keys. 17 const UnknownVariableValue = "74D93920-ED26-11E3-AC10-0800200C9A66" 18 19 // RawConfig is a structure that holds a piece of configuration 20 // where te overall structure is unknown since it will be used 21 // to configure a plugin or some other similar external component. 22 // 23 // RawConfigs can be interpolated with variables that come from 24 // other resources, user variables, etc. 25 // 26 // RawConfig supports a query-like interface to request 27 // information from deep within the structure. 28 type RawConfig struct { 29 Key string 30 Raw map[string]interface{} 31 Interpolations []ast.Node 32 Variables map[string]InterpolatedVariable 33 34 config map[string]interface{} 35 unknownKeys []string 36 } 37 38 // NewRawConfig creates a new RawConfig structure and populates the 39 // publicly readable struct fields. 40 func NewRawConfig(raw map[string]interface{}) (*RawConfig, error) { 41 result := &RawConfig{Raw: raw} 42 if err := result.init(); err != nil { 43 return nil, err 44 } 45 46 return result, nil 47 } 48 49 // Value returns the value of the configuration if this configuration 50 // has a Key set. If this does not have a Key set, nil will be returned. 51 func (r *RawConfig) Value() interface{} { 52 if c := r.Config(); c != nil { 53 if v, ok := c[r.Key]; ok { 54 return v 55 } 56 } 57 58 return r.Raw[r.Key] 59 } 60 61 // Config returns the entire configuration with the variables 62 // interpolated from any call to Interpolate. 63 // 64 // If any interpolated variables are unknown (value set to 65 // UnknownVariableValue), the first non-container (map, slice, etc.) element 66 // will be removed from the config. The keys of unknown variables 67 // can be found using the UnknownKeys function. 68 // 69 // By pruning out unknown keys from the configuration, the raw 70 // structure will always successfully decode into its ultimate 71 // structure using something like mapstructure. 72 func (r *RawConfig) Config() map[string]interface{} { 73 return r.config 74 } 75 76 // Interpolate uses the given mapping of variable values and uses 77 // those as the values to replace any variables in this raw 78 // configuration. 79 // 80 // Any prior calls to Interpolate are replaced with this one. 81 // 82 // If a variable key is missing, this will panic. 83 func (r *RawConfig) Interpolate(vs map[string]ast.Variable) error { 84 config := langEvalConfig(vs) 85 return r.interpolate(func(root ast.Node) (string, error) { 86 // We detect the variables again and check if the value of any 87 // of the variables is the computed value. If it is, then we 88 // treat this entire value as computed. 89 // 90 // We have to do this here before the `lang.Eval` because 91 // if any of the variables it depends on are computed, then 92 // the interpolation can fail at runtime for other reasons. Example: 93 // `${count.index+1}`: in a world where `count.index` is computed, 94 // this would fail a type check since the computed placeholder is 95 // a string, but realistically the whole value is just computed. 96 vars, err := DetectVariables(root) 97 if err != nil { 98 return "", err 99 } 100 for _, v := range vars { 101 varVal, ok := vs[v.FullKey()] 102 if ok && varVal.Value == UnknownVariableValue { 103 return UnknownVariableValue, nil 104 } 105 } 106 107 // None of the variables we need are computed, meaning we should 108 // be able to properly evaluate. 109 out, _, err := lang.Eval(root, config) 110 if err != nil { 111 return "", err 112 } 113 114 return out.(string), nil 115 }) 116 } 117 118 // Merge merges another RawConfig into this one (overriding any conflicting 119 // values in this config) and returns a new config. The original config 120 // is not modified. 121 func (r *RawConfig) Merge(other *RawConfig) *RawConfig { 122 // Merge the raw configurations 123 raw := make(map[string]interface{}) 124 for k, v := range r.Raw { 125 raw[k] = v 126 } 127 for k, v := range other.Raw { 128 raw[k] = v 129 } 130 131 // Create the result 132 result, err := NewRawConfig(raw) 133 if err != nil { 134 panic(err) 135 } 136 137 // Merge the interpolated results 138 result.config = make(map[string]interface{}) 139 for k, v := range r.config { 140 result.config[k] = v 141 } 142 for k, v := range other.config { 143 result.config[k] = v 144 } 145 146 // Build the unknown keys 147 unknownKeys := make(map[string]struct{}) 148 for _, k := range r.unknownKeys { 149 unknownKeys[k] = struct{}{} 150 } 151 for _, k := range other.unknownKeys { 152 unknownKeys[k] = struct{}{} 153 } 154 155 result.unknownKeys = make([]string, 0, len(unknownKeys)) 156 for k, _ := range unknownKeys { 157 result.unknownKeys = append(result.unknownKeys, k) 158 } 159 160 return result 161 } 162 163 func (r *RawConfig) init() error { 164 r.config = r.Raw 165 r.Interpolations = nil 166 r.Variables = nil 167 168 fn := func(node ast.Node) (string, error) { 169 r.Interpolations = append(r.Interpolations, node) 170 vars, err := DetectVariables(node) 171 if err != nil { 172 return "", err 173 } 174 175 for _, v := range vars { 176 if r.Variables == nil { 177 r.Variables = make(map[string]InterpolatedVariable) 178 } 179 180 r.Variables[v.FullKey()] = v 181 } 182 183 return "", nil 184 } 185 186 walker := &interpolationWalker{F: fn} 187 if err := reflectwalk.Walk(r.Raw, walker); err != nil { 188 return err 189 } 190 191 return nil 192 } 193 194 func (r *RawConfig) interpolate(fn interpolationWalkerFunc) error { 195 config, err := copystructure.Copy(r.Raw) 196 if err != nil { 197 return err 198 } 199 r.config = config.(map[string]interface{}) 200 201 w := &interpolationWalker{F: fn, Replace: true} 202 err = reflectwalk.Walk(r.config, w) 203 if err != nil { 204 return err 205 } 206 207 r.unknownKeys = w.unknownKeys 208 return nil 209 } 210 211 func (r *RawConfig) merge(r2 *RawConfig) *RawConfig { 212 rawRaw, err := copystructure.Copy(r.Raw) 213 if err != nil { 214 panic(err) 215 } 216 217 raw := rawRaw.(map[string]interface{}) 218 for k, v := range r2.Raw { 219 raw[k] = v 220 } 221 222 result, err := NewRawConfig(raw) 223 if err != nil { 224 panic(err) 225 } 226 227 return result 228 } 229 230 // UnknownKeys returns the keys of the configuration that are unknown 231 // because they had interpolated variables that must be computed. 232 func (r *RawConfig) UnknownKeys() []string { 233 return r.unknownKeys 234 } 235 236 // See GobEncode 237 func (r *RawConfig) GobDecode(b []byte) error { 238 var data gobRawConfig 239 err := gob.NewDecoder(bytes.NewReader(b)).Decode(&data) 240 if err != nil { 241 return err 242 } 243 244 r.Key = data.Key 245 r.Raw = data.Raw 246 247 return r.init() 248 } 249 250 // GobEncode is a custom Gob encoder to use so that we only include the 251 // raw configuration. Interpolated variables and such are lost and the 252 // tree of interpolated variables is recomputed on decode, since it is 253 // referentially transparent. 254 func (r *RawConfig) GobEncode() ([]byte, error) { 255 data := gobRawConfig{ 256 Key: r.Key, 257 Raw: r.Raw, 258 } 259 260 var buf bytes.Buffer 261 if err := gob.NewEncoder(&buf).Encode(data); err != nil { 262 return nil, err 263 } 264 265 return buf.Bytes(), nil 266 } 267 268 type gobRawConfig struct { 269 Key string 270 Raw map[string]interface{} 271 } 272 273 // langEvalConfig returns the evaluation configuration we use to execute. 274 func langEvalConfig(vs map[string]ast.Variable) *lang.EvalConfig { 275 funcMap := make(map[string]ast.Function) 276 for k, v := range Funcs { 277 funcMap[k] = v 278 } 279 funcMap["lookup"] = interpolationFuncLookup(vs) 280 281 return &lang.EvalConfig{ 282 GlobalScope: &ast.BasicScope{ 283 VarMap: vs, 284 FuncMap: funcMap, 285 }, 286 } 287 }