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

     1  package writer_test
     2  
     3  import (
     4  	"bytes"
     5  	"testing"
     6  
     7  	"github.com/buildpacks/lifecycle/buildpack"
     8  	"github.com/buildpacks/lifecycle/launch"
     9  	"github.com/buildpacks/lifecycle/platform/files"
    10  	"github.com/heroku/color"
    11  	"github.com/sclevine/spec"
    12  	"github.com/sclevine/spec/report"
    13  
    14  	"github.com/buildpacks/pack/internal/config"
    15  	"github.com/buildpacks/pack/internal/inspectimage"
    16  	"github.com/buildpacks/pack/internal/inspectimage/writer"
    17  	"github.com/buildpacks/pack/pkg/client"
    18  	"github.com/buildpacks/pack/pkg/logging"
    19  	h "github.com/buildpacks/pack/testhelpers"
    20  )
    21  
    22  func TestJSON(t *testing.T) {
    23  	color.Disable(true)
    24  	defer color.Disable(false)
    25  	spec.Run(t, "JSON Writer", testJSON, spec.Parallel(), spec.Report(report.Terminal{}))
    26  }
    27  
    28  func testJSON(t *testing.T, when spec.G, it spec.S) {
    29  	var (
    30  		assert = h.NewAssertionManager(t)
    31  		outBuf bytes.Buffer
    32  
    33  		remoteInfo            *client.ImageInfo
    34  		remoteInfoNoRebasable *client.ImageInfo
    35  		localInfo             *client.ImageInfo
    36  		localInfoNoRebasable  *client.ImageInfo
    37  
    38  		expectedLocalOutput = `{
    39    "local_info": {
    40      "stack": "test.stack.id.local",
    41      "rebasable": true,
    42      "base_image": {
    43        "top_layer": "some-local-top-layer",
    44        "reference": "some-local-run-image-reference"
    45      },
    46      "run_images": [
    47        {
    48          "name": "user-configured-mirror-for-local",
    49          "user_configured": true
    50        },
    51        {
    52          "name": "some-local-run-image"
    53        },
    54        {
    55          "name": "some-local-mirror"
    56        },
    57        {
    58          "name": "other-local-mirror"
    59        }
    60      ],
    61      "buildpacks": [
    62        {
    63          "homepage": "https://some-homepage-one",
    64          "id": "test.bp.one.local",
    65          "version": "1.0.0"
    66        },
    67        {
    68          "homepage": "https://some-homepage-two",
    69          "id": "test.bp.two.local",
    70          "version": "2.0.0"
    71        }
    72      ],
    73  	"extensions": null,
    74      "processes": [
    75        {
    76          "type": "some-local-type",
    77          "shell": "bash",
    78          "command": "/some/local command",
    79          "default": true,
    80          "args": [
    81            "some",
    82            "local",
    83            "args"
    84          ],
    85  		"working-dir": "/some-test-work-dir"
    86        },
    87        {
    88          "type": "other-local-type",
    89          "shell": "",
    90          "command": "/other/local/command",
    91          "default": false,
    92          "args": [
    93            "other",
    94            "local",
    95            "args"
    96          ],
    97  		"working-dir": "/other-test-work-dir"
    98        }
    99      ]
   100    }
   101  }`
   102  		expectedLocalNoRebasableOutput = `{
   103    "local_info": {
   104      "stack": "test.stack.id.local",
   105      "rebasable": false,
   106      "base_image": {
   107        "top_layer": "some-local-top-layer",
   108        "reference": "some-local-run-image-reference"
   109      },
   110      "run_images": [
   111        {
   112          "name": "user-configured-mirror-for-local",
   113          "user_configured": true
   114        },
   115        {
   116          "name": "some-local-run-image"
   117        },
   118        {
   119          "name": "some-local-mirror"
   120        },
   121        {
   122          "name": "other-local-mirror"
   123        }
   124      ],
   125      "buildpacks": [
   126        {
   127          "homepage": "https://some-homepage-one",
   128          "id": "test.bp.one.local",
   129          "version": "1.0.0"
   130        },
   131        {
   132          "homepage": "https://some-homepage-two",
   133          "id": "test.bp.two.local",
   134          "version": "2.0.0"
   135        }
   136      ],
   137  	"extensions": null,
   138      "processes": [
   139        {
   140          "type": "some-local-type",
   141          "shell": "bash",
   142          "command": "/some/local command",
   143          "default": true,
   144          "args": [
   145            "some",
   146            "local",
   147            "args"
   148          ],
   149  		"working-dir": "/some-test-work-dir"
   150        },
   151        {
   152          "type": "other-local-type",
   153          "shell": "",
   154          "command": "/other/local/command",
   155          "default": false,
   156          "args": [
   157            "other",
   158            "local",
   159            "args"
   160          ],
   161  		"working-dir": "/other-test-work-dir"
   162        }
   163      ]
   164    }
   165  }`
   166  		expectedRemoteOutput = `{  
   167    "remote_info": {
   168      "stack": "test.stack.id.remote",
   169      "rebasable": true,
   170      "base_image": {
   171        "top_layer": "some-remote-top-layer",
   172        "reference": "some-remote-run-image-reference"
   173      },
   174      "run_images": [
   175        {
   176          "name": "user-configured-mirror-for-remote",
   177          "user_configured": true
   178        },
   179        {
   180          "name": "some-remote-run-image"
   181        },
   182        {
   183          "name": "some-remote-mirror"
   184        },
   185        {
   186          "name": "other-remote-mirror"
   187        }
   188      ],
   189      "buildpacks": [
   190        {
   191          "id": "test.bp.one.remote",
   192          "version": "1.0.0",
   193          "homepage": "https://some-homepage-one"
   194        },
   195        {
   196          "id": "test.bp.two.remote",
   197          "version": "2.0.0",
   198          "homepage": "https://some-homepage-two"
   199        }
   200      ],
   201  	"extensions": null,
   202      "processes": [
   203        {
   204          "type": "some-remote-type",
   205          "shell": "bash",
   206          "command": "/some/remote command",
   207          "default": true,
   208          "args": [
   209            "some",
   210            "remote",
   211            "args"
   212          ],
   213  		"working-dir": "/some-test-work-dir"
   214        },
   215        {
   216          "type": "other-remote-type",
   217          "shell": "",
   218          "command": "/other/remote/command",
   219          "default": false,
   220          "args": [
   221            "other",
   222            "remote",
   223            "args"
   224          ],
   225  		"working-dir": "/other-test-work-dir"
   226        }
   227      ]
   228    }
   229  }`
   230  		expectedRemoteNoRebasableOutput = `{  
   231    "remote_info": {
   232      "stack": "test.stack.id.remote",
   233      "rebasable": false,
   234      "base_image": {
   235        "top_layer": "some-remote-top-layer",
   236        "reference": "some-remote-run-image-reference"
   237      },
   238      "run_images": [
   239        {
   240          "name": "user-configured-mirror-for-remote",
   241          "user_configured": true
   242        },
   243        {
   244          "name": "some-remote-run-image"
   245        },
   246        {
   247          "name": "some-remote-mirror"
   248        },
   249        {
   250          "name": "other-remote-mirror"
   251        }
   252      ],
   253      "buildpacks": [
   254        {
   255          "id": "test.bp.one.remote",
   256          "version": "1.0.0",
   257          "homepage": "https://some-homepage-one"
   258        },
   259        {
   260          "id": "test.bp.two.remote",
   261          "version": "2.0.0",
   262          "homepage": "https://some-homepage-two"
   263        }
   264      ],
   265  	"extensions": null,
   266      "processes": [
   267        {
   268          "type": "some-remote-type",
   269          "shell": "bash",
   270          "command": "/some/remote command",
   271          "default": true,
   272          "args": [
   273            "some",
   274            "remote",
   275            "args"
   276          ],
   277  		"working-dir": "/some-test-work-dir"
   278        },
   279        {
   280          "type": "other-remote-type",
   281          "shell": "",
   282          "command": "/other/remote/command",
   283          "default": false,
   284          "args": [
   285            "other",
   286            "remote",
   287            "args"
   288          ],
   289  		"working-dir": "/other-test-work-dir"
   290        }
   291      ]
   292    }
   293  }`
   294  	)
   295  
   296  	when("Print", func() {
   297  		it.Before(func() {
   298  			type someData struct {
   299  				String string
   300  				Bool   bool
   301  				Int    int
   302  				Nested struct {
   303  					String string
   304  				}
   305  			}
   306  
   307  			remoteInfo = &client.ImageInfo{
   308  				StackID: "test.stack.id.remote",
   309  				Buildpacks: []buildpack.GroupElement{
   310  					{ID: "test.bp.one.remote", Version: "1.0.0", Homepage: "https://some-homepage-one"},
   311  					{ID: "test.bp.two.remote", Version: "2.0.0", Homepage: "https://some-homepage-two"},
   312  				},
   313  				Base: files.RunImageForRebase{
   314  					TopLayer:  "some-remote-top-layer",
   315  					Reference: "some-remote-run-image-reference",
   316  				},
   317  				Stack: files.Stack{
   318  					RunImage: files.RunImageForExport{
   319  						Image:   "some-remote-run-image",
   320  						Mirrors: []string{"some-remote-mirror", "other-remote-mirror"},
   321  					},
   322  				},
   323  				BOM: []buildpack.BOMEntry{{
   324  					Require: buildpack.Require{
   325  						Name:    "name-1",
   326  						Version: "version-1",
   327  						Metadata: map[string]interface{}{
   328  							"RemoteData": someData{
   329  								String: "aString",
   330  								Bool:   true,
   331  								Int:    123,
   332  								Nested: struct {
   333  									String string
   334  								}{
   335  									String: "anotherString",
   336  								},
   337  							},
   338  						},
   339  					},
   340  					Buildpack: buildpack.GroupElement{ID: "test.bp.one.remote", Version: "1.0.0", Homepage: "https://some-homepage-one"},
   341  				}},
   342  				Processes: client.ProcessDetails{
   343  					DefaultProcess: &launch.Process{
   344  						Type:             "some-remote-type",
   345  						Command:          launch.RawCommand{Entries: []string{"/some/remote command"}},
   346  						Args:             []string{"some", "remote", "args"},
   347  						Direct:           false,
   348  						WorkingDirectory: "/some-test-work-dir",
   349  					},
   350  					OtherProcesses: []launch.Process{
   351  						{
   352  							Type:             "other-remote-type",
   353  							Command:          launch.RawCommand{Entries: []string{"/other/remote/command"}},
   354  							Args:             []string{"other", "remote", "args"},
   355  							Direct:           true,
   356  							WorkingDirectory: "/other-test-work-dir",
   357  						},
   358  					},
   359  				},
   360  				Rebasable: true,
   361  			}
   362  			remoteInfoNoRebasable = &client.ImageInfo{
   363  				StackID: "test.stack.id.remote",
   364  				Buildpacks: []buildpack.GroupElement{
   365  					{ID: "test.bp.one.remote", Version: "1.0.0", Homepage: "https://some-homepage-one"},
   366  					{ID: "test.bp.two.remote", Version: "2.0.0", Homepage: "https://some-homepage-two"},
   367  				},
   368  				Base: files.RunImageForRebase{
   369  					TopLayer:  "some-remote-top-layer",
   370  					Reference: "some-remote-run-image-reference",
   371  				},
   372  				Stack: files.Stack{
   373  					RunImage: files.RunImageForExport{
   374  						Image:   "some-remote-run-image",
   375  						Mirrors: []string{"some-remote-mirror", "other-remote-mirror"},
   376  					},
   377  				},
   378  				BOM: []buildpack.BOMEntry{{
   379  					Require: buildpack.Require{
   380  						Name:    "name-1",
   381  						Version: "version-1",
   382  						Metadata: map[string]interface{}{
   383  							"RemoteData": someData{
   384  								String: "aString",
   385  								Bool:   true,
   386  								Int:    123,
   387  								Nested: struct {
   388  									String string
   389  								}{
   390  									String: "anotherString",
   391  								},
   392  							},
   393  						},
   394  					},
   395  					Buildpack: buildpack.GroupElement{ID: "test.bp.one.remote", Version: "1.0.0", Homepage: "https://some-homepage-one"},
   396  				}},
   397  				Processes: client.ProcessDetails{
   398  					DefaultProcess: &launch.Process{
   399  						Type:             "some-remote-type",
   400  						Command:          launch.RawCommand{Entries: []string{"/some/remote command"}},
   401  						Args:             []string{"some", "remote", "args"},
   402  						Direct:           false,
   403  						WorkingDirectory: "/some-test-work-dir",
   404  					},
   405  					OtherProcesses: []launch.Process{
   406  						{
   407  							Type:             "other-remote-type",
   408  							Command:          launch.RawCommand{Entries: []string{"/other/remote/command"}},
   409  							Args:             []string{"other", "remote", "args"},
   410  							Direct:           true,
   411  							WorkingDirectory: "/other-test-work-dir",
   412  						},
   413  					},
   414  				},
   415  				Rebasable: false,
   416  			}
   417  
   418  			localInfo = &client.ImageInfo{
   419  				StackID: "test.stack.id.local",
   420  				Buildpacks: []buildpack.GroupElement{
   421  					{ID: "test.bp.one.local", Version: "1.0.0", Homepage: "https://some-homepage-one"},
   422  					{ID: "test.bp.two.local", Version: "2.0.0", Homepage: "https://some-homepage-two"},
   423  				},
   424  				Base: files.RunImageForRebase{
   425  					TopLayer:  "some-local-top-layer",
   426  					Reference: "some-local-run-image-reference",
   427  				},
   428  				Stack: files.Stack{
   429  					RunImage: files.RunImageForExport{
   430  						Image:   "some-local-run-image",
   431  						Mirrors: []string{"some-local-mirror", "other-local-mirror"},
   432  					},
   433  				},
   434  				BOM: []buildpack.BOMEntry{{
   435  					Require: buildpack.Require{
   436  						Name:    "name-1",
   437  						Version: "version-1",
   438  						Metadata: map[string]interface{}{
   439  							"LocalData": someData{
   440  								Bool: false,
   441  								Int:  456,
   442  							},
   443  						},
   444  					},
   445  					Buildpack: buildpack.GroupElement{ID: "test.bp.one.remote", Version: "1.0.0", Homepage: "https://some-homepage-one"},
   446  				}},
   447  				Processes: client.ProcessDetails{
   448  					DefaultProcess: &launch.Process{
   449  						Type:             "some-local-type",
   450  						Command:          launch.RawCommand{Entries: []string{"/some/local command"}},
   451  						Args:             []string{"some", "local", "args"},
   452  						Direct:           false,
   453  						WorkingDirectory: "/some-test-work-dir",
   454  					},
   455  					OtherProcesses: []launch.Process{
   456  						{
   457  							Type:             "other-local-type",
   458  							Command:          launch.RawCommand{Entries: []string{"/other/local/command"}},
   459  							Args:             []string{"other", "local", "args"},
   460  							Direct:           true,
   461  							WorkingDirectory: "/other-test-work-dir",
   462  						},
   463  					},
   464  				},
   465  				Rebasable: true,
   466  			}
   467  			localInfoNoRebasable = &client.ImageInfo{
   468  				StackID: "test.stack.id.local",
   469  				Buildpacks: []buildpack.GroupElement{
   470  					{ID: "test.bp.one.local", Version: "1.0.0", Homepage: "https://some-homepage-one"},
   471  					{ID: "test.bp.two.local", Version: "2.0.0", Homepage: "https://some-homepage-two"},
   472  				},
   473  				Base: files.RunImageForRebase{
   474  					TopLayer:  "some-local-top-layer",
   475  					Reference: "some-local-run-image-reference",
   476  				},
   477  				Stack: files.Stack{
   478  					RunImage: files.RunImageForExport{
   479  						Image:   "some-local-run-image",
   480  						Mirrors: []string{"some-local-mirror", "other-local-mirror"},
   481  					},
   482  				},
   483  				BOM: []buildpack.BOMEntry{{
   484  					Require: buildpack.Require{
   485  						Name:    "name-1",
   486  						Version: "version-1",
   487  						Metadata: map[string]interface{}{
   488  							"LocalData": someData{
   489  								Bool: false,
   490  								Int:  456,
   491  							},
   492  						},
   493  					},
   494  					Buildpack: buildpack.GroupElement{ID: "test.bp.one.remote", Version: "1.0.0", Homepage: "https://some-homepage-one"},
   495  				}},
   496  				Processes: client.ProcessDetails{
   497  					DefaultProcess: &launch.Process{
   498  						Type:             "some-local-type",
   499  						Command:          launch.RawCommand{Entries: []string{"/some/local command"}},
   500  						Args:             []string{"some", "local", "args"},
   501  						Direct:           false,
   502  						WorkingDirectory: "/some-test-work-dir",
   503  					},
   504  					OtherProcesses: []launch.Process{
   505  						{
   506  							Type:             "other-local-type",
   507  							Command:          launch.RawCommand{Entries: []string{"/other/local/command"}},
   508  							Args:             []string{"other", "local", "args"},
   509  							Direct:           true,
   510  							WorkingDirectory: "/other-test-work-dir",
   511  						},
   512  					},
   513  				},
   514  				Rebasable: false,
   515  			}
   516  
   517  			outBuf = bytes.Buffer{}
   518  		})
   519  
   520  		when("local and remote image exits", func() {
   521  			it("prints both local and remote image info in a JSON format", func() {
   522  				runImageMirrors := []config.RunImage{
   523  					{
   524  						Image:   "un-used-run-image",
   525  						Mirrors: []string{"un-used"},
   526  					},
   527  					{
   528  						Image:   "some-local-run-image",
   529  						Mirrors: []string{"user-configured-mirror-for-local"},
   530  					},
   531  					{
   532  						Image:   "some-remote-run-image",
   533  						Mirrors: []string{"user-configured-mirror-for-remote"},
   534  					},
   535  				}
   536  				sharedImageInfo := inspectimage.GeneralInfo{
   537  					Name:            "test-image",
   538  					RunImageMirrors: runImageMirrors,
   539  				}
   540  				jsonWriter := writer.NewJSON()
   541  
   542  				logger := logging.NewLogWithWriters(&outBuf, &outBuf)
   543  				err := jsonWriter.Print(logger, sharedImageInfo, localInfo, remoteInfo, nil, nil)
   544  				assert.Nil(err)
   545  
   546  				assert.ContainsJSON(outBuf.String(), `{ "image_name": "test-image" }`)
   547  				assert.ContainsJSON(outBuf.String(), expectedLocalOutput)
   548  				assert.ContainsJSON(outBuf.String(), expectedRemoteOutput)
   549  			})
   550  			it("prints both local and remote no rebasable images info in a JSON format", func() {
   551  				runImageMirrors := []config.RunImage{
   552  					{
   553  						Image:   "un-used-run-image",
   554  						Mirrors: []string{"un-used"},
   555  					},
   556  					{
   557  						Image:   "some-local-run-image",
   558  						Mirrors: []string{"user-configured-mirror-for-local"},
   559  					},
   560  					{
   561  						Image:   "some-remote-run-image",
   562  						Mirrors: []string{"user-configured-mirror-for-remote"},
   563  					},
   564  				}
   565  				sharedImageInfo := inspectimage.GeneralInfo{
   566  					Name:            "test-image",
   567  					RunImageMirrors: runImageMirrors,
   568  				}
   569  				jsonWriter := writer.NewJSON()
   570  
   571  				logger := logging.NewLogWithWriters(&outBuf, &outBuf)
   572  				err := jsonWriter.Print(logger, sharedImageInfo, localInfoNoRebasable, remoteInfoNoRebasable, nil, nil)
   573  				assert.Nil(err)
   574  
   575  				assert.ContainsJSON(outBuf.String(), `{ "image_name": "test-image" }`)
   576  				assert.ContainsJSON(outBuf.String(), expectedLocalNoRebasableOutput)
   577  				assert.ContainsJSON(outBuf.String(), expectedRemoteNoRebasableOutput)
   578  			})
   579  		})
   580  
   581  		when("only local image exists", func() {
   582  			it("prints local image info in JSON format", func() {
   583  				runImageMirrors := []config.RunImage{
   584  					{
   585  						Image:   "un-used-run-image",
   586  						Mirrors: []string{"un-used"},
   587  					},
   588  					{
   589  						Image:   "some-local-run-image",
   590  						Mirrors: []string{"user-configured-mirror-for-local"},
   591  					},
   592  					{
   593  						Image:   "some-remote-run-image",
   594  						Mirrors: []string{"user-configured-mirror-for-remote"},
   595  					},
   596  				}
   597  				sharedImageInfo := inspectimage.GeneralInfo{
   598  					Name:            "test-image",
   599  					RunImageMirrors: runImageMirrors,
   600  				}
   601  				jsonWriter := writer.NewJSON()
   602  
   603  				logger := logging.NewLogWithWriters(&outBuf, &outBuf)
   604  				err := jsonWriter.Print(logger, sharedImageInfo, localInfo, nil, nil, nil)
   605  				assert.Nil(err)
   606  
   607  				assert.ContainsJSON(outBuf.String(), `{ "image_name": "test-image" }`)
   608  				assert.ContainsJSON(outBuf.String(), expectedLocalOutput)
   609  
   610  				assert.NotContains(outBuf.String(), "test.stack.id.remote")
   611  				assert.ContainsJSON(outBuf.String(), expectedLocalOutput)
   612  			})
   613  		})
   614  
   615  		when("only remote image exists", func() {
   616  			it("prints remote image info in JSON format", func() {
   617  				runImageMirrors := []config.RunImage{
   618  					{
   619  						Image:   "un-used-run-image",
   620  						Mirrors: []string{"un-used"},
   621  					},
   622  					{
   623  						Image:   "some-local-run-image",
   624  						Mirrors: []string{"user-configured-mirror-for-local"},
   625  					},
   626  					{
   627  						Image:   "some-remote-run-image",
   628  						Mirrors: []string{"user-configured-mirror-for-remote"},
   629  					},
   630  				}
   631  				sharedImageInfo := inspectimage.GeneralInfo{
   632  					Name:            "test-image",
   633  					RunImageMirrors: runImageMirrors,
   634  				}
   635  				jsonWriter := writer.NewJSON()
   636  
   637  				logger := logging.NewLogWithWriters(&outBuf, &outBuf)
   638  				err := jsonWriter.Print(logger, sharedImageInfo, nil, remoteInfo, nil, nil)
   639  				assert.Nil(err)
   640  
   641  				assert.ContainsJSON(outBuf.String(), `{ "image_name": "test-image" }`)
   642  				assert.NotContains(outBuf.String(), "test.stack.id.local")
   643  				assert.ContainsJSON(outBuf.String(), expectedRemoteOutput)
   644  			})
   645  		})
   646  	})
   647  }