github.com/timsutton/packer@v1.3.2/command/validate.go (about)

     1  package command
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"log"
     7  	"strings"
     8  
     9  	"github.com/hashicorp/packer/fix"
    10  	"github.com/hashicorp/packer/packer"
    11  	"github.com/hashicorp/packer/template"
    12  
    13  	"github.com/google/go-cmp/cmp"
    14  	"github.com/posener/complete"
    15  )
    16  
    17  type ValidateCommand struct {
    18  	Meta
    19  }
    20  
    21  func (c *ValidateCommand) Run(args []string) int {
    22  	var cfgSyntaxOnly bool
    23  	flags := c.Meta.FlagSet("validate", FlagSetBuildFilter|FlagSetVars)
    24  	flags.Usage = func() { c.Ui.Say(c.Help()) }
    25  	flags.BoolVar(&cfgSyntaxOnly, "syntax-only", false, "check syntax only")
    26  	if err := flags.Parse(args); err != nil {
    27  		return 1
    28  	}
    29  
    30  	args = flags.Args()
    31  	if len(args) != 1 {
    32  		flags.Usage()
    33  		return 1
    34  	}
    35  
    36  	// Parse the template
    37  	tpl, err := template.ParseFile(args[0])
    38  	if err != nil {
    39  		c.Ui.Error(fmt.Sprintf("Failed to parse template: %s", err))
    40  		return 1
    41  	}
    42  
    43  	// If we're only checking syntax, then we're done already
    44  	if cfgSyntaxOnly {
    45  		c.Ui.Say("Syntax-only check passed. Everything looks okay.")
    46  		return 0
    47  	}
    48  
    49  	// Get the core
    50  	core, err := c.Meta.Core(tpl)
    51  	if err != nil {
    52  		c.Ui.Error(err.Error())
    53  		return 1
    54  	}
    55  
    56  	errs := make([]error, 0)
    57  	warnings := make(map[string][]string)
    58  
    59  	// Get the builds we care about
    60  	buildNames := c.Meta.BuildNames(core)
    61  	builds := make([]packer.Build, 0, len(buildNames))
    62  	for _, n := range buildNames {
    63  		b, err := core.Build(n)
    64  		if err != nil {
    65  			c.Ui.Error(fmt.Sprintf(
    66  				"Failed to initialize build '%s': %s",
    67  				n, err))
    68  			return 1
    69  		}
    70  
    71  		builds = append(builds, b)
    72  	}
    73  
    74  	// Check the configuration of all builds
    75  	for _, b := range builds {
    76  		log.Printf("Preparing build: %s", b.Name())
    77  		warns, err := b.Prepare()
    78  		if len(warns) > 0 {
    79  			warnings[b.Name()] = warns
    80  		}
    81  		if err != nil {
    82  			errs = append(errs, fmt.Errorf("Errors validating build '%s'. %s", b.Name(), err))
    83  		}
    84  	}
    85  
    86  	// Check if any of the configuration is fixable
    87  	var rawTemplateData map[string]interface{}
    88  	input := make(map[string]interface{})
    89  	templateData := make(map[string]interface{})
    90  	json.Unmarshal(tpl.RawContents, &rawTemplateData)
    91  	for k, v := range rawTemplateData {
    92  		if vals, ok := v.([]interface{}); ok {
    93  			if len(vals) == 0 {
    94  				continue
    95  			}
    96  		}
    97  		templateData[strings.ToLower(k)] = v
    98  		input[strings.ToLower(k)] = v
    99  	}
   100  
   101  	// fix rawTemplateData into input
   102  	for _, name := range fix.FixerOrder {
   103  		var err error
   104  		fixer, ok := fix.Fixers[name]
   105  		if !ok {
   106  			panic("fixer not found: " + name)
   107  		}
   108  		input, err = fixer.Fix(input)
   109  		if err != nil {
   110  			c.Ui.Error(fmt.Sprintf("Error checking against fixers: %s", err))
   111  			return 1
   112  		}
   113  	}
   114  	// delete empty top-level keys since the fixers seem to add them
   115  	// willy-nilly
   116  	for k := range input {
   117  		ml, ok := input[k].([]map[string]interface{})
   118  		if !ok {
   119  			continue
   120  		}
   121  		if len(ml) == 0 {
   122  			delete(input, k)
   123  		}
   124  	}
   125  	// marshal/unmarshal to make comparable to templateData
   126  	var fixedData map[string]interface{}
   127  	// Guaranteed to be valid json, so we can ignore errors
   128  	j, _ := json.Marshal(input)
   129  	json.Unmarshal(j, &fixedData)
   130  
   131  	if diff := cmp.Diff(templateData, fixedData); diff != "" {
   132  		c.Ui.Say("[warning] Fixable configuration found.")
   133  		c.Ui.Say("You may need to run `packer fix` to get your build to run")
   134  		c.Ui.Say("correctly. See debug log for more information.\n")
   135  		log.Printf("Fixable config differences:\n%s", diff)
   136  	}
   137  
   138  	if len(errs) > 0 {
   139  		c.Ui.Error("Template validation failed. Errors are shown below.\n")
   140  		for i, err := range errs {
   141  			c.Ui.Error(err.Error())
   142  
   143  			if (i + 1) < len(errs) {
   144  				c.Ui.Error("")
   145  			}
   146  		}
   147  		return 1
   148  	}
   149  
   150  	if len(warnings) > 0 {
   151  		c.Ui.Say("Template validation succeeded, but there were some warnings.")
   152  		c.Ui.Say("These are ONLY WARNINGS, and Packer will attempt to build the")
   153  		c.Ui.Say("template despite them, but they should be paid attention to.\n")
   154  
   155  		for build, warns := range warnings {
   156  			c.Ui.Say(fmt.Sprintf("Warnings for build '%s':\n", build))
   157  			for _, warning := range warns {
   158  				c.Ui.Say(fmt.Sprintf("* %s", warning))
   159  			}
   160  		}
   161  
   162  		return 0
   163  	}
   164  
   165  	c.Ui.Say("Template validated successfully.")
   166  	return 0
   167  }
   168  
   169  func (*ValidateCommand) Help() string {
   170  	helpText := `
   171  Usage: packer validate [options] TEMPLATE
   172  
   173    Checks the template is valid by parsing the template and also
   174    checking the configuration with the various builders, provisioners, etc.
   175  
   176    If it is not valid, the errors will be shown and the command will exit
   177    with a non-zero exit status. If it is valid, it will exit with a zero
   178    exit status.
   179  
   180  Options:
   181  
   182    -syntax-only           Only check syntax. Do not verify config of the template.
   183    -except=foo,bar,baz    Validate all builds other than these.
   184    -only=foo,bar,baz      Validate only these builds.
   185    -var 'key=value'       Variable for templates, can be used multiple times.
   186    -var-file=path         JSON file containing user variables.
   187  `
   188  
   189  	return strings.TrimSpace(helpText)
   190  }
   191  
   192  func (*ValidateCommand) Synopsis() string {
   193  	return "check that a template is valid"
   194  }
   195  
   196  func (*ValidateCommand) AutocompleteArgs() complete.Predictor {
   197  	return complete.PredictNothing
   198  }
   199  
   200  func (*ValidateCommand) AutocompleteFlags() complete.Flags {
   201  	return complete.Flags{
   202  		"-syntax-only": complete.PredictNothing,
   203  		"-except":      complete.PredictNothing,
   204  		"-only":        complete.PredictNothing,
   205  		"-var":         complete.PredictNothing,
   206  		"-var-file":    complete.PredictNothing,
   207  	}
   208  }