github.com/kimor79/packer@v0.8.7-0.20151221212622-d507b18eb4cf/template/parse.go (about)

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