github.com/hashicorp/packer@v1.14.3/command/validate_test.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: BUSL-1.1 3 4 package command 5 6 import ( 7 "fmt" 8 "path/filepath" 9 "testing" 10 11 "github.com/google/go-cmp/cmp" 12 packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 13 "github.com/hashicorp/packer/packer" 14 ) 15 16 func TestValidateCommand(t *testing.T) { 17 tt := []struct { 18 path string 19 exitCode int 20 extraArgs []string 21 }{ 22 {path: filepath.Join(testFixture("validate"), "build.json")}, 23 {path: filepath.Join(testFixture("validate"), "build.pkr.hcl")}, 24 {path: filepath.Join(testFixture("validate"), "build_with_vars.pkr.hcl")}, 25 {path: filepath.Join(testFixture("validate-invalid"), "bad_provisioner.json"), exitCode: 1}, 26 {path: filepath.Join(testFixture("validate-invalid"), "missing_build_block.pkr.hcl"), exitCode: 1}, 27 {path: filepath.Join(testFixture("validate"), "null_var.json"), exitCode: 1}, 28 {path: filepath.Join(testFixture("validate"), "var_foo_with_no_default.pkr.hcl"), exitCode: 1}, 29 30 {path: testFixture("hcl", "validation", "wrong_pause_before.pkr.hcl"), exitCode: 1}, 31 32 // wrong version fails 33 {path: filepath.Join(testFixture("version_req", "base_failure")), exitCode: 1}, 34 {path: filepath.Join(testFixture("version_req", "base_success")), exitCode: 0}, 35 36 // wrong version field 37 {path: filepath.Join(testFixture("version_req", "wrong_field_name")), exitCode: 1}, 38 39 // wrong packer block type 40 {path: filepath.Join(testFixture("validate", "invalid_block_type.pkr.hcl")), exitCode: 1}, 41 42 // wrong packer block 43 {path: filepath.Join(testFixture("validate", "invalid_packer_block.pkr.hcl")), exitCode: 1}, 44 45 // Should return multiple errors, 46 {path: filepath.Join(testFixture("validate", "circular_error.pkr.hcl")), exitCode: 1}, 47 48 // datasource could be unknown at that moment 49 {path: filepath.Join(testFixture("hcl", "data-source-validation.pkr.hcl")), exitCode: 0}, 50 51 // datasource unknown at validation-time without datasource evaluation -> fail on provisioner 52 {path: filepath.Join(testFixture("hcl", "local-ds-validate.pkr.hcl")), exitCode: 1}, 53 // datasource unknown at validation-time with datasource evaluation -> success 54 {path: filepath.Join(testFixture("hcl", "local-ds-validate.pkr.hcl")), exitCode: 0, extraArgs: []string{"--evaluate-datasources"}}, 55 } 56 57 for _, tc := range tt { 58 t.Run(tc.path, func(t *testing.T) { 59 c := &ValidateCommand{ 60 Meta: TestMetaFile(t), 61 } 62 tc := tc 63 args := tc.extraArgs 64 args = append(args, tc.path) 65 if code := c.Run(args); code != tc.exitCode { 66 fatalCommand(t, c.Meta) 67 } 68 }) 69 } 70 } 71 72 func TestValidateCommand_SkipDatasourceExecution(t *testing.T) { 73 datasourceMock := &packersdk.MockDatasource{} 74 meta := TestMetaFile(t) 75 meta.CoreConfig.Components.PluginConfig.DataSources = packer.MapOfDatasource{ 76 "mock": func() (packersdk.Datasource, error) { 77 return datasourceMock, nil 78 }, 79 } 80 c := &ValidateCommand{ 81 Meta: meta, 82 } 83 args := []string{filepath.Join(testFixture("validate"), "datasource.pkr.hcl")} 84 if code := c.Run(args); code != 0 { 85 fatalCommand(t, c.Meta) 86 } 87 if datasourceMock.ExecuteCalled { 88 t.Fatalf("Datasource should not be executed on validation") 89 } 90 if !datasourceMock.OutputSpecCalled { 91 t.Fatalf("Datasource OutPutSpec should be called on validation") 92 } 93 } 94 95 func TestValidateCommand_SyntaxOnly(t *testing.T) { 96 tt := []struct { 97 path string 98 exitCode int 99 }{ 100 {path: filepath.Join(testFixture("validate"), "build.json")}, 101 {path: filepath.Join(testFixture("validate"), "build.pkr.hcl")}, 102 {path: filepath.Join(testFixture("validate"), "build_with_vars.pkr.hcl")}, 103 {path: filepath.Join(testFixture("validate-invalid"), "bad_provisioner.json")}, 104 {path: filepath.Join(testFixture("validate-invalid"), "missing_build_block.pkr.hcl")}, 105 {path: filepath.Join(testFixture("validate-invalid"), "broken.json"), exitCode: 1}, 106 {path: filepath.Join(testFixture("validate"), "null_var.json")}, 107 {path: filepath.Join(testFixture("validate"), "var_foo_with_no_default.pkr.hcl")}, 108 } 109 110 for _, tc := range tt { 111 t.Run(tc.path, func(t *testing.T) { 112 c := &ValidateCommand{ 113 Meta: TestMetaFile(t), 114 } 115 c.CoreConfig.Version = "102.0.0" 116 tc := tc 117 args := []string{"-syntax-only", tc.path} 118 if code := c.Run(args); code != tc.exitCode { 119 fatalCommand(t, c.Meta) 120 } 121 }) 122 } 123 } 124 125 func TestValidateCommandOKVersion(t *testing.T) { 126 c := &ValidateCommand{ 127 Meta: TestMetaFile(t), 128 } 129 args := []string{ 130 filepath.Join(testFixture("validate"), "template.json"), 131 } 132 133 // This should pass with a valid configuration version 134 c.CoreConfig.Version = "102.0.0" 135 if code := c.Run(args); code != 0 { 136 fatalCommand(t, c.Meta) 137 } 138 } 139 140 func TestValidateCommandBadVersion(t *testing.T) { 141 c := &ValidateCommand{ 142 Meta: TestMetaFile(t), 143 } 144 args := []string{ 145 filepath.Join(testFixture("validate"), "template.json"), 146 } 147 148 // This should fail with an invalid configuration version 149 c.CoreConfig.Version = "100.0.0" 150 if code := c.Run(args); code != 1 { 151 t.Errorf("Expected exit code 1") 152 } 153 154 stdout, stderr := GetStdoutAndErrFromTestMeta(t, c.Meta) 155 expected := `Error: 156 157 This template requires Packer version 101.0.0 or higher; using 100.0.0 158 159 160 ` 161 162 if diff := cmp.Diff(expected, stderr); diff != "" { 163 t.Errorf("Unexpected output: %s", diff) 164 } 165 t.Log(stdout) 166 } 167 168 func TestValidateCommandExcept(t *testing.T) { 169 tt := []struct { 170 name string 171 args []string 172 exitCode int 173 }{ 174 { 175 name: "JSON: validate except build and post-processor", 176 args: []string{ 177 "-except=vanilla,pear", 178 filepath.Join(testFixture("validate"), "validate_except.json"), 179 }, 180 }, 181 { 182 name: "JSON: fail validate except build and post-processor", 183 args: []string{ 184 "-except=chocolate,apple", 185 filepath.Join(testFixture("validate"), "validate_except.json"), 186 }, 187 exitCode: 1, 188 }, 189 { 190 name: "HCL2: validate except build and post-processor", 191 args: []string{ 192 "-except=file.vanilla,pear", 193 filepath.Join(testFixture("validate"), "validate_except.pkr.hcl"), 194 }, 195 }, 196 { 197 name: "HCL2: fail validation except build and post-processor", 198 args: []string{ 199 "-except=file.chocolate,apple", 200 filepath.Join(testFixture("validate"), "validate_except.pkr.hcl"), 201 }, 202 exitCode: 1, 203 }, 204 } 205 206 c := &ValidateCommand{ 207 Meta: TestMetaFile(t), 208 } 209 c.CoreConfig.Version = "102.0.0" 210 211 for _, tc := range tt { 212 t.Run(tc.name, func(t *testing.T) { 213 defer cleanup() 214 215 tc := tc 216 if code := c.Run(tc.args); code != tc.exitCode { 217 fatalCommand(t, c.Meta) 218 } 219 }) 220 } 221 } 222 223 func TestValidateCommand_VarFiles(t *testing.T) { 224 tt := []struct { 225 name string 226 path string 227 varfile string 228 exitCode int 229 }{ 230 {name: "with basic HCL var-file definition", 231 path: filepath.Join(testFixture(filepath.Join("validate", "var-file-tests")), "basic.pkr.hcl"), 232 varfile: filepath.Join(testFixture(filepath.Join("validate", "var-file-tests")), "basic.pkrvars.hcl"), 233 exitCode: 0, 234 }, 235 {name: "with unused variable in var-file definition", 236 path: filepath.Join(testFixture(filepath.Join("validate", "var-file-tests")), "basic.pkr.hcl"), 237 varfile: filepath.Join(testFixture(filepath.Join("validate", "var-file-tests")), "undeclared.pkrvars.hcl"), 238 exitCode: 0, 239 }, 240 {name: "with unused variable in JSON var-file definition", 241 path: filepath.Join(testFixture(filepath.Join("validate", "var-file-tests")), "basic.pkr.hcl"), 242 varfile: filepath.Join(testFixture(filepath.Join("validate", "var-file-tests")), "undeclared.json"), 243 exitCode: 0, 244 }, 245 } 246 for _, tc := range tt { 247 t.Run(tc.path, func(t *testing.T) { 248 c := &ValidateCommand{ 249 Meta: TestMetaFile(t), 250 } 251 tc := tc 252 args := []string{"-var-file", tc.varfile, tc.path} 253 if code := c.Run(args); code != tc.exitCode { 254 fatalCommand(t, c.Meta) 255 } 256 }) 257 } 258 } 259 260 func TestValidateCommand_VarFilesWarnOnUndeclared(t *testing.T) { 261 tt := []struct { 262 name string 263 path string 264 varfile string 265 exitCode int 266 }{ 267 {name: "default warning with unused variable in HCL var-file definition", 268 path: filepath.Join(testFixture(filepath.Join("validate", "var-file-tests")), "basic.pkr.hcl"), 269 varfile: filepath.Join(testFixture(filepath.Join("validate", "var-file-tests")), "undeclared.pkrvars.hcl"), 270 exitCode: 0, 271 }, 272 {name: "default warning with unused variable in JSON var-file definition", 273 path: filepath.Join(testFixture(filepath.Join("validate", "var-file-tests")), "basic.pkr.hcl"), 274 varfile: filepath.Join(testFixture(filepath.Join("validate", "var-file-tests")), "undeclared.json"), 275 exitCode: 0, 276 }, 277 } 278 for _, tc := range tt { 279 t.Run(tc.path, func(t *testing.T) { 280 c := &ValidateCommand{ 281 Meta: TestMetaFile(t), 282 } 283 tc := tc 284 args := []string{"-var-file", tc.varfile, tc.path} 285 if code := c.Run(args); code != tc.exitCode { 286 fatalCommand(t, c.Meta) 287 } 288 289 stdout, stderr := GetStdoutAndErrFromTestMeta(t, c.Meta) 290 expected := `Warning: Undefined variable 291 292 The variable "unused" was set but was not declared as an input variable. 293 To declare variable "unused" place this block in one of your .pkr.hcl files, 294 such as variables.pkr.hcl 295 296 variable "unused" { 297 type = string 298 default = null 299 } 300 301 302 The configuration is valid. 303 ` 304 if diff := cmp.Diff(expected, stdout); diff != "" { 305 t.Errorf("Unexpected output: %s", diff) 306 } 307 t.Log(stderr) 308 }) 309 } 310 } 311 312 func TestValidateCommand_VarFilesDisableWarnOnUndeclared(t *testing.T) { 313 tt := []struct { 314 name string 315 path string 316 varfile string 317 exitCode int 318 }{ 319 {name: "no-warn-undeclared-var with unused variable in HCL var-file definition", 320 path: filepath.Join(testFixture(filepath.Join("validate", "var-file-tests")), "basic.pkr.hcl"), 321 varfile: filepath.Join(testFixture(filepath.Join("validate", "var-file-tests")), "undeclared.pkrvars.hcl"), 322 exitCode: 0, 323 }, 324 {name: "no-warn-undeclared-var with unused variable in JSON var-file definition", 325 path: filepath.Join(testFixture(filepath.Join("validate", "var-file-tests")), "basic.pkr.hcl"), 326 varfile: filepath.Join(testFixture(filepath.Join("validate", "var-file-tests")), "undeclared.json"), 327 exitCode: 0, 328 }, 329 } 330 for _, tc := range tt { 331 t.Run(tc.path, func(t *testing.T) { 332 c := &ValidateCommand{ 333 Meta: TestMetaFile(t), 334 } 335 tc := tc 336 args := []string{"-no-warn-undeclared-var", "-var-file", tc.varfile, tc.path} 337 if code := c.Run(args); code != tc.exitCode { 338 fatalCommand(t, c.Meta) 339 } 340 341 stdout, stderr := GetStdoutAndErrFromTestMeta(t, c.Meta) 342 expected := `The configuration is valid. 343 ` 344 if diff := cmp.Diff(expected, stdout); diff != "" { 345 t.Errorf("Unexpected output: %s", diff) 346 } 347 t.Log(stderr) 348 }) 349 } 350 } 351 352 func TestValidateCommand_ShowLineNumForMissing(t *testing.T) { 353 tt := []struct { 354 path string 355 exitCode int 356 extraArgs []string 357 }{ 358 {path: filepath.Join(testFixture("validate-invalid"), "missing_build_block.pkr.hcl"), exitCode: 1}, 359 } 360 361 for _, tc := range tt { 362 t.Run(tc.path, func(t *testing.T) { 363 c := &ValidateCommand{ 364 Meta: TestMetaFile(t), 365 } 366 tc := tc 367 args := tc.extraArgs 368 args = append(args, tc.path) 369 if code := c.Run(args); code != tc.exitCode { 370 fatalCommand(t, c.Meta) 371 } 372 373 stdout, stderr := GetStdoutAndErrFromTestMeta(t, c.Meta) 374 expected := fmt.Sprintf(`Error: Unknown source file.cho 375 376 on %s line 6: 377 (source code not available) 378 379 Known: [file.chocolate] 380 381 382 `, tc.path) 383 if diff := cmp.Diff(expected, stderr); diff != "" { 384 t.Errorf("Unexpected output: %s", diff) 385 } 386 t.Log(stdout) 387 }) 388 } 389 }