github.com/BarDweller/libpak@v0.0.0-20230630201634-8dd5cfc15ec9/layer_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 "net/http" 23 "os" 24 "path/filepath" 25 "testing" 26 "time" 27 28 "github.com/buildpacks/libcnb" 29 . "github.com/onsi/gomega" 30 "github.com/onsi/gomega/ghttp" 31 "github.com/sclevine/spec" 32 33 "github.com/BarDweller/libpak" 34 "github.com/BarDweller/libpak/bard" 35 ) 36 37 func testLayer(t *testing.T, context spec.G, it spec.S) { 38 var ( 39 Expect = NewWithT(t).Expect 40 41 layersDir string 42 layer libcnb.Layer 43 ) 44 45 it.Before(func() { 46 layersDir = t.TempDir() 47 layer.Path = filepath.Join(layersDir, "test-layer") 48 49 layer.Exec.Path = layer.Path 50 layer.Metadata = map[string]interface{}{} 51 layer.Profile = libcnb.Profile{} 52 }) 53 54 it.After(func() { 55 Expect(os.RemoveAll(layersDir)).To(Succeed()) 56 }) 57 58 context("LayerContributor", func() { 59 var ( 60 lc libpak.LayerContributor 61 ) 62 63 it.Before(func() { 64 lc.Logger = bard.NewLogger(bytes.NewBuffer(nil)) 65 lc.ExpectedMetadata = map[string]interface{}{ 66 "alpha": "test-alpha", 67 "bravo": map[string]interface{}{ 68 "bravo-1": "test-bravo-1", 69 "bravo-2": "test-bravo-2", 70 }, 71 } 72 }) 73 74 it("calls function with no existing metadata", func() { 75 var called bool 76 77 _, err := lc.Contribute(layer, func() (libcnb.Layer, error) { 78 called = true 79 return layer, nil 80 }) 81 Expect(err).NotTo(HaveOccurred()) 82 83 Expect(called).To(BeTrue()) 84 }) 85 86 it("calls function with non-matching metadata", func() { 87 88 layer.Metadata["alpha"] = "test-alpha" 89 90 var called bool 91 92 _, err := lc.Contribute(layer, func() (libcnb.Layer, error) { 93 called = true 94 return layer, nil 95 }) 96 Expect(err).NotTo(HaveOccurred()) 97 98 Expect(called).To(BeTrue()) 99 }) 100 101 context("reloads layers not restored", func() { 102 var called bool 103 104 it.Before(func() { 105 layer.Metadata = map[string]interface{}{ 106 "alpha": "test-alpha", 107 "bravo": map[string]interface{}{ 108 "bravo-1": "test-bravo-1", 109 "bravo-2": "test-bravo-2", 110 }, 111 } 112 called = false 113 }) 114 115 it("calls function with matching metadata but no layer directory on cache layer", func() { 116 Expect(os.WriteFile(fmt.Sprintf("%s.toml", layer.Path), []byte{}, 0644)).To(Succeed()) 117 Expect(os.RemoveAll(layer.Path)).To(Succeed()) 118 lc.ExpectedTypes.Cache = true 119 120 _, err := lc.Contribute(layer, func() (libcnb.Layer, error) { 121 called = true 122 return layer, nil 123 }) 124 Expect(err).NotTo(HaveOccurred()) 125 126 Expect(called).To(BeTrue()) 127 }) 128 129 it("calls function with matching metadata but no layer directory on build layer", func() { 130 Expect(os.WriteFile(fmt.Sprintf("%s.toml", layer.Path), []byte{}, 0644)).To(Succeed()) 131 Expect(os.RemoveAll(layer.Path)).To(Succeed()) 132 lc.ExpectedTypes.Build = true 133 134 _, err := lc.Contribute(layer, func() (libcnb.Layer, error) { 135 called = true 136 return layer, nil 137 }) 138 Expect(err).NotTo(HaveOccurred()) 139 140 Expect(called).To(BeTrue()) 141 }) 142 143 it("calls function with matching metadata but an empty layer directory on build layer", func() { 144 Expect(os.WriteFile(fmt.Sprintf("%s.toml", layer.Path), []byte{}, 0644)).To(Succeed()) 145 Expect(os.MkdirAll(layer.Path, 0755)).To(Succeed()) 146 lc.ExpectedTypes.Build = true 147 148 _, err := lc.Contribute(layer, func() (libcnb.Layer, error) { 149 called = true 150 return layer, nil 151 }) 152 Expect(err).NotTo(HaveOccurred()) 153 154 Expect(called).To(BeTrue()) 155 }) 156 157 it("does not call function with matching metadata when layer directory exists and has a file in it", func() { 158 Expect(os.WriteFile(fmt.Sprintf("%s.toml", layer.Path), []byte{}, 0644)).To(Succeed()) 159 Expect(os.MkdirAll(layer.Path, 0755)).To(Succeed()) 160 Expect(os.WriteFile(filepath.Join(layer.Path, "foo"), []byte{}, 0644)).To(Succeed()) 161 lc.ExpectedTypes.Build = true 162 163 _, err := lc.Contribute(layer, func() (libcnb.Layer, error) { 164 called = true 165 return layer, nil 166 }) 167 Expect(err).NotTo(HaveOccurred()) 168 169 Expect(called).To(BeFalse()) 170 }) 171 172 it("does not call function with matching metadata when layer TOML missing", func() { 173 Expect(os.MkdirAll(layer.Path, 0755)).To(Succeed()) 174 layer.Build = true 175 176 _, err := lc.Contribute(layer, func() (libcnb.Layer, error) { 177 called = true 178 return layer, nil 179 }) 180 Expect(err).NotTo(HaveOccurred()) 181 182 Expect(called).To(BeFalse()) 183 }) 184 }) 185 186 it("does not call function with matching metadata", func() { 187 layer.Metadata = map[string]interface{}{ 188 "alpha": "test-alpha", 189 "bravo": map[string]interface{}{ 190 "bravo-1": "test-bravo-1", 191 "bravo-2": "test-bravo-2", 192 }, 193 } 194 195 var called bool 196 197 _, err := lc.Contribute(layer, func() (libcnb.Layer, error) { 198 called = true 199 return layer, nil 200 }) 201 Expect(err).NotTo(HaveOccurred()) 202 203 Expect(called).To(BeFalse()) 204 }) 205 206 it("returns function error", func() { 207 _, err := lc.Contribute(layer, func() (libcnb.Layer, error) { 208 return libcnb.Layer{}, fmt.Errorf("test-error") 209 }) 210 Expect(err).To(MatchError("test-error")) 211 }) 212 213 it("adds expected metadata to layer", func() { 214 layer, err := lc.Contribute(layer, func() (libcnb.Layer, error) { 215 return layer, nil 216 }) 217 Expect(err).NotTo(HaveOccurred()) 218 219 Expect(layer.Metadata).To(Equal(map[string]interface{}{ 220 "alpha": "test-alpha", 221 "bravo": map[string]interface{}{ 222 "bravo-1": "test-bravo-1", 223 "bravo-2": "test-bravo-2", 224 }, 225 })) 226 }) 227 228 it("sets build layer flag", func() { 229 lc.ExpectedTypes.Build = true 230 layer, err := lc.Contribute(layer, func() (libcnb.Layer, error) { 231 return layer, nil 232 }) 233 Expect(err).NotTo(HaveOccurred()) 234 235 Expect(layer.LayerTypes.Build).To(BeTrue()) 236 }) 237 238 it("sets cache layer flag", func() { 239 lc.ExpectedTypes.Cache = true 240 layer, err := lc.Contribute(layer, func() (libcnb.Layer, error) { 241 return layer, nil 242 }) 243 Expect(err).NotTo(HaveOccurred()) 244 245 Expect(layer.LayerTypes.Cache).To(BeTrue()) 246 }) 247 248 it("sets launch layer flag", func() { 249 lc.ExpectedTypes.Launch = true 250 layer, err := lc.Contribute(layer, func() (libcnb.Layer, error) { 251 return layer, nil 252 }) 253 Expect(err).NotTo(HaveOccurred()) 254 255 Expect(layer.LayerTypes.Launch).To(BeTrue()) 256 }) 257 258 it("sets layer flags regardless of caching behavior (required for 0.6 API)", func() { 259 lc.ExpectedTypes.Launch = true 260 lc.ExpectedTypes.Cache = true 261 lc.ExpectedTypes.Build = true 262 263 layer.Metadata = map[string]interface{}{ 264 "alpha": "test-alpha", 265 "bravo": map[string]interface{}{ 266 "bravo-1": "test-bravo-1", 267 "bravo-2": "test-bravo-2", 268 }, 269 } 270 271 var called bool 272 273 layer, err := lc.Contribute(layer, func() (libcnb.Layer, error) { 274 called = true 275 return layer, nil 276 }) 277 Expect(err).NotTo(HaveOccurred()) 278 Expect(called).To(BeFalse()) 279 280 Expect(layer.LayerTypes.Launch).To(BeTrue()) 281 Expect(layer.LayerTypes.Cache).To(BeTrue()) 282 Expect(layer.LayerTypes.Build).To(BeTrue()) 283 }) 284 }) 285 286 context("DependencyLayerContributor", func() { 287 var ( 288 dependency libpak.BuildModuleDependency 289 dlc libpak.DependencyLayerContributor 290 server *ghttp.Server 291 ) 292 293 it.Before(func() { 294 RegisterTestingT(t) 295 server = ghttp.NewServer() 296 297 deprecationDate, err := time.Parse(time.RFC3339, "2021-04-01T00:00:00Z") 298 Expect(err).ToNot(HaveOccurred()) 299 300 dependency = libpak.BuildModuleDependency{ 301 ID: "test-id", 302 Name: "test-name", 303 Version: "1.1.1", 304 URI: fmt.Sprintf("%s/test-path", server.URL()), 305 SHA256: "576dd8416de5619ea001d9662291d62444d1292a38e96956bc4651c01f14bca1", 306 Stacks: []string{"test-stack"}, 307 Licenses: []libpak.BuildModuleDependencyLicense{ 308 { 309 Type: "test-type", 310 URI: "test-uri", 311 }, 312 }, 313 CPEs: []string{"cpe:2.3:a:some:jre:11.0.2:*:*:*:*:*:*:*"}, 314 PURL: "pkg:generic/some-java11@11.0.2?arch=amd64", 315 DeprecationDate: deprecationDate, 316 } 317 318 layer.Metadata = map[string]interface{}{} 319 320 dlc.Logger = bard.NewLogger(bytes.NewBuffer(nil)) 321 dlc.ExpectedMetadata = dependency 322 dlc.Dependency = dependency 323 dlc.DependencyCache.CachePath = layer.Path 324 dlc.DependencyCache.DownloadPath = layer.Path 325 }) 326 327 it.After(func() { 328 server.Close() 329 }) 330 331 it("calls function with no existing metadata", func() { 332 server.AppendHandlers(ghttp.RespondWith(http.StatusOK, "test-fixture")) 333 334 var called bool 335 336 _, err := dlc.Contribute(layer, func(artifact *os.File) (libcnb.Layer, error) { 337 defer artifact.Close() 338 339 called = true 340 return layer, nil 341 }) 342 Expect(err).NotTo(HaveOccurred()) 343 344 Expect(called).To(BeTrue()) 345 }) 346 347 it("modifies request", func() { 348 server.AppendHandlers(ghttp.CombineHandlers( 349 ghttp.VerifyHeaderKV("Test-Key", "test-value"), 350 ghttp.RespondWith(http.StatusOK, "test-fixture"), 351 )) 352 353 dlc.RequestModifierFuncs = append(dlc.RequestModifierFuncs, func(request *http.Request) (*http.Request, error) { 354 request.Header.Add("Test-Key", "test-value") 355 return request, nil 356 }) 357 358 _, err := dlc.Contribute(layer, func(artifact *os.File) (libcnb.Layer, error) { 359 defer artifact.Close() 360 return layer, nil 361 }) 362 Expect(err).NotTo(HaveOccurred()) 363 }) 364 365 it("calls function with non-matching metadata", func() { 366 layer.Metadata["alpha"] = "test-alpha" 367 368 server.AppendHandlers(ghttp.RespondWith(http.StatusOK, "test-fixture")) 369 370 var called bool 371 372 _, err := dlc.Contribute(layer, func(artifact *os.File) (libcnb.Layer, error) { 373 defer artifact.Close() 374 375 called = true 376 return layer, nil 377 }) 378 Expect(err).NotTo(HaveOccurred()) 379 380 Expect(called).To(BeTrue()) 381 }) 382 383 it("does not call function with matching metadata", func() { 384 layer.Metadata = map[string]interface{}{ 385 "id": dependency.ID, 386 "name": dependency.Name, 387 "version": dependency.Version, 388 "uri": dependency.URI, 389 "sha256": dependency.SHA256, 390 "stacks": []interface{}{dependency.Stacks[0]}, 391 "licenses": []map[string]interface{}{ 392 { 393 "type": dependency.Licenses[0].Type, 394 "uri": dependency.Licenses[0].URI, 395 }, 396 }, 397 "cpes": []interface{}{"cpe:2.3:a:some:jre:11.0.2:*:*:*:*:*:*:*"}, 398 "purl": "pkg:generic/some-java11@11.0.2?arch=amd64", 399 "deprecation_date": dependency.DeprecationDate, 400 } 401 402 var called bool 403 404 _, err := dlc.Contribute(layer, func(artifact *os.File) (libcnb.Layer, error) { 405 defer artifact.Close() 406 407 called = true 408 return layer, nil 409 }) 410 Expect(err).NotTo(HaveOccurred()) 411 412 Expect(called).To(BeFalse()) 413 }) 414 415 it("returns function error", func() { 416 server.AppendHandlers(ghttp.RespondWith(http.StatusOK, "test-fixture")) 417 418 _, err := dlc.Contribute(layer, func(artifact *os.File) (libcnb.Layer, error) { 419 defer artifact.Close() 420 421 return libcnb.Layer{}, fmt.Errorf("test-error") 422 }) 423 Expect(err).To(MatchError("test-error")) 424 }) 425 426 it("adds expected metadata to layer", func() { 427 server.AppendHandlers(ghttp.RespondWith(http.StatusOK, "test-fixture")) 428 429 layer, err := dlc.Contribute(layer, func(artifact *os.File) (libcnb.Layer, error) { 430 defer artifact.Close() 431 return layer, nil 432 }) 433 Expect(err).NotTo(HaveOccurred()) 434 435 Expect(layer.Metadata).To(Equal(map[string]interface{}{ 436 "id": dependency.ID, 437 "name": dependency.Name, 438 "version": dependency.Version, 439 "uri": dependency.URI, 440 "sha256": dependency.SHA256, 441 "stacks": []interface{}{dependency.Stacks[0]}, 442 "licenses": []map[string]interface{}{ 443 { 444 "type": dependency.Licenses[0].Type, 445 "uri": dependency.Licenses[0].URI, 446 }, 447 }, 448 "cpes": []interface{}{"cpe:2.3:a:some:jre:11.0.2:*:*:*:*:*:*:*"}, 449 "purl": "pkg:generic/some-java11@11.0.2?arch=amd64", 450 "deprecation_date": dependency.DeprecationDate, 451 })) 452 }) 453 454 it("sets layer flags regardless of caching behavior (required for 0.6 API)", func() { 455 layer.Metadata = map[string]interface{}{ 456 "id": dependency.ID, 457 "name": dependency.Name, 458 "version": dependency.Version, 459 "uri": dependency.URI, 460 "sha256": dependency.SHA256, 461 "stacks": []interface{}{dependency.Stacks[0]}, 462 "licenses": []map[string]interface{}{ 463 { 464 "type": dependency.Licenses[0].Type, 465 "uri": dependency.Licenses[0].URI, 466 }, 467 }, 468 "cpes": []interface{}{"cpe:2.3:a:some:jre:11.0.2:*:*:*:*:*:*:*"}, 469 "purl": "pkg:generic/some-java11@11.0.2?arch=amd64", 470 "deprecation_date": dependency.DeprecationDate, 471 } 472 dlc.ExpectedTypes.Launch = true 473 dlc.ExpectedTypes.Cache = true 474 dlc.ExpectedTypes.Build = true 475 476 var called bool 477 478 layer, err := dlc.Contribute(layer, func(artifact *os.File) (libcnb.Layer, error) { 479 defer artifact.Close() 480 481 called = true 482 return layer, nil 483 }) 484 Expect(err).NotTo(HaveOccurred()) 485 486 Expect(called).To(BeFalse()) 487 488 Expect(layer.LayerTypes.Launch).To(BeTrue()) 489 Expect(layer.LayerTypes.Cache).To(BeTrue()) 490 Expect(layer.LayerTypes.Build).To(BeTrue()) 491 }) 492 493 it("adds expected Syft SBOM file", func() { 494 server.AppendHandlers(ghttp.RespondWith(http.StatusOK, "test-fixture")) 495 496 layer, err := dlc.Contribute(layer, func(artifact *os.File) (libcnb.Layer, error) { 497 defer artifact.Close() 498 return layer, nil 499 }) 500 Expect(err).NotTo(HaveOccurred()) 501 502 outputFile := layer.SBOMPath(libcnb.SyftJSON) 503 Expect(outputFile).To(BeARegularFile()) 504 505 data, err := os.ReadFile(outputFile) 506 Expect(err).ToNot(HaveOccurred()) 507 Expect(string(data)).To(ContainSubstring(`"Artifacts":[`)) 508 Expect(string(data)).To(ContainSubstring(`"FoundBy":"libpak",`)) 509 Expect(string(data)).To(ContainSubstring(`"PURL":"pkg:generic/some-java11@11.0.2?arch=amd64"`)) 510 Expect(string(data)).To(ContainSubstring(`"Schema":{`)) 511 Expect(string(data)).To(ContainSubstring(`"Descriptor":{`)) 512 Expect(string(data)).To(ContainSubstring(`"Source":{`)) 513 }) 514 }) 515 516 context("HelperLayerContributor", func() { 517 var ( 518 buildpack libcnb.Buildpack 519 hlc libpak.HelperLayerContributor 520 ) 521 522 it.Before(func() { 523 buildpack.Info = libcnb.BuildpackInfo{ 524 ID: "test-id", 525 Name: "test-name", 526 Version: "test-version", 527 Homepage: "test-homepage", 528 } 529 530 buildpack.Path = t.TempDir() 531 file := filepath.Join(buildpack.Path, "bin") 532 Expect(os.MkdirAll(file, 0755)).To(Succeed()) 533 534 file = filepath.Join(file, "helper") 535 Expect(os.WriteFile(file, []byte{}, 0755)).To(Succeed()) 536 537 hlc = libpak.HelperLayerContributor{ 538 Path: file, 539 BuildpackInfo: buildpack.Info, 540 Logger: bard.NewLogger(bytes.NewBuffer(nil)), 541 Names: []string{"test-name-1", "test-name-2"}, 542 } 543 }) 544 545 it.After(func() { 546 Expect(os.RemoveAll(buildpack.Path)).To(Succeed()) 547 }) 548 549 it("calls function with no existing metadata", func() { 550 _, err := hlc.Contribute(layer) 551 Expect(err).NotTo(HaveOccurred()) 552 553 Expect(filepath.Join(layer.Exec.FilePath("test-name-1"))).To(BeAnExistingFile()) 554 }) 555 556 it("calls function with non-matching metadata", func() { 557 layer.Metadata["alpha"] = "other-alpha" 558 559 _, err := hlc.Contribute(layer) 560 Expect(err).NotTo(HaveOccurred()) 561 562 file := filepath.Join(layer.Exec.FilePath("test-name-1")) 563 Expect(file).To(BeAnExistingFile()) 564 Expect(os.Readlink(file)).To(Equal(filepath.Join(layer.Path, "helper"))) 565 566 file = filepath.Join(layer.Exec.FilePath("test-name-2")) 567 Expect(file).To(BeAnExistingFile()) 568 Expect(os.Readlink(file)).To(Equal(filepath.Join(layer.Path, "helper"))) 569 }) 570 571 it("does not call function with matching metadata", func() { 572 buildpackInfo := map[string]interface{}{ 573 "id": buildpack.Info.ID, 574 "name": buildpack.Info.Name, 575 "version": buildpack.Info.Version, 576 "homepage": buildpack.Info.Homepage, 577 "clear-env": buildpack.Info.ClearEnvironment, 578 "description": "", 579 } 580 layer.Metadata["buildpackInfo"] = buildpackInfo 581 layer.Metadata["helperNames"] = []interface{}{hlc.Names[0], hlc.Names[1]} 582 583 _, err := hlc.Contribute(layer) 584 585 Expect(err).NotTo(HaveOccurred()) 586 587 Expect(filepath.Join(layer.Exec.FilePath("test-name-1"))).NotTo(BeAnExistingFile()) 588 Expect(filepath.Join(layer.Exec.FilePath("test-name-2"))).NotTo(BeAnExistingFile()) 589 }) 590 591 it("adds expected metadata to layer", func() { 592 layer, err := hlc.Contribute(layer) 593 Expect(err).NotTo(HaveOccurred()) 594 595 buildpackInfo := map[string]interface{}{ 596 "id": buildpack.Info.ID, 597 "name": buildpack.Info.Name, 598 "version": buildpack.Info.Version, 599 "homepage": buildpack.Info.Homepage, 600 "clear-env": buildpack.Info.ClearEnvironment, 601 "description": "", 602 } 603 Expect(layer.Metadata).To(Equal(map[string]interface{}{"buildpackInfo": buildpackInfo, "helperNames": []interface{}{hlc.Names[0], hlc.Names[1]}})) 604 }) 605 606 it("sets layer flags regardless of caching behavior (required for 0.6 API)", func() { 607 buildpackInfo := map[string]interface{}{ 608 "id": buildpack.Info.ID, 609 "name": buildpack.Info.Name, 610 "version": buildpack.Info.Version, 611 "homepage": buildpack.Info.Homepage, 612 "clear-env": buildpack.Info.ClearEnvironment, 613 "description": "", 614 } 615 layer.Metadata["buildpackInfo"] = buildpackInfo 616 layer.Metadata["helperNames"] = []interface{}{hlc.Names[0], hlc.Names[1]} 617 618 // Launch is the only one set & always true 619 620 layer, err := hlc.Contribute(layer) 621 Expect(err).NotTo(HaveOccurred()) 622 623 Expect(filepath.Join(layer.Exec.FilePath("test-name-1"))).NotTo(BeAnExistingFile()) 624 Expect(filepath.Join(layer.Exec.FilePath("test-name-2"))).NotTo(BeAnExistingFile()) 625 626 Expect(layer.LayerTypes.Launch).To(BeTrue()) 627 Expect(layer.LayerTypes.Cache).To(BeFalse()) 628 Expect(layer.LayerTypes.Build).To(BeFalse()) 629 }) 630 631 it("adds expected Syft SBOM file", func() { 632 layer.Metadata = map[string]interface{}{} 633 634 _, err := hlc.Contribute(layer) 635 Expect(err).NotTo(HaveOccurred()) 636 637 Expect(filepath.Join(layer.Exec.FilePath("test-name-1"))).To(BeAnExistingFile()) 638 Expect(filepath.Join(layer.Exec.FilePath("test-name-2"))).To(BeAnExistingFile()) 639 640 outputFile := layer.SBOMPath(libcnb.SyftJSON) 641 Expect(outputFile).To(BeARegularFile()) 642 643 data, err := os.ReadFile(outputFile) 644 Expect(err).ToNot(HaveOccurred()) 645 Expect(string(data)).To(ContainSubstring(`"Artifacts":[`)) 646 Expect(string(data)).To(ContainSubstring(`"FoundBy":"libpak",`)) 647 Expect(string(data)).To(ContainSubstring(`"PURL":"pkg:generic/test-id@test-version"`)) 648 Expect(string(data)).To(ContainSubstring(`"CPEs":["cpe:2.3:a:test-id:test-name-1:test-version:*:*:*:*:*:*:*","cpe:2.3:a:test-id:test-name-2:test-version:*:*:*:*:*:*:*"]`)) 649 Expect(string(data)).To(ContainSubstring(`"Schema":{`)) 650 Expect(string(data)).To(ContainSubstring(`"Descriptor":{`)) 651 Expect(string(data)).To(ContainSubstring(`"Source":{`)) 652 }) 653 }) 654 }