github.com/hashicorp/packer@v1.14.3/command/init_test.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  //go:build amd64 && (darwin || windows || linux)
     5  
     6  package command
     7  
     8  import (
     9  	"fmt"
    10  	"log"
    11  	"os"
    12  	"runtime"
    13  	"sort"
    14  	"strings"
    15  	"testing"
    16  
    17  	"github.com/google/go-cmp/cmp"
    18  	"github.com/hashicorp/packer-plugin-sdk/acctest"
    19  	"golang.org/x/mod/sumdb/dirhash"
    20  )
    21  
    22  type testCaseInit struct {
    23  	name                                  string
    24  	setup                                 []func(*testing.T, testCaseInit)
    25  	Meta                                  Meta
    26  	inPluginFolder                        map[string]string
    27  	expectedPackerConfigDirHashBeforeInit string
    28  	inConfigFolder                        map[string]string
    29  	packerConfigDir                       string
    30  	packerUserFolder                      string
    31  	want                                  int
    32  	dirFiles                              []string
    33  	expectedPackerConfigDirHashAfterInit  string
    34  	moreTests                             []func(*testing.T, testCaseInit)
    35  }
    36  
    37  type testBuild struct {
    38  	want int
    39  }
    40  
    41  func (tb testBuild) fn(t *testing.T, tc testCaseInit) {
    42  	bc := BuildCommand{
    43  		Meta: tc.Meta,
    44  	}
    45  
    46  	args := []string{tc.packerUserFolder}
    47  	want := tb.want
    48  	if got := bc.Run(args); got != want {
    49  		t.Errorf("BuildCommand.Run() = %v, want %v", got, want)
    50  	}
    51  }
    52  
    53  func TestInitCommand_Run(t *testing.T) {
    54  	// These tests will try to optimise for doing the least amount of github api
    55  	// requests whilst testing the max amount of things at once. Hopefully they
    56  	// don't require a GH token just yet. Acc tests are run on linux, darwin and
    57  	// windows, so requests are done 3 times.
    58  
    59  	cfg := &configDirSingleton{map[string]string{}}
    60  
    61  	tests := []testCaseInit{
    62  		//	{
    63  		//		// here we pre-write plugins with valid checksums, Packer will
    64  		//		// see those as valid installations it did.
    65  		//		// the directory hash before and after init should be the same,
    66  		//		// that's a no-op. This also should do no GH query, so it is best
    67  		//		// to always run it.
    68  		//		//
    69  		//		// Note: cannot work with plugin changes since the fake binary
    70  		//		// isn't recognised  as a potential plugin, so Packer always
    71  		//		// installs it.
    72  		//		"already-installed-no-op",
    73  		//		nil,
    74  		//		TestMetaFile(t),
    75  		//		map[string]string{
    76  		//			"github.com/hashicorp/hashicups/packer-plugin-hashicups_v1.0.1_x5.0_darwin_amd64":                "1",
    77  		//			"github.com/hashicorp/hashicups/packer-plugin-hashicups_v1.0.1_x5.0_darwin_amd64_SHA256SUM":      "a23e48324f2d9b912a89354945562b21b0ae99133b31d3132e2e6671aba8e085",
    78  		//			"github.com/hashicorp/hashicups/packer-plugin-hashicups_v1.0.1_x5.0_windows_amd64.exe":           "1.exe",
    79  		//			"github.com/hashicorp/hashicups/packer-plugin-hashicups_v1.0.1_x5.0_windows_amd64.exe_SHA256SUM": "f1cf5865b35933b8e5195625ac8be44487b64007f223912cc5c1784e493e62b2",
    80  		//			"github.com/hashicorp/hashicups/packer-plugin-hashicups_v1.0.1_x5.0_linux_amd64":                 "1.out",
    81  		//			"github.com/hashicorp/hashicups/packer-plugin-hashicups_v1.0.1_x5.0_linux_amd64_SHA256SUM":       "0a4e4e1d6de28054f64946782a5eb92edc663e980ae0780fcb3a614d27c58506",
    82  		//		},
    83  		//		"h1:jQchMpyaQhkZYn0iguw6E6O4VCWxacYx2aR/RJJNLmo=",
    84  		//		map[string]string{
    85  		//			`cfg.pkr.hcl`: `
    86  		//				packer {
    87  		//					required_plugins {
    88  		//						comment = {
    89  		//							source  = "github.com/hashicorp/hashicups"
    90  		//							version = "v1.0.1"
    91  		//						}
    92  		//					}
    93  		//				}`,
    94  		//		},
    95  		//		cfg.dir("1_pkr_config"),
    96  		//		cfg.dir("1_pkr_user_folder"),
    97  		//		0,
    98  		//		nil,
    99  		//		"h1:jQchMpyaQhkZYn0iguw6E6O4VCWxacYx2aR/RJJNLmo=",
   100  		//		[]func(t *testing.T, tc testCaseInit){
   101  		//			// test that a build will not work since plugins are broken for
   102  		//			// this tests (they are not binaries).
   103  		//			testBuild{want: 1}.fn,
   104  		//		},
   105  		//	},
   106  		{
   107  			// here we pre-write plugins with valid checksums, Packer will
   108  			// see those as valid installations it did.
   109  			// But because we require version 1.0.2, we will upgrade.
   110  			"already-installed-upgrade",
   111  			[]func(t *testing.T, tc testCaseInit){
   112  				skipInitTestUnlessEnVar(acctest.TestEnvVar).fn,
   113  			},
   114  			TestMetaFile(t),
   115  			map[string]string{
   116  				"github.com/hashicorp/hashicups/packer-plugin-hashicups_v1.0.1_x5.0_darwin_amd64":                "1",
   117  				"github.com/hashicorp/hashicups/packer-plugin-hashicups_v1.0.1_x5.0_darwin_amd64_SHA256SUM":      "a23e48324f2d9b912a89354945562b21b0ae99133b31d3132e2e6671aba8e085",
   118  				"github.com/hashicorp/hashicups/packer-plugin-hashicups_v1.0.1_x5.0_windows_amd64.exe":           "1.exe",
   119  				"github.com/hashicorp/hashicups/packer-plugin-hashicups_v1.0.1_x5.0_windows_amd64.exe_SHA256SUM": "f1cf5865b35933b8e5195625ac8be44487b64007f223912cc5c1784e493e62b2",
   120  				"github.com/hashicorp/hashicups/packer-plugin-hashicups_v1.0.1_x5.0_linux_amd64":                 "1.out",
   121  				"github.com/hashicorp/hashicups/packer-plugin-hashicups_v1.0.1_x5.0_linux_amd64_SHA256SUM":       "0a4e4e1d6de28054f64946782a5eb92edc663e980ae0780fcb3a614d27c58506",
   122  			},
   123  			"h1:jQchMpyaQhkZYn0iguw6E6O4VCWxacYx2aR/RJJNLmo=",
   124  			map[string]string{
   125  				`cfg.pkr.hcl`: `
   126  					packer {
   127  						required_plugins {
   128  							hashicups = {
   129  								source  = "github.com/hashicorp/hashicups"
   130  								version = "v1.0.2"
   131  							}
   132  						}
   133  					}`,
   134  				`source.pkr.hcl`: `
   135  				source "null" "test" {
   136  					communicator = "none"
   137  				}
   138  				`,
   139  				`build.pkr.hcl`: `
   140  				build {
   141  					sources = ["null.test"]
   142  					provisioner "hashicups-toppings" {
   143  						toppings = ["sugar"] # Takes 5 seconds in the current state
   144  					}
   145  				}
   146  				`,
   147  			},
   148  			cfg.dir("2_pkr_config"),
   149  			cfg.dir("2_pkr_user_folder"),
   150  			0,
   151  			[]string{
   152  				"github.com/hashicorp/hashicups/packer-plugin-hashicups_v1.0.1_x5.0_darwin_amd64",
   153  				"github.com/hashicorp/hashicups/packer-plugin-hashicups_v1.0.1_x5.0_darwin_amd64_SHA256SUM",
   154  				"github.com/hashicorp/hashicups/packer-plugin-hashicups_v1.0.1_x5.0_linux_amd64",
   155  				"github.com/hashicorp/hashicups/packer-plugin-hashicups_v1.0.1_x5.0_linux_amd64_SHA256SUM",
   156  				"github.com/hashicorp/hashicups/packer-plugin-hashicups_v1.0.1_x5.0_windows_amd64.exe",
   157  				"github.com/hashicorp/hashicups/packer-plugin-hashicups_v1.0.1_x5.0_windows_amd64.exe_SHA256SUM",
   158  				map[string]string{
   159  					"darwin":  "github.com/hashicorp/hashicups/packer-plugin-hashicups_v1.0.2_x5.0_darwin_amd64_SHA256SUM",
   160  					"linux":   "github.com/hashicorp/hashicups/packer-plugin-hashicups_v1.0.2_x5.0_linux_amd64_SHA256SUM",
   161  					"windows": "github.com/hashicorp/hashicups/packer-plugin-hashicups_v1.0.2_x5.0_windows_amd64.exe_SHA256SUM",
   162  				}[runtime.GOOS],
   163  				map[string]string{
   164  					"darwin":  "github.com/hashicorp/hashicups/packer-plugin-hashicups_v1.0.2_x5.0_darwin_amd64",
   165  					"linux":   "github.com/hashicorp/hashicups/packer-plugin-hashicups_v1.0.2_x5.0_linux_amd64",
   166  					"windows": "github.com/hashicorp/hashicups/packer-plugin-hashicups_v1.0.2_x5.0_windows_amd64.exe",
   167  				}[runtime.GOOS],
   168  			},
   169  			map[string]string{
   170  				"darwin":  "h1:ptsMLvUeLsMMeXDJP2PWKAKIkE+kWVhOkhNYOYPJbSE=",
   171  				"linux":   "h1:ivCmyQ+/qNXfBsyeccGsa7P5232q7MUZk83B3yl80Ms=",
   172  				"windows": "h1:BeqAUnyGiBg9fVuf9Cn9a4h91bgdZ2U4kV7EuQKefcM=",
   173  			}[runtime.GOOS],
   174  			[]func(t *testing.T, tc testCaseInit){
   175  				// test that a build will work as the plugin was just installed
   176  				testBuild{want: 0}.fn,
   177  			},
   178  		},
   179  		{
   180  			"release-with-no-binary",
   181  			[]func(t *testing.T, tc testCaseInit){
   182  				skipInitTestUnlessEnVar(acctest.TestEnvVar).fn,
   183  			},
   184  			TestMetaFile(t),
   185  			nil,
   186  			"h1:47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=",
   187  			map[string]string{
   188  				`cfg.pkr.hcl`: `
   189  					packer {
   190  						required_plugins {
   191  							comment = {
   192  								source  = "github.com/sylviamoss/comment"
   193  								version = "v0.2.20"
   194  							}
   195  						}
   196  					}`,
   197  			},
   198  			cfg.dir("3_pkr_config"),
   199  			cfg.dir("3_pkr_user_folder"),
   200  			1,
   201  			nil,
   202  			"h1:47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=",
   203  			nil,
   204  		},
   205  		{
   206  			"unsupported-non-github-source-address",
   207  			[]func(t *testing.T, tc testCaseInit){
   208  				skipInitTestUnlessEnVar(acctest.TestEnvVar).fn,
   209  			},
   210  			TestMetaFile(t),
   211  			nil,
   212  			"h1:47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=",
   213  			map[string]string{
   214  				`cfg.pkr.hcl`: `
   215  					packer {
   216  						required_plugins {
   217  							comment = {
   218  								source  = "example.com/sylviamoss/comment"
   219  								version = "v0.2.19"
   220  							}
   221  						}
   222  					}`,
   223  			},
   224  			cfg.dir("6_pkr_config"),
   225  			cfg.dir("6_pkr_user_folder"),
   226  			1,
   227  			nil,
   228  			"h1:47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=",
   229  			nil,
   230  		},
   231  	}
   232  	for _, tt := range tests {
   233  		t.Run(tt.name, func(t *testing.T) {
   234  			log.Printf("starting %s", tt.name)
   235  			log.Printf("%#v", tt)
   236  			t.Cleanup(func() {
   237  				_ = os.RemoveAll(tt.packerConfigDir)
   238  			})
   239  			t.Cleanup(func() {
   240  				_ = os.RemoveAll(tt.packerUserFolder)
   241  			})
   242  			t.Setenv("PACKER_CONFIG_DIR", tt.packerConfigDir)
   243  			for _, init := range tt.setup {
   244  				init(t, tt)
   245  				if t.Skipped() {
   246  					return
   247  				}
   248  			}
   249  			createFiles(tt.packerConfigDir, tt.inPluginFolder)
   250  			createFiles(tt.packerUserFolder, tt.inConfigFolder)
   251  
   252  			hash, err := dirhash.HashDir(tt.packerConfigDir, "", dirhash.DefaultHash)
   253  			if err != nil {
   254  				t.Fatalf("HashDir: %v", err)
   255  			}
   256  			if diff := cmp.Diff(tt.expectedPackerConfigDirHashBeforeInit, hash); diff != "" {
   257  				t.Errorf("unexpected dir hash before init: +found -expected %s", diff)
   258  			}
   259  
   260  			args := []string{tt.packerUserFolder}
   261  
   262  			c := &InitCommand{
   263  				Meta: tt.Meta,
   264  			}
   265  
   266  			if err := c.CoreConfig.Components.PluginConfig.Discover(); err != nil {
   267  				t.Fatalf("Failed to discover plugins: %s", err)
   268  			}
   269  
   270  			c.CoreConfig.Components.PluginConfig.PluginDirectory = tt.packerConfigDir
   271  			if got := c.Run(args); got != tt.want {
   272  				t.Errorf("InitCommand.Run() = %v, want %v", got, tt.want)
   273  			}
   274  
   275  			if tt.dirFiles != nil {
   276  				dirFiles, err := dirhash.DirFiles(tt.packerConfigDir, "")
   277  				if err != nil {
   278  					t.Fatalf("DirFiles: %v", err)
   279  				}
   280  				sort.Strings(tt.dirFiles)
   281  				sort.Strings(dirFiles)
   282  				if diff := cmp.Diff(tt.dirFiles, dirFiles); diff != "" {
   283  					t.Errorf("found files differ: %v", diff)
   284  				}
   285  			}
   286  
   287  			hash, err = dirhash.HashDir(tt.packerConfigDir, "", dirhash.DefaultHash)
   288  			if err != nil {
   289  				t.Fatalf("HashDir: %v", err)
   290  			}
   291  			if diff := cmp.Diff(tt.expectedPackerConfigDirHashAfterInit, hash); diff != "" {
   292  				t.Errorf("unexpected dir hash after init: %s", diff)
   293  			}
   294  
   295  			for i, subTest := range tt.moreTests {
   296  				t.Run(fmt.Sprintf("-subtest-%d", i), func(t *testing.T) {
   297  					subTest(t, tt)
   298  				})
   299  			}
   300  		})
   301  	}
   302  }
   303  
   304  type skipInitTestUnlessEnVar string
   305  
   306  func (key skipInitTestUnlessEnVar) fn(t *testing.T, tc testCaseInit) {
   307  	// always run acc tests for now
   308  	// if os.Getenv(string(key)) == "" {
   309  	// 	t.Skipf("Acceptance test skipped unless env '%s' set", key)
   310  	// }
   311  }
   312  
   313  // TestInitCmd aims to test the init command, with output validation
   314  func TestInitCmd(t *testing.T) {
   315  	tests := []struct {
   316  		name         string
   317  		args         []string
   318  		expectedCode int
   319  		outputCheck  func(string, string) error
   320  	}{
   321  		{
   322  			name: "Ensure init warns on template without required_plugin blocks",
   323  			args: []string{
   324  				testFixture("hcl", "build-var-in-pp.pkr.hcl"),
   325  			},
   326  			expectedCode: 0,
   327  			outputCheck: func(stdout, stderr string) error {
   328  				if !strings.Contains(stdout, "No plugins requirement found") {
   329  					return fmt.Errorf("command should warn about plugin requirements not found, but did not")
   330  				}
   331  				return nil
   332  			},
   333  		},
   334  	}
   335  
   336  	for _, tt := range tests {
   337  		t.Run(tt.name, func(t *testing.T) {
   338  			c := &InitCommand{
   339  				Meta: TestMetaFile(t),
   340  			}
   341  
   342  			exitCode := c.Run(tt.args)
   343  			if exitCode != tt.expectedCode {
   344  				t.Errorf("process exit code mismatch: expected %d, got %d",
   345  					tt.expectedCode,
   346  					exitCode)
   347  			}
   348  
   349  			out, stderr := GetStdoutAndErrFromTestMeta(t, c.Meta)
   350  			err := tt.outputCheck(out, stderr)
   351  			if err != nil {
   352  				if len(out) != 0 {
   353  					t.Logf("command stdout: %q", out)
   354  				}
   355  
   356  				if len(stderr) != 0 {
   357  					t.Logf("command stderr: %q", stderr)
   358  				}
   359  				t.Error(err.Error())
   360  			}
   361  		})
   362  	}
   363  }