github.com/paketo-buildpacks/libpak@v1.70.0/dependency_cache_test.go (about) 1 /* 2 * Copyright 2018-2020 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 "io" 23 "net/http" 24 "net/url" 25 "os" 26 "path/filepath" 27 "testing" 28 "time" 29 30 "github.com/BurntSushi/toml" 31 "github.com/buildpacks/libcnb" 32 . "github.com/onsi/gomega" 33 "github.com/onsi/gomega/ghttp" 34 "github.com/sclevine/spec" 35 36 "github.com/paketo-buildpacks/libpak" 37 "github.com/paketo-buildpacks/libpak/bard" 38 ) 39 40 func testDependencyCache(t *testing.T, context spec.G, it spec.S) { 41 var ( 42 Expect = NewWithT(t).Expect 43 ) 44 45 context("NewDependencyCache", func() { 46 var ctx libcnb.BuildContext 47 48 it.Before(func() { 49 ctx = libcnb.BuildContext{ 50 Buildpack: libcnb.Buildpack{ 51 Info: libcnb.BuildpackInfo{ 52 ID: "some-buildpack-id", 53 Version: "some-buildpack-version", 54 }, 55 Path: "some/path", 56 }, 57 } 58 }) 59 60 it("set default CachePath and UserAgent", func() { 61 dependencyCache, err := libpak.NewDependencyCache(ctx) 62 Expect(err).NotTo(HaveOccurred()) 63 Expect(dependencyCache.CachePath).To(Equal(filepath.Join("some/path/dependencies"))) 64 Expect(dependencyCache.UserAgent).To(Equal("some-buildpack-id/some-buildpack-version")) 65 Expect(dependencyCache.Mappings).To(Equal(map[string]string{})) 66 }) 67 68 it("uses default timeout values", func() { 69 dependencyCache, err := libpak.NewDependencyCache(ctx) 70 Expect(err).NotTo(HaveOccurred()) 71 Expect(dependencyCache.HttpClientTimeouts.DialerTimeout).To(Equal(6 * time.Second)) 72 Expect(dependencyCache.HttpClientTimeouts.DialerKeepAlive).To(Equal(60 * time.Second)) 73 Expect(dependencyCache.HttpClientTimeouts.TLSHandshakeTimeout).To(Equal(5 * time.Second)) 74 Expect(dependencyCache.HttpClientTimeouts.ResponseHeaderTimeout).To(Equal(5 * time.Second)) 75 Expect(dependencyCache.HttpClientTimeouts.ExpectContinueTimeout).To(Equal(1 * time.Second)) 76 }) 77 78 context("custom timeout setttings", func() { 79 it.Before(func() { 80 t.Setenv("BP_DIALER_TIMEOUT", "7") 81 t.Setenv("BP_DIALER_KEEP_ALIVE", "50") 82 t.Setenv("BP_TLS_HANDSHAKE_TIMEOUT", "2") 83 t.Setenv("BP_RESPONSE_HEADER_TIMEOUT", "3") 84 t.Setenv("BP_EXPECT_CONTINUE_TIMEOUT", "2") 85 }) 86 87 it("uses custom timeout values", func() { 88 dependencyCache, err := libpak.NewDependencyCache(ctx) 89 Expect(err).NotTo(HaveOccurred()) 90 Expect(dependencyCache.HttpClientTimeouts.DialerTimeout).To(Equal(7 * time.Second)) 91 Expect(dependencyCache.HttpClientTimeouts.DialerKeepAlive).To(Equal(50 * time.Second)) 92 Expect(dependencyCache.HttpClientTimeouts.TLSHandshakeTimeout).To(Equal(2 * time.Second)) 93 Expect(dependencyCache.HttpClientTimeouts.ResponseHeaderTimeout).To(Equal(3 * time.Second)) 94 Expect(dependencyCache.HttpClientTimeouts.ExpectContinueTimeout).To(Equal(2 * time.Second)) 95 }) 96 }) 97 98 context("bindings with type dependencies exist", func() { 99 it.Before(func() { 100 ctx.Platform.Bindings = libcnb.Bindings{ 101 { 102 Type: "dependency-mapping", 103 Secret: map[string]string{ 104 "some-digest1": "some-uri1", 105 "some-digest2": "some-uri2", 106 }, 107 }, 108 { 109 Type: "not-dependency-mapping", 110 Secret: map[string]string{ 111 "some-thing": "other-thing", 112 }, 113 }, 114 { 115 Type: "dependency-mapping", 116 Secret: map[string]string{ 117 "some-digest3": "some-uri3", 118 "some-digest4": "some-uri4", 119 }, 120 }, 121 } 122 }) 123 124 it("sets Mappings", func() { 125 dependencyCache, err := libpak.NewDependencyCache(ctx) 126 Expect(err).NotTo(HaveOccurred()) 127 Expect(dependencyCache.Mappings).To(Equal( 128 map[string]string{ 129 "some-digest1": "some-uri1", 130 "some-digest2": "some-uri2", 131 "some-digest3": "some-uri3", 132 "some-digest4": "some-uri4", 133 }, 134 )) 135 }) 136 137 context("multiple bindings map the same digest", func() { 138 it.Before(func() { 139 ctx.Platform.Bindings = append(ctx.Platform.Bindings, libcnb.Binding{ 140 Type: "dependency-mapping", 141 Secret: map[string]string{ 142 "some-digest1": "other-uri", 143 }, 144 }) 145 }) 146 147 it("errors", func() { 148 _, err := libpak.NewDependencyCache(ctx) 149 Expect(err).To(HaveOccurred()) 150 }) 151 }) 152 }) 153 154 context("dependency mirror from environment variable", func() { 155 it.Before(func() { 156 t.Setenv("BP_DEPENDENCY_MIRROR", "https://env-var-mirror.acme.com") 157 t.Setenv("BP_DEPENDENCY_MIRROR_EXAMP__LE_COM", "https://examp-le.com") 158 }) 159 160 it("uses BP_DEPENDENCY_MIRROR environment variable", func() { 161 dependencyCache, err := libpak.NewDependencyCache(ctx) 162 Expect(err).NotTo(HaveOccurred()) 163 Expect(dependencyCache.DependencyMirrors["default"]).To(Equal("https://env-var-mirror.acme.com")) 164 Expect(dependencyCache.DependencyMirrors["examp-le.com"]).To(Equal("https://examp-le.com")) 165 }) 166 }) 167 168 context("dependency mirror from binding and environment variable", func() { 169 it.Before(func() { 170 t.Setenv("BP_DEPENDENCY_MIRROR_EXAMP__LE_COM", "https://examp-le.com") 171 ctx.Platform.Bindings = append(ctx.Platform.Bindings, libcnb.Binding{ 172 Type: "dependency-mirror", 173 Secret: map[string]string{ 174 "default": "https://bindings-mirror.acme.com", 175 "examp-le.com": "https://invalid.com", 176 }, 177 }) 178 }) 179 180 it("uses dependency-mirror binding", func() { 181 dependencyCache, err := libpak.NewDependencyCache(ctx) 182 Expect(err).NotTo(HaveOccurred()) 183 Expect(dependencyCache.DependencyMirrors["default"]).To(Equal("https://bindings-mirror.acme.com")) 184 }) 185 186 it("environment variable overrides binding", func() { 187 dependencyCache, err := libpak.NewDependencyCache(ctx) 188 Expect(err).NotTo(HaveOccurred()) 189 Expect(dependencyCache.DependencyMirrors["examp-le.com"]).To(Equal("https://examp-le.com")) 190 }) 191 }) 192 }) 193 194 context("artifacts", func() { 195 var ( 196 cachePath string 197 downloadPath string 198 dependency libpak.BuildpackDependency 199 dependencyCache libpak.DependencyCache 200 server *ghttp.Server 201 ) 202 203 it.Before(func() { 204 var err error 205 206 cachePath = t.TempDir() 207 Expect(err).NotTo(HaveOccurred()) 208 209 downloadPath = t.TempDir() 210 Expect(err).NotTo(HaveOccurred()) 211 212 RegisterTestingT(t) 213 server = ghttp.NewServer() 214 215 dependency = libpak.BuildpackDependency{ 216 ID: "test-id", 217 Name: "test-name", 218 Version: "1.1.1", 219 URI: fmt.Sprintf("%s/test-path", server.URL()), 220 SHA256: "576dd8416de5619ea001d9662291d62444d1292a38e96956bc4651c01f14bca1", 221 Stacks: []string{"test-stack"}, 222 DeprecationDate: time.Now(), 223 Licenses: []libpak.BuildpackDependencyLicense{ 224 { 225 Type: "test-type", 226 URI: "test-uri", 227 }, 228 }, 229 CPEs: []string{"cpe:2.3:a:some:jre:11.0.2:*:*:*:*:*:*:*"}, 230 PURL: "pkg:generic/some-java11@11.0.2?arch=amd64", 231 } 232 233 dependencyCache = libpak.DependencyCache{ 234 CachePath: cachePath, 235 DownloadPath: downloadPath, 236 UserAgent: "test-user-agent", 237 } 238 }) 239 240 it.After(func() { 241 Expect(os.RemoveAll(cachePath)).To(Succeed()) 242 Expect(os.RemoveAll(downloadPath)).To(Succeed()) 243 server.Close() 244 }) 245 246 copyFile := func(source string, destination string) { 247 in, err := os.Open(source) 248 Expect(err).NotTo(HaveOccurred()) 249 defer in.Close() 250 251 Expect(os.MkdirAll(filepath.Dir(destination), 0755)).To(Succeed()) 252 out, err := os.OpenFile(destination, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) 253 Expect(err).NotTo(HaveOccurred()) 254 defer out.Close() 255 256 _, err = io.Copy(out, in) 257 Expect(err).NotTo(HaveOccurred()) 258 } 259 260 writeTOML := func(destination string, v interface{}) { 261 Expect(os.MkdirAll(filepath.Dir(destination), 0755)).To(Succeed()) 262 out, err := os.OpenFile(destination, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) 263 Expect(err).NotTo(HaveOccurred()) 264 defer out.Close() 265 266 Expect(toml.NewEncoder(out).Encode(v)).To(Succeed()) 267 } 268 269 it("returns from cache path", func() { 270 copyFile(filepath.Join("testdata", "test-file"), filepath.Join(cachePath, dependency.SHA256, "test-path")) 271 writeTOML(filepath.Join(cachePath, fmt.Sprintf("%s.toml", dependency.SHA256)), dependency) 272 273 a, err := dependencyCache.Artifact(dependency) 274 Expect(err).NotTo(HaveOccurred()) 275 276 Expect(io.ReadAll(a)).To(Equal([]byte("test-fixture"))) 277 }) 278 279 it("returns from download path", func() { 280 copyFile(filepath.Join("testdata", "test-file"), filepath.Join(downloadPath, dependency.SHA256, "test-path")) 281 writeTOML(filepath.Join(downloadPath, fmt.Sprintf("%s.toml", dependency.SHA256)), dependency) 282 283 a, err := dependencyCache.Artifact(dependency) 284 Expect(err).NotTo(HaveOccurred()) 285 286 Expect(io.ReadAll(a)).To(Equal([]byte("test-fixture"))) 287 }) 288 289 it("downloads", func() { 290 server.AppendHandlers(ghttp.CombineHandlers( 291 ghttp.VerifyRequest(http.MethodGet, "/test-path", ""), 292 ghttp.RespondWith(http.StatusOK, "test-fixture"), 293 )) 294 295 a, err := dependencyCache.Artifact(dependency) 296 Expect(err).NotTo(HaveOccurred()) 297 298 Expect(io.ReadAll(a)).To(Equal([]byte("test-fixture"))) 299 }) 300 301 context("uri is overridden HTTP", func() { 302 it.Before(func() { 303 dependencyCache.Mappings = map[string]string{ 304 dependency.SHA256: fmt.Sprintf("%s/override-path", server.URL()), 305 } 306 }) 307 308 it("downloads from override uri", func() { 309 server.AppendHandlers(ghttp.CombineHandlers( 310 ghttp.VerifyRequest(http.MethodGet, "/override-path", ""), 311 ghttp.RespondWith(http.StatusOK, "test-fixture"), 312 )) 313 314 a, err := dependencyCache.Artifact(dependency) 315 Expect(err).NotTo(HaveOccurred()) 316 317 Expect(io.ReadAll(a)).To(Equal([]byte("test-fixture"))) 318 }) 319 }) 320 321 context("uri is overridden FILE", func() { 322 it.Before(func() { 323 sourcePath := t.TempDir() 324 sourceFile := filepath.Join(sourcePath, "source-file") 325 Expect(os.WriteFile(sourceFile, []byte("test-fixture"), 0644)).ToNot(HaveOccurred()) 326 327 dependencyCache.Mappings = map[string]string{ 328 dependency.SHA256: fmt.Sprintf("file://%s", sourceFile), 329 } 330 }) 331 332 it("downloads from override filesystem", func() { 333 a, err := dependencyCache.Artifact(dependency) 334 Expect(err).NotTo(HaveOccurred()) 335 336 Expect(io.ReadAll(a)).To(Equal([]byte("test-fixture"))) 337 }) 338 }) 339 340 context("dependency mirror is used https", func() { 341 var mirrorServer *ghttp.Server 342 343 it.Before(func() { 344 mirrorServer = ghttp.NewTLSServer() 345 dependencyCache.DependencyMirrors = map[string]string{} 346 }) 347 348 it.After(func() { 349 mirrorServer.Close() 350 }) 351 352 it("downloads from https mirror", func() { 353 url, err := url.Parse(mirrorServer.URL()) 354 Expect(err).NotTo(HaveOccurred()) 355 mirrorServer.AppendHandlers(ghttp.CombineHandlers( 356 ghttp.VerifyBasicAuth("username", "password"), 357 ghttp.VerifyRequest(http.MethodGet, "/foo/bar/test-path", ""), 358 ghttp.RespondWith(http.StatusOK, "test-fixture"), 359 )) 360 361 dependencyCache.DependencyMirrors["default"] = url.Scheme + "://" + "username:password@" + url.Host + "/foo/bar" 362 a, err := dependencyCache.Artifact(dependency) 363 Expect(err).NotTo(HaveOccurred()) 364 365 Expect(io.ReadAll(a)).To(Equal([]byte("test-fixture"))) 366 }) 367 368 it("downloads from https mirror preserving hostname", func() { 369 url, err := url.Parse(mirrorServer.URL()) 370 Expect(err).NotTo(HaveOccurred()) 371 mirrorServer.AppendHandlers(ghttp.CombineHandlers( 372 ghttp.VerifyRequest(http.MethodGet, "/"+url.Hostname()+"/test-path", ""), 373 ghttp.RespondWith(http.StatusOK, "test-fixture"), 374 )) 375 376 dependencyCache.DependencyMirrors["default"] = url.Scheme + "://" + url.Host + "/{originalHost}" 377 a, err := dependencyCache.Artifact(dependency) 378 Expect(err).NotTo(HaveOccurred()) 379 380 Expect(io.ReadAll(a)).To(Equal([]byte("test-fixture"))) 381 }) 382 383 it("downloads from https mirror host specific", func() { 384 url, err := url.Parse(mirrorServer.URL()) 385 Expect(err).NotTo(HaveOccurred()) 386 mirrorServer.AppendHandlers(ghttp.CombineHandlers( 387 ghttp.VerifyRequest(http.MethodGet, "/host-specific/test-path", ""), 388 ghttp.RespondWith(http.StatusOK, "test-fixture"), 389 )) 390 391 dependencyCache.DependencyMirrors["127.0.0.1"] = url.Scheme + "://" + url.Host + "/host-specific" 392 a, err := dependencyCache.Artifact(dependency) 393 Expect(err).NotTo(HaveOccurred()) 394 395 Expect(io.ReadAll(a)).To(Equal([]byte("test-fixture"))) 396 }) 397 }) 398 399 context("dependency mirror is used file", func() { 400 var ( 401 mirrorPath string 402 mirrorPathPreservedHost string 403 ) 404 405 it.Before(func() { 406 var err error 407 mirrorPath, err = os.MkdirTemp("", "mirror-path") 408 Expect(err).NotTo(HaveOccurred()) 409 originalUrl, err := url.Parse(dependency.URI) 410 Expect(err).NotTo(HaveOccurred()) 411 mirrorPathPreservedHost = filepath.Join(mirrorPath, originalUrl.Hostname(), "prefix") 412 Expect(os.MkdirAll(mirrorPathPreservedHost, os.ModePerm)).NotTo(HaveOccurred()) 413 dependencyCache.DependencyMirrors = map[string]string{} 414 }) 415 416 it.After(func() { 417 Expect(os.RemoveAll(mirrorPath)).To(Succeed()) 418 }) 419 420 it("downloads from file mirror", func() { 421 mirrorFile := filepath.Join(mirrorPath, "test-path") 422 Expect(os.WriteFile(mirrorFile, []byte("test-fixture"), 0644)).ToNot(HaveOccurred()) 423 424 dependencyCache.DependencyMirrors["default"] = "file://" + mirrorPath 425 a, err := dependencyCache.Artifact(dependency) 426 Expect(err).NotTo(HaveOccurred()) 427 428 Expect(io.ReadAll(a)).To(Equal([]byte("test-fixture"))) 429 }) 430 431 it("downloads from file mirror preserving hostname", func() { 432 mirrorFilePreservedHost := filepath.Join(mirrorPathPreservedHost, "test-path") 433 Expect(os.WriteFile(mirrorFilePreservedHost, []byte("test-fixture"), 0644)).ToNot(HaveOccurred()) 434 435 dependencyCache.DependencyMirrors["default"] = "file://" + mirrorPath + "/{originalHost}" + "/prefix" 436 a, err := dependencyCache.Artifact(dependency) 437 Expect(err).NotTo(HaveOccurred()) 438 439 Expect(io.ReadAll(a)).To(Equal([]byte("test-fixture"))) 440 }) 441 }) 442 443 it("fails with invalid SHA256", func() { 444 server.AppendHandlers(ghttp.RespondWith(http.StatusOK, "invalid-fixture")) 445 446 _, err := dependencyCache.Artifact(dependency) 447 Expect(err).To(HaveOccurred()) 448 }) 449 450 it("skips cache with empty SHA256", func() { 451 copyFile(filepath.Join("testdata", "test-file"), filepath.Join(cachePath, dependency.SHA256, "test-path")) 452 writeTOML(filepath.Join(cachePath, fmt.Sprintf("%s.toml", dependency.SHA256)), dependency) 453 copyFile(filepath.Join("testdata", "test-file"), filepath.Join(downloadPath, dependency.SHA256, "test-path")) 454 writeTOML(filepath.Join(downloadPath, fmt.Sprintf("%s.toml", dependency.SHA256)), dependency) 455 456 dependency.SHA256 = "" 457 server.AppendHandlers(ghttp.RespondWith(http.StatusOK, "alternate-fixture")) 458 459 a, err := dependencyCache.Artifact(dependency) 460 Expect(err).NotTo(HaveOccurred()) 461 462 Expect(io.ReadAll(a)).To(Equal([]byte("alternate-fixture"))) 463 }) 464 465 it("sets User-Agent", func() { 466 server.AppendHandlers(ghttp.CombineHandlers( 467 ghttp.VerifyHeaderKV("User-Agent", "test-user-agent"), 468 ghttp.RespondWith(http.StatusOK, "test-fixture"), 469 )) 470 471 a, err := dependencyCache.Artifact(dependency) 472 Expect(err).NotTo(HaveOccurred()) 473 474 Expect(io.ReadAll(a)).To(Equal([]byte("test-fixture"))) 475 }) 476 477 it("modifies request", func() { 478 server.AppendHandlers(ghttp.CombineHandlers( 479 ghttp.VerifyHeaderKV("User-Agent", "test-user-agent"), 480 ghttp.VerifyHeaderKV("Test-Key", "test-value"), 481 ghttp.RespondWith(http.StatusOK, "test-fixture"), 482 )) 483 484 a, err := dependencyCache.Artifact(dependency, func(request *http.Request) (*http.Request, error) { 485 request.Header.Add("Test-Key", "test-value") 486 return request, nil 487 }) 488 Expect(err).NotTo(HaveOccurred()) 489 490 Expect(io.ReadAll(a)).To(Equal([]byte("test-fixture"))) 491 }) 492 493 it("hide uri credentials from log", func() { 494 server.AppendHandlers(ghttp.CombineHandlers( 495 ghttp.RespondWith(http.StatusOK, "test-fixture"), 496 )) 497 498 url, err := url.ParseRequestURI(dependency.URI) 499 Expect(err).NotTo(HaveOccurred()) 500 credentials := "username:password" 501 uriWithBasicCreds := url.Scheme + "://" + credentials + "@" + url.Hostname() + ":" + url.Port() + url.Path 502 dependency.URI = uriWithBasicCreds 503 504 var logBuffer bytes.Buffer 505 dependencyCache.Logger = bard.NewLogger(&logBuffer) 506 507 // Make sure the password is not part of the log output. 508 a, errA := dependencyCache.Artifact(dependency) 509 Expect(errA).NotTo(HaveOccurred()) 510 Expect(a).NotTo(BeNil()) 511 Expect(logBuffer.String()).NotTo(ContainSubstring("password")) 512 logBuffer.Reset() 513 514 // Make sure the password is not part of the log output when an error occurs. 515 dependency.URI = "foo://username:password@acme.com" 516 b, errB := dependencyCache.Artifact(dependency) 517 Expect(errB).To(HaveOccurred()) 518 Expect(b).To(BeNil()) 519 Expect(logBuffer.String()).NotTo(ContainSubstring("password")) 520 }) 521 }) 522 }