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 }