github.com/BarDweller/libpak@v0.0.0-20230630201634-8dd5cfc15ec9/buildmodule_test.go (about) 1 /* 2 * Copyright 2018-2023 the original author or authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * https://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package libpak_test 18 19 import ( 20 "bytes" 21 "fmt" 22 "os" 23 "testing" 24 "time" 25 26 "github.com/BurntSushi/toml" 27 . "github.com/onsi/gomega" 28 29 "github.com/sclevine/spec" 30 31 "github.com/BarDweller/libpak" 32 "github.com/BarDweller/libpak/bard" 33 "github.com/BarDweller/libpak/internal" 34 "github.com/BarDweller/libpak/sbom" 35 ) 36 37 func testBuildpack(t *testing.T, context spec.G, it spec.S) { 38 var ( 39 Expect = NewWithT(t).Expect 40 ) 41 42 it("is equal after toml Marshal and Unmarshal", func() { 43 dependency := libpak.BuildModuleDependency{ 44 ID: "test-id", 45 Name: "test-name", 46 Version: "1.1.1", 47 URI: "test-uri", 48 SHA256: "test-sha256", 49 DeprecationDate: time.Now(), 50 Stacks: []string{"test-stack"}, 51 Licenses: []libpak.BuildModuleDependencyLicense{ 52 { 53 Type: "test-type", 54 URI: "test-uri", 55 }, 56 }, 57 } 58 59 bytes, err := internal.Marshal(dependency) 60 Expect(err).NotTo(HaveOccurred()) 61 62 var newDependency libpak.BuildModuleDependency 63 err = toml.Unmarshal(bytes, &newDependency) 64 Expect(err).NotTo(HaveOccurred()) 65 66 Expect(dependency.Equals(newDependency)).To(BeTrue()) 67 }) 68 69 it("renders dependency as a SyftArtifact", func() { 70 dependency := libpak.BuildModuleDependency{ 71 ID: "test-id", 72 Name: "test-name", 73 Version: "1.1.1", 74 URI: "test-uri", 75 SHA256: "test-sha256", 76 Stacks: []string{"test-stack"}, 77 Licenses: []libpak.BuildModuleDependencyLicense{ 78 { 79 Type: "test-type", 80 URI: "test-uri", 81 }, 82 }, 83 CPEs: []string{"test-cpe1", "test-cpe2"}, 84 PURL: "test-purl", 85 } 86 87 Expect(dependency.AsSyftArtifact()).To(Equal(sbom.SyftArtifact{ 88 ID: "46713835f08d90b7", 89 Name: "test-name", 90 Version: "1.1.1", 91 Type: "UnknownPackage", 92 FoundBy: "libpak", 93 Licenses: []string{"test-type"}, 94 Locations: []sbom.SyftLocation{{Path: "buildpack.toml"}}, 95 CPEs: []string{"test-cpe1", "test-cpe2"}, 96 PURL: "test-purl", 97 })) 98 }) 99 100 it("calculates dependency deprecation", func() { 101 deprecatedDependency := libpak.BuildModuleDependency{ 102 ID: "test-id", 103 DeprecationDate: time.Now().UTC(), 104 } 105 106 soonDeprecatedDependency := libpak.BuildModuleDependency{ 107 ID: "test-id", 108 DeprecationDate: time.Now().UTC().Add(30 * 24 * time.Hour), 109 } 110 111 Expect(deprecatedDependency.IsDeprecated()).To(BeTrue()) 112 Expect(deprecatedDependency.IsSoonDeprecated()).To(BeFalse()) 113 Expect(soonDeprecatedDependency.IsDeprecated()).To(BeFalse()) 114 Expect(soonDeprecatedDependency.IsSoonDeprecated()).To(BeTrue()) 115 }) 116 117 context("NewBuildpackMetadata", func() { 118 it("deserializes metadata", func() { 119 actual := map[string]interface{}{ 120 "configurations": []map[string]interface{}{ 121 { 122 "name": "test-name", 123 "default": "test-default", 124 "description": "test-description", 125 }, 126 }, 127 "dependencies": []map[string]interface{}{ 128 { 129 "id": "test-id", 130 "name": "test-name", 131 "version": "1.1.1", 132 "uri": "test-uri", 133 "sha256": "test-sha256", 134 "stacks": []interface{}{"test-stack"}, 135 "licenses": []map[string]interface{}{ 136 { 137 "type": "test-type", 138 "uri": "test-uri", 139 }, 140 }, 141 "cpes": []interface{}{"cpe:2.3:a:test-id:1.1.1"}, 142 "purl": "pkg:generic:test-id@1.1.1", 143 "deprecation_date": "2021-12-31T15:59:00-08:00", 144 }, 145 }, 146 "include-files": []interface{}{"test-include-file"}, 147 "pre-package": "test-pre-package", 148 } 149 150 deprecationDate, err := time.Parse(time.RFC3339, "2021-12-31T15:59:00-08:00") 151 Expect(err).ToNot(HaveOccurred()) 152 153 expected := libpak.BuildModuleMetadata{ 154 Configurations: []libpak.BuildModuleConfiguration{ 155 { 156 Name: "test-name", 157 Default: "test-default", 158 Description: "test-description", 159 }, 160 }, 161 Dependencies: []libpak.BuildModuleDependency{ 162 { 163 ID: "test-id", 164 Name: "test-name", 165 Version: "1.1.1", 166 URI: "test-uri", 167 SHA256: "test-sha256", 168 Stacks: []string{"test-stack"}, 169 Licenses: []libpak.BuildModuleDependencyLicense{ 170 { 171 Type: "test-type", 172 URI: "test-uri", 173 }, 174 }, 175 CPEs: []string{"cpe:2.3:a:test-id:1.1.1"}, 176 PURL: "pkg:generic:test-id@1.1.1", 177 DeprecationDate: deprecationDate, 178 }, 179 }, 180 IncludeFiles: []string{"test-include-file"}, 181 PrePackage: "test-pre-package", 182 } 183 184 Expect(libpak.NewBuildModuleMetadata(actual)).To(Equal(expected)) 185 }) 186 }) 187 188 context("ConfigurationResolver", func() { 189 var ( 190 resolver = libpak.ConfigurationResolver{ 191 Configurations: []libpak.BuildModuleConfiguration{ 192 {Name: "TEST_KEY_1", Default: "test-default-value-1"}, 193 {Name: "TEST_KEY_2", Default: "test-default-value-2"}, 194 {Name: "TEST_BOOL_3", Default: "true"}, 195 {Name: "TEST_BOOL_4", Default: "false"}, 196 {Name: "TEST_BOOL_6", Default: "test-value"}, 197 }, 198 } 199 ) 200 201 it.Before(func() { 202 Expect(os.Setenv("TEST_KEY_1", "test-value-1")).To(Succeed()) 203 Expect(os.Setenv("TEST_BOOL_1", "true")).To(Succeed()) 204 Expect(os.Setenv("TEST_BOOL_2", "false")).To(Succeed()) 205 }) 206 207 it.After(func() { 208 Expect(os.Unsetenv("TEST_KEY_1")).To(Succeed()) 209 Expect(os.Unsetenv("TEST_BOOL_1")).To(Succeed()) 210 Expect(os.Unsetenv("TEST_BOOL_2")).To(Succeed()) 211 }) 212 213 it("returns configured value", func() { 214 v, ok := resolver.Resolve("TEST_KEY_1") 215 Expect(v).To(Equal("test-value-1")) 216 Expect(ok).To(BeTrue()) 217 }) 218 219 it("returns default value", func() { 220 v, ok := resolver.Resolve("TEST_KEY_2") 221 Expect(v).To(Equal("test-default-value-2")) 222 Expect(ok).To(BeFalse()) 223 }) 224 225 it("returns unknown value", func() { 226 v, ok := resolver.Resolve("TEST_KEY_3") 227 Expect(v).To(Equal("")) 228 Expect(ok).To(BeFalse()) 229 }) 230 231 it("returns configured bool", func() { 232 Expect(resolver.ResolveBool("TEST_BOOL_1")).To(BeTrue()) 233 Expect(resolver.ResolveBool("TEST_BOOL_2")).To(BeFalse()) 234 }) 235 236 it("returns default bool", func() { 237 Expect(resolver.ResolveBool("TEST_BOOL_3")).To(BeTrue()) 238 Expect(resolver.ResolveBool("TEST_BOOL_4")).To(BeFalse()) 239 }) 240 241 it("returns false for unset", func() { 242 Expect(resolver.ResolveBool("TEST_BOOL_5")).To(BeFalse()) 243 }) 244 245 it("return false for invalid", func() { 246 Expect(resolver.ResolveBool("TEST_BOOL_6")).To(BeFalse()) 247 }) 248 }) 249 250 context("DependencyResolver", func() { 251 var ( 252 resolver libpak.DependencyResolver 253 ) 254 255 context("Resolve", func() { 256 257 it("filters by id", func() { 258 resolver.Dependencies = []libpak.BuildModuleDependency{ 259 { 260 ID: "test-id-1", 261 Name: "test-name", 262 Version: "1.0", 263 URI: "test-uri", 264 SHA256: "test-sha256", 265 Stacks: []string{"test-stack-1", "test-stack-2"}, 266 }, 267 { 268 ID: "test-id-2", 269 Name: "test-name", 270 Version: "1.0", 271 URI: "test-uri", 272 SHA256: "test-sha256", 273 Stacks: []string{"test-stack-1", "test-stack-2"}, 274 }, 275 } 276 resolver.StackID = "test-stack-1" 277 278 Expect(resolver.Resolve("test-id-2", "1.0")).To(Equal(libpak.BuildModuleDependency{ 279 ID: "test-id-2", 280 Name: "test-name", 281 Version: "1.0", 282 URI: "test-uri", 283 SHA256: "test-sha256", 284 Stacks: []string{"test-stack-1", "test-stack-2"}, 285 })) 286 }) 287 288 it("filters by version constraint", func() { 289 resolver.Dependencies = []libpak.BuildModuleDependency{ 290 { 291 ID: "test-id", 292 Name: "test-name", 293 Version: "1.0", 294 URI: "test-uri", 295 SHA256: "test-sha256", 296 Stacks: []string{"test-stack-1", "test-stack-2"}, 297 }, 298 { 299 ID: "test-id", 300 Name: "test-name", 301 Version: "2.0", 302 URI: "test-uri", 303 SHA256: "test-sha256", 304 Stacks: []string{"test-stack-1", "test-stack-2"}, 305 }, 306 } 307 resolver.StackID = "test-stack-1" 308 309 Expect(resolver.Resolve("test-id", "2.0")).To(Equal(libpak.BuildModuleDependency{ 310 ID: "test-id", 311 Name: "test-name", 312 Version: "2.0", 313 URI: "test-uri", 314 SHA256: "test-sha256", 315 Stacks: []string{"test-stack-1", "test-stack-2"}, 316 })) 317 }) 318 319 it("filters by stack", func() { 320 resolver.Dependencies = []libpak.BuildModuleDependency{ 321 { 322 ID: "test-id", 323 Name: "test-name", 324 Version: "1.0", 325 URI: "test-uri", 326 SHA256: "test-sha256", 327 Stacks: []string{"test-stack-1", "test-stack-2"}, 328 }, 329 { 330 ID: "test-id", 331 Name: "test-name", 332 Version: "1.0", 333 URI: "test-uri", 334 SHA256: "test-sha256", 335 Stacks: []string{"test-stack-1", "test-stack-3"}, 336 }, 337 } 338 resolver.StackID = "test-stack-3" 339 340 Expect(resolver.Resolve("test-id", "1.0")).To(Equal(libpak.BuildModuleDependency{ 341 ID: "test-id", 342 Name: "test-name", 343 Version: "1.0", 344 URI: "test-uri", 345 SHA256: "test-sha256", 346 Stacks: []string{"test-stack-1", "test-stack-3"}, 347 })) 348 }) 349 350 it("filters by stack and supports the wildcard stack", func() { 351 resolver.Dependencies = []libpak.BuildModuleDependency{ 352 { 353 ID: "test-id", 354 Name: "test-name", 355 Version: "1.0", 356 URI: "test-uri", 357 SHA256: "test-sha256", 358 Stacks: []string{"test-stack-1", "test-stack-2"}, 359 }, 360 { 361 ID: "test-id", 362 Name: "test-name", 363 Version: "1.0", 364 URI: "test-uri", 365 SHA256: "test-sha256", 366 Stacks: []string{"*"}, 367 }, 368 } 369 resolver.StackID = "test-stack-3" 370 371 Expect(resolver.Resolve("test-id", "1.0")).To(Equal(libpak.BuildModuleDependency{ 372 ID: "test-id", 373 Name: "test-name", 374 Version: "1.0", 375 URI: "test-uri", 376 SHA256: "test-sha256", 377 Stacks: []string{"*"}, 378 })) 379 }) 380 381 it("filters by stack and treats no stacks as the wildcard stack", func() { 382 resolver.Dependencies = []libpak.BuildModuleDependency{ 383 { 384 ID: "test-id", 385 Name: "test-name", 386 Version: "1.0", 387 URI: "test-uri", 388 SHA256: "test-sha256", 389 Stacks: []string{"test-stack-1", "test-stack-2"}, 390 }, 391 { 392 ID: "test-id", 393 Name: "test-name", 394 Version: "1.0", 395 URI: "test-uri", 396 SHA256: "test-sha256", 397 Stacks: []string{}, 398 }, 399 } 400 resolver.StackID = "test-stack-3" 401 402 Expect(resolver.Resolve("test-id", "1.0")).To(Equal(libpak.BuildModuleDependency{ 403 ID: "test-id", 404 Name: "test-name", 405 Version: "1.0", 406 URI: "test-uri", 407 SHA256: "test-sha256", 408 Stacks: []string{}, 409 })) 410 }) 411 412 it("returns the best dependency", func() { 413 resolver.Dependencies = []libpak.BuildModuleDependency{ 414 { 415 ID: "test-id", 416 Name: "test-name", 417 Version: "1.1", 418 URI: "test-uri", 419 SHA256: "test-sha256", 420 Stacks: []string{"test-stack-1", "test-stack-2"}, 421 }, 422 { 423 ID: "test-id", 424 Name: "test-name", 425 Version: "1.0", 426 URI: "test-uri", 427 SHA256: "test-sha256", 428 Stacks: []string{"test-stack-1", "test-stack-3"}, 429 }, 430 } 431 resolver.StackID = "test-stack-1" 432 433 Expect(resolver.Resolve("test-id", "1.*")).To(Equal(libpak.BuildModuleDependency{ 434 ID: "test-id", 435 Name: "test-name", 436 Version: "1.1", 437 URI: "test-uri", 438 SHA256: "test-sha256", 439 Stacks: []string{"test-stack-1", "test-stack-2"}, 440 })) 441 }) 442 443 it("returns the best dependency after filtering", func() { 444 resolver.Dependencies = []libpak.BuildModuleDependency{ 445 { 446 ID: "test-id-1", 447 Name: "test-name-1", 448 Version: "1.9.1", 449 URI: "test-uri", 450 SHA256: "test-sha256", 451 Stacks: []string{"test-stack-1"}, 452 }, 453 { 454 ID: "test-id-1", 455 Name: "test-name-1", 456 Version: "1.9.1", 457 URI: "test-uri", 458 SHA256: "test-sha256", 459 Stacks: []string{"test-stack-2"}, 460 }, 461 { 462 ID: "test-id-2", 463 Name: "test-name-2", 464 Version: "1.8.5", 465 URI: "test-uri", 466 SHA256: "test-sha256", 467 Stacks: []string{"test-stack-2"}, 468 }, 469 { 470 ID: "test-id-2", 471 Name: "test-name-2", 472 Version: "1.8.6", 473 URI: "test-uri", 474 SHA256: "test-sha256", 475 Stacks: []string{"test-stack-1"}, 476 }, 477 { 478 ID: "test-id-2", 479 Name: "test-name-2", 480 Version: "1.8.6", 481 URI: "test-uri", 482 SHA256: "test-sha256", 483 Stacks: []string{"test-stack-2"}, 484 }, 485 { 486 ID: "test-id-2", 487 Name: "test-name-2", 488 Version: "1.9.0", 489 URI: "test-uri", 490 SHA256: "test-sha256", 491 Stacks: []string{"test-stack-1"}, 492 }, 493 { 494 ID: "test-id-2", 495 Name: "test-name-2", 496 Version: "1.9.0", 497 URI: "test-uri", 498 SHA256: "test-sha256", 499 Stacks: []string{"test-stack-2"}, 500 }, 501 } 502 resolver.StackID = "test-stack-2" 503 504 Expect(resolver.Resolve("test-id-2", "")).To(Equal(libpak.BuildModuleDependency{ 505 ID: "test-id-2", 506 Name: "test-name-2", 507 Version: "1.9.0", 508 URI: "test-uri", 509 SHA256: "test-sha256", 510 Stacks: []string{"test-stack-2"}, 511 })) 512 }) 513 514 it("returns error if there are no matching dependencies", func() { 515 resolver.Dependencies = []libpak.BuildModuleDependency{ 516 { 517 ID: "test-id", 518 Name: "test-name", 519 Version: "1.0", 520 URI: "test-uri", 521 SHA256: "test-sha256", 522 Stacks: []string{"test-stack-1", "test-stack-2"}, 523 }, 524 { 525 ID: "test-id", 526 Name: "test-name", 527 Version: "1.0", 528 URI: "test-uri", 529 SHA256: "test-sha256", 530 Stacks: []string{"test-stack-1", "test-stack-3"}, 531 }, 532 { 533 ID: "test-id-2", 534 Name: "test-name", 535 Version: "1.1", 536 URI: "test-uri", 537 SHA256: "test-sha256", 538 Stacks: []string{"test-stack-1", "test-stack-3"}, 539 }, 540 } 541 resolver.StackID = "test-stack-1" 542 543 _, err := resolver.Resolve("test-id-2", "1.0") 544 Expect(err).To(HaveOccurred()) 545 Expect(err).To(MatchError(libpak.NoValidDependenciesError{Message: "no valid dependencies for test-id-2, 1.0, and test-stack-1 in [(test-id, 1.0, [test-stack-1 test-stack-2]) (test-id, 1.0, [test-stack-1 test-stack-3]) (test-id-2, 1.1, [test-stack-1 test-stack-3])]"})) 546 }) 547 548 it("substitutes all wildcard for unspecified version constraint", func() { 549 resolver.Dependencies = []libpak.BuildModuleDependency{ 550 { 551 ID: "test-id", 552 Name: "test-name", 553 Version: "1.1", 554 URI: "test-uri", 555 SHA256: "test-sha256", 556 Stacks: []string{"test-stack-1", "test-stack-2"}, 557 }, 558 } 559 resolver.StackID = "test-stack-1" 560 561 Expect(resolver.Resolve("test-id", "")).To(Equal(libpak.BuildModuleDependency{ 562 ID: "test-id", 563 Name: "test-name", 564 Version: "1.1", 565 URI: "test-uri", 566 SHA256: "test-sha256", 567 Stacks: []string{"test-stack-1", "test-stack-2"}, 568 })) 569 }) 570 571 it("prints outdated dependencies", func() { 572 buff := bytes.NewBuffer(nil) 573 logger := bard.NewLogger(buff) 574 resolver.Logger = &logger 575 soonDeprecated := time.Now().UTC().Add(30 * 24 * time.Hour) 576 notSoSoonDeprecated := time.Now().UTC().Add(60 * 24 * time.Hour) 577 resolver.Dependencies = []libpak.BuildModuleDependency{ 578 { 579 ID: "missing-deprecation-date", 580 Name: "missing-deprecation-date", 581 Version: "1.1", 582 }, 583 { 584 ID: "valid-dependency", 585 Name: "valid-dependency", 586 Version: "1.1", 587 DeprecationDate: notSoSoonDeprecated, 588 }, 589 { 590 ID: "soon-deprecated-dependency", 591 Name: "soon-deprecated-dependency", 592 Version: "1.1", 593 DeprecationDate: soonDeprecated, 594 }, 595 { 596 ID: "deprecated-dependency", 597 Name: "deprecated-dependency", 598 Version: "1.1", 599 DeprecationDate: time.Now().UTC(), 600 }, 601 } 602 603 for _, dependency := range resolver.Dependencies { 604 resolver.Resolve(dependency.ID, "") 605 } 606 607 Expect(buff.String()).To(Equal(fmt.Sprintf(" \x1b[33mDeprecation Notice:\x1b[0m\n\x1b[2m \x1b[33mVersion 1.1 of soon-deprecated-dependency will be deprecated after %s.\x1b[0m\x1b[2m\x1b[0m\n\x1b[2m \x1b[33mMigrate your application to a supported version of soon-deprecated-dependency before this time.\x1b[0m\x1b[2m\x1b[0m\n \x1b[33mDeprecation Notice:\x1b[0m\n\x1b[2m \x1b[33mVersion 1.1 of deprecated-dependency is deprecated.\x1b[0m\x1b[2m\x1b[0m\n\x1b[2m \x1b[33mMigrate your application to a supported version of deprecated-dependency.\x1b[0m\x1b[2m\x1b[0m\n", soonDeprecated.Format("2006-01-02")))) 608 }) 609 610 }) 611 612 it("indicates whether error is NoValidDependenciesError", func() { 613 Expect(libpak.IsNoValidDependencies(nil)).To(BeFalse()) 614 Expect(libpak.IsNoValidDependencies(fmt.Errorf("test-error"))).To(BeFalse()) 615 Expect(libpak.IsNoValidDependencies(libpak.NoValidDependenciesError{})).To(BeTrue()) 616 }) 617 }) 618 }