github.com/buildpacks/pack@v0.33.3-0.20240516162812-884dd1837311/internal/builder/writer/toml_test.go (about) 1 package writer_test 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "testing" 8 9 "github.com/pelletier/go-toml" 10 11 "github.com/Masterminds/semver" 12 "github.com/buildpacks/lifecycle/api" 13 "github.com/heroku/color" 14 "github.com/sclevine/spec" 15 "github.com/sclevine/spec/report" 16 17 pubbldr "github.com/buildpacks/pack/builder" 18 "github.com/buildpacks/pack/internal/builder" 19 "github.com/buildpacks/pack/internal/builder/writer" 20 "github.com/buildpacks/pack/internal/config" 21 "github.com/buildpacks/pack/pkg/client" 22 "github.com/buildpacks/pack/pkg/dist" 23 "github.com/buildpacks/pack/pkg/logging" 24 h "github.com/buildpacks/pack/testhelpers" 25 ) 26 27 func TestTOML(t *testing.T) { 28 color.Disable(true) 29 defer color.Disable(false) 30 spec.Run(t, "Builder Writer", testTOML, spec.Parallel(), spec.Report(report.Terminal{})) 31 } 32 33 func testTOML(t *testing.T, when spec.G, it spec.S) { 34 const ( 35 expectedRemoteRunImages = ` [[remote_info.run_images]] 36 name = "first/local" 37 user_configured = true 38 39 [[remote_info.run_images]] 40 name = "second/local" 41 user_configured = true 42 43 [[remote_info.run_images]] 44 name = "some/run-image" 45 46 [[remote_info.run_images]] 47 name = "first/default" 48 49 [[remote_info.run_images]] 50 name = "second/default"` 51 52 expectedLocalRunImages = ` [[local_info.run_images]] 53 name = "first/local" 54 user_configured = true 55 56 [[local_info.run_images]] 57 name = "second/local" 58 user_configured = true 59 60 [[local_info.run_images]] 61 name = "some/run-image" 62 63 [[local_info.run_images]] 64 name = "first/local-default" 65 66 [[local_info.run_images]] 67 name = "second/local-default"` 68 69 expectedLocalBuildpacks = ` [[local_info.buildpacks]] 70 id = "test.top.nested" 71 version = "test.top.nested.version" 72 73 [[local_info.buildpacks]] 74 id = "test.nested" 75 homepage = "http://geocities.com/top-bp" 76 77 [[local_info.buildpacks]] 78 id = "test.bp.one" 79 version = "test.bp.one.version" 80 homepage = "http://geocities.com/cool-bp" 81 82 [[local_info.buildpacks]] 83 id = "test.bp.two" 84 version = "test.bp.two.version" 85 86 [[local_info.buildpacks]] 87 id = "test.bp.three" 88 version = "test.bp.three.version"` 89 90 expectedRemoteBuildpacks = ` [[remote_info.buildpacks]] 91 id = "test.top.nested" 92 version = "test.top.nested.version" 93 94 [[remote_info.buildpacks]] 95 id = "test.nested" 96 homepage = "http://geocities.com/top-bp" 97 98 [[remote_info.buildpacks]] 99 id = "test.bp.one" 100 version = "test.bp.one.version" 101 homepage = "http://geocities.com/cool-bp" 102 103 [[remote_info.buildpacks]] 104 id = "test.bp.two" 105 version = "test.bp.two.version" 106 107 [[remote_info.buildpacks]] 108 id = "test.bp.three" 109 version = "test.bp.three.version"` 110 111 expectedLocalDetectionOrder = ` [[local_info.detection_order]] 112 113 [[local_info.detection_order.buildpacks]] 114 id = "test.top.nested" 115 version = "test.top.nested.version" 116 117 [[local_info.detection_order.buildpacks.buildpacks]] 118 id = "test.nested" 119 homepage = "http://geocities.com/top-bp" 120 121 [[local_info.detection_order.buildpacks.buildpacks.buildpacks]] 122 id = "test.bp.one" 123 version = "test.bp.one.version" 124 homepage = "http://geocities.com/cool-bp" 125 optional = true 126 127 [[local_info.detection_order.buildpacks.buildpacks]] 128 id = "test.bp.three" 129 version = "test.bp.three.version" 130 optional = true 131 132 [[local_info.detection_order.buildpacks.buildpacks]] 133 id = "test.nested.two" 134 version = "test.nested.two.version" 135 136 [[local_info.detection_order.buildpacks.buildpacks.buildpacks]] 137 id = "test.bp.one" 138 version = "test.bp.one.version" 139 homepage = "http://geocities.com/cool-bp" 140 optional = true 141 cyclic = true 142 143 [[local_info.detection_order.buildpacks]] 144 id = "test.bp.two" 145 version = "test.bp.two.version" 146 optional = true 147 148 [[local_info.detection_order]] 149 id = "test.bp.three" 150 version = "test.bp.three.version"` 151 152 expectedRemoteDetectionOrder = ` [[remote_info.detection_order]] 153 154 [[remote_info.detection_order.buildpacks]] 155 id = "test.top.nested" 156 version = "test.top.nested.version" 157 158 [[remote_info.detection_order.buildpacks.buildpacks]] 159 id = "test.nested" 160 homepage = "http://geocities.com/top-bp" 161 162 [[remote_info.detection_order.buildpacks.buildpacks.buildpacks]] 163 id = "test.bp.one" 164 version = "test.bp.one.version" 165 homepage = "http://geocities.com/cool-bp" 166 optional = true 167 168 [[remote_info.detection_order.buildpacks.buildpacks]] 169 id = "test.bp.three" 170 version = "test.bp.three.version" 171 optional = true 172 173 [[remote_info.detection_order.buildpacks.buildpacks]] 174 id = "test.nested.two" 175 version = "test.nested.two.version" 176 177 [[remote_info.detection_order.buildpacks.buildpacks.buildpacks]] 178 id = "test.bp.one" 179 version = "test.bp.one.version" 180 homepage = "http://geocities.com/cool-bp" 181 optional = true 182 cyclic = true 183 184 [[remote_info.detection_order.buildpacks]] 185 id = "test.bp.two" 186 version = "test.bp.two.version" 187 optional = true 188 189 [[remote_info.detection_order]] 190 id = "test.bp.three" 191 version = "test.bp.three.version"` 192 193 stackWithMixins = ` [stack] 194 id = "test.stack.id" 195 mixins = ["mixin1", "mixin2", "build:mixin3", "build:mixin4"]` 196 ) 197 198 var ( 199 assert = h.NewAssertionManager(t) 200 outBuf bytes.Buffer 201 202 remoteInfo *client.BuilderInfo 203 localInfo *client.BuilderInfo 204 205 expectedRemoteInfo = fmt.Sprintf(`[remote_info] 206 description = "Some remote description" 207 208 [remote_info.created_by] 209 Name = "Pack CLI" 210 Version = "1.2.3" 211 212 [remote_info.stack] 213 id = "test.stack.id" 214 215 [remote_info.lifecycle] 216 version = "6.7.8" 217 218 [remote_info.lifecycle.buildpack_apis] 219 deprecated = [] 220 supported = ["1.2", "2.3"] 221 222 [remote_info.lifecycle.platform_apis] 223 deprecated = ["0.1", "1.2"] 224 supported = ["4.5"] 225 226 %s 227 228 %s 229 230 %s`, expectedRemoteRunImages, expectedRemoteBuildpacks, expectedRemoteDetectionOrder) 231 232 expectedLocalInfo = fmt.Sprintf(`[local_info] 233 description = "Some local description" 234 235 [local_info.created_by] 236 Name = "Pack CLI" 237 Version = "4.5.6" 238 239 [local_info.stack] 240 id = "test.stack.id" 241 242 [local_info.lifecycle] 243 version = "4.5.6" 244 245 [local_info.lifecycle.buildpack_apis] 246 deprecated = ["4.5", "6.7"] 247 supported = ["8.9", "10.11"] 248 249 [local_info.lifecycle.platform_apis] 250 deprecated = [] 251 supported = ["7.8"] 252 253 %s 254 255 %s 256 257 %s`, expectedLocalRunImages, expectedLocalBuildpacks, expectedLocalDetectionOrder) 258 259 expectedPrettifiedTOML = fmt.Sprintf(`builder_name = "test-builder" 260 trusted = false 261 default = false 262 263 %s 264 265 %s`, expectedRemoteInfo, expectedLocalInfo) 266 ) 267 268 when("Print", func() { 269 it.Before(func() { 270 remoteInfo = &client.BuilderInfo{ 271 Description: "Some remote description", 272 Stack: "test.stack.id", 273 Mixins: []string{"mixin1", "mixin2", "build:mixin3", "build:mixin4"}, 274 RunImages: []pubbldr.RunImageConfig{{Image: "some/run-image", Mirrors: []string{"first/default", "second/default"}}}, 275 Buildpacks: buildpacks, 276 Order: order, 277 BuildpackLayers: dist.ModuleLayers{}, 278 Lifecycle: builder.LifecycleDescriptor{ 279 Info: builder.LifecycleInfo{ 280 Version: &builder.Version{ 281 Version: *semver.MustParse("6.7.8"), 282 }, 283 }, 284 APIs: builder.LifecycleAPIs{ 285 Buildpack: builder.APIVersions{ 286 Deprecated: nil, 287 Supported: builder.APISet{api.MustParse("1.2"), api.MustParse("2.3")}, 288 }, 289 Platform: builder.APIVersions{ 290 Deprecated: builder.APISet{api.MustParse("0.1"), api.MustParse("1.2")}, 291 Supported: builder.APISet{api.MustParse("4.5")}, 292 }, 293 }, 294 }, 295 CreatedBy: builder.CreatorMetadata{ 296 Name: "Pack CLI", 297 Version: "1.2.3", 298 }, 299 } 300 301 localInfo = &client.BuilderInfo{ 302 Description: "Some local description", 303 Stack: "test.stack.id", 304 Mixins: []string{"mixin1", "mixin2", "build:mixin3", "build:mixin4"}, 305 RunImages: []pubbldr.RunImageConfig{{Image: "some/run-image", Mirrors: []string{"first/local-default", "second/local-default"}}}, 306 Buildpacks: buildpacks, 307 Order: order, 308 BuildpackLayers: dist.ModuleLayers{}, 309 Lifecycle: builder.LifecycleDescriptor{ 310 Info: builder.LifecycleInfo{ 311 Version: &builder.Version{ 312 Version: *semver.MustParse("4.5.6"), 313 }, 314 }, 315 APIs: builder.LifecycleAPIs{ 316 Buildpack: builder.APIVersions{ 317 Deprecated: builder.APISet{api.MustParse("4.5"), api.MustParse("6.7")}, 318 Supported: builder.APISet{api.MustParse("8.9"), api.MustParse("10.11")}, 319 }, 320 Platform: builder.APIVersions{ 321 Deprecated: nil, 322 Supported: builder.APISet{api.MustParse("7.8")}, 323 }, 324 }, 325 }, 326 CreatedBy: builder.CreatorMetadata{ 327 Name: "Pack CLI", 328 Version: "4.5.6", 329 }, 330 } 331 }) 332 333 it("prints both local remote builders as valid TOML", func() { 334 tomlWriter := writer.NewTOML() 335 336 logger := logging.NewLogWithWriters(&outBuf, &outBuf) 337 err := tomlWriter.Print(logger, localRunImages, localInfo, remoteInfo, nil, nil, sharedBuilderInfo) 338 assert.Nil(err) 339 340 assert.Succeeds(validTOMLOutput(outBuf)) 341 assert.Nil(err) 342 343 assert.ContainsTOML(outBuf.String(), expectedPrettifiedTOML) 344 }) 345 346 when("builder doesn't exist locally or remotely", func() { 347 it("returns an error", func() { 348 tomlWriter := writer.NewTOML() 349 350 logger := logging.NewLogWithWriters(&outBuf, &outBuf) 351 err := tomlWriter.Print(logger, localRunImages, nil, nil, nil, nil, sharedBuilderInfo) 352 assert.ErrorWithMessage(err, "unable to find builder 'test-builder' locally or remotely") 353 }) 354 }) 355 356 when("builder doesn't exist locally", func() { 357 it("shows null for local builder, and normal output for remote", func() { 358 tomlWriter := writer.NewTOML() 359 360 logger := logging.NewLogWithWriters(&outBuf, &outBuf) 361 err := tomlWriter.Print(logger, localRunImages, nil, remoteInfo, nil, nil, sharedBuilderInfo) 362 assert.Nil(err) 363 364 assert.Succeeds(validTOMLOutput(outBuf)) 365 assert.Nil(err) 366 367 assert.NotContains(outBuf.String(), "local_info") 368 assert.ContainsTOML(outBuf.String(), expectedRemoteInfo) 369 }) 370 }) 371 372 when("builder doesn't exist remotely", func() { 373 it("shows null for remote builder, and normal output for local", func() { 374 tomlWriter := writer.NewTOML() 375 376 logger := logging.NewLogWithWriters(&outBuf, &outBuf) 377 err := tomlWriter.Print(logger, localRunImages, localInfo, nil, nil, nil, sharedBuilderInfo) 378 assert.Nil(err) 379 380 assert.Succeeds(validTOMLOutput(outBuf)) 381 assert.Nil(err) 382 383 assert.NotContains(outBuf.String(), "remote_info") 384 assert.ContainsTOML(outBuf.String(), expectedLocalInfo) 385 }) 386 }) 387 388 when("localErr is an error", func() { 389 it("returns the error, and doesn't write any toml output", func() { 390 expectedErr := errors.New("failed to retrieve local info") 391 392 tomlWriter := writer.NewTOML() 393 394 logger := logging.NewLogWithWriters(&outBuf, &outBuf) 395 err := tomlWriter.Print(logger, localRunImages, localInfo, remoteInfo, expectedErr, nil, sharedBuilderInfo) 396 assert.ErrorWithMessage(err, "preparing output for 'test-builder': failed to retrieve local info") 397 398 assert.Equal(outBuf.String(), "") 399 }) 400 }) 401 402 when("remoteErr is an error", func() { 403 it("returns the error, and doesn't write any toml output", func() { 404 expectedErr := errors.New("failed to retrieve remote info") 405 406 tomlWriter := writer.NewTOML() 407 408 logger := logging.NewLogWithWriters(&outBuf, &outBuf) 409 err := tomlWriter.Print(logger, localRunImages, localInfo, remoteInfo, nil, expectedErr, sharedBuilderInfo) 410 assert.ErrorWithMessage(err, "preparing output for 'test-builder': failed to retrieve remote info") 411 412 assert.Equal(outBuf.String(), "") 413 }) 414 }) 415 416 when("logger is verbose", func() { 417 it("displays mixins associated with the stack", func() { 418 tomlWriter := writer.NewTOML() 419 420 logger := logging.NewLogWithWriters(&outBuf, &outBuf, logging.WithVerbose()) 421 err := tomlWriter.Print(logger, localRunImages, localInfo, remoteInfo, nil, nil, sharedBuilderInfo) 422 assert.Nil(err) 423 424 assert.Succeeds(validTOMLOutput(outBuf)) 425 assert.ContainsTOML(outBuf.String(), stackWithMixins) 426 }) 427 }) 428 429 when("no run images are specified", func() { 430 it("omits run images from output", func() { 431 localInfo.RunImages = []pubbldr.RunImageConfig{} 432 remoteInfo.RunImages = []pubbldr.RunImageConfig{} 433 emptyLocalRunImages := []config.RunImage{} 434 435 tomlWriter := writer.NewTOML() 436 437 logger := logging.NewLogWithWriters(&outBuf, &outBuf, logging.WithVerbose()) 438 err := tomlWriter.Print(logger, emptyLocalRunImages, localInfo, remoteInfo, nil, nil, sharedBuilderInfo) 439 assert.Nil(err) 440 441 assert.Succeeds(validTOMLOutput(outBuf)) 442 443 assert.NotContains(outBuf.String(), "run_images") 444 }) 445 }) 446 447 when("no buildpacks are specified", func() { 448 it("omits buildpacks from output", func() { 449 localInfo.Buildpacks = []dist.ModuleInfo{} 450 remoteInfo.Buildpacks = []dist.ModuleInfo{} 451 452 tomlWriter := writer.NewTOML() 453 454 logger := logging.NewLogWithWriters(&outBuf, &outBuf, logging.WithVerbose()) 455 err := tomlWriter.Print(logger, localRunImages, localInfo, remoteInfo, nil, nil, sharedBuilderInfo) 456 assert.Nil(err) 457 458 assert.Succeeds(validTOMLOutput(outBuf)) 459 460 assert.NotContains(outBuf.String(), "local_info.buildpacks") 461 assert.NotContains(outBuf.String(), "remote_info.buildpacks") 462 }) 463 }) 464 465 when("no detection order is specified", func() { 466 it("omits dection order in output", func() { 467 localInfo.Order = pubbldr.DetectionOrder{} 468 remoteInfo.Order = pubbldr.DetectionOrder{} 469 470 tomlWriter := writer.NewTOML() 471 472 logger := logging.NewLogWithWriters(&outBuf, &outBuf, logging.WithVerbose()) 473 err := tomlWriter.Print(logger, localRunImages, localInfo, remoteInfo, nil, nil, sharedBuilderInfo) 474 assert.Nil(err) 475 476 assert.Succeeds(validTOMLOutput(outBuf)) 477 assert.NotContains(outBuf.String(), "detection_order") 478 }) 479 }) 480 }) 481 } 482 483 func validTOMLOutput(source bytes.Buffer) error { 484 err := toml.NewDecoder(&source).Decode(&struct{}{}) 485 if err != nil { 486 return fmt.Errorf("failed to unmarshal to toml: %w", err) 487 } 488 return nil 489 }