1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-01-16 05:16:08 +02:00

feat(cnbbuild) enable multi image build (#3521)

Co-authored-by: Ralf Pannemans <ralf.pannemans@sap.com>
Co-authored-by: Pavel Busko <pavel.busko@sap.com>
Co-authored-by: Sumit Kulhadia <sumit.kulhadia@sap.com>
This commit is contained in:
Ralf Pannemans 2022-02-15 14:39:14 +01:00 committed by GitHub
parent 32acc9f91f
commit 4b2f61589d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 449 additions and 138 deletions

View File

@ -21,6 +21,8 @@ import (
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/piperutils"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/imdario/mergo"
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
ignore "github.com/sabhiram/go-gitignore"
)
@ -44,8 +46,12 @@ type cnbBuildUtilsBundle struct {
*docker.Client
}
type cnbBuildTelemetry struct {
Version int `json:"version"`
Data []cnbBuildTelemetryData `json:"data"`
}
type cnbBuildTelemetryData struct {
Version int `json:"version"`
ImageTag string `json:"imageTag"`
AdditionalTags []string `json:"additionalTags"`
BindingKeys []string `json:"bindingKeys"`
@ -76,6 +82,32 @@ type cnbBuildTelemetryDataProjectDescriptor struct {
ExcludeUsed bool `json:"excludeUsed"`
}
func processConfigs(main cnbBuildOptions, multipleImages []map[string]interface{}) ([]cnbBuildOptions, error) {
var result []cnbBuildOptions
if len(multipleImages) == 0 {
result = append(result, main)
return result, nil
}
for _, conf := range multipleImages {
var structuredConf cnbBuildOptions
err := mapstructure.Decode(conf, &structuredConf)
if err != nil {
return nil, err
}
err = mergo.Merge(&structuredConf, main)
if err != nil {
return nil, err
}
result = append(result, structuredConf)
}
return result, nil
}
func setCustomBuildpacks(bpacks []string, dockerCreds string, utils cnbutils.BuildUtils) (string, string, error) {
buildpacksPath := "/tmp/buildpacks"
orderPath := "/tmp/buildpacks/order.toml"
@ -107,8 +139,11 @@ func cnbBuild(config cnbBuildOptions, telemetryData *telemetry.CustomData, commo
utils := newCnbBuildUtils()
client := &piperhttp.Client{}
cnbTelemetry := cnbBuildTelemetry{
Version: 2,
}
err := runCnbBuild(&config, telemetryData, utils, commonPipelineEnvironment, client)
err := callCnbBuild(&config, telemetryData, &cnbTelemetry, utils, commonPipelineEnvironment, client)
if err != nil {
log.Entry().WithError(err).Fatal("step execution failed")
}
@ -166,6 +201,22 @@ func isZip(path string) bool {
}
}
func cleanDir(dir string, utils cnbutils.BuildUtils) error {
dirContent, err := utils.Glob(filepath.Join(dir, "*"))
if err != nil {
return err
}
for _, obj := range dirContent {
err = utils.RemoveAll(obj)
if err != nil {
return err
}
}
return nil
}
func copyFile(source, target string, utils cnbutils.BuildUtils) error {
targetDir := filepath.Dir(target)
@ -243,7 +294,15 @@ func prepareDockerConfig(source string, utils cnbutils.BuildUtils) (string, erro
log.Entry().Debugf("Renaming docker config file from '%s' to 'config.json'", filepath.Base(source))
newPath := filepath.Join(filepath.Dir(source), "config.json")
err := utils.FileRename(source, newPath)
alreadyExists, err := utils.FileExists(newPath)
if err != nil {
return "", err
}
if alreadyExists {
return newPath, nil
}
err = utils.FileRename(source, newPath)
if err != nil {
return "", err
}
@ -275,18 +334,18 @@ func linkTargetFolder(utils cnbutils.BuildUtils, source, target string) error {
return utils.Symlink(targetPath, linkPath)
}
func (c *cnbBuildOptions) mergeEnvVars(vars map[string]interface{}) {
if c.BuildEnvVars == nil {
c.BuildEnvVars = vars
func (config *cnbBuildOptions) mergeEnvVars(vars map[string]interface{}) {
if config.BuildEnvVars == nil {
config.BuildEnvVars = vars
return
}
for k, v := range vars {
_, exists := c.BuildEnvVars[k]
_, exists := config.BuildEnvVars[k]
if !exists {
c.BuildEnvVars[k] = v
config.BuildEnvVars[k] = v
}
}
}
@ -301,7 +360,11 @@ func (config *cnbBuildOptions) resolvePath(utils cnbutils.BuildUtils) (pathEnum,
if config.Path == "" {
return pathEnumRoot, pwd, nil
}
source := config.Path
source, err := utils.Abs(config.Path)
if err != nil {
log.SetErrorCategory(log.ErrorConfiguration)
return "", "", errors.Wrapf(err, "Failed to resolve absolute path for '%s'", config.Path)
}
dir, err := utils.DirExists(source)
if err != nil {
@ -317,7 +380,7 @@ func (config *cnbBuildOptions) resolvePath(utils cnbutils.BuildUtils) (pathEnum,
}
func addConfigTelemetryData(utils cnbutils.BuildUtils, data *cnbBuildTelemetryData, config *cnbBuildOptions) {
bindingKeys := []string{}
var bindingKeys []string
for k := range config.Bindings {
bindingKeys = append(bindingKeys, k)
}
@ -358,10 +421,31 @@ func addProjectDescriptorTelemetryData(data *cnbBuildTelemetryData, descriptor p
data.ProjectDescriptor.ExcludeUsed = descriptor.Exclude != nil
}
func runCnbBuild(config *cnbBuildOptions, telemetryData *telemetry.CustomData, utils cnbutils.BuildUtils, commonPipelineEnvironment *cnbBuildCommonPipelineEnvironment, httpClient piperhttp.Sender) error {
func callCnbBuild(config *cnbBuildOptions, telemetryData *telemetry.CustomData, telemetry *cnbBuildTelemetry, utils cnbutils.BuildUtils, commonPipelineEnvironment *cnbBuildCommonPipelineEnvironment, httpClient piperhttp.Sender) error {
mergedConfigs, err := processConfigs(*config, config.MultipleImages)
if err != nil {
return errors.Wrap(err, "failed to process config")
}
for _, c := range mergedConfigs {
err = runCnbBuild(&c, telemetryData, telemetry, utils, commonPipelineEnvironment, httpClient)
if err != nil {
return err
}
}
telemetryData.Custom1Label = "cnbBuildStepData"
customData, err := json.Marshal(telemetry)
if err != nil {
return errors.Wrap(err, "failed to marshal custom telemetry data")
}
telemetryData.Custom1 = string(customData)
return nil
}
func runCnbBuild(config *cnbBuildOptions, telemetryData *telemetry.CustomData, telemetry *cnbBuildTelemetry, utils cnbutils.BuildUtils, commonPipelineEnvironment *cnbBuildCommonPipelineEnvironment, httpClient piperhttp.Sender) error {
var err error
customTelemetryData := cnbBuildTelemetryData{Version: 1}
customTelemetryData := cnbBuildTelemetryData{}
addConfigTelemetryData(utils, &customTelemetryData, config)
err = isBuilder(utils)
@ -373,18 +457,18 @@ func runCnbBuild(config *cnbBuildOptions, telemetryData *telemetry.CustomData, u
include := ignore.CompileIgnoreLines("**/*")
exclude := ignore.CompileIgnoreLines("piper", ".pipeline")
projDescExists, err := utils.FileExists(config.ProjectDescriptor)
projDescPath, err := project.ResolvePath(config.ProjectDescriptor, config.Path, utils)
if err != nil {
log.SetErrorCategory(log.ErrorConfiguration)
return errors.Wrap(err, "failed to check if project descriptor exists")
}
var projectID string
if projDescExists {
descriptor, err := project.ParseDescriptor(config.ProjectDescriptor, utils, httpClient)
if projDescPath != "" {
descriptor, err := project.ParseDescriptor(projDescPath, utils, httpClient)
if err != nil {
log.SetErrorCategory(log.ErrorConfiguration)
return errors.Wrapf(err, "failed to parse %s", config.ProjectDescriptor)
return errors.Wrapf(err, "failed to parse %s", projDescPath)
}
addProjectDescriptorTelemetryData(&customTelemetryData, *descriptor)
@ -412,17 +496,14 @@ func runCnbBuild(config *cnbBuildOptions, telemetryData *telemetry.CustomData, u
}
customTelemetryData.Buildpacks.Overall = config.Buildpacks
customTelemetryData.BuildEnv.KeyValues = privacy.FilterEnv(config.BuildEnvVars)
telemetry.Data = append(telemetry.Data, customTelemetryData)
telemetryData.Custom1Label = "cnbBuildStepData"
customData, err := json.Marshal(customTelemetryData)
if err != nil {
log.SetErrorCategory(log.ErrorCustom)
return errors.Wrap(err, "failed to marshal custom telemetry data")
if commonPipelineEnvironment.container.imageNameTag == "" {
commonPipelineEnvironment.container.registryURL = fmt.Sprintf("%s://%s", targetImage.ContainerRegistry.Scheme, targetImage.ContainerRegistry.Host)
commonPipelineEnvironment.container.imageNameTag = fmt.Sprintf("%v:%v", targetImage.ContainerImageName, targetImage.ContainerImageTag)
}
telemetryData.Custom1 = string(customData)
commonPipelineEnvironment.container.registryURL = fmt.Sprintf("%s://%s", targetImage.ContainerRegistry.Scheme, targetImage.ContainerRegistry.Host)
commonPipelineEnvironment.container.imageNameTag = fmt.Sprintf("%v:%v", targetImage.ContainerImageName, targetImage.ContainerImageTag)
commonPipelineEnvironment.container.imageNameTags = append(commonPipelineEnvironment.container.imageNameTags, fmt.Sprintf("%v:%v", targetImage.ContainerImageName, targetImage.ContainerImageTag))
commonPipelineEnvironment.container.imageNames = append(commonPipelineEnvironment.container.imageNames, targetImage.ContainerImageName)
if config.BuildEnvVars != nil && len(config.BuildEnvVars) > 0 {
log.Entry().Infof("Setting custom environment variables: '%v'", config.BuildEnvVars)
@ -451,6 +532,16 @@ func runCnbBuild(config *cnbBuildOptions, telemetryData *telemetry.CustomData, u
target := "/workspace"
pathType, source, err := config.resolvePath(utils)
if err != nil {
log.SetErrorCategory(log.ErrorBuild)
return errors.Wrapf(err, "could no resolve path")
}
err = cleanDir(target, utils)
if err != nil {
log.SetErrorCategory(log.ErrorBuild)
return errors.Wrapf(err, "failed to clean up target folder %s", target)
}
if pathType != pathEnumArchive {
err = copyProject(source, target, include, exclude, utils)
@ -467,13 +558,7 @@ func runCnbBuild(config *cnbBuildOptions, telemetryData *telemetry.CustomData, u
}
if ok, _ := utils.FileExists(filepath.Join(target, "pom.xml")); ok {
pwd, err := utils.Getwd()
if err != nil {
log.SetErrorCategory(log.ErrorBuild)
return errors.Wrap(err, "failed to get current working directory")
}
err = linkTargetFolder(utils, pwd, target)
err = linkTargetFolder(utils, source, target)
if err != nil {
log.SetErrorCategory(log.ErrorBuild)
return err
@ -502,14 +587,6 @@ func runCnbBuild(config *cnbBuildOptions, telemetryData *telemetry.CustomData, u
return errors.Wrap(err, "failed to generate CNB_REGISTRY_AUTH")
}
if dockerConfigFile != "" {
err = utils.FileRemove(dockerConfigFile)
if err != nil {
log.SetErrorCategory(log.ErrorBuild)
return errors.Wrap(err, "failed to remove docker config.json file")
}
}
if len(config.CustomTLSCertificateLinks) > 0 {
caCertificates := "/tmp/ca-certificates.crt"
_, err := utils.Copy("/etc/ssl/certs/ca-certificates.crt", caCertificates)

View File

@ -18,23 +18,26 @@ import (
)
type cnbBuildOptions struct {
ContainerImageName string `json:"containerImageName,omitempty"`
ContainerImageTag string `json:"containerImageTag,omitempty"`
ContainerRegistryURL string `json:"containerRegistryUrl,omitempty"`
Buildpacks []string `json:"buildpacks,omitempty"`
BuildEnvVars map[string]interface{} `json:"buildEnvVars,omitempty"`
Path string `json:"path,omitempty"`
ProjectDescriptor string `json:"projectDescriptor,omitempty"`
DockerConfigJSON string `json:"dockerConfigJSON,omitempty"`
CustomTLSCertificateLinks []string `json:"customTlsCertificateLinks,omitempty"`
AdditionalTags []string `json:"additionalTags,omitempty"`
Bindings map[string]interface{} `json:"bindings,omitempty"`
ContainerImageName string `json:"containerImageName,omitempty"`
ContainerImageTag string `json:"containerImageTag,omitempty"`
ContainerRegistryURL string `json:"containerRegistryUrl,omitempty"`
Buildpacks []string `json:"buildpacks,omitempty"`
BuildEnvVars map[string]interface{} `json:"buildEnvVars,omitempty"`
Path string `json:"path,omitempty"`
ProjectDescriptor string `json:"projectDescriptor,omitempty"`
DockerConfigJSON string `json:"dockerConfigJSON,omitempty"`
CustomTLSCertificateLinks []string `json:"customTlsCertificateLinks,omitempty"`
AdditionalTags []string `json:"additionalTags,omitempty"`
Bindings map[string]interface{} `json:"bindings,omitempty"`
MultipleImages []map[string]interface{} `json:"multipleImages,omitempty"`
}
type cnbBuildCommonPipelineEnvironment struct {
container struct {
registryURL string
imageNameTag string
registryURL string
imageNameTag string
imageNames []string
imageNameTags []string
}
}
@ -46,6 +49,8 @@ func (p *cnbBuildCommonPipelineEnvironment) persist(path, resourceName string) {
}{
{category: "container", name: "registryUrl", value: p.container.registryURL},
{category: "container", name: "imageNameTag", value: p.container.imageNameTag},
{category: "container", name: "imageNames", value: p.container.imageNames},
{category: "container", name: "imageNameTags", value: p.container.imageNameTags},
}
errCount := 0
@ -160,7 +165,7 @@ func addCnbBuildFlags(cmd *cobra.Command, stepConfig *cnbBuildOptions) {
cmd.Flags().StringSliceVar(&stepConfig.Buildpacks, "buildpacks", []string{}, "List of custom buildpacks to use in the form of '$HOSTNAME/$REPO[:$TAG]'.")
cmd.Flags().StringVar(&stepConfig.Path, "path", os.Getenv("PIPER_path"), "The path should either point to a directory with your sources or an artifact in zip format.\nThis property determines the input to the buildpack.\n")
cmd.Flags().StringVar(&stepConfig.ProjectDescriptor, "projectDescriptor", `project.toml`, "Path to the project.toml file.\nSee [buildpacks.io](https://buildpacks.io/docs/reference/config/project-descriptor/) for the reference.\nParameters passed to the cnbBuild step will take precedence over the parameters set in the project.toml file, except the `env` block.\nEnvironment variables declared in a project descriptor file, will be merged with the `buildEnvVars` property, with the `buildEnvVars` having a precedence.\n\nNote: Inline buildpacks (see [specification](https://buildpacks.io/docs/reference/config/project-descriptor/#build-_table-optional_)) are not supported yet.\n")
cmd.Flags().StringVar(&stepConfig.ProjectDescriptor, "projectDescriptor", `project.toml`, "Relative path to the project.toml file.\nSee [buildpacks.io](https://buildpacks.io/docs/reference/config/project-descriptor/) for the reference.\nParameters passed to the cnbBuild step will take precedence over the parameters set in the project.toml file, except the `env` block.\nEnvironment variables declared in a project descriptor file, will be merged with the `buildEnvVars` property, with the `buildEnvVars` having a precedence.\n\nNote: The project descriptor path should be relative to what is set in the [path](#path) property. If the `path` property is pointing to a zip archive (e.g. jar file), project descriptor path will be relative to the root of the workspace.\nNote: Inline buildpacks (see [specification](https://buildpacks.io/docs/reference/config/project-descriptor/#build-_table-optional_)) are not supported yet.\n")
cmd.Flags().StringVar(&stepConfig.DockerConfigJSON, "dockerConfigJSON", os.Getenv("PIPER_dockerConfigJSON"), "Path to the file `.docker/config.json` - this is typically provided by your CI/CD system. You can find more details about the Docker credentials in the [Docker documentation](https://docs.docker.com/engine/reference/commandline/login/).")
cmd.Flags().StringSliceVar(&stepConfig.CustomTLSCertificateLinks, "customTlsCertificateLinks", []string{}, "List containing download links of custom TLS certificates. This is required to ensure trusted connections to registries with custom certificates.")
cmd.Flags().StringSliceVar(&stepConfig.AdditionalTags, "additionalTags", []string{}, "List of tags which will be pushed to the registry (additionally to the provided `containerImageTag`), e.g. \"latest\".")
@ -311,6 +316,14 @@ func cnbBuildMetadata() config.StepData {
Mandatory: false,
Aliases: []config.Alias{},
},
{
Name: "multipleImages",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "[]map[string]interface{}",
Mandatory: false,
Aliases: []config.Alias{{Name: "images"}},
},
},
},
Containers: []config.Container{
@ -324,6 +337,8 @@ func cnbBuildMetadata() config.StepData {
Parameters: []map[string]interface{}{
{"name": "container/registryUrl"},
{"name": "container/imageNameTag"},
{"name": "container/imageNames", "type": "[]string"},
{"name": "container/imageNameTags", "type": "[]string"},
},
},
},

View File

@ -29,10 +29,11 @@ func addBuilderFiles(utils *cnbutils.MockUtils) {
utils.FilesMock.AddFile(creatorPath, []byte(`xyz`))
}
func assertLifecycleCalls(t *testing.T, runner *mock.ExecMockRunner) {
assert.Equal(t, creatorPath, runner.Calls[0].Exec)
func assertLifecycleCalls(t *testing.T, runner *mock.ExecMockRunner, callNo int) {
require.GreaterOrEqual(t, len(runner.Calls), callNo)
assert.Equal(t, creatorPath, runner.Calls[callNo-1].Exec)
for _, arg := range []string{"-no-color", "-buildpacks", "/cnb/buildpacks", "-order", "/cnb/order.toml", "-platform", "/tmp/platform"} {
assert.Contains(t, runner.Calls[0].Params, arg)
assert.Contains(t, runner.Calls[callNo-1].Params, arg)
}
}
@ -58,12 +59,12 @@ func TestRunCnbBuild(t *testing.T) {
utils.FilesMock.AddFile("project.toml", []byte(projectToml))
addBuilderFiles(&utils)
err := runCnbBuild(&config, &telemetry.CustomData{}, &utils, &commonPipelineEnvironment, &piperhttp.Client{})
err := callCnbBuild(&config, &telemetry.CustomData{}, &cnbBuildTelemetry{}, &utils, &commonPipelineEnvironment, &piperhttp.Client{})
assert.NoError(t, err)
require.NoError(t, err)
runner := utils.ExecMockRunner
assert.Contains(t, runner.Env, "CNB_REGISTRY_AUTH={\"my-registry\":\"Basic dXNlcjpwYXNz\"}")
assertLifecycleCalls(t, runner)
assertLifecycleCalls(t, runner, 1)
assert.Contains(t, runner.Calls[0].Params, fmt.Sprintf("%s/%s:%s", imageRegistry, config.ContainerImageName, config.ContainerImageTag))
assert.Equal(t, config.ContainerRegistryURL, commonPipelineEnvironment.container.registryURL)
assert.Equal(t, "my-image:0.0.1", commonPipelineEnvironment.container.imageNameTag)
@ -89,21 +90,22 @@ func TestRunCnbBuild(t *testing.T) {
addBuilderFiles(&utils)
telemetryData := telemetry.CustomData{}
err := runCnbBuild(&config, &telemetryData, &utils, &commonPipelineEnvironment, &piperhttp.Client{})
err := callCnbBuild(&config, &telemetryData, &cnbBuildTelemetry{}, &utils, &commonPipelineEnvironment, &piperhttp.Client{})
require.NoError(t, err)
runner := utils.ExecMockRunner
assert.Contains(t, runner.Env, "CNB_REGISTRY_AUTH={\"my-registry\":\"Basic dXNlcjpwYXNz\"}")
assertLifecycleCalls(t, runner)
assertLifecycleCalls(t, runner, 1)
assert.Contains(t, runner.Calls[0].Params, fmt.Sprintf("%s/%s:%s", imageRegistry, "io-buildpacks-my-app", config.ContainerImageTag))
assert.Equal(t, config.ContainerRegistryURL, commonPipelineEnvironment.container.registryURL)
assert.Equal(t, "io-buildpacks-my-app:0.0.1", commonPipelineEnvironment.container.imageNameTag)
customDataAsString := telemetryData.Custom1
customData := cnbBuildTelemetryData{}
customData := cnbBuildTelemetry{}
err = json.Unmarshal([]byte(customDataAsString), &customData)
require.NoError(t, err)
assert.Equal(t, "root", string(customData.Path))
assert.Equal(t, 1, len(customData.Data))
assert.Equal(t, "root", string(customData.Data[0].Path))
})
t.Run("success case (registry with https)", func(t *testing.T) {
@ -120,12 +122,12 @@ func TestRunCnbBuild(t *testing.T) {
utils.FilesMock.AddFile(config.DockerConfigJSON, []byte(`{"auths":{"my-registry":{"auth":"dXNlcjpwYXNz"}}}`))
addBuilderFiles(&utils)
err := runCnbBuild(&config, &telemetry.CustomData{}, &utils, &commonPipelineEnvironment, &piperhttp.Client{})
err := callCnbBuild(&config, &telemetry.CustomData{}, &cnbBuildTelemetry{}, &utils, &commonPipelineEnvironment, &piperhttp.Client{})
assert.NoError(t, err)
require.NoError(t, err)
runner := utils.ExecMockRunner
assert.Contains(t, runner.Env, "CNB_REGISTRY_AUTH={\"my-registry\":\"Basic dXNlcjpwYXNz\"}")
assertLifecycleCalls(t, runner)
assertLifecycleCalls(t, runner, 1)
assert.Contains(t, runner.Calls[0].Params, fmt.Sprintf("%s/%s:%s", imageRegistry, config.ContainerImageName, config.ContainerImageTag))
assert.Equal(t, config.ContainerRegistryURL, commonPipelineEnvironment.container.registryURL)
assert.Equal(t, "my-image:0.0.1", commonPipelineEnvironment.container.imageNameTag)
@ -145,12 +147,12 @@ func TestRunCnbBuild(t *testing.T) {
utils.FilesMock.AddFile(config.DockerConfigJSON, []byte(`{"auths":{"my-registry":{"auth":"dXNlcjpwYXNz"}}}`))
addBuilderFiles(&utils)
err := runCnbBuild(&config, &telemetry.CustomData{}, &utils, &commonPipelineEnvironment, &piperhttp.Client{})
err := callCnbBuild(&config, &telemetry.CustomData{}, &cnbBuildTelemetry{}, &utils, &commonPipelineEnvironment, &piperhttp.Client{})
assert.NoError(t, err)
require.NoError(t, err)
runner := utils.ExecMockRunner
assert.Contains(t, runner.Env, "CNB_REGISTRY_AUTH={\"my-registry\":\"Basic dXNlcjpwYXNz\"}")
assertLifecycleCalls(t, runner)
assertLifecycleCalls(t, runner, 1)
assert.Contains(t, runner.Calls[0].Params, fmt.Sprintf("%s/%s:%s", config.ContainerRegistryURL, config.ContainerImageName, config.ContainerImageTag))
assert.Equal(t, fmt.Sprintf("https://%s", config.ContainerRegistryURL), commonPipelineEnvironment.container.registryURL)
assert.Equal(t, "my-image:0.0.1", commonPipelineEnvironment.container.imageNameTag)
@ -175,9 +177,9 @@ func TestRunCnbBuild(t *testing.T) {
utils.FilesMock.AddFile(config.DockerConfigJSON, []byte(`{"auths":{"my-registry":{"auth":"dXNlcjpwYXNz"}}}`))
addBuilderFiles(&utils)
err := runCnbBuild(&config, &telemetry.CustomData{}, &utils, &commonPipelineEnvironment, &piperhttp.Client{})
err := callCnbBuild(&config, &telemetry.CustomData{}, &cnbBuildTelemetry{}, &utils, &commonPipelineEnvironment, &piperhttp.Client{})
assert.NoError(t, err)
require.NoError(t, err)
runner := utils.ExecMockRunner
assert.Contains(t, runner.Env, "CNB_REGISTRY_AUTH={\"my-registry\":\"Basic dXNlcjpwYXNz\"}")
assert.Equal(t, creatorPath, runner.Calls[0].Exec)
@ -190,7 +192,7 @@ func TestRunCnbBuild(t *testing.T) {
renamedFileExists, _ := utils.FileExists("/path/to/config.json")
assert.False(t, initialFileExists)
assert.False(t, renamedFileExists)
assert.True(t, renamedFileExists)
})
t.Run("success case (customTlsCertificates)", func(t *testing.T) {
@ -218,18 +220,18 @@ func TestRunCnbBuild(t *testing.T) {
utils.FilesMock.AddFile(config.DockerConfigJSON, []byte(`{"auths":{"my-registry":{"auth":"dXNlcjpwYXNz"}}}`))
addBuilderFiles(&utils)
err := runCnbBuild(&config, &telemetry.CustomData{}, &utils, &commonPipelineEnvironment, client)
assert.NoError(t, err)
err := callCnbBuild(&config, &telemetry.CustomData{}, &cnbBuildTelemetry{}, &utils, &commonPipelineEnvironment, client)
require.NoError(t, err)
result, err := utils.FilesMock.FileRead(caCertsTmpFile)
assert.NoError(t, err)
require.NoError(t, err)
assert.Equal(t, "test\ntestCert\ntestCert\n", string(result))
assert.NoError(t, err)
require.NoError(t, err)
runner := utils.ExecMockRunner
assert.Contains(t, runner.Env, "CNB_REGISTRY_AUTH={\"my-registry\":\"Basic dXNlcjpwYXNz\"}")
assert.Contains(t, runner.Env, fmt.Sprintf("SSL_CERT_FILE=%s", caCertsTmpFile))
assertLifecycleCalls(t, runner)
assertLifecycleCalls(t, runner, 1)
assert.Contains(t, runner.Calls[0].Params, fmt.Sprintf("%s/%s:%s", config.ContainerRegistryURL, config.ContainerImageName, config.ContainerImageTag))
})
@ -248,11 +250,11 @@ func TestRunCnbBuild(t *testing.T) {
utils.FilesMock.AddFile(config.DockerConfigJSON, []byte(`{"auths":{"my-registry":{"auth":"dXNlcjpwYXNz"}}}`))
addBuilderFiles(&utils)
err := runCnbBuild(&config, &telemetry.CustomData{}, &utils, &commonPipelineEnvironment, &piperhttp.Client{})
assert.NoError(t, err)
err := callCnbBuild(&config, &telemetry.CustomData{}, &cnbBuildTelemetry{}, &utils, &commonPipelineEnvironment, &piperhttp.Client{})
require.NoError(t, err)
runner := utils.ExecMockRunner
assertLifecycleCalls(t, runner)
assertLifecycleCalls(t, runner, 1)
assert.Contains(t, runner.Calls[0].Params, fmt.Sprintf("%s/%s:%s", config.ContainerRegistryURL, config.ContainerImageName, config.ContainerImageTag))
assert.Contains(t, runner.Calls[0].Params, fmt.Sprintf("%s/%s:3", config.ContainerRegistryURL, config.ContainerImageName))
assert.Contains(t, runner.Calls[0].Params, fmt.Sprintf("%s/%s:3.1", config.ContainerRegistryURL, config.ContainerImageName))
@ -275,11 +277,11 @@ func TestRunCnbBuild(t *testing.T) {
utils.FilesMock.AddFile("/workspace/pom.xml", []byte("test"))
addBuilderFiles(&utils)
err := runCnbBuild(&config, &telemetry.CustomData{}, &utils, &cnbBuildCommonPipelineEnvironment{}, &piperhttp.Client{})
assert.NoError(t, err)
err := callCnbBuild(&config, &telemetry.CustomData{}, &cnbBuildTelemetry{}, &utils, &cnbBuildCommonPipelineEnvironment{}, &piperhttp.Client{})
require.NoError(t, err)
runner := utils.ExecMockRunner
assertLifecycleCalls(t, runner)
assertLifecycleCalls(t, runner, 1)
assert.True(t, utils.FilesMock.HasCreatedSymlink("/jenkins/target", "/workspace/target"))
})
@ -299,11 +301,11 @@ func TestRunCnbBuild(t *testing.T) {
utils.FilesMock.AddFile(config.DockerConfigJSON, []byte(`{"auths":{"my-registry":{"auth":"dXNlcjpwYXNz"}}}`))
addBuilderFiles(&utils)
err := runCnbBuild(&config, &telemetry.CustomData{}, &utils, &cnbBuildCommonPipelineEnvironment{}, &piperhttp.Client{})
assert.NoError(t, err)
err := callCnbBuild(&config, &telemetry.CustomData{}, &cnbBuildTelemetry{}, &utils, &cnbBuildCommonPipelineEnvironment{}, &piperhttp.Client{})
require.NoError(t, err)
runner := utils.ExecMockRunner
assertLifecycleCalls(t, runner)
assertLifecycleCalls(t, runner, 1)
assert.False(t, utils.FilesMock.HasCreatedSymlink("/jenkins/target", "/workspace/target"))
})
@ -322,7 +324,7 @@ func TestRunCnbBuild(t *testing.T) {
utils.FilesMock.AddFile(config.DockerConfigJSON, []byte(`{"auths":{"my-registry":"dXNlcjpwYXNz"}}`))
addBuilderFiles(&utils)
err := runCnbBuild(&config, &telemetry.CustomData{}, &utils, &commonPipelineEnvironment, &piperhttp.Client{})
err := callCnbBuild(&config, &telemetry.CustomData{}, &cnbBuildTelemetry{}, &utils, &commonPipelineEnvironment, &piperhttp.Client{})
assert.EqualError(t, err, "failed to generate CNB_REGISTRY_AUTH: json: cannot unmarshal string into Go struct field ConfigFile.auths of type types.AuthConfig")
})
@ -339,7 +341,7 @@ func TestRunCnbBuild(t *testing.T) {
utils := newCnbBuildTestsUtils()
addBuilderFiles(&utils)
err := runCnbBuild(&config, &telemetry.CustomData{}, &utils, &commonPipelineEnvironment, &piperhttp.Client{})
err := callCnbBuild(&config, &telemetry.CustomData{}, &cnbBuildTelemetry{}, &utils, &commonPipelineEnvironment, &piperhttp.Client{})
assert.EqualError(t, err, "failed to generate CNB_REGISTRY_AUTH: could not read 'not-there/config.json'")
})
@ -356,7 +358,7 @@ func TestRunCnbBuild(t *testing.T) {
utils := newCnbBuildTestsUtils()
addBuilderFiles(&utils)
err := runCnbBuild(&config, &telemetry.CustomData{}, &utils, &commonPipelineEnvironment, &piperhttp.Client{})
err := callCnbBuild(&config, &telemetry.CustomData{}, &cnbBuildTelemetry{}, &utils, &commonPipelineEnvironment, &piperhttp.Client{})
assert.EqualError(t, err, "failed to rename DockerConfigJSON file 'not-there': renaming file 'not-there' is not supported, since it does not exist, or is not a leaf-entry")
})
@ -367,7 +369,7 @@ func TestRunCnbBuild(t *testing.T) {
utils := newCnbBuildTestsUtils()
err := runCnbBuild(&config, &telemetry.CustomData{}, &utils, &commonPipelineEnvironment, &piperhttp.Client{})
err := callCnbBuild(&config, &telemetry.CustomData{}, &cnbBuildTelemetry{}, &utils, &commonPipelineEnvironment, &piperhttp.Client{})
assert.EqualError(t, err, "the provided dockerImage is not a valid builder: binary '/cnb/lifecycle/creator' not found")
})
@ -387,7 +389,7 @@ func TestRunCnbBuild(t *testing.T) {
utils.FilesMock.AddFile(config.DockerConfigJSON, []byte(`{"auths":{"my-registry":{"auth":"dXNlcjpwYXNz"}}}`))
addBuilderFiles(&utils)
err := runCnbBuild(&config, &telemetry.CustomData{}, &utils, &commonPipelineEnvironment, &piperhttp.Client{})
err := callCnbBuild(&config, &telemetry.CustomData{}, &cnbBuildTelemetry{}, &utils, &commonPipelineEnvironment, &piperhttp.Client{})
assert.EqualError(t, err, "failed to copy certificates: cannot copy '/etc/ssl/certs/ca-certificates.crt': file does not exist")
})
@ -409,7 +411,8 @@ func TestRunCnbBuild(t *testing.T) {
utils := newCnbBuildTestsUtils()
utils.FilesMock.AddFile(config.DockerConfigJSON, []byte(`{"auths":{"my-registry":{"auth":"dXNlcjpwYXNz"}}}`))
utils.FilesMock.AddFile("project.toml", []byte(`[project]
utils.FilesMock.AddDir("target")
utils.FilesMock.AddFile("target/project.toml", []byte(`[project]
id = "test"
name = "test"
version = "1.0.0"
@ -421,35 +424,36 @@ exclude = ["*.tar"]
[[build.buildpacks]]
uri = "some-buildpack"`))
utils.FilesMock.AddFile("a_file", []byte(`{}`))
utils.FilesMock.AddDir("target")
utils.FilesMock.AddFile("target/somelib.jar", []byte(`FFFFFF`))
addBuilderFiles(&utils)
telemetryData := telemetry.CustomData{}
err := runCnbBuild(&config, &telemetryData, &utils, &commonPipelineEnvironment, &piperhttp.Client{})
assert.NoError(t, err)
cnbTelemetry := cnbBuildTelemetry{Version: 2}
err := callCnbBuild(&config, &telemetryData, &cnbTelemetry, &utils, &commonPipelineEnvironment, &piperhttp.Client{})
require.NoError(t, err)
customDataAsString := telemetryData.Custom1
customData := cnbBuildTelemetryData{}
customData := cnbBuildTelemetry{}
err = json.Unmarshal([]byte(customDataAsString), &customData)
assert.NoError(t, err)
assert.Equal(t, 1, customData.Version)
assert.Equal(t, "3.1.5", customData.ImageTag)
assert.Equal(t, "folder", string(customData.Path))
assert.Contains(t, customData.AdditionalTags, "latest")
assert.Contains(t, customData.BindingKeys, "SECRET")
require.NoError(t, err)
assert.Equal(t, 2, customData.Version)
require.Equal(t, 1, len(customData.Data))
assert.Equal(t, "3.1.5", customData.Data[0].ImageTag)
assert.Equal(t, "folder", string(customData.Data[0].Path))
assert.Contains(t, customData.Data[0].AdditionalTags, "latest")
assert.Contains(t, customData.Data[0].BindingKeys, "SECRET")
assert.Contains(t, customData.Buildpacks.FromConfig, "paketobuildpacks/java")
assert.NotContains(t, customData.Buildpacks.FromProjectDescriptor, "paketobuildpacks/java")
assert.Contains(t, customData.Buildpacks.FromProjectDescriptor, "<redacted>")
assert.NotContains(t, customData.Buildpacks.Overall, "<redacted>")
assert.Contains(t, customData.Buildpacks.Overall, "paketobuildpacks/java")
assert.Contains(t, customData.Data[0].Buildpacks.FromConfig, "paketobuildpacks/java")
assert.NotContains(t, customData.Data[0].Buildpacks.FromProjectDescriptor, "paketobuildpacks/java")
assert.Contains(t, customData.Data[0].Buildpacks.FromProjectDescriptor, "<redacted>")
assert.NotContains(t, customData.Data[0].Buildpacks.Overall, "<redacted>")
assert.Contains(t, customData.Data[0].Buildpacks.Overall, "paketobuildpacks/java")
assert.True(t, customData.ProjectDescriptor.Used)
assert.False(t, customData.ProjectDescriptor.IncludeUsed)
assert.True(t, customData.ProjectDescriptor.ExcludeUsed)
assert.True(t, customData.Data[0].ProjectDescriptor.Used)
assert.False(t, customData.Data[0].ProjectDescriptor.IncludeUsed)
assert.True(t, customData.Data[0].ProjectDescriptor.ExcludeUsed)
})
t.Run("success case (build env telemetry was added)", func(t *testing.T) {
@ -486,27 +490,70 @@ uri = "some-buildpack"
addBuilderFiles(&utils)
telemetryData := telemetry.CustomData{}
err := runCnbBuild(&config, &telemetryData, &utils, &commonPipelineEnvironment, &piperhttp.Client{})
assert.NoError(t, err)
cnbTelemetry := cnbBuildTelemetry{Version: 2}
err := callCnbBuild(&config, &telemetryData, &cnbTelemetry, &utils, &commonPipelineEnvironment, &piperhttp.Client{})
require.NoError(t, err)
customDataAsString := telemetryData.Custom1
customData := cnbBuildTelemetryData{}
customData := cnbBuildTelemetry{}
err = json.Unmarshal([]byte(customDataAsString), &customData)
assert.NoError(t, err)
assert.Contains(t, customData.BuildEnv.KeysFromConfig, "CONFIG_KEY")
assert.NotContains(t, customData.BuildEnv.KeysFromProjectDescriptor, "CONFIG_KEY")
assert.Contains(t, customData.BuildEnv.KeysOverall, "CONFIG_KEY")
require.NoError(t, err)
require.Equal(t, 1, len(customData.Data))
assert.Contains(t, customData.Data[0].BuildEnv.KeysFromConfig, "CONFIG_KEY")
assert.NotContains(t, customData.Data[0].BuildEnv.KeysFromProjectDescriptor, "CONFIG_KEY")
assert.Contains(t, customData.Data[0].BuildEnv.KeysOverall, "CONFIG_KEY")
assert.NotContains(t, customData.BuildEnv.KeysFromConfig, "PROJECT_KEY")
assert.Contains(t, customData.BuildEnv.KeysFromProjectDescriptor, "PROJECT_KEY")
assert.Contains(t, customData.BuildEnv.KeysOverall, "PROJECT_KEY")
assert.NotContains(t, customData.Data[0].BuildEnv.KeysFromConfig, "PROJECT_KEY")
assert.Contains(t, customData.Data[0].BuildEnv.KeysFromProjectDescriptor, "PROJECT_KEY")
assert.Contains(t, customData.Data[0].BuildEnv.KeysOverall, "PROJECT_KEY")
assert.Equal(t, "8", customData.BuildEnv.KeyValues["BP_JVM_VERSION"])
assert.Equal(t, "11", customData.BuildEnv.KeyValues["BP_NODE_VERSION"])
assert.NotContains(t, customData.BuildEnv.KeyValues, "PROJECT_KEY")
assert.Equal(t, "8", customData.Data[0].BuildEnv.KeyValues["BP_JVM_VERSION"])
assert.Equal(t, "11", customData.Data[0].BuildEnv.KeyValues["BP_NODE_VERSION"])
assert.NotContains(t, customData.Data[0].BuildEnv.KeyValues, "PROJECT_KEY")
assert.Contains(t, customData.Buildpacks.Overall, "some-buildpack")
assert.Contains(t, customData.Data[0].Buildpacks.Overall, "some-buildpack")
})
t.Run("success case (multiple images configured)", func(t *testing.T) {
t.Parallel()
commonPipelineEnvironment := cnbBuildCommonPipelineEnvironment{}
config := cnbBuildOptions{
ContainerImageTag: "3.1.5",
ContainerRegistryURL: imageRegistry,
DockerConfigJSON: "/path/to/my-config.json",
AdditionalTags: []string{"3", "3.1", "3.1", "3.1.5"},
MultipleImages: []map[string]interface{}{{"ContainerImageName": "my-image-0"}, {"ContainerImageName": "my-image-1"}},
}
expectedImageCount := len(config.MultipleImages)
utils := newCnbBuildTestsUtils()
utils.FilesMock.AddFile(config.DockerConfigJSON, []byte(`{"auths":{"my-registry":{"auth":"dXNlcjpwYXNz"}}}`))
addBuilderFiles(&utils)
telemetryData := telemetry.CustomData{}
err := callCnbBuild(&config, &telemetryData, &cnbBuildTelemetry{}, &utils, &commonPipelineEnvironment, &piperhttp.Client{})
require.NoError(t, err)
customDataAsString := telemetryData.Custom1
customData := cnbBuildTelemetry{}
err = json.Unmarshal([]byte(customDataAsString), &customData)
require.Equal(t, expectedImageCount, len(customData.Data))
runner := utils.ExecMockRunner
require.Equal(t, expectedImageCount, len(runner.Calls))
for i, call := range runner.Calls {
assert.Equal(t, 4, len(customData.Data[i].AdditionalTags))
assertLifecycleCalls(t, runner, i+1)
containerImageName := fmt.Sprintf("my-image-%d", i)
assert.Contains(t, call.Params, fmt.Sprintf("%s/%s:%s", config.ContainerRegistryURL, containerImageName, config.ContainerImageTag))
assert.Contains(t, call.Params, fmt.Sprintf("%s/%s:3", config.ContainerRegistryURL, containerImageName))
assert.Contains(t, call.Params, fmt.Sprintf("%s/%s:3.1", config.ContainerRegistryURL, containerImageName))
assert.Contains(t, call.Params, fmt.Sprintf("%s/%s:3.1.5", config.ContainerRegistryURL, containerImageName))
}
assert.Equal(t, "my-image-0:3.1.5", commonPipelineEnvironment.container.imageNameTag)
})
}

1
go.mod
View File

@ -30,6 +30,7 @@ require (
github.com/hashicorp/go-retryablehttp v0.7.0
github.com/hashicorp/vault v1.8.5
github.com/hashicorp/vault/api v1.1.2-0.20210713235431-1fc8af4c041f
github.com/imdario/mergo v0.3.12
github.com/influxdata/influxdb-client-go/v2 v2.5.1
github.com/jarcoal/httpmock v1.0.8
github.com/magiconair/properties v1.8.5

View File

@ -124,7 +124,7 @@ func TestNonZipPath(t *testing.T) {
container.whenRunningPiperCommand("cnbBuild", "--containerImageName", "not-found", "--containerImageTag", "0.0.1", "--containerRegistryUrl", registryURL, "--path", "mta.yaml")
container.assertHasOutput(t, "Copying 'mta.yaml' into '/workspace' failed: application path must be a directory or zip")
container.assertHasOutput(t, "Copying '/project/mta.yaml' into '/workspace' failed: application path must be a directory or zip")
container.terminate(t)
}
@ -211,3 +211,25 @@ func TestBindings(t *testing.T) {
container.assertHasFile(t, "/tmp/platform/bindings/dummy-binding/dummy.yml")
container.terminate(t)
}
func TestMultiImage(t *testing.T) {
t.Parallel()
ctx := context.Background()
registryContainer := setupDockerRegistry(t, ctx)
defer registryContainer.Terminate(ctx)
container := givenThisContainer(t, IntegrationTestDockerExecRunnerBundle{
Image: "paketobuildpacks/builder:full",
User: "cnb",
TestDir: []string{"testdata", "TestCnbIntegration"},
Network: fmt.Sprintf("container:%s", registryContainer.GetContainerID()),
})
container.whenRunningPiperCommand("cnbBuild", "--customConfig", "config_multi_image.yml")
container.assertHasOutput(t, "Previous image with name \"localhost:5000/io-buildpacks-my-app:latest\" not found")
container.assertHasOutput(t, "Saving localhost:5000/io-buildpacks-my-app:latest...")
container.assertHasOutput(t, "Previous image with name \"localhost:5000/go-app:v1.0.0\" not found")
container.assertHasOutput(t, "Saving localhost:5000/go-app:v1.0.0...")
container.terminate(t)
}

View File

@ -0,0 +1,9 @@
steps:
cnbBuild:
containerRegistryUrl: localhost:5000
containerImageTag: latest
multipleImages:
- path: project
- containerImageName: go-app
containerImageTag: v1.0.0
path: zip/go.zip

View File

@ -0,0 +1,42 @@
package project
import (
"path/filepath"
"github.com/SAP/jenkins-library/pkg/cnbutils"
"github.com/SAP/jenkins-library/pkg/log"
)
// 1. If "path" is a directory, check for "${path}/${descriptor}"
// 2. If "path" is a file, check for "${PWD}/${descriptor}"
func ResolvePath(descriptor, path string, utils cnbutils.BuildUtils) (string, error) {
isDir, err := utils.DirExists(path)
if err != nil {
return "", err
}
if isDir {
return getFilename(path, descriptor, utils)
}
pwd, err := utils.Getwd()
if err != nil {
return "", err
}
return getFilename(pwd, descriptor, utils)
}
func getFilename(folder, filename string, utils cnbutils.BuildUtils) (string, error) {
descPath := filepath.Join(folder, filename)
exists, err := utils.FileExists(descPath)
if err != nil {
return "", err
}
if exists {
return descPath, nil
}
log.Entry().Infof("Project descriptor with the path '%s' was not found", descPath)
return "", nil
}

View File

@ -0,0 +1,63 @@
package project_test
import (
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/SAP/jenkins-library/pkg/cnbutils"
"github.com/SAP/jenkins-library/pkg/cnbutils/project"
"github.com/SAP/jenkins-library/pkg/mock"
)
func TestResolvePath(t *testing.T) {
t.Run("project descriptor and no path is maintained, it is located in the root", func(t *testing.T) {
utils := &cnbutils.MockUtils{
FilesMock: &mock.FilesMock{},
}
utils.AddFile("project.toml", []byte(""))
location, err := project.ResolvePath("project.toml", "", utils)
require.NoError(t, err)
assert.Equal(t, "project.toml", location)
})
t.Run("project descriptor and path is is a file, it is located in the root", func(t *testing.T) {
utils := &cnbutils.MockUtils{
FilesMock: &mock.FilesMock{},
}
utils.CurrentDir = "/workdir"
utils.AddFile("project.toml", []byte(""))
utils.AddFile("test/file.zip", []byte(""))
location, err := project.ResolvePath("project.toml", "test/file.zip", utils)
require.NoError(t, err)
assert.Equal(t, "/workdir/project.toml", location)
})
t.Run("project descriptor and path is is a dir, it is located in the path", func(t *testing.T) {
utils := &cnbutils.MockUtils{
FilesMock: &mock.FilesMock{},
}
utils.AddFile("test/project.toml", []byte(""))
location, err := project.ResolvePath("project.toml", "test", utils)
require.NoError(t, err)
assert.Equal(t, filepath.Join("test", "project.toml"), location)
})
t.Run("project descriptor does not exist, empty string", func(t *testing.T) {
utils := &cnbutils.MockUtils{
FilesMock: &mock.FilesMock{},
}
location, err := project.ResolvePath("project.toml", "", utils)
require.NoError(t, err)
assert.Equal(t, "", location)
})
}

View File

@ -37,7 +37,7 @@ type stepInfo struct {
Secrets []config.StepSecrets
}
//StepGoTemplate ...
// StepGoTemplate ...
const stepGoTemplate = `// Code generated by piper's step-generator. DO NOT EDIT.
package cmd
@ -167,10 +167,10 @@ func {{.CobraCmdFuncName}}() *cobra.Command {
handler := func() {
{{- range $notused, $oRes := .OutputResources }}
{{ index $oRes "name" }}.persist(
{{- if eq (index $oRes "type") "reports" -}}stepConfig,
{{- if $.ExportPrefix}}{{ $.ExportPrefix }}.{{end}}GeneralConfig.GCPJsonKeyFilePath,
{{- if $.ExportPrefix}}{{ $.ExportPrefix }}.{{end}}GeneralConfig.GCSBucketId,
{{- if $.ExportPrefix}}{{ $.ExportPrefix }}.{{end}}GeneralConfig.GCSFolderPath,
{{- if eq (index $oRes "type") "reports" -}}stepConfig,
{{- if $.ExportPrefix}}{{ $.ExportPrefix }}.{{end}}GeneralConfig.GCPJsonKeyFilePath,
{{- if $.ExportPrefix}}{{ $.ExportPrefix }}.{{end}}GeneralConfig.GCSBucketId,
{{- if $.ExportPrefix}}{{ $.ExportPrefix }}.{{end}}GeneralConfig.GCSFolderPath,
{{- if $.ExportPrefix}}{{ $.ExportPrefix }}.{{end}}GeneralConfig.GCSSubFolder
{{- else -}}
{{if $.ExportPrefix}}{{ $.ExportPrefix }}.{{end}}GeneralConfig.EnvRootPath, {{ index $oRes "name" | quote }}{{- end -}}
@ -330,7 +330,7 @@ func {{ .StepName }}Metadata() config.StepData {
}
`
//StepTestGoTemplate ...
// StepTestGoTemplate ...
const stepTestGoTemplate = `package cmd
import (
@ -574,7 +574,7 @@ func ProcessMetaFiles(metadataFiles []string, targetDir string, stepHelperData S
}
func setDefaultParameters(stepData *config.StepData) (bool, error) {
//ToDo: custom function for default handling, support all relevant parameter types
// ToDo: custom function for default handling, support all relevant parameter types
osImportRequired := false
for k, param := range stepData.Spec.Inputs.Parameters {
@ -591,7 +591,7 @@ func setDefaultParameters(stepData *config.StepData) (bool, error) {
case "[]string":
// ToDo: Check if default should be read from env
param.Default = "[]string{}"
case "map[string]interface{}":
case "map[string]interface{}", "[]map[string]interface{}":
// Currently we don't need to set a default here since in this case the default
// is never used. Needs to be changed in case we enable cli parameter handling
// for that type.
@ -612,7 +612,7 @@ func setDefaultParameters(stepData *config.StepData) (bool, error) {
param.Default = fmt.Sprintf("`%v`", param.Default)
case "[]string":
param.Default = fmt.Sprintf("[]string{`%v`}", strings.Join(getStringSliceFromInterface(param.Default), "`, `"))
case "map[string]interface{}":
case "map[string]interface{}", "[]map[string]interface{}":
// Currently we don't need to set a default here since in this case the default
// is never used. Needs to be changed in case we enable cli parameter handling
// for that type.
@ -771,7 +771,7 @@ func MetadataFiles(sourceDirectory string) ([]string, error) {
}
func isCLIParam(myType string) bool {
return myType != "map[string]interface{}"
return myType != "map[string]interface{}" && myType != "[]map[string]interface{}"
}
func stepTemplate(myStepInfo stepInfo, templateName, goTemplate string) []byte {
@ -812,7 +812,7 @@ func longName(long string) string {
}
func resourceFieldType(fieldType string) string {
//TODO: clarify why fields are initialized with <nil> and tags are initialized with ''
// TODO: clarify why fields are initialized with <nil> and tags are initialized with ''
if len(fieldType) == 0 || fieldType == "<nil>" {
return "string"
}

View File

@ -113,11 +113,12 @@ spec:
- name: projectDescriptor
type: string
description: |
Path to the project.toml file.
Relative path to the project.toml file.
See [buildpacks.io](https://buildpacks.io/docs/reference/config/project-descriptor/) for the reference.
Parameters passed to the cnbBuild step will take precedence over the parameters set in the project.toml file, except the `env` block.
Environment variables declared in a project descriptor file, will be merged with the `buildEnvVars` property, with the `buildEnvVars` having a precedence.
Note: The project descriptor path should be relative to what is set in the [path](#path) property. If the `path` property is pointing to a zip archive (e.g. jar file), project descriptor path will be relative to the root of the workspace.
Note: Inline buildpacks (see [specification](https://buildpacks.io/docs/reference/config/project-descriptor/#build-_table-optional_)) are not supported yet.
default: project.toml
scope:
@ -188,6 +189,36 @@ spec:
- PARAMETERS
- STAGES
- STEPS
- name: multipleImages
aliases:
- name: images
type: "[]map[string]interface{}"
description: |
This parameter is only needed if `cnbBuild` should create multiple images.
Otherwise it can be ignored!!!
In case of multiple images, this array contains one entry for each image. That
entry can override any parameter from the main section, e.g.
```yaml
containerImageTag: latest
containerRegistryUrl: docker.io/example
dockerConfigJsonCredentialsId: CREDENTIALS
multipleImages:
- containerImageName: java-app
buildpacks:
- "java"
path: "source/java"
- containerImageName: nodejs-app
containerImageTag: v1.0.0
buildpacks:
- "nodejs"
path: "source/nodejs"
```
scope:
- PARAMETERS
- STAGES
- STEPS
outputs:
resources:
- name: commonPipelineEnvironment
@ -195,5 +226,9 @@ spec:
params:
- name: container/registryUrl
- name: container/imageNameTag
- name: container/imageNames
type: "[]string"
- name: container/imageNameTags
type: "[]string"
containers:
- image: "paketobuildpacks/builder:full"