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  }