github.com/hashicorp/packer@v1.14.3/packer/plugin-getter/plugins_test.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package plugingetter
     5  
     6  import (
     7  	"archive/zip"
     8  	"bytes"
     9  	"crypto/sha256"
    10  	"encoding/json"
    11  	"fmt"
    12  	"io"
    13  	"log"
    14  	"os"
    15  	"path/filepath"
    16  	"runtime"
    17  	"strings"
    18  	"testing"
    19  
    20  	"github.com/google/go-cmp/cmp"
    21  	"github.com/hashicorp/go-version"
    22  	"github.com/hashicorp/packer/hcl2template/addrs"
    23  )
    24  
    25  var (
    26  	pluginFolderOne = filepath.Join("testdata", "plugins")
    27  
    28  	pluginFolderTwo = filepath.Join("testdata", "plugins_2")
    29  )
    30  
    31  func TestRequirement_InstallLatestFromGithub(t *testing.T) {
    32  	type fields struct {
    33  		Identifier         string
    34  		VersionConstraints string
    35  	}
    36  	type args struct {
    37  		opts InstallOptions
    38  	}
    39  	tests := []struct {
    40  		name    string
    41  		fields  fields
    42  		args    args
    43  		want    *Installation
    44  		wantErr bool
    45  	}{
    46  		{"already-installed-same-api-version",
    47  			fields{"amazon", "v1.2.3"},
    48  			args{InstallOptions{
    49  				[]Getter{
    50  					&mockPluginGetter{
    51  						Name: "github.com",
    52  						Releases: []Release{
    53  							{Version: "v1.2.3"},
    54  						},
    55  						ChecksumFileEntries: map[string][]ChecksumFileEntry{
    56  							"1.2.3": {{
    57  								// here the checksum file tells us what zipfiles
    58  								// to expect. maybe we could cache the zip file
    59  								// ? but then the plugin is present on the drive
    60  								// twice.
    61  								Filename: "packer-plugin-amazon_v1.2.3_x5.0_darwin_amd64.zip",
    62  								Checksum: "1337c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
    63  							}},
    64  						},
    65  					},
    66  				},
    67  				pluginFolderOne,
    68  				false,
    69  				BinaryInstallationOptions{
    70  					APIVersionMajor: "5", APIVersionMinor: "0",
    71  					OS: "darwin", ARCH: "amd64",
    72  					Checksummers: []Checksummer{
    73  						{
    74  							Type: "sha256",
    75  							Hash: sha256.New(),
    76  						},
    77  					},
    78  				},
    79  			}},
    80  			nil, false},
    81  
    82  		{"already-installed-compatible-api-minor-version",
    83  			// here 'packer' uses the procol version 5.1 which is compatible
    84  			// with the 5.0 one of an already installed plugin.
    85  			fields{"amazon", "v1.2.3"},
    86  			args{InstallOptions{
    87  				[]Getter{
    88  					&mockPluginGetter{
    89  						Name: "github.com",
    90  						Releases: []Release{
    91  							{Version: "v1.2.3"},
    92  						},
    93  						ChecksumFileEntries: map[string][]ChecksumFileEntry{
    94  							"1.2.3": {{
    95  								Filename: "packer-plugin-amazon_v1.2.3_x5.0_darwin_amd64.zip",
    96  								Checksum: "1337c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
    97  							}},
    98  						},
    99  					},
   100  				},
   101  				pluginFolderOne,
   102  				false,
   103  				BinaryInstallationOptions{
   104  					APIVersionMajor: "5", APIVersionMinor: "1",
   105  					OS: "darwin", ARCH: "amd64",
   106  					Checksummers: []Checksummer{
   107  						{
   108  							Type: "sha256",
   109  							Hash: sha256.New(),
   110  						},
   111  					},
   112  				},
   113  			}},
   114  			nil, false},
   115  
   116  		{"ignore-incompatible-higher-protocol-version",
   117  			// here 'packer' needs a binary with protocol version 5.0, and a
   118  			// working plugin is already installed; but a plugin with version
   119  			// 6.0 is available locally and remotely. It simply needs to be
   120  			// ignored.
   121  			fields{"amazon", ">= v1"},
   122  			args{InstallOptions{
   123  				[]Getter{
   124  					&mockPluginGetter{
   125  						Name: "github.com",
   126  						Releases: []Release{
   127  							{Version: "v1.2.3"},
   128  							{Version: "v1.2.4"},
   129  							{Version: "v1.2.5"},
   130  							{Version: "v2.0.0"},
   131  						},
   132  						ChecksumFileEntries: map[string][]ChecksumFileEntry{
   133  							"2.0.0": {{
   134  								Filename: "packer-plugin-amazon_v2.0.0_x6.0_darwin_amd64.zip",
   135  								Checksum: "1337c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
   136  							}},
   137  							"1.2.5": {{
   138  								Filename: "packer-plugin-amazon_v1.2.5_x5.0_darwin_amd64.zip",
   139  								Checksum: "1337c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
   140  							}},
   141  						},
   142  					},
   143  				},
   144  				pluginFolderOne,
   145  				false,
   146  				BinaryInstallationOptions{
   147  					APIVersionMajor: "5", APIVersionMinor: "0",
   148  					OS: "darwin", ARCH: "amd64",
   149  					Checksummers: []Checksummer{
   150  						{
   151  							Type: "sha256",
   152  							Hash: sha256.New(),
   153  						},
   154  					},
   155  				},
   156  			}},
   157  			nil, false},
   158  
   159  		{"upgrade-with-diff-protocol-version",
   160  			// here we have something locally and test that a newer version will
   161  			// be installed, the newer version has a lower minor protocol
   162  			// version than the one we support.
   163  			fields{"amazon", ">= v2"},
   164  			args{InstallOptions{
   165  				[]Getter{
   166  					&mockPluginGetter{
   167  						Name: "github.com",
   168  						Releases: []Release{
   169  							{Version: "v1.2.3"},
   170  							{Version: "v1.2.4"},
   171  							{Version: "v1.2.5"},
   172  							{Version: "v2.0.0"},
   173  							{Version: "v2.1.0"},
   174  							{Version: "v2.10.0"},
   175  						},
   176  						ChecksumFileEntries: map[string][]ChecksumFileEntry{
   177  							"2.10.0": {{
   178  								Filename: "packer-plugin-amazon_v2.10.0_x6.0_darwin_amd64.zip",
   179  								Checksum: "5763f8b5b5ed248894e8511a089cf399b96c7ef92d784fb30ee6242a7cb35bce",
   180  							}},
   181  						},
   182  						Zips: map[string]io.ReadCloser{
   183  							"github.com/hashicorp/packer-plugin-amazon/packer-plugin-amazon_v2.10.0_x6.0_darwin_amd64.zip": zipFile(map[string]string{
   184  								// Make the false plugin echo an output that matches a subset of `describe` for install to work
   185  								//
   186  								// Note: this won't work on Windows as they don't have bin/sh, but this will
   187  								// eventually be replaced by acceptance tests.
   188  								"packer-plugin-amazon_v2.10.0_x6.0_darwin_amd64": `#!/bin/sh
   189  echo '{"version":"v2.10.0","api_version":"x6.0"}'`,
   190  							}),
   191  						},
   192  					},
   193  				},
   194  				pluginFolderTwo,
   195  				false,
   196  				BinaryInstallationOptions{
   197  					APIVersionMajor: "6", APIVersionMinor: "1",
   198  					OS: "darwin", ARCH: "amd64",
   199  					Checksummers: []Checksummer{
   200  						{
   201  							Type: "sha256",
   202  							Hash: sha256.New(),
   203  						},
   204  					},
   205  				},
   206  			}},
   207  			&Installation{
   208  				BinaryPath: "testdata/plugins_2/github.com/hashicorp/amazon/packer-plugin-amazon_v2.10.0_x6.0_darwin_amd64",
   209  				Version:    "v2.10.0",
   210  			}, false},
   211  
   212  		{"upgrade-with-same-protocol-version",
   213  			// here we have something locally and test that a newer version will
   214  			// be installed.
   215  			fields{"amazon", ">= v2"},
   216  			args{InstallOptions{
   217  				[]Getter{
   218  					&mockPluginGetter{
   219  						Name: "github.com",
   220  						Releases: []Release{
   221  							{Version: "v1.2.3"},
   222  							{Version: "v1.2.4"},
   223  							{Version: "v1.2.5"},
   224  							{Version: "v2.0.0"},
   225  							{Version: "v2.1.0"},
   226  							{Version: "v2.10.0"},
   227  							{Version: "v2.10.1"},
   228  						},
   229  						ChecksumFileEntries: map[string][]ChecksumFileEntry{
   230  							"2.10.1": {{
   231  								Filename: "packer-plugin-amazon_v2.10.1_x6.1_darwin_amd64.zip",
   232  								Checksum: "51451da5cd7f1ecd8699668d806bafe58a9222430842afbefdc62a6698dab260",
   233  							}},
   234  						},
   235  						Zips: map[string]io.ReadCloser{
   236  							"github.com/hashicorp/packer-plugin-amazon/packer-plugin-amazon_v2.10.1_x6.1_darwin_amd64.zip": zipFile(map[string]string{
   237  								// Make the false plugin echo an output that matches a subset of `describe` for install to work
   238  								//
   239  								// Note: this won't work on Windows as they don't have bin/sh, but this will
   240  								// eventually be replaced by acceptance tests.
   241  								"packer-plugin-amazon_v2.10.1_x6.1_darwin_amd64": `#!/bin/sh
   242  echo '{"version":"v2.10.1","api_version":"x6.1"}'`,
   243  							}),
   244  						},
   245  					},
   246  				},
   247  				pluginFolderTwo,
   248  				false,
   249  				BinaryInstallationOptions{
   250  					APIVersionMajor: "6", APIVersionMinor: "1",
   251  					OS: "darwin", ARCH: "amd64",
   252  					Checksummers: []Checksummer{
   253  						{
   254  							Type: "sha256",
   255  							Hash: sha256.New(),
   256  						},
   257  					},
   258  				},
   259  			}},
   260  			&Installation{
   261  				BinaryPath: "testdata/plugins_2/github.com/hashicorp/amazon/packer-plugin-amazon_v2.10.1_x6.1_darwin_amd64",
   262  				Version:    "v2.10.1",
   263  			}, false},
   264  
   265  		{"upgrade-with-one-missing-checksum-file",
   266  			// here we have something locally and test that a newer version will
   267  			// be installed.
   268  			fields{"amazon", ">= v2"},
   269  			args{InstallOptions{
   270  				[]Getter{
   271  					&mockPluginGetter{
   272  						Name: "github.com",
   273  						Releases: []Release{
   274  							{Version: "v1.2.3"},
   275  							{Version: "v1.2.4"},
   276  							{Version: "v1.2.5"},
   277  							{Version: "v2.0.0"},
   278  							{Version: "v2.1.0"},
   279  							{Version: "v2.10.0"},
   280  							{Version: "v2.10.1"},
   281  						},
   282  						ChecksumFileEntries: map[string][]ChecksumFileEntry{
   283  							"2.10.0": {{
   284  								Filename: "packer-plugin-amazon_v2.10.0_x6.1_linux_amd64.zip",
   285  								Checksum: "5196f57f37e18bfeac10168db6915caae0341bfc4168ebc3d2b959d746cebd0a",
   286  							}},
   287  						},
   288  						Zips: map[string]io.ReadCloser{
   289  							"github.com/hashicorp/packer-plugin-amazon/packer-plugin-amazon_v2.10.0_x6.1_linux_amd64.zip": zipFile(map[string]string{
   290  								// Make the false plugin echo an output that matches a subset of `describe` for install to work
   291  								//
   292  								// Note: this won't work on Windows as they don't have bin/sh, but this will
   293  								// eventually be replaced by acceptance tests.
   294  								"packer-plugin-amazon_v2.10.0_x6.1_linux_amd64": `#!/bin/sh
   295  echo '{"version":"v2.10.0","api_version":"x6.1"}'`,
   296  							}),
   297  						},
   298  					},
   299  				},
   300  				pluginFolderTwo,
   301  				false,
   302  				BinaryInstallationOptions{
   303  					APIVersionMajor: "6", APIVersionMinor: "1",
   304  					OS: "linux", ARCH: "amd64",
   305  					Checksummers: []Checksummer{
   306  						{
   307  							Type: "sha256",
   308  							Hash: sha256.New(),
   309  						},
   310  					},
   311  				},
   312  			}},
   313  			&Installation{
   314  				BinaryPath: "testdata/plugins_2/github.com/hashicorp/amazon/packer-plugin-amazon_v2.10.0_x6.1_linux_amd64",
   315  				Version:    "v2.10.0",
   316  			}, false},
   317  
   318  		{"wrong-zip-checksum",
   319  			// here we have something locally and test that a newer version with
   320  			// a wrong checksum will not be installed and error.
   321  			fields{"amazon", ">= v2"},
   322  			args{InstallOptions{
   323  				[]Getter{
   324  					&mockPluginGetter{
   325  						Name: "github.com",
   326  						Releases: []Release{
   327  							{Version: "v2.10.0"},
   328  						},
   329  						ChecksumFileEntries: map[string][]ChecksumFileEntry{
   330  							"2.10.0": {{
   331  								Filename: "packer-plugin-amazon_v2.10.0_x6.0_darwin_amd64.zip",
   332  								Checksum: "133713371337133713371337c4a152edd277366a7f71ff3812583e4a35dd0d4a",
   333  							}},
   334  						},
   335  						Zips: map[string]io.ReadCloser{
   336  							"github.com/hashicorp/packer-plugin-amazon/packer-plugin-amazon_v2.10.0_x6.0_darwin_amd64.zip": zipFile(map[string]string{
   337  								"packer-plugin-amazon_v2.10.0_x6.0_darwin_amd64": "h4xx",
   338  							}),
   339  						},
   340  					},
   341  				},
   342  				pluginFolderTwo,
   343  				false,
   344  				BinaryInstallationOptions{
   345  					APIVersionMajor: "6", APIVersionMinor: "1",
   346  					OS: "darwin", ARCH: "amd64",
   347  					Checksummers: []Checksummer{
   348  						{
   349  							Type: "sha256",
   350  							Hash: sha256.New(),
   351  						},
   352  					},
   353  				},
   354  			}},
   355  
   356  			nil, true},
   357  
   358  		{"wrong-local-checksum",
   359  			// here we have something wrong locally and test that a newer
   360  			// version with a wrong checksum will not be installed
   361  			// this should totally error.
   362  			fields{"amazon", ">= v1"},
   363  			args{InstallOptions{
   364  				[]Getter{
   365  					&mockPluginGetter{
   366  						Name: "github.com",
   367  						Releases: []Release{
   368  							{Version: "v2.10.0"},
   369  						},
   370  						ChecksumFileEntries: map[string][]ChecksumFileEntry{
   371  							"2.10.0": {{
   372  								Filename: "packer-plugin-amazon_v2.10.0_x6.0_darwin_amd64.zip",
   373  								Checksum: "133713371337133713371337c4a152edd277366a7f71ff3812583e4a35dd0d4a",
   374  							}},
   375  						},
   376  						Zips: map[string]io.ReadCloser{
   377  							"github.com/hashicorp/packer-plugin-amazon/packer-plugin-amazon_v2.10.0_x6.0_darwin_amd64.zip": zipFile(map[string]string{
   378  								"packer-plugin-amazon_v2.10.0_x6.0_darwin_amd64": "h4xx",
   379  							}),
   380  						},
   381  					},
   382  				},
   383  				pluginFolderTwo,
   384  				false,
   385  				BinaryInstallationOptions{
   386  					APIVersionMajor: "6", APIVersionMinor: "1",
   387  					OS: "darwin", ARCH: "amd64",
   388  					Checksummers: []Checksummer{
   389  						{
   390  							Type: "sha256",
   391  							Hash: sha256.New(),
   392  						},
   393  					},
   394  				},
   395  			}},
   396  
   397  			nil, true},
   398  	}
   399  	for _, tt := range tests {
   400  		t.Run(tt.name, func(t *testing.T) {
   401  			switch tt.name {
   402  			case "upgrade-with-diff-protocol-version",
   403  				"upgrade-with-same-protocol-version",
   404  				"upgrade-with-one-missing-checksum-file":
   405  				if runtime.GOOS != "windows" {
   406  					break
   407  				}
   408  				t.Skipf("Test %q cannot run on Windows because of a shell script being invoked, skipping.", tt.name)
   409  			}
   410  
   411  			log.Printf("starting %s test", tt.name)
   412  
   413  			identifier, err := addrs.ParsePluginSourceString("github.com/hashicorp/" + tt.fields.Identifier)
   414  			if err != nil {
   415  				t.Fatalf("ParsePluginSourceString(%q): %v", tt.fields.Identifier, err)
   416  			}
   417  			cts, err := version.NewConstraint(tt.fields.VersionConstraints)
   418  			if err != nil {
   419  				t.Fatalf("version.NewConstraint(%q): %v", tt.fields.Identifier, err)
   420  			}
   421  			pr := &Requirement{
   422  				Identifier:         identifier,
   423  				VersionConstraints: cts,
   424  			}
   425  			got, err := pr.InstallLatest(tt.args.opts)
   426  			if (err != nil) != tt.wantErr {
   427  				t.Errorf("Requirement.InstallLatest() error = %v, wantErr %v", err, tt.wantErr)
   428  				return
   429  			}
   430  			if diff := cmp.Diff(got, tt.want); diff != "" {
   431  				t.Errorf("Requirement.InstallLatest() %s", diff)
   432  			}
   433  			if tt.want != nil && tt.want.BinaryPath != "" {
   434  				// Cleanup.
   435  				// These two files should be here by now and os.Remove will fail if
   436  				// they aren't.
   437  				if err := os.Remove(filepath.Clean(tt.want.BinaryPath)); err != nil {
   438  					t.Fatal(err)
   439  				}
   440  				if err := os.Remove(filepath.Clean(tt.want.BinaryPath + "_SHA256SUM")); err != nil {
   441  					t.Fatal(err)
   442  				}
   443  			}
   444  		})
   445  	}
   446  }
   447  
   448  func TestRequirement_InstallLatestFromOfficialRelease(t *testing.T) {
   449  	type fields struct {
   450  		Identifier         string
   451  		VersionConstraints string
   452  	}
   453  	type args struct {
   454  		opts InstallOptions
   455  	}
   456  	tests := []struct {
   457  		name    string
   458  		fields  fields
   459  		args    args
   460  		want    *Installation
   461  		wantErr bool
   462  	}{
   463  		{"already-installed-same-api-version",
   464  			fields{"amazon", "v1.2.3"},
   465  			args{InstallOptions{
   466  				[]Getter{
   467  					&mockPluginGetter{
   468  						Name: "releases.hashicorp.com",
   469  						Releases: []Release{
   470  							{Version: "v1.2.3"},
   471  						},
   472  						ChecksumFileEntries: map[string][]ChecksumFileEntry{
   473  							"1.2.3": {{
   474  								// here the checksum file tells us what zipfiles
   475  								// to expect. maybe we could cache the zip file
   476  								// ? but then the plugin is present on the drive
   477  								// twice.
   478  								Filename: "packer-plugin-amazon_1.2.3_darwin_amd64.zip",
   479  								Checksum: "1337c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
   480  							}},
   481  						},
   482  						Manifest: map[string]io.ReadCloser{
   483  							"github.com/hashicorp/packer-plugin-amazon/packer-plugin-amazon_1.2.3_darwin_amd64_manifest.json": manifestFile(map[string]map[string]string{
   484  								"metadata": {"protocol_version": "5.0"},
   485  							}),
   486  						},
   487  					},
   488  				},
   489  				pluginFolderOne,
   490  				false,
   491  				BinaryInstallationOptions{
   492  					APIVersionMajor: "5", APIVersionMinor: "0",
   493  					OS: "darwin", ARCH: "amd64",
   494  					Checksummers: []Checksummer{
   495  						{
   496  							Type: "sha256",
   497  							Hash: sha256.New(),
   498  						},
   499  					},
   500  				},
   501  			}},
   502  			nil, false},
   503  
   504  		{"already-installed-compatible-api-minor-version",
   505  			// here 'packer' uses the procol version 5.1 which is compatible
   506  			// with the 5.0 one of an already installed plugin.
   507  			fields{"amazon", "v1.2.3"},
   508  			args{InstallOptions{
   509  				[]Getter{
   510  					&mockPluginGetter{
   511  						Name: "releases.hashicorp.com",
   512  						Releases: []Release{
   513  							{Version: "v1.2.3"},
   514  						},
   515  						ChecksumFileEntries: map[string][]ChecksumFileEntry{
   516  							"1.2.3": {{
   517  								Filename: "packer-plugin-amazon_1.2.3_darwin_amd64.zip",
   518  								Checksum: "1337c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
   519  							}},
   520  						},
   521  						Manifest: map[string]io.ReadCloser{
   522  							"github.com/hashicorp/packer-plugin-amazon/packer-plugin-amazon_1.2.3_darwin_amd64_manifest.json": manifestFile(map[string]map[string]string{
   523  								"metadata": {"protocol_version": "5.0"},
   524  							}),
   525  						},
   526  					},
   527  				},
   528  				pluginFolderOne,
   529  				false,
   530  				BinaryInstallationOptions{
   531  					APIVersionMajor: "5", APIVersionMinor: "1",
   532  					OS: "darwin", ARCH: "amd64",
   533  					Checksummers: []Checksummer{
   534  						{
   535  							Type: "sha256",
   536  							Hash: sha256.New(),
   537  						},
   538  					},
   539  				},
   540  			}},
   541  			nil, false},
   542  
   543  		{"ignore-incompatible-higher-protocol-version",
   544  			// here 'packer' needs a binary with protocol version 5.0, and a
   545  			// working plugin is already installed; but a plugin with version
   546  			// 6.0 is available locally and remotely. It simply needs to be
   547  			// ignored.
   548  			fields{"amazon", ">= v1"},
   549  			args{InstallOptions{
   550  				[]Getter{
   551  					&mockPluginGetter{
   552  						Name: "releases.hashicorp.com",
   553  						Releases: []Release{
   554  							{Version: "v1.2.3"},
   555  							{Version: "v1.2.4"},
   556  							{Version: "v1.2.5"},
   557  							{Version: "v2.0.0"},
   558  						},
   559  						ChecksumFileEntries: map[string][]ChecksumFileEntry{
   560  							"1.2.5": {{
   561  								Filename: "packer-plugin-amazon_1.2.5_darwin_amd64.zip",
   562  								Checksum: "1337c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
   563  							}},
   564  						},
   565  						Manifest: map[string]io.ReadCloser{
   566  							"github.com/hashicorp/packer-plugin-amazon/packer-plugin-amazon_1.2.5_darwin_amd64_manifest.json": manifestFile(map[string]map[string]string{
   567  								"metadata": {"protocol_version": "5.0"},
   568  							}),
   569  						},
   570  					},
   571  				},
   572  				pluginFolderOne,
   573  				false,
   574  				BinaryInstallationOptions{
   575  					APIVersionMajor: "5", APIVersionMinor: "0",
   576  					OS: "darwin", ARCH: "amd64",
   577  					Checksummers: []Checksummer{
   578  						{
   579  							Type: "sha256",
   580  							Hash: sha256.New(),
   581  						},
   582  					},
   583  				},
   584  			}},
   585  			nil, false},
   586  
   587  		{"upgrade-with-diff-protocol-version",
   588  			// here we have something locally and test that a newer version will
   589  			// be installed, the newer version has a lower minor protocol
   590  			// version than the one we support.
   591  			fields{"amazon", ">= v2"},
   592  			args{InstallOptions{
   593  				[]Getter{
   594  					&mockPluginGetter{
   595  						Name: "releases.hashicorp.com",
   596  						Releases: []Release{
   597  							{Version: "v1.2.3"},
   598  							{Version: "v1.2.4"},
   599  							{Version: "v1.2.5"},
   600  							{Version: "v2.0.0"},
   601  							{Version: "v2.1.0"},
   602  							{Version: "v2.10.0"},
   603  						},
   604  						ChecksumFileEntries: map[string][]ChecksumFileEntry{
   605  							"2.10.0": {{
   606  								Filename: "packer-plugin-amazon_2.10.0_darwin_amd64.zip",
   607  								Checksum: "5763f8b5b5ed248894e8511a089cf399b96c7ef92d784fb30ee6242a7cb35bce",
   608  							}},
   609  						},
   610  						Zips: map[string]io.ReadCloser{
   611  							"github.com/hashicorp/packer-plugin-amazon/packer-plugin-amazon_2.10.0_darwin_amd64.zip": zipFile(map[string]string{
   612  								// Make the false plugin echo an output that matches a subset of `describe` for install to work
   613  								//
   614  								// Note: this won't work on Windows as they don't have bin/sh, but this will
   615  								// eventually be replaced by acceptance tests.
   616  								"packer-plugin-amazon_v2.10.0_x6.0_darwin_amd64": `#!/bin/sh
   617  echo '{"version":"v2.10.0","api_version":"x6.0"}'`,
   618  							}),
   619  						},
   620  						Manifest: map[string]io.ReadCloser{
   621  							"github.com/hashicorp/packer-plugin-amazon/packer-plugin-amazon_2.10.0_darwin_amd64_manifest.json": manifestFile(map[string]map[string]string{
   622  								"metadata": {"protocol_version": "6.0"},
   623  							}),
   624  						},
   625  					},
   626  				},
   627  				pluginFolderTwo,
   628  				false,
   629  				BinaryInstallationOptions{
   630  					APIVersionMajor: "6", APIVersionMinor: "1",
   631  					OS: "darwin", ARCH: "amd64",
   632  					Checksummers: []Checksummer{
   633  						{
   634  							Type: "sha256",
   635  							Hash: sha256.New(),
   636  						},
   637  					},
   638  				},
   639  			}},
   640  			&Installation{
   641  				BinaryPath: "testdata/plugins_2/github.com/hashicorp/amazon/packer-plugin-amazon_v2.10.0_x6.0_darwin_amd64",
   642  				Version:    "v2.10.0",
   643  			}, false},
   644  
   645  		{"wrong-zip-checksum",
   646  			// here we have something locally and test that a newer version with
   647  			// a wrong checksum will not be installed and error.
   648  			fields{"amazon", ">= v2"},
   649  			args{InstallOptions{
   650  				[]Getter{
   651  					&mockPluginGetter{
   652  						Name: "releases.hashicorp.com",
   653  						Releases: []Release{
   654  							{Version: "v2.10.0"},
   655  						},
   656  						ChecksumFileEntries: map[string][]ChecksumFileEntry{
   657  							"2.10.0": {{
   658  								Filename: "packer-plugin-amazon_2.10.0_darwin_amd64.zip",
   659  								Checksum: "133713371337133713371337c4a152edd277366a7f71ff3812583e4a35dd0d4a",
   660  							}},
   661  						},
   662  						Zips: map[string]io.ReadCloser{
   663  							"github.com/hashicorp/packer-plugin-amazon/packer-plugin-amazon_2.10.0_darwin_amd64.zip": zipFile(map[string]string{
   664  								"packer-plugin-amazon_v2.10.0_x6.0_darwin_amd64": "h4xx",
   665  							}),
   666  						},
   667  						Manifest: map[string]io.ReadCloser{
   668  							"github.com/hashicorp/packer-plugin-amazon/packer-plugin-amazon_2.10.0_darwin_amd64_manifest.json": manifestFile(map[string]map[string]string{
   669  								"metadata": {"protocol_version": "6.0"},
   670  							}),
   671  						},
   672  					},
   673  				},
   674  				pluginFolderTwo,
   675  				false,
   676  				BinaryInstallationOptions{
   677  					APIVersionMajor: "6", APIVersionMinor: "1",
   678  					OS: "darwin", ARCH: "amd64",
   679  					Checksummers: []Checksummer{
   680  						{
   681  							Type: "sha256",
   682  							Hash: sha256.New(),
   683  						},
   684  					},
   685  				},
   686  			}},
   687  
   688  			nil, true},
   689  
   690  		{"wrong-local-checksum",
   691  			// here we have something wrong locally and test that a newer
   692  			// version with a wrong checksum will not be installed
   693  			// this should totally error.
   694  			fields{"amazon", ">= v1"},
   695  			args{InstallOptions{
   696  				[]Getter{
   697  					&mockPluginGetter{
   698  						Name: "releases.hashicorp.com",
   699  						Releases: []Release{
   700  							{Version: "v2.10.0"},
   701  						},
   702  						ChecksumFileEntries: map[string][]ChecksumFileEntry{
   703  							"2.10.0": {{
   704  								Filename: "packer-plugin-amazon_2.10.0_darwin_amd64.zip",
   705  								Checksum: "133713371337133713371337c4a152edd277366a7f71ff3812583e4a35dd0d4a",
   706  							}},
   707  						},
   708  						Zips: map[string]io.ReadCloser{
   709  							"github.com/hashicorp/packer-plugin-amazon/packer-plugin-amazon_2.10.0_darwin_amd64.zip": zipFile(map[string]string{
   710  								"packer-plugin-amazon_v2.10.0_x6.0_darwin_amd64": "h4xx",
   711  							}),
   712  						},
   713  						Manifest: map[string]io.ReadCloser{
   714  							"github.com/hashicorp/packer-plugin-amazon/packer-plugin-amazon_2.10.0_darwin_amd64_manifest.json": manifestFile(map[string]map[string]string{
   715  								"metadata": {"protocol_version": "6.0"},
   716  							}),
   717  						},
   718  					},
   719  				},
   720  				pluginFolderTwo,
   721  				false,
   722  				BinaryInstallationOptions{
   723  					APIVersionMajor: "6", APIVersionMinor: "1",
   724  					OS: "darwin", ARCH: "amd64",
   725  					Checksummers: []Checksummer{
   726  						{
   727  							Type: "sha256",
   728  							Hash: sha256.New(),
   729  						},
   730  					},
   731  				},
   732  			}},
   733  
   734  			nil, true},
   735  	}
   736  	for _, tt := range tests {
   737  		t.Run(tt.name, func(t *testing.T) {
   738  			switch tt.name {
   739  			case "upgrade-with-diff-protocol-version",
   740  				"upgrade-with-same-protocol-version",
   741  				"upgrade-with-one-missing-checksum-file":
   742  				if runtime.GOOS != "windows" {
   743  					break
   744  				}
   745  				t.Skipf("Test %q cannot run on Windows because of a shell script being invoked, skipping.", tt.name)
   746  			}
   747  
   748  			log.Printf("starting %s test", tt.name)
   749  
   750  			identifier, err := addrs.ParsePluginSourceString("github.com/hashicorp/" + tt.fields.Identifier)
   751  			if err != nil {
   752  				t.Fatalf("ParsePluginSourceString(%q): %v", tt.fields.Identifier, err)
   753  			}
   754  			cts, err := version.NewConstraint(tt.fields.VersionConstraints)
   755  			if err != nil {
   756  				t.Fatalf("version.NewConstraint(%q): %v", tt.fields.Identifier, err)
   757  			}
   758  			pr := &Requirement{
   759  				Identifier:         identifier,
   760  				VersionConstraints: cts,
   761  			}
   762  			got, err := pr.InstallLatest(tt.args.opts)
   763  			if (err != nil) != tt.wantErr {
   764  				t.Errorf("Requirement.InstallLatest() error = %v, wantErr %v", err, tt.wantErr)
   765  				return
   766  			}
   767  			if diff := cmp.Diff(got, tt.want); diff != "" {
   768  				t.Errorf("Requirement.InstallLatest() %s", diff)
   769  			}
   770  			if tt.want != nil && tt.want.BinaryPath != "" {
   771  				// Cleanup.
   772  				// These two files should be here by now and os.Remove will fail if
   773  				// they aren't.
   774  				if err := os.Remove(filepath.Clean(tt.want.BinaryPath)); err != nil {
   775  					t.Fatal(err)
   776  				}
   777  				if err := os.Remove(filepath.Clean(tt.want.BinaryPath + "_SHA256SUM")); err != nil {
   778  					t.Fatal(err)
   779  				}
   780  			}
   781  		})
   782  	}
   783  }
   784  
   785  type mockPluginGetter struct {
   786  	Releases            []Release
   787  	ChecksumFileEntries map[string][]ChecksumFileEntry
   788  	Zips                map[string]io.ReadCloser
   789  	Name                string
   790  	APIMajor            string
   791  	APIMinor            string
   792  	Manifest            map[string]io.ReadCloser
   793  }
   794  
   795  func (g *mockPluginGetter) Init(req *Requirement, entry *ChecksumFileEntry) error {
   796  	filename := entry.Filename
   797  	res := strings.TrimPrefix(filename, req.FilenamePrefix())
   798  	// res now looks like v0.2.12_x5.0_freebsd_amd64.zip
   799  
   800  	entry.Ext = filepath.Ext(res)
   801  
   802  	res = strings.TrimSuffix(res, entry.Ext)
   803  	// res now looks like v0.2.12_x5.0_freebsd_amd64
   804  
   805  	parts := strings.Split(res, "_")
   806  	// ["v0.2.12", "x5.0", "freebsd", "amd64"]
   807  
   808  	if g.Name == "github.com" {
   809  		if len(parts) < 4 {
   810  			return fmt.Errorf("malformed filename expected %s{version}_x{protocol-version}_{os}_{arch}", req.FilenamePrefix())
   811  		}
   812  
   813  		entry.BinVersion, entry.ProtVersion, entry.Os, entry.Arch = parts[0], parts[1], parts[2], parts[3]
   814  	} else {
   815  		if len(parts) < 3 {
   816  			return fmt.Errorf("malformed filename expected %s{version}_{os}_{arch}", req.FilenamePrefix())
   817  		}
   818  
   819  		entry.BinVersion, entry.Os, entry.Arch = parts[0], parts[1], parts[2]
   820  		entry.BinVersion = strings.TrimPrefix(entry.BinVersion, "v")
   821  	}
   822  
   823  	return nil
   824  }
   825  
   826  func (g *mockPluginGetter) Validate(opt GetOptions, expectedVersion string, installOpts BinaryInstallationOptions, entry *ChecksumFileEntry) error {
   827  	if g.Name == "github.com" {
   828  		expectedBinVersion := "v" + expectedVersion
   829  
   830  		if entry.BinVersion != expectedBinVersion {
   831  			return fmt.Errorf("wrong version: %s does not match expected %s", entry.BinVersion, expectedBinVersion)
   832  		}
   833  		if entry.Os != installOpts.OS || entry.Arch != installOpts.ARCH {
   834  			return fmt.Errorf("wrong system, expected %s_%s", installOpts.OS, installOpts.ARCH)
   835  		}
   836  
   837  		return installOpts.CheckProtocolVersion(entry.ProtVersion)
   838  	} else {
   839  		if entry.BinVersion != expectedVersion {
   840  			return fmt.Errorf("wrong version: %s does not match expected %s", entry.BinVersion, expectedVersion)
   841  		}
   842  		if entry.Os != installOpts.OS || entry.Arch != installOpts.ARCH {
   843  			return fmt.Errorf("wrong system, expected %s_%s got %s_%s", installOpts.OS, installOpts.ARCH, entry.Os, entry.Arch)
   844  		}
   845  
   846  		manifest, err := g.Get("meta", opt)
   847  		if err != nil {
   848  			return err
   849  		}
   850  
   851  		var data ManifestMeta
   852  		body, err := io.ReadAll(manifest)
   853  		if err != nil {
   854  			log.Printf("Failed to unmarshal manifest json: %s", err)
   855  			return err
   856  		}
   857  
   858  		err = json.Unmarshal(body, &data)
   859  		if err != nil {
   860  			log.Printf("Failed to unmarshal manifest json: %s", err)
   861  			return err
   862  		}
   863  
   864  		err = installOpts.CheckProtocolVersion("x" + data.Metadata.ProtocolVersion)
   865  		if err != nil {
   866  			return err
   867  		}
   868  
   869  		g.APIMajor = strings.Split(data.Metadata.ProtocolVersion, ".")[0]
   870  		g.APIMinor = strings.Split(data.Metadata.ProtocolVersion, ".")[1]
   871  
   872  		log.Printf("#### versions API %s.%s, entry %s.%s", g.APIMajor, g.APIMinor, entry.ProtVersion, entry.BinVersion)
   873  
   874  		return nil
   875  	}
   876  }
   877  
   878  func (g *mockPluginGetter) ExpectedFileName(pr *Requirement, version string, entry *ChecksumFileEntry, zipFileName string) string {
   879  	if g.Name == "github.com" {
   880  		return zipFileName
   881  	} else {
   882  		pluginSourceParts := strings.Split(pr.Identifier.Source, "/")
   883  
   884  		// We need to verify that the plugin source is in the expected format
   885  		return strings.Join([]string{fmt.Sprintf("packer-plugin-%s", pluginSourceParts[2]),
   886  			"v" + version,
   887  			"x" + g.APIMajor + "." + g.APIMinor,
   888  			entry.Os,
   889  			entry.Arch + ".zip",
   890  		}, "_")
   891  	}
   892  }
   893  
   894  func (g *mockPluginGetter) Get(what string, options GetOptions) (io.ReadCloser, error) {
   895  
   896  	var toEncode interface{}
   897  	switch what {
   898  	case "releases":
   899  		toEncode = g.Releases
   900  	case "sha256":
   901  		enc, ok := g.ChecksumFileEntries[options.version.String()]
   902  		if !ok {
   903  			return nil, fmt.Errorf("No checksum available for version %q", options.version.String())
   904  		}
   905  		toEncode = enc
   906  	case "zip":
   907  		// Note: we'll act as if the plugin sources would always be github sources for now.
   908  		// This test will need to be updated if/when we move on to support other sources.
   909  		parts := options.PluginRequirement.Identifier.Parts()
   910  		acc := fmt.Sprintf("%s/%s/packer-plugin-%s/%s", parts[0], parts[1], parts[2], options.ExpectedZipFilename())
   911  
   912  		zip, found := g.Zips[acc]
   913  		if found == false {
   914  			panic(fmt.Sprintf("could not find zipfile %s. %v", acc, g.Zips))
   915  		}
   916  		return zip, nil
   917  	case "meta":
   918  		// Note: we'll act as if the plugin sources would always be github sources for now.
   919  		// This test will need to be updated if/when we move on to support other sources.
   920  		parts := options.PluginRequirement.Identifier.Parts()
   921  		acc := fmt.Sprintf("%s/%s/packer-plugin-%s/packer-plugin-%s_%s_%s_%s_manifest.json", parts[0], parts[1], parts[2], parts[2], options.version, options.BinaryInstallationOptions.OS, options.BinaryInstallationOptions.ARCH)
   922  
   923  		manifest, found := g.Manifest[acc]
   924  		if found == false {
   925  			panic(fmt.Sprintf("could not find manifest file %s. %v", acc, g.Zips))
   926  		}
   927  		return manifest, nil
   928  	default:
   929  		panic("Don't know how to get " + what)
   930  	}
   931  
   932  	read, write := io.Pipe()
   933  	go func() {
   934  		if err := json.NewEncoder(write).Encode(toEncode); err != nil {
   935  			panic(err)
   936  		}
   937  	}()
   938  	return io.NopCloser(read), nil
   939  }
   940  
   941  func zipFile(content map[string]string) io.ReadCloser {
   942  	buff := bytes.NewBuffer(nil)
   943  	zipWriter := zip.NewWriter(buff)
   944  	for fileName, content := range content {
   945  		header := &zip.FileHeader{
   946  			Name:             fileName,
   947  			UncompressedSize: uint32(len([]byte(content))),
   948  		}
   949  		fWriter, err := zipWriter.CreateHeader(header)
   950  		if err != nil {
   951  			panic(err)
   952  		}
   953  		_, err = io.Copy(fWriter, strings.NewReader(content))
   954  		if err != nil {
   955  			panic(err)
   956  		}
   957  	}
   958  	err := zipWriter.Close()
   959  	if err != nil {
   960  		panic(err)
   961  	}
   962  	return io.NopCloser(buff)
   963  }
   964  
   965  func manifestFile(content map[string]map[string]string) io.ReadCloser {
   966  	jsonBytes, err := json.Marshal(content)
   967  	if err != nil {
   968  		fmt.Println("Error marshaling JSON:", err)
   969  	}
   970  
   971  	buffer := bytes.NewBuffer(jsonBytes)
   972  	return io.NopCloser(buffer)
   973  }
   974  
   975  var _ Getter = &mockPluginGetter{}
   976  
   977  func Test_LessInstallList(t *testing.T) {
   978  	tests := []struct {
   979  		name       string
   980  		installs   InstallList
   981  		expectLess bool
   982  	}{
   983  		{
   984  			"v1.2.1 < v1.2.2 => true",
   985  			InstallList{
   986  				&Installation{
   987  					BinaryPath: "host/org/plugin",
   988  					Version:    "v1.2.1",
   989  					APIVersion: "x5.0",
   990  				},
   991  				&Installation{
   992  					BinaryPath: "host/org/plugin",
   993  					Version:    "v1.2.2",
   994  					APIVersion: "x5.0",
   995  				},
   996  			},
   997  			true,
   998  		},
   999  		{
  1000  			// Impractical with the changes to the loading model
  1001  			"v1.2.1 = v1.2.1 => false",
  1002  			InstallList{
  1003  				&Installation{
  1004  					BinaryPath: "host/org/plugin",
  1005  					Version:    "v1.2.1",
  1006  					APIVersion: "x5.0",
  1007  				},
  1008  				&Installation{
  1009  					BinaryPath: "host/org/plugin",
  1010  					Version:    "v1.2.1",
  1011  					APIVersion: "x5.0",
  1012  				},
  1013  			},
  1014  			false,
  1015  		},
  1016  		{
  1017  			"v1.2.2 < v1.2.1 => false",
  1018  			InstallList{
  1019  				&Installation{
  1020  					BinaryPath: "host/org/plugin",
  1021  					Version:    "v1.2.2",
  1022  					APIVersion: "x5.0",
  1023  				},
  1024  				&Installation{
  1025  					BinaryPath: "host/org/plugin",
  1026  					Version:    "v1.2.1",
  1027  					APIVersion: "x5.0",
  1028  				},
  1029  			},
  1030  			false,
  1031  		},
  1032  		{
  1033  			"v1.2.2-dev < v1.2.2 => true",
  1034  			InstallList{
  1035  				&Installation{
  1036  					BinaryPath: "host/org/plugin",
  1037  					Version:    "v1.2.2-dev",
  1038  					APIVersion: "x5.0",
  1039  				},
  1040  				&Installation{
  1041  					BinaryPath: "host/org/plugin",
  1042  					Version:    "v1.2.2",
  1043  					APIVersion: "x5.0",
  1044  				},
  1045  			},
  1046  			true,
  1047  		},
  1048  		{
  1049  			"v1.2.2 < v1.2.2-dev => false",
  1050  			InstallList{
  1051  				&Installation{
  1052  					BinaryPath: "host/org/plugin",
  1053  					Version:    "v1.2.2",
  1054  					APIVersion: "x5.0",
  1055  				},
  1056  				&Installation{
  1057  					BinaryPath: "host/org/plugin",
  1058  					Version:    "v1.2.2-dev",
  1059  					APIVersion: "x5.0",
  1060  				},
  1061  			},
  1062  			false,
  1063  		},
  1064  		{
  1065  			"v1.2.1 < v1.2.2-dev => true",
  1066  			InstallList{
  1067  				&Installation{
  1068  					BinaryPath: "host/org/plugin",
  1069  					Version:    "v1.2.1",
  1070  					APIVersion: "x5.0",
  1071  				},
  1072  				&Installation{
  1073  					BinaryPath: "host/org/plugin",
  1074  					Version:    "v1.2.2-dev",
  1075  					APIVersion: "x5.0",
  1076  				},
  1077  			},
  1078  			true,
  1079  		},
  1080  		{
  1081  			"v1.2.3 < v1.2.2-dev => false",
  1082  			InstallList{
  1083  				&Installation{
  1084  					BinaryPath: "host/org/plugin",
  1085  					Version:    "v1.2.3",
  1086  					APIVersion: "x5.0",
  1087  				},
  1088  				&Installation{
  1089  					BinaryPath: "host/org/plugin",
  1090  					Version:    "v1.2.2-dev",
  1091  					APIVersion: "x5.0",
  1092  				},
  1093  			},
  1094  			false,
  1095  		},
  1096  		{
  1097  			"v1.2.3_x5.0 < v1.2.3_x5.1 => true",
  1098  			InstallList{
  1099  				&Installation{
  1100  					BinaryPath: "host/org/plugin",
  1101  					Version:    "v1.2.3",
  1102  					APIVersion: "x5.0",
  1103  				},
  1104  				&Installation{
  1105  					BinaryPath: "host/org/plugin",
  1106  					Version:    "v1.2.3",
  1107  					APIVersion: "x5.1",
  1108  				},
  1109  			},
  1110  			true,
  1111  		},
  1112  		{
  1113  			"v1.2.3_x5.0 < v1.2.3_x5.0 => false",
  1114  			InstallList{
  1115  				&Installation{
  1116  					BinaryPath: "host/org/plugin",
  1117  					Version:    "v1.2.3",
  1118  					APIVersion: "x5.0",
  1119  				},
  1120  				&Installation{
  1121  					BinaryPath: "host/org/plugin",
  1122  					Version:    "v1.2.3",
  1123  					APIVersion: "x5.0",
  1124  				},
  1125  			},
  1126  			false,
  1127  		},
  1128  		{
  1129  			"v1.2.3_x4.15 < v1.2.3_x5.0 => true",
  1130  			InstallList{
  1131  				&Installation{
  1132  					BinaryPath: "host/org/plugin",
  1133  					Version:    "v1.2.3",
  1134  					APIVersion: "x4.15",
  1135  				},
  1136  				&Installation{
  1137  					BinaryPath: "host/org/plugin",
  1138  					Version:    "v1.2.3",
  1139  					APIVersion: "x5.0",
  1140  				},
  1141  			},
  1142  			true,
  1143  		},
  1144  		{
  1145  			"v1.2.3_x9.0 < v1.2.3_x10.0 => true",
  1146  			InstallList{
  1147  				&Installation{
  1148  					BinaryPath: "host/org/plugin",
  1149  					Version:    "v1.2.3",
  1150  					APIVersion: "x9.0",
  1151  				},
  1152  				&Installation{
  1153  					BinaryPath: "host/org/plugin",
  1154  					Version:    "v1.2.3",
  1155  					APIVersion: "x10.0",
  1156  				},
  1157  			},
  1158  			true,
  1159  		},
  1160  		{
  1161  			"v1.2.3_x5.9 < v1.2.3_x5.10 => true",
  1162  			InstallList{
  1163  				&Installation{
  1164  					BinaryPath: "host/org/plugin",
  1165  					Version:    "v1.2.3",
  1166  					APIVersion: "x5.9",
  1167  				},
  1168  				&Installation{
  1169  					BinaryPath: "host/org/plugin",
  1170  					Version:    "v1.2.3",
  1171  					APIVersion: "x5.10",
  1172  				},
  1173  			},
  1174  			true,
  1175  		},
  1176  		{
  1177  			"v1.2.3_x5.0 < v1.2.3_x4.15 => false",
  1178  			InstallList{
  1179  				&Installation{
  1180  					BinaryPath: "host/org/plugin",
  1181  					Version:    "v1.2.3",
  1182  					APIVersion: "x5.0",
  1183  				},
  1184  				&Installation{
  1185  					BinaryPath: "host/org/plugin",
  1186  					Version:    "v1.2.3",
  1187  					APIVersion: "x4.15",
  1188  				},
  1189  			},
  1190  			false,
  1191  		},
  1192  	}
  1193  
  1194  	for _, tt := range tests {
  1195  		t.Run(tt.name, func(t *testing.T) {
  1196  			isLess := tt.installs.Less(0, 1)
  1197  			if isLess != tt.expectLess {
  1198  				t.Errorf("Less mismatch for %s_%s < %s_%s, expected %t, got %t",
  1199  					tt.installs[0].Version,
  1200  					tt.installs[0].APIVersion,
  1201  					tt.installs[1].Version,
  1202  					tt.installs[1].APIVersion,
  1203  					tt.expectLess, isLess)
  1204  			}
  1205  		})
  1206  	}
  1207  }