github.com/emate/packer@v0.8.1-0.20150625195101-fe0fde195dc6/template/parse.go (about) 1 package template 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io" 8 "os" 9 "path/filepath" 10 "sort" 11 12 "github.com/hashicorp/go-multierror" 13 "github.com/mitchellh/mapstructure" 14 ) 15 16 // rawTemplate is the direct JSON document format of the template file. 17 // This is what is decoded directly from the file, and then it is turned 18 // into a Template object thereafter. 19 type rawTemplate struct { 20 MinVersion string `mapstructure:"min_packer_version"` 21 Description string 22 23 Builders []map[string]interface{} 24 Push map[string]interface{} 25 PostProcessors []interface{} `mapstructure:"post-processors"` 26 Provisioners []map[string]interface{} 27 Variables map[string]interface{} 28 29 RawContents []byte 30 } 31 32 // Template returns the actual Template object built from this raw 33 // structure. 34 func (r *rawTemplate) Template() (*Template, error) { 35 var result Template 36 var errs error 37 38 // Copy some literals 39 result.Description = r.Description 40 result.MinVersion = r.MinVersion 41 result.RawContents = r.RawContents 42 43 // Gather the variables 44 if len(r.Variables) > 0 { 45 result.Variables = make(map[string]*Variable, len(r.Variables)) 46 } 47 for k, rawV := range r.Variables { 48 var v Variable 49 50 // Variable is required if the value is exactly nil 51 v.Required = rawV == nil 52 53 // Weak decode the default if we have one 54 if err := r.decoder(&v.Default, nil).Decode(rawV); err != nil { 55 errs = multierror.Append(errs, fmt.Errorf( 56 "variable %s: %s", k, err)) 57 continue 58 } 59 60 result.Variables[k] = &v 61 } 62 63 // Let's start by gathering all the builders 64 if len(r.Builders) > 0 { 65 result.Builders = make(map[string]*Builder, len(r.Builders)) 66 } 67 for i, rawB := range r.Builders { 68 var b Builder 69 if err := mapstructure.WeakDecode(rawB, &b); err != nil { 70 errs = multierror.Append(errs, fmt.Errorf( 71 "builder %d: %s", i+1, err)) 72 continue 73 } 74 75 // Set the raw configuration and delete any special keys 76 b.Config = rawB 77 delete(b.Config, "name") 78 delete(b.Config, "type") 79 if len(b.Config) == 0 { 80 b.Config = nil 81 } 82 83 // If there is no type set, it is an error 84 if b.Type == "" { 85 errs = multierror.Append(errs, fmt.Errorf( 86 "builder %d: missing 'type'", i+1)) 87 continue 88 } 89 90 // The name defaults to the type if it isn't set 91 if b.Name == "" { 92 b.Name = b.Type 93 } 94 95 // If this builder already exists, it is an error 96 if _, ok := result.Builders[b.Name]; ok { 97 errs = multierror.Append(errs, fmt.Errorf( 98 "builder %d: builder with name '%s' already exists", 99 i+1, b.Name)) 100 continue 101 } 102 103 // Append the builders 104 result.Builders[b.Name] = &b 105 } 106 107 // Gather all the post-processors 108 if len(r.PostProcessors) > 0 { 109 result.PostProcessors = make([][]*PostProcessor, 0, len(r.PostProcessors)) 110 } 111 for i, v := range r.PostProcessors { 112 // Parse the configurations. We need to do this because post-processors 113 // can take three different formats. 114 configs, err := r.parsePostProcessor(i, v) 115 if err != nil { 116 errs = multierror.Append(errs, err) 117 continue 118 } 119 120 // Parse the PostProcessors out of the configs 121 pps := make([]*PostProcessor, 0, len(configs)) 122 for j, c := range configs { 123 var pp PostProcessor 124 if err := r.decoder(&pp, nil).Decode(c); err != nil { 125 errs = multierror.Append(errs, fmt.Errorf( 126 "post-processor %d.%d: %s", i+1, j+1, err)) 127 continue 128 } 129 130 // Type is required 131 if pp.Type == "" { 132 errs = multierror.Append(errs, fmt.Errorf( 133 "post-processor %d.%d: type is required", i+1, j+1)) 134 continue 135 } 136 137 // Set the configuration 138 delete(c, "except") 139 delete(c, "only") 140 delete(c, "keep_input_artifact") 141 delete(c, "type") 142 if len(c) > 0 { 143 pp.Config = c 144 } 145 146 pps = append(pps, &pp) 147 } 148 149 result.PostProcessors = append(result.PostProcessors, pps) 150 } 151 152 // Gather all the provisioners 153 if len(r.Provisioners) > 0 { 154 result.Provisioners = make([]*Provisioner, 0, len(r.Provisioners)) 155 } 156 for i, v := range r.Provisioners { 157 var p Provisioner 158 if err := r.decoder(&p, nil).Decode(v); err != nil { 159 errs = multierror.Append(errs, fmt.Errorf( 160 "provisioner %d: %s", i+1, err)) 161 continue 162 } 163 164 // Type is required before any richer validation 165 if p.Type == "" { 166 errs = multierror.Append(errs, fmt.Errorf( 167 "provisioner %d: missing 'type'", i+1)) 168 continue 169 } 170 171 // Copy the configuration 172 delete(v, "except") 173 delete(v, "only") 174 delete(v, "override") 175 delete(v, "pause_before") 176 delete(v, "type") 177 if len(v) > 0 { 178 p.Config = v 179 } 180 181 // TODO: stuff 182 result.Provisioners = append(result.Provisioners, &p) 183 } 184 185 // Push 186 if len(r.Push) > 0 { 187 var p Push 188 if err := r.decoder(&p, nil).Decode(r.Push); err != nil { 189 errs = multierror.Append(errs, fmt.Errorf( 190 "push: %s", err)) 191 } 192 193 result.Push = p 194 } 195 196 // If we have errors, return those with a nil result 197 if errs != nil { 198 return nil, errs 199 } 200 201 return &result, nil 202 } 203 204 func (r *rawTemplate) decoder( 205 result interface{}, 206 md *mapstructure.Metadata) *mapstructure.Decoder { 207 d, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 208 DecodeHook: mapstructure.StringToTimeDurationHookFunc(), 209 Metadata: md, 210 Result: result, 211 }) 212 if err != nil { 213 // This really shouldn't happen since we have firm control over 214 // all the arguments and they're all unit tested. So we use a 215 // panic here to note this would definitely be a bug. 216 panic(err) 217 } 218 return d 219 } 220 221 func (r *rawTemplate) parsePostProcessor( 222 i int, raw interface{}) ([]map[string]interface{}, error) { 223 switch v := raw.(type) { 224 case string: 225 return []map[string]interface{}{ 226 {"type": v}, 227 }, nil 228 case map[string]interface{}: 229 return []map[string]interface{}{v}, nil 230 case []interface{}: 231 var err error 232 result := make([]map[string]interface{}, len(v)) 233 for j, innerRaw := range v { 234 switch innerV := innerRaw.(type) { 235 case string: 236 result[j] = map[string]interface{}{"type": innerV} 237 case map[string]interface{}: 238 result[j] = innerV 239 case []interface{}: 240 err = multierror.Append(err, fmt.Errorf( 241 "post-processor %d.%d: sequence not allowed to be nested in a sequence", 242 i+1, j+1)) 243 default: 244 err = multierror.Append(err, fmt.Errorf( 245 "post-processor %d.%d: unknown format", 246 i+1, j+1)) 247 } 248 } 249 250 if err != nil { 251 return nil, err 252 } 253 254 return result, nil 255 default: 256 return nil, fmt.Errorf("post-processor %d: bad format", i+1) 257 } 258 } 259 260 // Parse takes the given io.Reader and parses a Template object out of it. 261 func Parse(r io.Reader) (*Template, error) { 262 // Create a buffer to copy what we read 263 var buf bytes.Buffer 264 r = io.TeeReader(r, &buf) 265 266 // First, decode the object into an interface{}. We do this instead of 267 // the rawTemplate directly because we'd rather use mapstructure to 268 // decode since it has richer errors. 269 var raw interface{} 270 if err := json.NewDecoder(r).Decode(&raw); err != nil { 271 return nil, err 272 } 273 274 // Create our decoder 275 var md mapstructure.Metadata 276 var rawTpl rawTemplate 277 rawTpl.RawContents = buf.Bytes() 278 decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 279 Metadata: &md, 280 Result: &rawTpl, 281 }) 282 if err != nil { 283 return nil, err 284 } 285 286 // Do the actual decode into our structure 287 if err := decoder.Decode(raw); err != nil { 288 return nil, err 289 } 290 291 // Build an error if there are unused root level keys 292 if len(md.Unused) > 0 { 293 sort.Strings(md.Unused) 294 for _, unused := range md.Unused { 295 // Ignore keys starting with '_' as comments 296 if unused[0] == '_' { 297 continue 298 } 299 300 err = multierror.Append(err, fmt.Errorf( 301 "Unknown root level key in template: '%s'", unused)) 302 } 303 } 304 if err != nil { 305 return nil, err 306 } 307 308 // Return the template parsed from the raw structure 309 return rawTpl.Template() 310 } 311 312 // ParseFile is the same as Parse but is a helper to automatically open 313 // a file for parsing. 314 func ParseFile(path string) (*Template, error) { 315 f, err := os.Open(path) 316 if err != nil { 317 return nil, err 318 } 319 defer f.Close() 320 321 tpl, err := Parse(f) 322 if err != nil { 323 return nil, err 324 } 325 326 if !filepath.IsAbs(path) { 327 path, err = filepath.Abs(path) 328 if err != nil { 329 return nil, err 330 } 331 } 332 333 tpl.Path = path 334 return tpl, nil 335 }