github.com/buildpacks/pack@v0.33.3-0.20240516162812-884dd1837311/internal/builder/writer/yaml_test.go (about)

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