github.com/SAP/jenkins-library@v1.362.0/cmd/cnbBuild_test.go (about)

     1  //go:build unit
     2  // +build unit
     3  
     4  package cmd
     5  
     6  import (
     7  	"fmt"
     8  	"net/http"
     9  	"path/filepath"
    10  	"testing"
    11  
    12  	"github.com/SAP/jenkins-library/pkg/cnbutils"
    13  	piperconf "github.com/SAP/jenkins-library/pkg/config"
    14  	piperhttp "github.com/SAP/jenkins-library/pkg/http"
    15  	"github.com/SAP/jenkins-library/pkg/mock"
    16  	"github.com/SAP/jenkins-library/pkg/telemetry"
    17  	v1 "github.com/google/go-containerregistry/pkg/v1"
    18  	"github.com/google/go-containerregistry/pkg/v1/fake"
    19  	"github.com/jarcoal/httpmock"
    20  	"github.com/stretchr/testify/assert"
    21  	"github.com/stretchr/testify/require"
    22  )
    23  
    24  const imageRegistry = "some-registry"
    25  
    26  func newCnbBuildTestsUtils() cnbutils.MockUtils {
    27  	imageStub := func(imageRef, target string) (v1.Image, error) {
    28  		fakeImage := &fake.FakeImage{}
    29  		var imageConfig v1.Config
    30  		switch imageRef {
    31  		case "pre-test":
    32  			imageConfig = v1.Config{
    33  				Labels: map[string]string{
    34  					"io.buildpacks.buildpackage.metadata": "{\"id\": \"pre-testbuildpack\", \"version\": \"0.0.1\"}",
    35  				},
    36  			}
    37  		case "post-test":
    38  			imageConfig = v1.Config{
    39  				Labels: map[string]string{
    40  					"io.buildpacks.buildpackage.metadata": "{\"id\": \"post-testbuildpack\", \"version\": \"0.0.1\"}",
    41  				},
    42  			}
    43  		default:
    44  			imageConfig = v1.Config{
    45  				Labels: map[string]string{
    46  					"io.buildpacks.buildpackage.metadata": "{\"id\": \"testbuildpack\", \"version\": \"0.0.1\"}",
    47  				},
    48  			}
    49  		}
    50  
    51  		fakeImage.ConfigFileReturns(&v1.ConfigFile{
    52  			Config: imageConfig,
    53  		}, nil)
    54  
    55  		return fakeImage, nil
    56  	}
    57  
    58  	utils := cnbutils.MockUtils{
    59  		ExecMockRunner: &mock.ExecMockRunner{},
    60  		FilesMock:      &mock.FilesMock{},
    61  		DownloadMock: &mock.DownloadMock{
    62  			ImageContentStub: imageStub,
    63  			ImageInfoStub: func(imageRef string) (v1.Image, error) {
    64  				return imageStub(imageRef, "")
    65  			},
    66  		},
    67  	}
    68  
    69  	utils.AddFile("/cnb/order.toml", []byte(`[[order]]
    70      [[order.group]]
    71        id = "buildpacks/java"
    72        version = "1.8.0"
    73  [[order]]
    74      [[order.group]]
    75        id = "buildpacks/nodejs"
    76        version = "1.6.0"`))
    77  	utils.AddFile("/layers/report.toml", []byte(`[build]
    78  [image]
    79  tags = ["localhost:5000/not-found:0.0.1"]
    80  digest = "sha256:52eac630560210e5ae13eb10797c4246d6f02d425f32b9430ca00bde697c79ec"
    81  manifest-size = 2388`))
    82  	return utils
    83  }
    84  
    85  func addBuilderFiles(utils *cnbutils.MockUtils) {
    86  	utils.FilesMock.AddFile(creatorPath, []byte(`xyz`))
    87  }
    88  
    89  func assertLifecycleCalls(t *testing.T, runner *mock.ExecMockRunner, callNo int) {
    90  	require.GreaterOrEqual(t, len(runner.Calls), callNo)
    91  	assert.Equal(t, creatorPath, runner.Calls[callNo-1].Exec)
    92  	for _, arg := range []string{"-no-color", "-buildpacks", "/cnb/buildpacks", "-order", "/cnb/order.toml", "-platform", "/tmp/platform"} {
    93  		assert.Contains(t, runner.Calls[callNo-1].Params, arg)
    94  	}
    95  }
    96  
    97  func assetBuildEnv(t *testing.T, utils cnbutils.MockUtils, key, value string) bool {
    98  	env, err := utils.FilesMock.ReadFile(filepath.Join("/tmp/platform/env/", key))
    99  	if !assert.NoError(t, err) {
   100  		return false
   101  	}
   102  	return assert.Equal(t, value, string(env))
   103  }
   104  
   105  func TestRunCnbBuild(t *testing.T) {
   106  	configOptions.OpenFile = piperconf.OpenPiperFile
   107  
   108  	t.Setenv("CNB_USER_ID", "1000")
   109  	t.Setenv("CNB_GROUP_ID", "1000")
   110  
   111  	t.Run("prefers direct configuration", func(t *testing.T) {
   112  		t.Parallel()
   113  		commonPipelineEnvironment := cnbBuildCommonPipelineEnvironment{}
   114  		config := cnbBuildOptions{
   115  			ContainerImageName:   "my-image",
   116  			ContainerImageTag:    "0.0.1",
   117  			ContainerRegistryURL: fmt.Sprintf("https://%s", imageRegistry),
   118  			DockerConfigJSON:     "/path/to/config.json",
   119  			RunImage:             "my-run-image",
   120  			DefaultProcess:       "my-process",
   121  		}
   122  
   123  		projectToml := `[project]
   124  		id = "io.buildpacks.my-app"
   125  		`
   126  
   127  		utils := newCnbBuildTestsUtils()
   128  		utils.FilesMock.AddFile(config.DockerConfigJSON, []byte(`{"auths":{"my-registry":{"auth":"dXNlcjpwYXNz"}}}`))
   129  		utils.FilesMock.AddFile("project.toml", []byte(projectToml))
   130  		addBuilderFiles(&utils)
   131  
   132  		err := callCnbBuild(&config, &telemetry.CustomData{}, &utils, &commonPipelineEnvironment, &piperhttp.Client{})
   133  
   134  		require.NoError(t, err)
   135  		runner := utils.ExecMockRunner
   136  		assert.Contains(t, runner.Env, "CNB_REGISTRY_AUTH={\"my-registry\":\"Basic dXNlcjpwYXNz\"}")
   137  		assertLifecycleCalls(t, runner, 2)
   138  		assert.Contains(t, runner.Calls[1].Params, fmt.Sprintf("%s/%s:%s", imageRegistry, config.ContainerImageName, config.ContainerImageTag))
   139  		assert.Contains(t, runner.Calls[1].Params, "-run-image")
   140  		assert.Contains(t, runner.Calls[1].Params, "my-run-image")
   141  		assert.Contains(t, runner.Calls[1].Params, "-process-type")
   142  		assert.Contains(t, runner.Calls[1].Params, "my-process")
   143  		assert.Equal(t, config.ContainerRegistryURL, commonPipelineEnvironment.container.registryURL)
   144  		assert.Equal(t, "my-image:0.0.1", commonPipelineEnvironment.container.imageNameTag)
   145  		assert.Equal(t, `{"cnbBuild":[{"dockerImage":"paketobuildpacks/builder-jammy-base:latest"}]}`, commonPipelineEnvironment.custom.buildSettingsInfo)
   146  	})
   147  
   148  	t.Run("prefers project descriptor", func(t *testing.T) {
   149  		t.Parallel()
   150  		commonPipelineEnvironment := cnbBuildCommonPipelineEnvironment{}
   151  		config := cnbBuildOptions{
   152  			ContainerImageTag:    "0.0.1",
   153  			ContainerRegistryURL: fmt.Sprintf("https://%s", imageRegistry),
   154  			DockerConfigJSON:     "/path/to/config.json",
   155  			ProjectDescriptor:    "project.toml",
   156  		}
   157  
   158  		projectToml := `[project]
   159  		id = "io.buildpacks.my-app"
   160  		`
   161  
   162  		utils := newCnbBuildTestsUtils()
   163  		utils.FilesMock.AddFile(config.DockerConfigJSON, []byte(`{"auths":{"my-registry":{"auth":"dXNlcjpwYXNz"}}}`))
   164  		utils.FilesMock.AddFile("project.toml", []byte(projectToml))
   165  		addBuilderFiles(&utils)
   166  
   167  		telemetryData := &telemetry.CustomData{}
   168  		err := callCnbBuild(&config, telemetryData, &utils, &commonPipelineEnvironment, &piperhttp.Client{})
   169  
   170  		require.NoError(t, err)
   171  		runner := utils.ExecMockRunner
   172  		assert.Contains(t, runner.Env, "CNB_REGISTRY_AUTH={\"my-registry\":\"Basic dXNlcjpwYXNz\"}")
   173  		assertLifecycleCalls(t, runner, 2)
   174  		assert.Contains(t, runner.Calls[1].Params, fmt.Sprintf("%s/%s:%s", imageRegistry, "io-buildpacks-my-app", config.ContainerImageTag))
   175  		assert.Equal(t, config.ContainerRegistryURL, commonPipelineEnvironment.container.registryURL)
   176  		assert.Equal(t, "io-buildpacks-my-app:0.0.1", commonPipelineEnvironment.container.imageNameTag)
   177  
   178  		assert.Equal(t, "sha256:52eac630560210e5ae13eb10797c4246d6f02d425f32b9430ca00bde697c79ec", commonPipelineEnvironment.container.imageDigest)
   179  		assert.Contains(t, commonPipelineEnvironment.container.imageDigests, "sha256:52eac630560210e5ae13eb10797c4246d6f02d425f32b9430ca00bde697c79ec")
   180  	})
   181  
   182  	t.Run("success case (registry with https)", func(t *testing.T) {
   183  		t.Parallel()
   184  		commonPipelineEnvironment := cnbBuildCommonPipelineEnvironment{}
   185  		config := cnbBuildOptions{
   186  			ContainerImageName:   "my-image",
   187  			ContainerImageTag:    "0.0.1",
   188  			ContainerRegistryURL: fmt.Sprintf("https://%s", imageRegistry),
   189  			DockerConfigJSON:     "/path/to/config.json",
   190  		}
   191  
   192  		utils := newCnbBuildTestsUtils()
   193  		utils.FilesMock.AddFile(config.DockerConfigJSON, []byte(`{"auths":{"my-registry":{"auth":"dXNlcjpwYXNz"}}}`))
   194  		addBuilderFiles(&utils)
   195  
   196  		err := callCnbBuild(&config, &telemetry.CustomData{}, &utils, &commonPipelineEnvironment, &piperhttp.Client{})
   197  
   198  		require.NoError(t, err)
   199  		runner := utils.ExecMockRunner
   200  		assert.Contains(t, runner.Env, "CNB_REGISTRY_AUTH={\"my-registry\":\"Basic dXNlcjpwYXNz\"}")
   201  		assertLifecycleCalls(t, runner, 2)
   202  		assert.Contains(t, runner.Calls[1].Params, fmt.Sprintf("%s/%s:%s", imageRegistry, config.ContainerImageName, config.ContainerImageTag))
   203  		assert.Equal(t, config.ContainerRegistryURL, commonPipelineEnvironment.container.registryURL)
   204  		assert.Equal(t, "my-image:0.0.1", commonPipelineEnvironment.container.imageNameTag)
   205  	})
   206  
   207  	t.Run("success case (registry without https)", func(t *testing.T) {
   208  		t.Parallel()
   209  		commonPipelineEnvironment := cnbBuildCommonPipelineEnvironment{}
   210  		config := cnbBuildOptions{
   211  			ContainerImageName:   "my-image",
   212  			ContainerImageTag:    "0.0.1",
   213  			ContainerRegistryURL: imageRegistry,
   214  			DockerConfigJSON:     "/path/to/config.json",
   215  		}
   216  
   217  		utils := newCnbBuildTestsUtils()
   218  		utils.FilesMock.AddFile(config.DockerConfigJSON, []byte(`{"auths":{"my-registry":{"auth":"dXNlcjpwYXNz"}}}`))
   219  		addBuilderFiles(&utils)
   220  
   221  		err := callCnbBuild(&config, &telemetry.CustomData{}, &utils, &commonPipelineEnvironment, &piperhttp.Client{})
   222  
   223  		require.NoError(t, err)
   224  		runner := utils.ExecMockRunner
   225  		assert.Contains(t, runner.Env, "CNB_REGISTRY_AUTH={\"my-registry\":\"Basic dXNlcjpwYXNz\"}")
   226  		assertLifecycleCalls(t, runner, 2)
   227  		assert.Contains(t, runner.Calls[1].Params, fmt.Sprintf("%s/%s:%s", config.ContainerRegistryURL, config.ContainerImageName, config.ContainerImageTag))
   228  		assert.Equal(t, fmt.Sprintf("https://%s", config.ContainerRegistryURL), commonPipelineEnvironment.container.registryURL)
   229  		assert.Equal(t, "my-image:0.0.1", commonPipelineEnvironment.container.imageNameTag)
   230  	})
   231  
   232  	t.Run("success case (custom buildpacks and custom env variables with expand, renaming docker conf file, additional tag)", func(t *testing.T) {
   233  		t.Setenv("BAR", "BAZZ")
   234  		config := cnbBuildOptions{
   235  			ContainerImageName:   "my-image",
   236  			ContainerImageTag:    "0.0.1",
   237  			ContainerRegistryURL: imageRegistry,
   238  			DockerConfigJSON:     "/path/to/test.json",
   239  			Buildpacks:           []string{"test"},
   240  			ExpandBuildEnvVars:   true,
   241  			BuildEnvVars: map[string]interface{}{
   242  				"FOO": "${BAR}",
   243  			},
   244  			AdditionalTags: []string{"latest"},
   245  		}
   246  
   247  		utils := newCnbBuildTestsUtils()
   248  		utils.FilesMock.AddFile(config.DockerConfigJSON, []byte(`{"auths":{"my-registry":{"auth":"dXNlcjpwYXNz"}}}`))
   249  		addBuilderFiles(&utils)
   250  
   251  		err := callCnbBuild(&config, &telemetry.CustomData{}, &utils, &cnbBuildCommonPipelineEnvironment{}, &piperhttp.Client{})
   252  
   253  		require.NoError(t, err)
   254  		runner := utils.ExecMockRunner
   255  		assert.Contains(t, runner.Env, "CNB_REGISTRY_AUTH={\"my-registry\":\"Basic dXNlcjpwYXNz\"}")
   256  		assert.Equal(t, creatorPath, runner.Calls[1].Exec)
   257  		assert.Contains(t, runner.Calls[1].Params, "/tmp/buildpacks")
   258  		assert.Contains(t, runner.Calls[1].Params, "/tmp/buildpacks/order.toml")
   259  		assert.Contains(t, runner.Calls[1].Params, fmt.Sprintf("%s/%s:%s", config.ContainerRegistryURL, config.ContainerImageName, config.ContainerImageTag))
   260  		assert.Contains(t, runner.Calls[1].Params, fmt.Sprintf("%s/%s:latest", config.ContainerRegistryURL, config.ContainerImageName))
   261  
   262  		copiedFileExists, _ := utils.FileExists("/tmp/config.json")
   263  		assert.True(t, copiedFileExists)
   264  
   265  		assetBuildEnv(t, utils, "FOO", "BAZZ")
   266  	})
   267  
   268  	t.Run("success case (custom buildpacks, pre and post buildpacks and custom env variables, renaming docker conf file, additional tag)", func(t *testing.T) {
   269  		t.Parallel()
   270  		config := cnbBuildOptions{
   271  			ContainerImageName:   "my-image",
   272  			ContainerImageTag:    "0.0.1",
   273  			ContainerRegistryURL: imageRegistry,
   274  			DockerConfigJSON:     "/path/to/test.json",
   275  			PreBuildpacks:        []string{"pre-test"},
   276  			PostBuildpacks:       []string{"post-test"},
   277  			Buildpacks:           []string{"test"},
   278  			ExpandBuildEnvVars:   false,
   279  			BuildEnvVars: map[string]interface{}{
   280  				"FOO": "${BAR}",
   281  			},
   282  			AdditionalTags: []string{"latest"},
   283  		}
   284  
   285  		utils := newCnbBuildTestsUtils()
   286  		utils.FilesMock.AddFile(config.DockerConfigJSON, []byte(`{"auths":{"my-registry":{"auth":"dXNlcjpwYXNz"}}}`))
   287  		addBuilderFiles(&utils)
   288  
   289  		err := callCnbBuild(&config, &telemetry.CustomData{}, &utils, &cnbBuildCommonPipelineEnvironment{}, &piperhttp.Client{})
   290  
   291  		require.NoError(t, err)
   292  		runner := utils.ExecMockRunner
   293  		assert.Contains(t, runner.Env, "CNB_REGISTRY_AUTH={\"my-registry\":\"Basic dXNlcjpwYXNz\"}")
   294  		assert.Equal(t, creatorPath, runner.Calls[1].Exec)
   295  		assert.Contains(t, runner.Calls[1].Params, "/tmp/buildpacks")
   296  		assert.Contains(t, runner.Calls[1].Params, "/tmp/buildpacks/order.toml")
   297  		assert.Contains(t, runner.Calls[1].Params, fmt.Sprintf("%s/%s:%s", config.ContainerRegistryURL, config.ContainerImageName, config.ContainerImageTag))
   298  		assert.Contains(t, runner.Calls[1].Params, fmt.Sprintf("%s/%s:latest", config.ContainerRegistryURL, config.ContainerImageName))
   299  
   300  		copiedFileExists, _ := utils.FileExists("/tmp/config.json")
   301  		assert.True(t, copiedFileExists)
   302  
   303  		assetBuildEnv(t, utils, "FOO", "${BAR}")
   304  	})
   305  
   306  	t.Run("success case (custom pre and post buildpacks and custom env variables, renaming docker conf file, additional tag)", func(t *testing.T) {
   307  		t.Parallel()
   308  		config := cnbBuildOptions{
   309  			ContainerImageName:   "my-image",
   310  			ContainerImageTag:    "0.0.1",
   311  			ContainerRegistryURL: imageRegistry,
   312  			DockerConfigJSON:     "/path/to/test.json",
   313  			PostBuildpacks:       []string{"post-test"},
   314  			PreBuildpacks:        []string{"pre-test"},
   315  			BuildEnvVars: map[string]interface{}{
   316  				"FOO": "BAR",
   317  			},
   318  			AdditionalTags: []string{"latest"},
   319  		}
   320  
   321  		utils := newCnbBuildTestsUtils()
   322  		utils.FilesMock.AddFile(config.DockerConfigJSON, []byte(`{"auths":{"my-registry":{"auth":"dXNlcjpwYXNz"}}}`))
   323  		addBuilderFiles(&utils)
   324  
   325  		err := callCnbBuild(&config, &telemetry.CustomData{}, &utils, &cnbBuildCommonPipelineEnvironment{}, &piperhttp.Client{})
   326  
   327  		require.NoError(t, err)
   328  		runner := utils.ExecMockRunner
   329  		assert.Contains(t, runner.Env, "CNB_REGISTRY_AUTH={\"my-registry\":\"Basic dXNlcjpwYXNz\"}")
   330  		assert.Equal(t, creatorPath, runner.Calls[1].Exec)
   331  		assert.Contains(t, runner.Calls[1].Params, "/tmp/buildpacks")
   332  		assert.Contains(t, runner.Calls[1].Params, "/tmp/buildpacks/order.toml")
   333  		assert.Contains(t, runner.Calls[1].Params, fmt.Sprintf("%s/%s:%s", config.ContainerRegistryURL, config.ContainerImageName, config.ContainerImageTag))
   334  		assert.Contains(t, runner.Calls[1].Params, fmt.Sprintf("%s/%s:latest", config.ContainerRegistryURL, config.ContainerImageName))
   335  
   336  		copiedFileExists, _ := utils.FileExists("/tmp/config.json")
   337  		assert.True(t, copiedFileExists)
   338  	})
   339  
   340  	t.Run("success case (customTlsCertificates)", func(t *testing.T) {
   341  		t.Parallel()
   342  		httpmock.Activate()
   343  		defer httpmock.DeactivateAndReset()
   344  		httpmock.RegisterResponder(http.MethodGet, "https://test-cert.com/cert.crt", httpmock.NewStringResponder(200, "testCert"))
   345  		client := &piperhttp.Client{}
   346  		client.SetOptions(piperhttp.ClientOptions{MaxRetries: -1, UseDefaultTransport: true})
   347  
   348  		caCertsFile := "/etc/ssl/certs/ca-certificates.crt"
   349  		caCertsTmpFile := "/tmp/ca-certificates.crt"
   350  		registry := "some-registry"
   351  		config := cnbBuildOptions{
   352  			ContainerImageName:        "my-image",
   353  			ContainerImageTag:         "0.0.1",
   354  			ContainerRegistryURL:      registry,
   355  			DockerConfigJSON:          "/path/to/config.json",
   356  			CustomTLSCertificateLinks: []string{"https://test-cert.com/cert.crt", "https://test-cert.com/cert.crt"},
   357  		}
   358  
   359  		utils := newCnbBuildTestsUtils()
   360  		utils.FilesMock.AddFile(caCertsFile, []byte("test\n"))
   361  		utils.FilesMock.AddFile(config.DockerConfigJSON, []byte(`{"auths":{"my-registry":{"auth":"dXNlcjpwYXNz"}}}`))
   362  		addBuilderFiles(&utils)
   363  
   364  		err := callCnbBuild(&config, &telemetry.CustomData{}, &utils, &cnbBuildCommonPipelineEnvironment{}, client)
   365  		require.NoError(t, err)
   366  
   367  		result, err := utils.FilesMock.FileRead(caCertsTmpFile)
   368  		require.NoError(t, err)
   369  		assert.Equal(t, "test\ntestCert\ntestCert\n", string(result))
   370  
   371  		require.NoError(t, err)
   372  		runner := utils.ExecMockRunner
   373  		assert.Contains(t, runner.Env, "CNB_REGISTRY_AUTH={\"my-registry\":\"Basic dXNlcjpwYXNz\"}")
   374  		assert.Contains(t, runner.Env, fmt.Sprintf("SSL_CERT_FILE=%s", caCertsTmpFile))
   375  		assertLifecycleCalls(t, runner, 2)
   376  		assert.Contains(t, runner.Calls[1].Params, fmt.Sprintf("%s/%s:%s", config.ContainerRegistryURL, config.ContainerImageName, config.ContainerImageTag))
   377  	})
   378  
   379  	t.Run("success case (additionalTags)", func(t *testing.T) {
   380  		t.Parallel()
   381  		config := cnbBuildOptions{
   382  			ContainerImageName:   "my-image",
   383  			ContainerImageTag:    "3.1.5",
   384  			ContainerRegistryURL: imageRegistry,
   385  			DockerConfigJSON:     "/path/to/config.json",
   386  			AdditionalTags:       []string{"3", "3.1", "3.1", "3.1.5"},
   387  		}
   388  
   389  		utils := newCnbBuildTestsUtils()
   390  		utils.FilesMock.AddFile(config.DockerConfigJSON, []byte(`{"auths":{"my-registry":{"auth":"dXNlcjpwYXNz"}}}`))
   391  		addBuilderFiles(&utils)
   392  
   393  		err := callCnbBuild(&config, &telemetry.CustomData{}, &utils, &cnbBuildCommonPipelineEnvironment{}, &piperhttp.Client{})
   394  		require.NoError(t, err)
   395  
   396  		runner := utils.ExecMockRunner
   397  		assertLifecycleCalls(t, runner, 2)
   398  		assert.Contains(t, runner.Calls[1].Params, fmt.Sprintf("%s/%s:%s", config.ContainerRegistryURL, config.ContainerImageName, config.ContainerImageTag))
   399  		assert.Contains(t, runner.Calls[1].Params, fmt.Sprintf("%s/%s:3", config.ContainerRegistryURL, config.ContainerImageName))
   400  		assert.Contains(t, runner.Calls[1].Params, fmt.Sprintf("%s/%s:3.1", config.ContainerRegistryURL, config.ContainerImageName))
   401  		assert.Contains(t, runner.Calls[1].Params, fmt.Sprintf("%s/%s:3.1.5", config.ContainerRegistryURL, config.ContainerImageName))
   402  	})
   403  
   404  	t.Run("success case: build environment variables", func(t *testing.T) {
   405  		t.Parallel()
   406  		commonPipelineEnvironment := cnbBuildCommonPipelineEnvironment{}
   407  		config := cnbBuildOptions{
   408  			ContainerImageTag:    "0.0.1",
   409  			ContainerRegistryURL: fmt.Sprintf("https://%s", imageRegistry),
   410  			ProjectDescriptor:    "project.toml",
   411  			BuildEnvVars: map[string]interface{}{
   412  				"OPTIONS_KEY": "OPTIONS_VALUE",
   413  				"OVERWRITE":   "this should win",
   414  			},
   415  		}
   416  
   417  		projectToml := `[project]
   418  		id = "io.buildpacks.my-app"
   419  
   420  		[[build.env]]
   421  		name="PROJECT_DESCRIPTOR_KEY"
   422  		value="PROJECT_DESCRIPTOR_VALUE"
   423  
   424  		[[build.env]]
   425  		name="OVERWRITE"
   426  		value="this should be overwritten"
   427  		`
   428  
   429  		utils := newCnbBuildTestsUtils()
   430  		utils.FilesMock.AddFile("project.toml", []byte(projectToml))
   431  		addBuilderFiles(&utils)
   432  
   433  		telemetryData := telemetry.CustomData{}
   434  		err := callCnbBuild(&config, &telemetryData, &utils, &commonPipelineEnvironment, &piperhttp.Client{})
   435  
   436  		require.NoError(t, err)
   437  		assertLifecycleCalls(t, utils.ExecMockRunner, 2)
   438  
   439  		assetBuildEnv(t, utils, "OPTIONS_KEY", "OPTIONS_VALUE")
   440  		assetBuildEnv(t, utils, "PROJECT_DESCRIPTOR_KEY", "PROJECT_DESCRIPTOR_VALUE")
   441  		assetBuildEnv(t, utils, "OVERWRITE", "this should win")
   442  	})
   443  
   444  	t.Run("pom.xml exists (symlink for the target folder)", func(t *testing.T) {
   445  		t.Parallel()
   446  		config := cnbBuildOptions{
   447  			ContainerImageName:   "my-image",
   448  			ContainerImageTag:    "3.1.5",
   449  			ContainerRegistryURL: imageRegistry,
   450  			DockerConfigJSON:     "/path/to/config.json",
   451  		}
   452  
   453  		utils := newCnbBuildTestsUtils()
   454  		utils.FilesMock.CurrentDir = "/jenkins"
   455  		utils.FilesMock.AddDir("/jenkins")
   456  		utils.FilesMock.AddFile(config.DockerConfigJSON, []byte(`{"auths":{"my-registry":{"auth":"dXNlcjpwYXNz"}}}`))
   457  		utils.FilesMock.AddFile("/workspace/pom.xml", []byte("test"))
   458  		addBuilderFiles(&utils)
   459  
   460  		err := callCnbBuild(&config, &telemetry.CustomData{}, &utils, &cnbBuildCommonPipelineEnvironment{}, &piperhttp.Client{})
   461  		require.NoError(t, err)
   462  
   463  		runner := utils.ExecMockRunner
   464  		assertLifecycleCalls(t, runner, 2)
   465  
   466  		assert.True(t, utils.FilesMock.HasCreatedSymlink("/jenkins/target", "/workspace/target"))
   467  	})
   468  
   469  	t.Run("no pom.xml exists (no symlink for the target folder)", func(t *testing.T) {
   470  		t.Parallel()
   471  		config := cnbBuildOptions{
   472  			ContainerImageName:   "my-image",
   473  			ContainerImageTag:    "3.1.5",
   474  			ContainerRegistryURL: imageRegistry,
   475  			DockerConfigJSON:     "/path/to/config.json",
   476  		}
   477  
   478  		utils := newCnbBuildTestsUtils()
   479  		utils.FilesMock.CurrentDir = "/jenkins"
   480  		utils.FilesMock.AddDir("/jenkins")
   481  		utils.FilesMock.AddFile(config.DockerConfigJSON, []byte(`{"auths":{"my-registry":{"auth":"dXNlcjpwYXNz"}}}`))
   482  		addBuilderFiles(&utils)
   483  
   484  		err := callCnbBuild(&config, &telemetry.CustomData{}, &utils, &cnbBuildCommonPipelineEnvironment{}, &piperhttp.Client{})
   485  		require.NoError(t, err)
   486  
   487  		runner := utils.ExecMockRunner
   488  		assertLifecycleCalls(t, runner, 2)
   489  
   490  		assert.False(t, utils.FilesMock.HasCreatedSymlink("/jenkins/target", "/workspace/target"))
   491  	})
   492  
   493  	t.Run("error case: Invalid DockerConfigJSON file", func(t *testing.T) {
   494  		t.Parallel()
   495  		config := cnbBuildOptions{
   496  			ContainerImageTag:    "0.0.1",
   497  			ContainerRegistryURL: imageRegistry,
   498  			ContainerImageName:   "my-image",
   499  			DockerConfigJSON:     "/path/to/config.json",
   500  		}
   501  
   502  		utils := newCnbBuildTestsUtils()
   503  		utils.FilesMock.AddFile(config.DockerConfigJSON, []byte(`{"auths":{"my-registry":"dXNlcjpwYXNz"}}`))
   504  		addBuilderFiles(&utils)
   505  
   506  		err := callCnbBuild(&config, &telemetry.CustomData{}, &utils, &cnbBuildCommonPipelineEnvironment{}, &piperhttp.Client{})
   507  		assert.EqualError(t, err, "failed to generate CNB_REGISTRY_AUTH: json: cannot unmarshal string into Go struct field ConfigFile.auths of type types.AuthConfig")
   508  	})
   509  
   510  	t.Run("error case: DockerConfigJSON file not there (config.json)", func(t *testing.T) {
   511  		t.Parallel()
   512  		config := cnbBuildOptions{
   513  			ContainerImageTag:    "0.0.1",
   514  			ContainerRegistryURL: imageRegistry,
   515  			ContainerImageName:   "my-image",
   516  			DockerConfigJSON:     "not-there/config.json",
   517  		}
   518  
   519  		utils := newCnbBuildTestsUtils()
   520  		addBuilderFiles(&utils)
   521  
   522  		err := callCnbBuild(&config, &telemetry.CustomData{}, &utils, &cnbBuildCommonPipelineEnvironment{}, &piperhttp.Client{})
   523  		assert.EqualError(t, err, "failed to create/rename DockerConfigJSON file: cannot copy 'not-there/config.json': file does not exist")
   524  	})
   525  
   526  	t.Run("error case: DockerConfigJSON file not there (not config.json)", func(t *testing.T) {
   527  		t.Parallel()
   528  		config := cnbBuildOptions{
   529  			ContainerImageTag:    "0.0.1",
   530  			ContainerRegistryURL: imageRegistry,
   531  			ContainerImageName:   "my-image",
   532  			DockerConfigJSON:     "not-there",
   533  		}
   534  
   535  		utils := newCnbBuildTestsUtils()
   536  		addBuilderFiles(&utils)
   537  
   538  		err := callCnbBuild(&config, &telemetry.CustomData{}, &utils, &cnbBuildCommonPipelineEnvironment{}, &piperhttp.Client{})
   539  		assert.EqualError(t, err, "failed to create/rename DockerConfigJSON file: cannot copy 'not-there': file does not exist")
   540  	})
   541  
   542  	t.Run("error case: dockerImage is not a valid builder", func(t *testing.T) {
   543  		t.Parallel()
   544  		config := cnbBuildOptions{}
   545  
   546  		utils := newCnbBuildTestsUtils()
   547  
   548  		err := callCnbBuild(&config, &telemetry.CustomData{}, &utils, &cnbBuildCommonPipelineEnvironment{}, &piperhttp.Client{})
   549  		assert.EqualError(t, err, "the provided dockerImage is not a valid builder: binary '/cnb/lifecycle/creator' not found")
   550  	})
   551  
   552  	t.Run("error case: builder image does not contain tls certificates", func(t *testing.T) {
   553  		t.Parallel()
   554  		config := cnbBuildOptions{
   555  			ContainerImageName:        "my-image",
   556  			ContainerImageTag:         "0.0.1",
   557  			ContainerRegistryURL:      imageRegistry,
   558  			DockerConfigJSON:          "/path/to/config.json",
   559  			Buildpacks:                []string{"test"},
   560  			CustomTLSCertificateLinks: []string{"http://example.com/certs.pem"},
   561  		}
   562  
   563  		utils := newCnbBuildTestsUtils()
   564  		utils.FilesMock.AddFile(config.DockerConfigJSON, []byte(`{"auths":{"my-registry":{"auth":"dXNlcjpwYXNz"}}}`))
   565  		addBuilderFiles(&utils)
   566  
   567  		err := callCnbBuild(&config, &telemetry.CustomData{}, &utils, &cnbBuildCommonPipelineEnvironment{}, &piperhttp.Client{})
   568  		assert.EqualError(t, err, "failed to copy certificates: cannot copy '/etc/ssl/certs/ca-certificates.crt': file does not exist")
   569  	})
   570  
   571  	t.Run("success case (telemetry was added)", func(t *testing.T) {
   572  		t.Parallel()
   573  		registry := "some-registry"
   574  		config := cnbBuildOptions{
   575  			ContainerImageName:   "my-image",
   576  			ContainerImageTag:    "3.1.5",
   577  			ContainerRegistryURL: registry,
   578  			MultipleImages: []map[string]interface{}{
   579  				{
   580  					"runImage": "foo",
   581  				},
   582  				{
   583  					"runImage": "bar",
   584  				},
   585  			},
   586  		}
   587  
   588  		utils := newCnbBuildTestsUtils()
   589  		addBuilderFiles(&utils)
   590  
   591  		telemetryData := &telemetry.CustomData{}
   592  		err := callCnbBuild(&config, telemetryData, &utils, &cnbBuildCommonPipelineEnvironment{}, &piperhttp.Client{})
   593  		require.NoError(t, err)
   594  
   595  		assert.Equal(t, "paketobuildpacks/builder-jammy-base:latest", telemetryData.CnbBuilder)
   596  		assert.Equal(t, "foo,bar", telemetryData.CnbRunImage)
   597  	})
   598  
   599  	t.Run("error case, multiple artifacts in path", func(t *testing.T) {
   600  		t.Parallel()
   601  		config := cnbBuildOptions{
   602  			ContainerImageName:   "my-image",
   603  			ContainerImageTag:    "3.1.5",
   604  			ContainerRegistryURL: fmt.Sprintf("https://%s", imageRegistry),
   605  			DockerConfigJSON:     "/path/to/config.json",
   606  			ProjectDescriptor:    "project.toml",
   607  			AdditionalTags:       []string{"latest"},
   608  			Buildpacks:           []string{"paketobuildpacks/java", "gcr.io/paketo-buildpacks/node"},
   609  			Path:                 "target/*.jar",
   610  		}
   611  
   612  		utils := newCnbBuildTestsUtils()
   613  		utils.FilesMock.AddFile(config.DockerConfigJSON, []byte(`{"auths":{"my-registry":{"auth":"dXNlcjpwYXNz"}}}`))
   614  		utils.FilesMock.AddDir("target")
   615  		utils.FilesMock.AddFile("target/app.jar", []byte(`FFFFFF`))
   616  		utils.FilesMock.AddFile("target/app-src.jar", []byte(`FFFFFF`))
   617  
   618  		addBuilderFiles(&utils)
   619  
   620  		telemetryData := telemetry.CustomData{}
   621  		err := callCnbBuild(&config, &telemetryData, &utils, &cnbBuildCommonPipelineEnvironment{}, &piperhttp.Client{})
   622  		require.EqualError(t, err, "could not resolve path: Failed to resolve glob for 'target/*.jar', matching 2 file(s)")
   623  	})
   624  
   625  	t.Run("success case, artifacts found by glob", func(t *testing.T) {
   626  		t.Parallel()
   627  		commonPipelineEnvironment := cnbBuildCommonPipelineEnvironment{}
   628  		config := cnbBuildOptions{
   629  			ContainerImageName:   "my-image",
   630  			ContainerImageTag:    "3.1.5",
   631  			ContainerRegistryURL: fmt.Sprintf("https://%s", imageRegistry),
   632  			DockerConfigJSON:     "/path/to/config.json",
   633  			ProjectDescriptor:    "project.toml",
   634  			AdditionalTags:       []string{"latest"},
   635  			Buildpacks:           []string{"paketobuildpacks/java", "gcr.io/paketo-buildpacks/node"},
   636  			Path:                 "**/target",
   637  		}
   638  
   639  		utils := newCnbBuildTestsUtils()
   640  		utils.FilesMock.AddFile(config.DockerConfigJSON, []byte(`{"auths":{"my-registry":{"auth":"dXNlcjpwYXNz"}}}`))
   641  		utils.FilesMock.AddDir("target")
   642  		utils.FilesMock.AddFile("target/app.jar", []byte(`FFFFFF`))
   643  
   644  		addBuilderFiles(&utils)
   645  
   646  		telemetryData := telemetry.CustomData{}
   647  		err := callCnbBuild(&config, &telemetryData, &utils, &commonPipelineEnvironment, &piperhttp.Client{})
   648  
   649  		require.NoError(t, err)
   650  		runner := utils.ExecMockRunner
   651  		assert.Contains(t, runner.Env, "CNB_REGISTRY_AUTH={\"my-registry\":\"Basic dXNlcjpwYXNz\"}")
   652  		assert.Contains(t, runner.Calls[1].Params, fmt.Sprintf("%s/%s:%s", imageRegistry, config.ContainerImageName, config.ContainerImageTag))
   653  		assert.Equal(t, config.ContainerRegistryURL, commonPipelineEnvironment.container.registryURL)
   654  		assert.Equal(t, "my-image:3.1.5", commonPipelineEnvironment.container.imageNameTag)
   655  	})
   656  
   657  	t.Run("success case (multiple images configured)", func(t *testing.T) {
   658  		t.Parallel()
   659  		commonPipelineEnvironment := cnbBuildCommonPipelineEnvironment{}
   660  		config := cnbBuildOptions{
   661  			ContainerImageTag:    "3.1.5",
   662  			ContainerRegistryURL: imageRegistry,
   663  			DockerConfigJSON:     "/path/to/my-config.json",
   664  			AdditionalTags:       []string{"3", "3.1", "3.1", "3.1.5"},
   665  			MultipleImages:       []map[string]interface{}{{"ContainerImageName": "my-image-0", "ContainerImageAlias": "simple"}, {"ContainerImageName": "my-image-1"}},
   666  		}
   667  
   668  		expectedImageCount := len(config.MultipleImages)
   669  
   670  		utils := newCnbBuildTestsUtils()
   671  		utils.FilesMock.AddFile(config.DockerConfigJSON, []byte(`{"auths":{"my-registry":{"auth":"dXNlcjpwYXNz"}}}`))
   672  		addBuilderFiles(&utils)
   673  
   674  		telemetryData := &telemetry.CustomData{}
   675  		err := callCnbBuild(&config, telemetryData, &utils, &commonPipelineEnvironment, &piperhttp.Client{})
   676  		require.NoError(t, err)
   677  
   678  		runner := utils.ExecMockRunner
   679  		require.Equal(t, expectedImageCount, len(runner.Calls)-1)
   680  		for i, call := range runner.Calls {
   681  			if i == 0 { // first call is -version
   682  				continue
   683  			}
   684  			lifecycleCall := i - 1
   685  			assertLifecycleCalls(t, runner, i+1)
   686  			containerImageName := fmt.Sprintf("my-image-%d", lifecycleCall)
   687  			assert.Contains(t, call.Params, fmt.Sprintf("%s/%s:%s", config.ContainerRegistryURL, containerImageName, config.ContainerImageTag))
   688  			assert.Contains(t, call.Params, fmt.Sprintf("%s/%s:3", config.ContainerRegistryURL, containerImageName))
   689  			assert.Contains(t, call.Params, fmt.Sprintf("%s/%s:3.1", config.ContainerRegistryURL, containerImageName))
   690  			assert.Contains(t, call.Params, fmt.Sprintf("%s/%s:3.1.5", config.ContainerRegistryURL, containerImageName))
   691  		}
   692  
   693  		assert.Equal(t, "my-image-0:3.1.5", commonPipelineEnvironment.container.imageNameTag)
   694  		assert.Equal(t, []string{"simple", "my-image-1"}, commonPipelineEnvironment.container.imageNames)
   695  	})
   696  }