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 }