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  }