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