github.com/daniellockard/packer@v0.7.6-0.20141210173435-5a9390934716/packer/template.go (about)

     1  package packer
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"github.com/hashicorp/go-version"
     7  	"github.com/mitchellh/mapstructure"
     8  	jsonutil "github.com/mitchellh/packer/common/json"
     9  	"io"
    10  	"io/ioutil"
    11  	"os"
    12  	"sort"
    13  	"text/template"
    14  	"time"
    15  )
    16  
    17  // The rawTemplate struct represents the structure of a template read
    18  // directly from a file. The builders and other components map just to
    19  // "interface{}" pointers since we actually don't know what their contents
    20  // are until we read the "type" field.
    21  type rawTemplate struct {
    22  	MinimumPackerVersion string `mapstructure:"min_packer_version"`
    23  
    24  	Description    string
    25  	Builders       []map[string]interface{}
    26  	Hooks          map[string][]string
    27  	Push           PushConfig
    28  	PostProcessors []interface{} `mapstructure:"post-processors"`
    29  	Provisioners   []map[string]interface{}
    30  	Variables      map[string]interface{}
    31  }
    32  
    33  // The Template struct represents a parsed template, parsed into the most
    34  // completed form it can be without additional processing by the caller.
    35  type Template struct {
    36  	Description    string
    37  	Variables      map[string]RawVariable
    38  	Builders       map[string]RawBuilderConfig
    39  	Hooks          map[string][]string
    40  	Push           *PushConfig
    41  	PostProcessors [][]RawPostProcessorConfig
    42  	Provisioners   []RawProvisionerConfig
    43  }
    44  
    45  // PushConfig is the configuration structure for the push settings.
    46  type PushConfig struct {
    47  	Name    string
    48  	Address string
    49  	BaseDir string `mapstructure:"base_dir"`
    50  	Include []string
    51  	Exclude []string
    52  	Token   string
    53  	VCS     bool
    54  }
    55  
    56  // The RawBuilderConfig struct represents a raw, unprocessed builder
    57  // configuration. It contains the name of the builder as well as the
    58  // raw configuration. If requested, this is used to compile into a full
    59  // builder configuration at some point.
    60  type RawBuilderConfig struct {
    61  	Name string
    62  	Type string
    63  
    64  	RawConfig interface{}
    65  }
    66  
    67  // RawPostProcessorConfig represents a raw, unprocessed post-processor
    68  // configuration. It contains the type of the post processor as well as the
    69  // raw configuration that is handed to the post-processor for it to process.
    70  type RawPostProcessorConfig struct {
    71  	TemplateOnlyExcept `mapstructure:",squash"`
    72  
    73  	Type              string
    74  	KeepInputArtifact bool `mapstructure:"keep_input_artifact"`
    75  	RawConfig         map[string]interface{}
    76  }
    77  
    78  // RawProvisionerConfig represents a raw, unprocessed provisioner configuration.
    79  // It contains the type of the provisioner as well as the raw configuration
    80  // that is handed to the provisioner for it to process.
    81  type RawProvisionerConfig struct {
    82  	TemplateOnlyExcept `mapstructure:",squash"`
    83  
    84  	Type           string
    85  	Override       map[string]interface{}
    86  	RawPauseBefore string `mapstructure:"pause_before"`
    87  
    88  	RawConfig interface{}
    89  
    90  	pauseBefore time.Duration
    91  }
    92  
    93  // RawVariable represents a variable configuration within a template.
    94  type RawVariable struct {
    95  	Default  string // The default value for this variable
    96  	Required bool   // If the variable is required or not
    97  	Value    string // The set value for this variable
    98  	HasValue bool   // True if the value was set
    99  }
   100  
   101  // ParseTemplate takes a byte slice and parses a Template from it, returning
   102  // the template and possibly errors while loading the template. The error
   103  // could potentially be a MultiError, representing multiple errors. Knowing
   104  // and checking for this can be useful, if you wish to format it in a certain
   105  // way.
   106  //
   107  // The second parameter, vars, are the values for a set of user variables.
   108  func ParseTemplate(data []byte, vars map[string]string) (t *Template, err error) {
   109  	var rawTplInterface interface{}
   110  	err = jsonutil.Unmarshal(data, &rawTplInterface)
   111  	if err != nil {
   112  		return
   113  	}
   114  
   115  	// Decode the raw template interface into the actual rawTemplate
   116  	// structure, checking for any extranneous keys along the way.
   117  	var md mapstructure.Metadata
   118  	var rawTpl rawTemplate
   119  	decoderConfig := &mapstructure.DecoderConfig{
   120  		Metadata: &md,
   121  		Result:   &rawTpl,
   122  	}
   123  
   124  	decoder, err := mapstructure.NewDecoder(decoderConfig)
   125  	if err != nil {
   126  		return
   127  	}
   128  
   129  	err = decoder.Decode(rawTplInterface)
   130  	if err != nil {
   131  		return
   132  	}
   133  
   134  	if rawTpl.MinimumPackerVersion != "" {
   135  		// TODO: NOPE! Replace this
   136  		Version := "1.0"
   137  		vCur, err := version.NewVersion(Version)
   138  		if err != nil {
   139  			panic(err)
   140  		}
   141  		vReq, err := version.NewVersion(rawTpl.MinimumPackerVersion)
   142  		if err != nil {
   143  			return nil, fmt.Errorf(
   144  				"'minimum_packer_version' error: %s", err)
   145  		}
   146  
   147  		if vCur.LessThan(vReq) {
   148  			return nil, fmt.Errorf(
   149  				"Template requires Packer version %s. "+
   150  					"Running version is %s.",
   151  				vReq, vCur)
   152  		}
   153  	}
   154  
   155  	errors := make([]error, 0)
   156  
   157  	if len(md.Unused) > 0 {
   158  		sort.Strings(md.Unused)
   159  		for _, unused := range md.Unused {
   160  			errors = append(
   161  				errors, fmt.Errorf("Unknown root level key in template: '%s'", unused))
   162  		}
   163  	}
   164  
   165  	t = &Template{}
   166  	t.Description = rawTpl.Description
   167  	t.Variables = make(map[string]RawVariable)
   168  	t.Builders = make(map[string]RawBuilderConfig)
   169  	t.Hooks = rawTpl.Hooks
   170  	t.Push = &rawTpl.Push
   171  	t.PostProcessors = make([][]RawPostProcessorConfig, len(rawTpl.PostProcessors))
   172  	t.Provisioners = make([]RawProvisionerConfig, len(rawTpl.Provisioners))
   173  
   174  	// Gather all the variables
   175  	for k, v := range rawTpl.Variables {
   176  		var variable RawVariable
   177  		variable.Required = v == nil
   178  
   179  		// Create a new mapstructure decoder in order to decode the default
   180  		// value since this is the only value in the regular template that
   181  		// can be weakly typed.
   182  		decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   183  			Result:           &variable.Default,
   184  			WeaklyTypedInput: true,
   185  		})
   186  		if err != nil {
   187  			// This should never happen.
   188  			panic(err)
   189  		}
   190  
   191  		err = decoder.Decode(v)
   192  		if err != nil {
   193  			errors = append(errors,
   194  				fmt.Errorf("Error decoding default value for user var '%s': %s", k, err))
   195  			continue
   196  		}
   197  
   198  		// Set the value of this variable if we have it
   199  		if val, ok := vars[k]; ok {
   200  			variable.HasValue = true
   201  			variable.Value = val
   202  			delete(vars, k)
   203  		}
   204  
   205  		t.Variables[k] = variable
   206  	}
   207  
   208  	// Gather all the builders
   209  	for i, v := range rawTpl.Builders {
   210  		var raw RawBuilderConfig
   211  		if err := mapstructure.Decode(v, &raw); err != nil {
   212  			if merr, ok := err.(*mapstructure.Error); ok {
   213  				for _, err := range merr.Errors {
   214  					errors = append(errors, fmt.Errorf("builder %d: %s", i+1, err))
   215  				}
   216  			} else {
   217  				errors = append(errors, fmt.Errorf("builder %d: %s", i+1, err))
   218  			}
   219  
   220  			continue
   221  		}
   222  
   223  		if raw.Type == "" {
   224  			errors = append(errors, fmt.Errorf("builder %d: missing 'type'", i+1))
   225  			continue
   226  		}
   227  
   228  		// Attempt to get the name of the builder. If the "name" key
   229  		// missing, use the "type" field, which is guaranteed to exist
   230  		// at this point.
   231  		if raw.Name == "" {
   232  			raw.Name = raw.Type
   233  		}
   234  
   235  		// Check if we already have a builder with this name and error if so
   236  		if _, ok := t.Builders[raw.Name]; ok {
   237  			errors = append(errors, fmt.Errorf("builder with name '%s' already exists", raw.Name))
   238  			continue
   239  		}
   240  
   241  		// Now that we have the name, remove it from the config - as the builder
   242  		// itself doesn't know about, and it will cause a validation error.
   243  		delete(v, "name")
   244  
   245  		raw.RawConfig = v
   246  
   247  		t.Builders[raw.Name] = raw
   248  	}
   249  
   250  	// Gather all the post-processors. This is a complicated process since there
   251  	// are actually three different formats that the user can use to define
   252  	// a post-processor.
   253  	for i, rawV := range rawTpl.PostProcessors {
   254  		rawPP, err := parsePostProcessor(i, rawV)
   255  		if err != nil {
   256  			errors = append(errors, err...)
   257  			continue
   258  		}
   259  
   260  		configs := make([]RawPostProcessorConfig, 0, len(rawPP))
   261  		for j, pp := range rawPP {
   262  			var config RawPostProcessorConfig
   263  			if err := mapstructure.Decode(pp, &config); err != nil {
   264  				if merr, ok := err.(*mapstructure.Error); ok {
   265  					for _, err := range merr.Errors {
   266  						errors = append(errors,
   267  							fmt.Errorf("Post-processor #%d.%d: %s", i+1, j+1, err))
   268  					}
   269  				} else {
   270  					errors = append(errors,
   271  						fmt.Errorf("Post-processor %d.%d: %s", i+1, j+1, err))
   272  				}
   273  
   274  				continue
   275  			}
   276  
   277  			if config.Type == "" {
   278  				errors = append(errors,
   279  					fmt.Errorf("Post-processor %d.%d: missing 'type'", i+1, j+1))
   280  				continue
   281  			}
   282  
   283  			// Remove the input keep_input_artifact option
   284  			config.TemplateOnlyExcept.Prune(pp)
   285  			delete(pp, "keep_input_artifact")
   286  
   287  			// Verify that the only settings are good
   288  			if errs := config.TemplateOnlyExcept.Validate(t.Builders); len(errs) > 0 {
   289  				for _, err := range errs {
   290  					errors = append(errors,
   291  						fmt.Errorf("Post-processor %d.%d: %s", i+1, j+1, err))
   292  				}
   293  
   294  				continue
   295  			}
   296  
   297  			config.RawConfig = pp
   298  
   299  			// Add it to the list of configs
   300  			configs = append(configs, config)
   301  		}
   302  
   303  		t.PostProcessors[i] = configs
   304  	}
   305  
   306  	// Gather all the provisioners
   307  	for i, v := range rawTpl.Provisioners {
   308  		raw := &t.Provisioners[i]
   309  		if err := mapstructure.Decode(v, raw); err != nil {
   310  			if merr, ok := err.(*mapstructure.Error); ok {
   311  				for _, err := range merr.Errors {
   312  					errors = append(errors, fmt.Errorf("provisioner %d: %s", i+1, err))
   313  				}
   314  			} else {
   315  				errors = append(errors, fmt.Errorf("provisioner %d: %s", i+1, err))
   316  			}
   317  
   318  			continue
   319  		}
   320  
   321  		if raw.Type == "" {
   322  			errors = append(errors, fmt.Errorf("provisioner %d: missing 'type'", i+1))
   323  			continue
   324  		}
   325  
   326  		// Delete the keys that we used
   327  		raw.TemplateOnlyExcept.Prune(v)
   328  		delete(v, "override")
   329  
   330  		// Verify that the override keys exist...
   331  		for name, _ := range raw.Override {
   332  			if _, ok := t.Builders[name]; !ok {
   333  				errors = append(
   334  					errors, fmt.Errorf("provisioner %d: build '%s' not found for override", i+1, name))
   335  			}
   336  		}
   337  
   338  		// Verify that the only settings are good
   339  		if errs := raw.TemplateOnlyExcept.Validate(t.Builders); len(errs) > 0 {
   340  			for _, err := range errs {
   341  				errors = append(errors,
   342  					fmt.Errorf("provisioner %d: %s", i+1, err))
   343  			}
   344  		}
   345  
   346  		// Setup the pause settings
   347  		if raw.RawPauseBefore != "" {
   348  			duration, err := time.ParseDuration(raw.RawPauseBefore)
   349  			if err != nil {
   350  				errors = append(
   351  					errors, fmt.Errorf(
   352  						"provisioner %d: pause_before invalid: %s",
   353  						i+1, err))
   354  			}
   355  
   356  			raw.pauseBefore = duration
   357  		}
   358  
   359  		// Remove the pause_before setting if it is there so that we don't
   360  		// get template validation errors later.
   361  		delete(v, "pause_before")
   362  
   363  		raw.RawConfig = v
   364  	}
   365  
   366  	if len(t.Builders) == 0 {
   367  		errors = append(errors, fmt.Errorf("No builders are defined in the template."))
   368  	}
   369  
   370  	// Verify that all the variable sets were for real variables.
   371  	for k, _ := range vars {
   372  		errors = append(errors, fmt.Errorf("Unknown user variables: %s", k))
   373  	}
   374  
   375  	// If there were errors, we put it into a MultiError and return
   376  	if len(errors) > 0 {
   377  		err = &MultiError{errors}
   378  		t = nil
   379  		return
   380  	}
   381  
   382  	return
   383  }
   384  
   385  // ParseTemplateFile takes the given template file and parses it into
   386  // a single template.
   387  func ParseTemplateFile(path string, vars map[string]string) (*Template, error) {
   388  	var data []byte
   389  
   390  	if path == "-" {
   391  		// Read from stdin...
   392  		buf := new(bytes.Buffer)
   393  		_, err := io.Copy(buf, os.Stdin)
   394  		if err != nil {
   395  			return nil, err
   396  		}
   397  
   398  		data = buf.Bytes()
   399  	} else {
   400  		var err error
   401  		data, err = ioutil.ReadFile(path)
   402  		if err != nil {
   403  			return nil, err
   404  		}
   405  	}
   406  
   407  	return ParseTemplate(data, vars)
   408  }
   409  
   410  func parsePostProcessor(i int, rawV interface{}) (result []map[string]interface{}, errors []error) {
   411  	switch v := rawV.(type) {
   412  	case string:
   413  		result = []map[string]interface{}{
   414  			{"type": v},
   415  		}
   416  	case map[string]interface{}:
   417  		result = []map[string]interface{}{v}
   418  	case []interface{}:
   419  		result = make([]map[string]interface{}, len(v))
   420  		errors = make([]error, 0)
   421  		for j, innerRawV := range v {
   422  			switch innerV := innerRawV.(type) {
   423  			case string:
   424  				result[j] = map[string]interface{}{"type": innerV}
   425  			case map[string]interface{}:
   426  				result[j] = innerV
   427  			case []interface{}:
   428  				errors = append(
   429  					errors,
   430  					fmt.Errorf("Post-processor %d.%d: sequences not allowed to be nested in sequences", i+1, j+1))
   431  			default:
   432  				errors = append(errors, fmt.Errorf("Post-processor %d.%d is in a bad format.", i+1, j+1))
   433  			}
   434  		}
   435  
   436  		if len(errors) == 0 {
   437  			errors = nil
   438  		}
   439  	default:
   440  		result = nil
   441  		errors = []error{fmt.Errorf("Post-processor %d is in a bad format.", i+1)}
   442  	}
   443  
   444  	return
   445  }
   446  
   447  // BuildNames returns a slice of the available names of builds that
   448  // this template represents.
   449  func (t *Template) BuildNames() []string {
   450  	names := make([]string, 0, len(t.Builders))
   451  	for name, _ := range t.Builders {
   452  		names = append(names, name)
   453  	}
   454  
   455  	return names
   456  }
   457  
   458  // Build returns a Build for the given name.
   459  //
   460  // If the build does not exist as part of this template, an error is
   461  // returned.
   462  func (t *Template) Build(name string, components *ComponentFinder) (b Build, err error) {
   463  	// Setup the Builder
   464  	builderConfig, ok := t.Builders[name]
   465  	if !ok {
   466  		err = fmt.Errorf("No such build found in template: %s", name)
   467  		return
   468  	}
   469  
   470  	// We panic if there is no builder function because this is really
   471  	// an internal bug that always needs to be fixed, not an error.
   472  	if components.Builder == nil {
   473  		panic("no builder function")
   474  	}
   475  
   476  	// Panic if there are provisioners on the template but no provisioner
   477  	// component finder. This is always an internal error, so we panic.
   478  	if len(t.Provisioners) > 0 && components.Provisioner == nil {
   479  		panic("no provisioner function")
   480  	}
   481  
   482  	builder, err := components.Builder(builderConfig.Type)
   483  	if err != nil {
   484  		return
   485  	}
   486  
   487  	if builder == nil {
   488  		err = fmt.Errorf("Builder type not found: %s", builderConfig.Type)
   489  		return
   490  	}
   491  
   492  	// Process the name
   493  	tpl, variables, err := t.NewConfigTemplate()
   494  	if err != nil {
   495  		return nil, err
   496  	}
   497  
   498  	rawName := name
   499  	name, err = tpl.Process(name, nil)
   500  	if err != nil {
   501  		return nil, err
   502  	}
   503  
   504  	// Gather the Hooks
   505  	hooks := make(map[string][]Hook)
   506  	for tplEvent, tplHooks := range t.Hooks {
   507  		curHooks := make([]Hook, 0, len(tplHooks))
   508  
   509  		for _, hookName := range tplHooks {
   510  			var hook Hook
   511  			hook, err = components.Hook(hookName)
   512  			if err != nil {
   513  				return
   514  			}
   515  
   516  			if hook == nil {
   517  				err = fmt.Errorf("Hook not found: %s", hookName)
   518  				return
   519  			}
   520  
   521  			curHooks = append(curHooks, hook)
   522  		}
   523  
   524  		hooks[tplEvent] = curHooks
   525  	}
   526  
   527  	// Prepare the post-processors
   528  	postProcessors := make([][]coreBuildPostProcessor, 0, len(t.PostProcessors))
   529  	for _, rawPPs := range t.PostProcessors {
   530  		current := make([]coreBuildPostProcessor, 0, len(rawPPs))
   531  		for _, rawPP := range rawPPs {
   532  			if rawPP.TemplateOnlyExcept.Skip(rawName) {
   533  				continue
   534  			}
   535  
   536  			pp, err := components.PostProcessor(rawPP.Type)
   537  			if err != nil {
   538  				return nil, err
   539  			}
   540  
   541  			if pp == nil {
   542  				return nil, fmt.Errorf("PostProcessor type not found: %s", rawPP.Type)
   543  			}
   544  
   545  			current = append(current, coreBuildPostProcessor{
   546  				processor:         pp,
   547  				processorType:     rawPP.Type,
   548  				config:            rawPP.RawConfig,
   549  				keepInputArtifact: rawPP.KeepInputArtifact,
   550  			})
   551  		}
   552  
   553  		// If we have no post-processors in this chain, just continue.
   554  		// This can happen if the post-processors skip certain builds.
   555  		if len(current) == 0 {
   556  			continue
   557  		}
   558  
   559  		postProcessors = append(postProcessors, current)
   560  	}
   561  
   562  	// Prepare the provisioners
   563  	provisioners := make([]coreBuildProvisioner, 0, len(t.Provisioners))
   564  	for _, rawProvisioner := range t.Provisioners {
   565  		if rawProvisioner.TemplateOnlyExcept.Skip(rawName) {
   566  			continue
   567  		}
   568  
   569  		var provisioner Provisioner
   570  		provisioner, err = components.Provisioner(rawProvisioner.Type)
   571  		if err != nil {
   572  			return
   573  		}
   574  
   575  		if provisioner == nil {
   576  			err = fmt.Errorf("Provisioner type not found: %s", rawProvisioner.Type)
   577  			return
   578  		}
   579  
   580  		configs := make([]interface{}, 1, 2)
   581  		configs[0] = rawProvisioner.RawConfig
   582  
   583  		if rawProvisioner.Override != nil {
   584  			if override, ok := rawProvisioner.Override[name]; ok {
   585  				configs = append(configs, override)
   586  			}
   587  		}
   588  
   589  		if rawProvisioner.pauseBefore > 0 {
   590  			provisioner = &PausedProvisioner{
   591  				PauseBefore: rawProvisioner.pauseBefore,
   592  				Provisioner: provisioner,
   593  			}
   594  		}
   595  
   596  		coreProv := coreBuildProvisioner{provisioner, configs}
   597  		provisioners = append(provisioners, coreProv)
   598  	}
   599  
   600  	b = &coreBuild{
   601  		name:           name,
   602  		builder:        builder,
   603  		builderConfig:  builderConfig.RawConfig,
   604  		builderType:    builderConfig.Type,
   605  		hooks:          hooks,
   606  		postProcessors: postProcessors,
   607  		provisioners:   provisioners,
   608  		variables:      variables,
   609  	}
   610  
   611  	return
   612  }
   613  
   614  //Build a ConfigTemplate object populated by the values within a
   615  //parsed template
   616  func (t *Template) NewConfigTemplate() (c *ConfigTemplate, variables map[string]string, err error) {
   617  
   618  	// Prepare the variable template processor, which is a bit unique
   619  	// because we don't allow user variable usage and we add a function
   620  	// to read from the environment.
   621  	varTpl, err := NewConfigTemplate()
   622  	if err != nil {
   623  		return nil, nil, err
   624  	}
   625  	varTpl.Funcs(template.FuncMap{
   626  		"env":  templateEnv,
   627  		"user": templateDisableUser,
   628  	})
   629  
   630  	// Prepare the variables
   631  	var varErrors []error
   632  	variables = make(map[string]string)
   633  	for k, v := range t.Variables {
   634  		if v.Required && !v.HasValue {
   635  			varErrors = append(varErrors,
   636  				fmt.Errorf("Required user variable '%s' not set", k))
   637  		}
   638  
   639  		var val string
   640  		if v.HasValue {
   641  			val = v.Value
   642  		} else {
   643  			val, err = varTpl.Process(v.Default, nil)
   644  			if err != nil {
   645  				varErrors = append(varErrors,
   646  					fmt.Errorf("Error processing user variable '%s': %s'", k, err))
   647  			}
   648  		}
   649  
   650  		variables[k] = val
   651  	}
   652  
   653  	if len(varErrors) > 0 {
   654  		return nil, variables, &MultiError{varErrors}
   655  	}
   656  
   657  	// Process the name
   658  	tpl, err := NewConfigTemplate()
   659  	if err != nil {
   660  		return nil, variables, err
   661  	}
   662  	tpl.UserVars = variables
   663  
   664  	return tpl, variables, nil
   665  }
   666  
   667  // TemplateOnlyExcept contains the logic required for "only" and "except"
   668  // meta-parameters.
   669  type TemplateOnlyExcept struct {
   670  	Only   []string
   671  	Except []string
   672  }
   673  
   674  // Prune will prune out the used values from the raw map.
   675  func (t *TemplateOnlyExcept) Prune(raw map[string]interface{}) {
   676  	delete(raw, "except")
   677  	delete(raw, "only")
   678  }
   679  
   680  // Skip tests if we should skip putting this item onto a build.
   681  func (t *TemplateOnlyExcept) Skip(name string) bool {
   682  	if len(t.Only) > 0 {
   683  		onlyFound := false
   684  		for _, n := range t.Only {
   685  			if n == name {
   686  				onlyFound = true
   687  				break
   688  			}
   689  		}
   690  
   691  		if !onlyFound {
   692  			// Skip this provisioner
   693  			return true
   694  		}
   695  	}
   696  
   697  	// If the name is in the except list, then skip that
   698  	for _, n := range t.Except {
   699  		if n == name {
   700  			return true
   701  		}
   702  	}
   703  
   704  	return false
   705  }
   706  
   707  // Validates the only/except parameters.
   708  func (t *TemplateOnlyExcept) Validate(b map[string]RawBuilderConfig) (e []error) {
   709  	if len(t.Only) > 0 && len(t.Except) > 0 {
   710  		e = append(e,
   711  			fmt.Errorf("Only one of 'only' or 'except' may be specified."))
   712  	}
   713  
   714  	if len(t.Only) > 0 {
   715  		for _, n := range t.Only {
   716  			if _, ok := b[n]; !ok {
   717  				e = append(e,
   718  					fmt.Errorf("'only' specified builder '%s' not found", n))
   719  			}
   720  		}
   721  	}
   722  
   723  	for _, n := range t.Except {
   724  		if _, ok := b[n]; !ok {
   725  			e = append(e,
   726  				fmt.Errorf("'except' specified builder '%s' not found", n))
   727  		}
   728  	}
   729  
   730  	return
   731  }