mirror of
https://github.com/SAP/jenkins-library.git
synced 2025-02-21 19:48:53 +02:00
kanikoExecute: add multiple build (#4461)
* kanikoExecute: add MultipleImages option --------- Co-authored-by: Egor Balakin <egor.balakin@sap.com>
This commit is contained in:
parent
b474eb2de7
commit
e2bf31872b
@ -2,6 +2,7 @@ package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"strings"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/buildsettings"
|
||||
@ -134,117 +135,222 @@ func runKanikoExecute(config *kanikoExecuteOptions, telemetryData *telemetry.Cus
|
||||
}
|
||||
commonPipelineEnvironment.custom.buildSettingsInfo = buildSettingsInfo
|
||||
|
||||
if !piperutils.ContainsString(config.BuildOptions, "--destination") {
|
||||
dest := []string{"--no-push"}
|
||||
if len(config.ContainerRegistryURL) > 0 && len(config.ContainerImageName) > 0 && len(config.ContainerImageTag) > 0 {
|
||||
containerRegistry, err := docker.ContainerRegistryFromURL(config.ContainerRegistryURL)
|
||||
if err != nil {
|
||||
log.SetErrorCategory(log.ErrorConfiguration)
|
||||
return errors.Wrapf(err, "failed to read registry url %v", config.ContainerRegistryURL)
|
||||
}
|
||||
switch {
|
||||
case config.ContainerMultiImageBuild:
|
||||
log.Entry().Debugf("Multi-image build activated for image name '%v'", config.ContainerImageName)
|
||||
|
||||
commonPipelineEnvironment.container.registryURL = config.ContainerRegistryURL
|
||||
|
||||
// Docker image tags don't allow plus signs in tags, thus replacing with dash
|
||||
containerImageTag := strings.ReplaceAll(config.ContainerImageTag, "+", "-")
|
||||
|
||||
if config.ContainerMultiImageBuild {
|
||||
log.Entry().Debugf("Multi-image build activated for image name '%v'", config.ContainerImageName)
|
||||
imageListWithFilePath, err := docker.ImageListWithFilePath(config.ContainerImageName, config.ContainerMultiImageBuildExcludes, config.ContainerMultiImageBuildTrimDir, fileUtils)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to identify image list for multi image build: %w", err)
|
||||
}
|
||||
if len(imageListWithFilePath) == 0 {
|
||||
return fmt.Errorf("no docker files to process, please check exclude list")
|
||||
}
|
||||
for image, file := range imageListWithFilePath {
|
||||
log.Entry().Debugf("Building image '%v' using file '%v'", image, file)
|
||||
containerImageNameAndTag := fmt.Sprintf("%v:%v", image, containerImageTag)
|
||||
dest = []string{"--destination", fmt.Sprintf("%v/%v", containerRegistry, containerImageNameAndTag)}
|
||||
buildOpts := append(config.BuildOptions, dest...)
|
||||
err = runKaniko(file, buildOpts, config.ReadImageDigest, execRunner, fileUtils, commonPipelineEnvironment)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to build image '%v' using '%v': %w", image, file, err)
|
||||
}
|
||||
commonPipelineEnvironment.container.imageNames = append(commonPipelineEnvironment.container.imageNames, image)
|
||||
commonPipelineEnvironment.container.imageNameTags = append(commonPipelineEnvironment.container.imageNameTags, containerImageNameAndTag)
|
||||
}
|
||||
|
||||
// for compatibility reasons also fill single imageNameTag field with "root" image in commonPipelineEnvironment
|
||||
// only consider if it has been built
|
||||
// ToDo: reconsider and possibly remove at a later point
|
||||
if len(imageListWithFilePath[config.ContainerImageName]) > 0 {
|
||||
containerImageNameAndTag := fmt.Sprintf("%v:%v", config.ContainerImageName, containerImageTag)
|
||||
commonPipelineEnvironment.container.imageNameTag = containerImageNameAndTag
|
||||
}
|
||||
if config.CreateBOM {
|
||||
//Syft for multi image, generates bom-docker-(1/2/3).xml
|
||||
return syft.GenerateSBOM(config.SyftDownloadURL, "/kaniko/.docker", execRunner, fileUtils, httpClient, commonPipelineEnvironment.container.registryURL, commonPipelineEnvironment.container.imageNameTags)
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
commonPipelineEnvironment.container.imageNames = append(commonPipelineEnvironment.container.imageNames, config.ContainerImageName)
|
||||
commonPipelineEnvironment.container.imageNameTags = append(commonPipelineEnvironment.container.imageNameTags, fmt.Sprintf("%v:%v", config.ContainerImageName, containerImageTag))
|
||||
}
|
||||
|
||||
log.Entry().Debugf("Single image build for image name '%v'", config.ContainerImageName)
|
||||
containerImageNameAndTag := fmt.Sprintf("%v:%v", config.ContainerImageName, containerImageTag)
|
||||
dest = []string{"--destination", fmt.Sprintf("%v/%v", containerRegistry, containerImageNameAndTag)}
|
||||
commonPipelineEnvironment.container.imageNameTag = containerImageNameAndTag
|
||||
} else if len(config.ContainerImage) > 0 {
|
||||
log.Entry().Debugf("Single image build for image '%v'", config.ContainerImage)
|
||||
containerRegistry, err := docker.ContainerRegistryFromImage(config.ContainerImage)
|
||||
if err != nil {
|
||||
log.SetErrorCategory(log.ErrorConfiguration)
|
||||
return errors.Wrapf(err, "invalid registry part in image %v", config.ContainerImage)
|
||||
}
|
||||
// errors are already caught with previous call to docker.ContainerRegistryFromImage
|
||||
containerImageName, _ := docker.ContainerImageNameFromImage(config.ContainerImage)
|
||||
containerImageNameTag, _ := docker.ContainerImageNameTagFromImage(config.ContainerImage)
|
||||
dest = []string{"--destination", config.ContainerImage}
|
||||
commonPipelineEnvironment.container.registryURL = fmt.Sprintf("https://%v", containerRegistry)
|
||||
commonPipelineEnvironment.container.imageNameTag = containerImageNameTag
|
||||
commonPipelineEnvironment.container.imageNameTags = append(commonPipelineEnvironment.container.imageNameTags, containerImageNameTag)
|
||||
commonPipelineEnvironment.container.imageNames = append(commonPipelineEnvironment.container.imageNames, containerImageName)
|
||||
if config.ContainerRegistryURL == "" {
|
||||
return fmt.Errorf("empty ContainerRegistryURL")
|
||||
}
|
||||
if config.ContainerImageName == "" {
|
||||
return fmt.Errorf("empty ContainerImageName")
|
||||
}
|
||||
if config.ContainerImageTag == "" {
|
||||
return fmt.Errorf("empty ContainerImageTag")
|
||||
}
|
||||
config.BuildOptions = append(config.BuildOptions, dest...)
|
||||
} else {
|
||||
log.Entry().Infof("Running Kaniko build with destination defined via buildOptions: %v", config.BuildOptions)
|
||||
|
||||
destination := ""
|
||||
containerRegistry, err := docker.ContainerRegistryFromURL(config.ContainerRegistryURL)
|
||||
if err != nil {
|
||||
log.SetErrorCategory(log.ErrorConfiguration)
|
||||
return errors.Wrapf(err, "failed to read registry url %v", config.ContainerRegistryURL)
|
||||
}
|
||||
|
||||
commonPipelineEnvironment.container.registryURL = config.ContainerRegistryURL
|
||||
|
||||
// Docker image tags don't allow plus signs in tags, thus replacing with dash
|
||||
containerImageTag := strings.ReplaceAll(config.ContainerImageTag, "+", "-")
|
||||
|
||||
imageListWithFilePath, err := docker.ImageListWithFilePath(config.ContainerImageName, config.ContainerMultiImageBuildExcludes, config.ContainerMultiImageBuildTrimDir, fileUtils)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to identify image list for multi image build: %w", err)
|
||||
}
|
||||
if len(imageListWithFilePath) == 0 {
|
||||
return fmt.Errorf("no docker files to process, please check exclude list")
|
||||
}
|
||||
for image, file := range imageListWithFilePath {
|
||||
log.Entry().Debugf("Building image '%v' using file '%v'", image, file)
|
||||
containerImageNameAndTag := fmt.Sprintf("%v:%v", image, containerImageTag)
|
||||
buildOpts := append(config.BuildOptions, "--destination", fmt.Sprintf("%v/%v", containerRegistry, containerImageNameAndTag))
|
||||
if err = runKaniko(file, buildOpts, config.ReadImageDigest, execRunner, fileUtils, commonPipelineEnvironment); err != nil {
|
||||
return fmt.Errorf("failed to build image '%v' using '%v': %w", image, file, err)
|
||||
}
|
||||
commonPipelineEnvironment.container.imageNames = append(commonPipelineEnvironment.container.imageNames, image)
|
||||
commonPipelineEnvironment.container.imageNameTags = append(commonPipelineEnvironment.container.imageNameTags, containerImageNameAndTag)
|
||||
}
|
||||
|
||||
// for compatibility reasons also fill single imageNameTag field with "root" image in commonPipelineEnvironment
|
||||
// only consider if it has been built
|
||||
// ToDo: reconsider and possibly remove at a later point
|
||||
if len(imageListWithFilePath[config.ContainerImageName]) > 0 {
|
||||
containerImageNameAndTag := fmt.Sprintf("%v:%v", config.ContainerImageName, containerImageTag)
|
||||
commonPipelineEnvironment.container.imageNameTag = containerImageNameAndTag
|
||||
}
|
||||
if config.CreateBOM {
|
||||
// Syft for multi image, generates bom-docker-(1/2/3).xml
|
||||
return syft.GenerateSBOM(config.SyftDownloadURL, "/kaniko/.docker", execRunner, fileUtils, httpClient, commonPipelineEnvironment.container.registryURL, commonPipelineEnvironment.container.imageNameTags)
|
||||
}
|
||||
return nil
|
||||
|
||||
case config.MultipleImages != nil:
|
||||
log.Entry().Debugf("multipleImages build activated")
|
||||
parsedMultipleImages, err := parseMultipleImages(config.MultipleImages)
|
||||
if err != nil {
|
||||
log.SetErrorCategory(log.ErrorConfiguration)
|
||||
return errors.Wrap(err, "failed to parse multipleImages param")
|
||||
}
|
||||
|
||||
for _, entry := range parsedMultipleImages {
|
||||
switch {
|
||||
case entry.ContextSubPath == "":
|
||||
return fmt.Errorf("multipleImages: empty contextSubPath")
|
||||
case entry.ContainerImageName != "":
|
||||
containerRegistry, err := docker.ContainerRegistryFromURL(config.ContainerRegistryURL)
|
||||
if err != nil {
|
||||
log.SetErrorCategory(log.ErrorConfiguration)
|
||||
return errors.Wrapf(err, "multipleImages: failed to read registry url %v", config.ContainerRegistryURL)
|
||||
}
|
||||
|
||||
if entry.ContainerImageTag == "" {
|
||||
if config.ContainerImageTag == "" {
|
||||
return fmt.Errorf("both multipleImages containerImageTag and config.containerImageTag are empty")
|
||||
}
|
||||
entry.ContainerImageTag = config.ContainerImageTag
|
||||
}
|
||||
// Docker image tags don't allow plus signs in tags, thus replacing with dash
|
||||
containerImageTag := strings.ReplaceAll(entry.ContainerImageTag, "+", "-")
|
||||
containerImageNameAndTag := fmt.Sprintf("%v:%v", entry.ContainerImageName, containerImageTag)
|
||||
|
||||
log.Entry().Debugf("multipleImages: image build '%v'", entry.ContainerImageName)
|
||||
|
||||
buildOptions := append(config.BuildOptions,
|
||||
"--context-sub-path", entry.ContextSubPath,
|
||||
"--destination", fmt.Sprintf("%v/%v", containerRegistry, containerImageNameAndTag),
|
||||
)
|
||||
if err = runKaniko(config.DockerfilePath, buildOptions, config.ReadImageDigest, execRunner, fileUtils, commonPipelineEnvironment); err != nil {
|
||||
return fmt.Errorf("multipleImages: failed to build image '%v' using '%v': %w", entry.ContainerImageName, config.DockerfilePath, err)
|
||||
}
|
||||
|
||||
commonPipelineEnvironment.container.imageNameTags = append(commonPipelineEnvironment.container.imageNameTags, containerImageNameAndTag)
|
||||
commonPipelineEnvironment.container.imageNames = append(commonPipelineEnvironment.container.imageNames, entry.ContainerImageName)
|
||||
|
||||
case entry.ContainerImage != "":
|
||||
containerImageName, err := docker.ContainerImageNameFromImage(entry.ContainerImage)
|
||||
if err != nil {
|
||||
log.SetErrorCategory(log.ErrorConfiguration)
|
||||
return errors.Wrapf(err, "invalid name part in image %v", entry.ContainerImage)
|
||||
}
|
||||
containerImageNameTag, err := docker.ContainerImageNameTagFromImage(entry.ContainerImage)
|
||||
if err != nil {
|
||||
log.SetErrorCategory(log.ErrorConfiguration)
|
||||
return errors.Wrapf(err, "invalid tag part in image %v", entry.ContainerImage)
|
||||
}
|
||||
|
||||
log.Entry().Debugf("multipleImages: image build '%v'", containerImageName)
|
||||
|
||||
buildOptions := append(config.BuildOptions,
|
||||
"--context-sub-path", entry.ContextSubPath,
|
||||
"--destination", entry.ContainerImage,
|
||||
)
|
||||
if err = runKaniko(config.DockerfilePath, buildOptions, config.ReadImageDigest, execRunner, fileUtils, commonPipelineEnvironment); err != nil {
|
||||
return fmt.Errorf("multipleImages: failed to build image '%v' using '%v': %w", containerImageName, config.DockerfilePath, err)
|
||||
}
|
||||
|
||||
commonPipelineEnvironment.container.imageNameTags = append(commonPipelineEnvironment.container.imageNameTags, containerImageNameTag)
|
||||
commonPipelineEnvironment.container.imageNames = append(commonPipelineEnvironment.container.imageNames, containerImageName)
|
||||
default:
|
||||
return fmt.Errorf("multipleImages: either containerImageName or containerImage must be filled")
|
||||
}
|
||||
}
|
||||
|
||||
// for compatibility reasons also fill single imageNameTag field with "root" image in commonPipelineEnvironment
|
||||
containerImageNameAndTag := fmt.Sprintf("%v:%v", config.ContainerImageName, config.ContainerImageTag)
|
||||
commonPipelineEnvironment.container.imageNameTag = containerImageNameAndTag
|
||||
commonPipelineEnvironment.container.registryURL = config.ContainerRegistryURL
|
||||
|
||||
if config.CreateBOM {
|
||||
// Syft for multi image, generates bom-docker-(1/2/3).xml
|
||||
return syft.GenerateSBOM(config.SyftDownloadURL, "/kaniko/.docker", execRunner, fileUtils, httpClient, commonPipelineEnvironment.container.registryURL, commonPipelineEnvironment.container.imageNameTags)
|
||||
}
|
||||
return nil
|
||||
|
||||
case piperutils.ContainsString(config.BuildOptions, "--destination"):
|
||||
log.Entry().Infof("Running Kaniko build with destination defined via buildOptions: %v", config.BuildOptions)
|
||||
|
||||
for i, o := range config.BuildOptions {
|
||||
if o == "--destination" && i+1 < len(config.BuildOptions) {
|
||||
destination = config.BuildOptions[i+1]
|
||||
break
|
||||
destination := config.BuildOptions[i+1]
|
||||
|
||||
containerRegistry, err := docker.ContainerRegistryFromImage(destination)
|
||||
if err != nil {
|
||||
log.SetErrorCategory(log.ErrorConfiguration)
|
||||
return errors.Wrapf(err, "invalid registry part in image %v", destination)
|
||||
}
|
||||
if commonPipelineEnvironment.container.registryURL == "" {
|
||||
commonPipelineEnvironment.container.registryURL = fmt.Sprintf("https://%v", containerRegistry)
|
||||
}
|
||||
|
||||
// errors are already caught with previous call to docker.ContainerRegistryFromImage
|
||||
containerImageName, _ := docker.ContainerImageNameFromImage(destination)
|
||||
containerImageNameTag, _ := docker.ContainerImageNameTagFromImage(destination)
|
||||
|
||||
if commonPipelineEnvironment.container.imageNameTag == "" {
|
||||
commonPipelineEnvironment.container.imageNameTag = containerImageNameTag
|
||||
}
|
||||
commonPipelineEnvironment.container.imageNameTags = append(commonPipelineEnvironment.container.imageNameTags, containerImageNameTag)
|
||||
commonPipelineEnvironment.container.imageNames = append(commonPipelineEnvironment.container.imageNames, containerImageName)
|
||||
}
|
||||
}
|
||||
|
||||
containerRegistry, err := docker.ContainerRegistryFromImage(destination)
|
||||
case config.ContainerRegistryURL != "" && config.ContainerImageName != "" && config.ContainerImageTag != "":
|
||||
log.Entry().Debugf("Single image build for image name '%v'", config.ContainerImageName)
|
||||
|
||||
containerRegistry, err := docker.ContainerRegistryFromURL(config.ContainerRegistryURL)
|
||||
if err != nil {
|
||||
log.SetErrorCategory(log.ErrorConfiguration)
|
||||
return errors.Wrapf(err, "invalid registry part in image %v", destination)
|
||||
return errors.Wrapf(err, "failed to read registry url %v", config.ContainerRegistryURL)
|
||||
}
|
||||
|
||||
containerImageName, _ := docker.ContainerImageNameFromImage(destination)
|
||||
containerImageNameTag, _ := docker.ContainerImageNameTagFromImage(destination)
|
||||
// Docker image tags don't allow plus signs in tags, thus replacing with dash
|
||||
containerImageTag := strings.ReplaceAll(config.ContainerImageTag, "+", "-")
|
||||
containerImageNameAndTag := fmt.Sprintf("%v:%v", config.ContainerImageName, containerImageTag)
|
||||
|
||||
commonPipelineEnvironment.container.registryURL = config.ContainerRegistryURL
|
||||
commonPipelineEnvironment.container.imageNameTag = containerImageNameAndTag
|
||||
commonPipelineEnvironment.container.imageNameTags = append(commonPipelineEnvironment.container.imageNameTags, containerImageNameAndTag)
|
||||
commonPipelineEnvironment.container.imageNames = append(commonPipelineEnvironment.container.imageNames, config.ContainerImageName)
|
||||
config.BuildOptions = append(config.BuildOptions, "--destination", fmt.Sprintf("%v/%v", containerRegistry, containerImageNameAndTag))
|
||||
|
||||
case config.ContainerImage != "":
|
||||
log.Entry().Debugf("Single image build for image '%v'", config.ContainerImage)
|
||||
|
||||
containerRegistry, err := docker.ContainerRegistryFromImage(config.ContainerImage)
|
||||
if err != nil {
|
||||
log.SetErrorCategory(log.ErrorConfiguration)
|
||||
return errors.Wrapf(err, "invalid registry part in image %v", config.ContainerImage)
|
||||
}
|
||||
|
||||
// errors are already caught with previous call to docker.ContainerRegistryFromImage
|
||||
containerImageName, _ := docker.ContainerImageNameFromImage(config.ContainerImage)
|
||||
containerImageNameTag, _ := docker.ContainerImageNameTagFromImage(config.ContainerImage)
|
||||
|
||||
commonPipelineEnvironment.container.registryURL = fmt.Sprintf("https://%v", containerRegistry)
|
||||
commonPipelineEnvironment.container.imageNameTag = containerImageNameTag
|
||||
commonPipelineEnvironment.container.imageNameTags = append(commonPipelineEnvironment.container.imageNameTags, containerImageNameTag)
|
||||
commonPipelineEnvironment.container.imageNames = append(commonPipelineEnvironment.container.imageNames, containerImageName)
|
||||
config.BuildOptions = append(config.BuildOptions, "--destination", config.ContainerImage)
|
||||
default:
|
||||
config.BuildOptions = append(config.BuildOptions, "--no-push")
|
||||
}
|
||||
|
||||
// no support for building multiple containers
|
||||
kanikoErr := runKaniko(config.DockerfilePath, config.BuildOptions, config.ReadImageDigest, execRunner, fileUtils, commonPipelineEnvironment)
|
||||
if kanikoErr != nil {
|
||||
return kanikoErr
|
||||
if err = runKaniko(config.DockerfilePath, config.BuildOptions, config.ReadImageDigest, execRunner, fileUtils, commonPipelineEnvironment); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if config.CreateBOM {
|
||||
// Syft for single image, generates bom-docker-0.xml
|
||||
return syft.GenerateSBOM(config.SyftDownloadURL, "/kaniko/.docker", execRunner, fileUtils, httpClient, commonPipelineEnvironment.container.registryURL, commonPipelineEnvironment.container.imageNameTags)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -254,7 +360,9 @@ func runKaniko(dockerFilepath string, buildOptions []string, readDigest bool, ex
|
||||
return fmt.Errorf("failed to get current working directory: %w", err)
|
||||
}
|
||||
|
||||
kanikoOpts := []string{"--dockerfile", dockerFilepath, "--context", cwd}
|
||||
// kaniko build context needs a proper prefix, for local directory it is 'dir://'
|
||||
// for more details see https://github.com/GoogleContainerTools/kaniko#kaniko-build-contexts
|
||||
kanikoOpts := []string{"--dockerfile", dockerFilepath, "--context", "dir://" + cwd}
|
||||
kanikoOpts = append(kanikoOpts, buildOptions...)
|
||||
|
||||
tmpDir, err := fileUtils.TempDir("", "*-kanikoExecute")
|
||||
@ -280,7 +388,6 @@ func runKaniko(dockerFilepath string, buildOptions []string, readDigest bool, ex
|
||||
|
||||
if b, err := fileUtils.FileExists(digestFilePath); err == nil && b {
|
||||
digest, err := fileUtils.FileRead(digestFilePath)
|
||||
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error while reading image digest")
|
||||
}
|
||||
@ -289,9 +396,31 @@ func runKaniko(dockerFilepath string, buildOptions []string, readDigest bool, ex
|
||||
|
||||
log.Entry().Debugf("image digest: %s", digestStr)
|
||||
|
||||
commonPipelineEnvironment.container.imageDigest = string(digestStr)
|
||||
commonPipelineEnvironment.container.imageDigest = digestStr
|
||||
commonPipelineEnvironment.container.imageDigests = append(commonPipelineEnvironment.container.imageDigests, digestStr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type multipleImageConf struct {
|
||||
ContextSubPath string `json:"contextSubPath,omitempty"`
|
||||
ContainerImageName string `json:"containerImageName,omitempty"`
|
||||
ContainerImageTag string `json:"containerImageTag,omitempty"`
|
||||
ContainerImage string `json:"containerImage,omitempty"`
|
||||
}
|
||||
|
||||
func parseMultipleImages(src []map[string]interface{}) ([]multipleImageConf, error) {
|
||||
var result []multipleImageConf
|
||||
|
||||
for _, conf := range src {
|
||||
var structuredConf multipleImageConf
|
||||
if err := mapstructure.Decode(conf, &structuredConf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result = append(result, structuredConf)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
@ -22,26 +22,27 @@ import (
|
||||
)
|
||||
|
||||
type kanikoExecuteOptions struct {
|
||||
BuildOptions []string `json:"buildOptions,omitempty"`
|
||||
BuildSettingsInfo string `json:"buildSettingsInfo,omitempty"`
|
||||
ContainerBuildOptions string `json:"containerBuildOptions,omitempty"`
|
||||
ContainerImage string `json:"containerImage,omitempty"`
|
||||
ContainerImageName string `json:"containerImageName,omitempty" validate:"required_if=ContainerMultiImageBuild true"`
|
||||
ContainerImageTag string `json:"containerImageTag,omitempty"`
|
||||
ContainerMultiImageBuild bool `json:"containerMultiImageBuild,omitempty"`
|
||||
ContainerMultiImageBuildExcludes []string `json:"containerMultiImageBuildExcludes,omitempty"`
|
||||
ContainerMultiImageBuildTrimDir string `json:"containerMultiImageBuildTrimDir,omitempty"`
|
||||
ContainerPreparationCommand string `json:"containerPreparationCommand,omitempty"`
|
||||
ContainerRegistryURL string `json:"containerRegistryUrl,omitempty"`
|
||||
ContainerRegistryUser string `json:"containerRegistryUser,omitempty"`
|
||||
ContainerRegistryPassword string `json:"containerRegistryPassword,omitempty"`
|
||||
CustomTLSCertificateLinks []string `json:"customTlsCertificateLinks,omitempty"`
|
||||
DockerConfigJSON string `json:"dockerConfigJSON,omitempty"`
|
||||
DockerfilePath string `json:"dockerfilePath,omitempty"`
|
||||
TargetArchitectures []string `json:"targetArchitectures,omitempty"`
|
||||
ReadImageDigest bool `json:"readImageDigest,omitempty"`
|
||||
CreateBOM bool `json:"createBOM,omitempty"`
|
||||
SyftDownloadURL string `json:"syftDownloadUrl,omitempty"`
|
||||
BuildOptions []string `json:"buildOptions,omitempty"`
|
||||
BuildSettingsInfo string `json:"buildSettingsInfo,omitempty"`
|
||||
ContainerBuildOptions string `json:"containerBuildOptions,omitempty"`
|
||||
ContainerImage string `json:"containerImage,omitempty"`
|
||||
ContainerImageName string `json:"containerImageName,omitempty" validate:"required_if=ContainerMultiImageBuild true"`
|
||||
ContainerImageTag string `json:"containerImageTag,omitempty"`
|
||||
MultipleImages []map[string]interface{} `json:"multipleImages,omitempty"`
|
||||
ContainerMultiImageBuild bool `json:"containerMultiImageBuild,omitempty"`
|
||||
ContainerMultiImageBuildExcludes []string `json:"containerMultiImageBuildExcludes,omitempty"`
|
||||
ContainerMultiImageBuildTrimDir string `json:"containerMultiImageBuildTrimDir,omitempty"`
|
||||
ContainerPreparationCommand string `json:"containerPreparationCommand,omitempty"`
|
||||
ContainerRegistryURL string `json:"containerRegistryUrl,omitempty"`
|
||||
ContainerRegistryUser string `json:"containerRegistryUser,omitempty"`
|
||||
ContainerRegistryPassword string `json:"containerRegistryPassword,omitempty"`
|
||||
CustomTLSCertificateLinks []string `json:"customTlsCertificateLinks,omitempty"`
|
||||
DockerConfigJSON string `json:"dockerConfigJSON,omitempty"`
|
||||
DockerfilePath string `json:"dockerfilePath,omitempty"`
|
||||
TargetArchitectures []string `json:"targetArchitectures,omitempty"`
|
||||
ReadImageDigest bool `json:"readImageDigest,omitempty"`
|
||||
CreateBOM bool `json:"createBOM,omitempty"`
|
||||
SyftDownloadURL string `json:"syftDownloadUrl,omitempty"`
|
||||
}
|
||||
|
||||
type kanikoExecuteCommonPipelineEnvironment struct {
|
||||
@ -299,6 +300,7 @@ func addKanikoExecuteFlags(cmd *cobra.Command, stepConfig *kanikoExecuteOptions)
|
||||
cmd.Flags().StringVar(&stepConfig.ContainerImage, "containerImage", os.Getenv("PIPER_containerImage"), "Defines the full name of the Docker image to be created including registry, image name and tag like `my.docker.registry/path/myImageName:myTag`. If `containerImage` is not provided, then `containerImageName` or `--destination` (via buildOptions) should be provided.")
|
||||
cmd.Flags().StringVar(&stepConfig.ContainerImageName, "containerImageName", os.Getenv("PIPER_containerImageName"), "Name of the container which will be built - will be used instead of parameter `containerImage`. If `containerImageName` is not provided, then `containerImage` or `--destination` (via buildOptions) should be provided.")
|
||||
cmd.Flags().StringVar(&stepConfig.ContainerImageTag, "containerImageTag", os.Getenv("PIPER_containerImageTag"), "Tag of the container which will be built - will be used instead of parameter `containerImage`")
|
||||
|
||||
cmd.Flags().BoolVar(&stepConfig.ContainerMultiImageBuild, "containerMultiImageBuild", false, "Defines if multiple containers should be build. Dockerfiles are used using the pattern **/Dockerfile*. Excludes can be defined via [`containerMultiImageBuildExcludes`](#containermultiimagebuildexscludes).")
|
||||
cmd.Flags().StringSliceVar(&stepConfig.ContainerMultiImageBuildExcludes, "containerMultiImageBuildExcludes", []string{}, "Defines a list of Dockerfile paths to exclude from the build when using [`containerMultiImageBuild`](#containermultiimagebuild).")
|
||||
cmd.Flags().StringVar(&stepConfig.ContainerMultiImageBuildTrimDir, "containerMultiImageBuildTrimDir", os.Getenv("PIPER_containerMultiImageBuildTrimDir"), "Defines a trailing directory part which should not be considered in the final image name.")
|
||||
@ -394,6 +396,14 @@ func kanikoExecuteMetadata() config.StepData {
|
||||
Aliases: []config.Alias{{Name: "artifactVersion"}},
|
||||
Default: os.Getenv("PIPER_containerImageTag"),
|
||||
},
|
||||
{
|
||||
Name: "multipleImages",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "[]map[string]interface{}",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{{Name: "images"}},
|
||||
},
|
||||
{
|
||||
Name: "containerMultiImageBuild",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
|
@ -93,7 +93,7 @@ func TestRunKanikoExecute(t *testing.T) {
|
||||
|
||||
assert.Equal(t, "/kaniko/executor", execRunner.Calls[1].Exec)
|
||||
cwd, _ := fileUtils.Getwd()
|
||||
assert.Equal(t, []string{"--dockerfile", "Dockerfile", "--context", cwd, "--skip-tls-verify-pull", "--destination", "myImage:tag"}, execRunner.Calls[1].Params)
|
||||
assert.Equal(t, []string{"--dockerfile", "Dockerfile", "--context", "dir://" + cwd, "--skip-tls-verify-pull", "--destination", "myImage:tag"}, execRunner.Calls[1].Params)
|
||||
|
||||
assert.Contains(t, commonPipelineEnvironment.custom.buildSettingsInfo, `"mavenExecuteBuild":[{"dockerImage":"maven"}]`)
|
||||
assert.Contains(t, commonPipelineEnvironment.custom.buildSettingsInfo, `"kanikoExecute":[{"dockerImage":"gcr.io/kaniko-project/executor:debug"}]`)
|
||||
@ -144,7 +144,7 @@ func TestRunKanikoExecute(t *testing.T) {
|
||||
|
||||
assert.Equal(t, "/kaniko/executor", execRunner.Calls[1].Exec)
|
||||
cwd, _ := fileUtils.Getwd()
|
||||
assert.Equal(t, []string{"--dockerfile", "Dockerfile", "--context", cwd, "--skip-tls-verify-pull", "--destination", "myImage:tag", "--digest-file", "/tmp/*-kanikoExecutetest/digest.txt"}, execRunner.Calls[1].Params)
|
||||
assert.Equal(t, []string{"--dockerfile", "Dockerfile", "--context", "dir://" + cwd, "--skip-tls-verify-pull", "--destination", "myImage:tag", "--digest-file", "/tmp/*-kanikoExecutetest/digest.txt"}, execRunner.Calls[1].Params)
|
||||
|
||||
assert.Contains(t, commonPipelineEnvironment.custom.buildSettingsInfo, `"mavenExecuteBuild":[{"dockerImage":"maven"}]`)
|
||||
assert.Contains(t, commonPipelineEnvironment.custom.buildSettingsInfo, `"kanikoExecute":[{"dockerImage":"gcr.io/kaniko-project/executor:debug"}]`)
|
||||
@ -194,7 +194,7 @@ func TestRunKanikoExecute(t *testing.T) {
|
||||
|
||||
assert.Equal(t, "/kaniko/executor", execRunner.Calls[1].Exec)
|
||||
cwd, _ := fileUtils.Getwd()
|
||||
assert.Equal(t, []string{"--dockerfile", "Dockerfile", "--context", cwd, "--skip-tls-verify-pull", "--destination", "my.registry.com:50000/myImage:1.2.3-a-x"}, execRunner.Calls[1].Params)
|
||||
assert.Equal(t, []string{"--dockerfile", "Dockerfile", "--context", "dir://" + cwd, "--skip-tls-verify-pull", "--destination", "my.registry.com:50000/myImage:1.2.3-a-x"}, execRunner.Calls[1].Params)
|
||||
|
||||
assert.Equal(t, "myImage:1.2.3-a-x", commonPipelineEnvironment.container.imageNameTag)
|
||||
assert.Equal(t, "https://my.registry.com:50000", commonPipelineEnvironment.container.registryURL)
|
||||
@ -238,7 +238,7 @@ func TestRunKanikoExecute(t *testing.T) {
|
||||
|
||||
assert.Equal(t, "/kaniko/executor", execRunner.Calls[1].Exec)
|
||||
cwd, _ := fileUtils.Getwd()
|
||||
assert.Equal(t, []string{"--dockerfile", "Dockerfile", "--context", cwd, "--skip-tls-verify-pull", "--destination", "my.other.registry.com:50000/myImage:3.2.1-a-x"}, execRunner.Calls[1].Params)
|
||||
assert.Equal(t, []string{"--dockerfile", "Dockerfile", "--context", "dir://" + cwd, "--skip-tls-verify-pull", "--destination", "my.other.registry.com:50000/myImage:3.2.1-a-x"}, execRunner.Calls[1].Params)
|
||||
|
||||
assert.Equal(t, "myImage:3.2.1-a-x", commonPipelineEnvironment.container.imageNameTag)
|
||||
assert.Equal(t, "https://my.other.registry.com:50000", commonPipelineEnvironment.container.registryURL)
|
||||
@ -300,7 +300,7 @@ func TestRunKanikoExecute(t *testing.T) {
|
||||
assert.Equal(t, `{"auths":{}}`, string(c))
|
||||
|
||||
cwd, _ := fileUtils.Getwd()
|
||||
assert.Equal(t, []string{"--dockerfile", "Dockerfile", "--context", cwd, "--skip-tls-verify-pull", "--no-push"}, execRunner.Calls[1].Params)
|
||||
assert.Equal(t, []string{"--dockerfile", "Dockerfile", "--context", "dir://" + cwd, "--skip-tls-verify-pull", "--no-push"}, execRunner.Calls[1].Params)
|
||||
})
|
||||
|
||||
t.Run("success case - backward compatibility", func(t *testing.T) {
|
||||
@ -327,7 +327,7 @@ func TestRunKanikoExecute(t *testing.T) {
|
||||
|
||||
assert.NoError(t, err)
|
||||
cwd, _ := fileUtils.Getwd()
|
||||
assert.Equal(t, []string{"--dockerfile", "Dockerfile", "--context", cwd, "--skip-tls-verify-pull", "--destination", "myImage:tag"}, execRunner.Calls[1].Params)
|
||||
assert.Equal(t, []string{"--dockerfile", "Dockerfile", "--context", "dir://" + cwd, "--skip-tls-verify-pull", "--destination", "myImage:tag"}, execRunner.Calls[1].Params)
|
||||
})
|
||||
|
||||
t.Run("success case - createBOM", func(t *testing.T) {
|
||||
@ -391,9 +391,9 @@ func TestRunKanikoExecute(t *testing.T) {
|
||||
|
||||
cwd, _ := fileUtils.Getwd()
|
||||
expectedParams := [][]string{
|
||||
{"--dockerfile", "Dockerfile", "--context", cwd, "--destination", "my.registry.com:50000/myImage:myTag"},
|
||||
{"--dockerfile", filepath.Join("sub1", "Dockerfile"), "--context", cwd, "--destination", "my.registry.com:50000/myImage-sub1:myTag"},
|
||||
{"--dockerfile", filepath.Join("sub2", "Dockerfile"), "--context", cwd, "--destination", "my.registry.com:50000/myImage-sub2:myTag"},
|
||||
{"--dockerfile", "Dockerfile", "--context", "dir://" + cwd, "--destination", "my.registry.com:50000/myImage:myTag"},
|
||||
{"--dockerfile", filepath.Join("sub1", "Dockerfile"), "--context", "dir://" + cwd, "--destination", "my.registry.com:50000/myImage-sub1:myTag"},
|
||||
{"--dockerfile", filepath.Join("sub2", "Dockerfile"), "--context", "dir://" + cwd, "--destination", "my.registry.com:50000/myImage-sub2:myTag"},
|
||||
}
|
||||
// need to go this way since we cannot count on the correct order
|
||||
for _, call := range execRunner.Calls {
|
||||
@ -447,8 +447,8 @@ func TestRunKanikoExecute(t *testing.T) {
|
||||
|
||||
cwd, _ := fileUtils.Getwd()
|
||||
expectedParams := [][]string{
|
||||
{"--dockerfile", filepath.Join("sub1", "Dockerfile"), "--context", cwd, "--destination", "my.registry.com:50000/myImage-sub1:myTag"},
|
||||
{"--dockerfile", filepath.Join("sub2", "Dockerfile"), "--context", cwd, "--destination", "my.registry.com:50000/myImage-sub2:myTag"},
|
||||
{"--dockerfile", filepath.Join("sub1", "Dockerfile"), "--context", "dir://" + cwd, "--destination", "my.registry.com:50000/myImage-sub1:myTag"},
|
||||
{"--dockerfile", filepath.Join("sub2", "Dockerfile"), "--context", "dir://" + cwd, "--destination", "my.registry.com:50000/myImage-sub2:myTag"},
|
||||
}
|
||||
// need to go this way since we cannot count on the correct order
|
||||
for _, call := range execRunner.Calls {
|
||||
@ -507,9 +507,9 @@ func TestRunKanikoExecute(t *testing.T) {
|
||||
|
||||
cwd, _ := fileUtils.Getwd()
|
||||
expectedParams := [][]string{
|
||||
{"--dockerfile", "Dockerfile", "--context", cwd, "--destination", "my.registry.com:50000/myImage:myTag"},
|
||||
{"--dockerfile", filepath.Join("sub1", "Dockerfile"), "--context", cwd, "--destination", "my.registry.com:50000/myImage-sub1:myTag"},
|
||||
{"--dockerfile", filepath.Join("sub2", "Dockerfile"), "--context", cwd, "--destination", "my.registry.com:50000/myImage-sub2:myTag"},
|
||||
{"--dockerfile", "Dockerfile", "--context", "dir://" + cwd, "--destination", "my.registry.com:50000/myImage:myTag"},
|
||||
{"--dockerfile", filepath.Join("sub1", "Dockerfile"), "--context", "dir://" + cwd, "--destination", "my.registry.com:50000/myImage-sub1:myTag"},
|
||||
{"--dockerfile", filepath.Join("sub2", "Dockerfile"), "--context", "dir://" + cwd, "--destination", "my.registry.com:50000/myImage-sub2:myTag"},
|
||||
{"packages", "registry:my.registry.com:50000/myImage:myTag", "-o", "cyclonedx-xml", "--file"},
|
||||
{"packages", "registry:my.registry.com:50000/myImage-sub1:myTag", "-o", "cyclonedx-xml", "--file"},
|
||||
{"packages", "registry:my.registry.com:50000/myImage-sub2:myTag", "-o", "cyclonedx-xml", "--file"},
|
||||
@ -614,6 +614,84 @@ func TestRunKanikoExecute(t *testing.T) {
|
||||
assert.Equal(t, `{"auths":{"https://my.registry.com:50000":{"auth":"ZHVtbXlVc2VyOmR1bW15UGFzc3dvcmQ="}}}`, string(c))
|
||||
})
|
||||
|
||||
t.Run("success case - multi context build with CreateBOM", func(t *testing.T) {
|
||||
config := &kanikoExecuteOptions{
|
||||
ContainerImageName: "myImage",
|
||||
ContainerImageTag: "myTag",
|
||||
ContainerRegistryURL: "https://my.registry.com:50000",
|
||||
DockerConfigJSON: "path/to/docker/config.json",
|
||||
DockerfilePath: "Dockerfile",
|
||||
CreateBOM: true,
|
||||
SyftDownloadURL: "http://test-syft-url.io",
|
||||
MultipleImages: []map[string]interface{}{
|
||||
{
|
||||
"contextSubPath": "/test1",
|
||||
"containerImageName": "myImageOne",
|
||||
},
|
||||
{
|
||||
"contextSubPath": "/test2",
|
||||
"containerImageName": "myImageTwo",
|
||||
"containerImageTag": "myTagTwo",
|
||||
},
|
||||
},
|
||||
}
|
||||
execRunner := &mock.ExecMockRunner{}
|
||||
commonPipelineEnvironment := kanikoExecuteCommonPipelineEnvironment{}
|
||||
fileUtils := &mock.FilesMock{}
|
||||
fileUtils.AddFile("path/to/docker/config.json", []byte(`{"auths":{"custom":"test"}}`))
|
||||
fileUtils.AddFile("Dockerfile", []byte("some content"))
|
||||
fileUtils.AddFile("test1/test", []byte("some content test1"))
|
||||
fileUtils.AddFile("test2/test", []byte("some content test2"))
|
||||
|
||||
httpmock.Activate()
|
||||
defer httpmock.DeactivateAndReset()
|
||||
fakeArchive, err := fileUtils.CreateArchive(map[string][]byte{"syft": []byte("test")})
|
||||
assert.NoError(t, err)
|
||||
|
||||
httpmock.RegisterResponder(http.MethodGet, "http://test-syft-url.io", httpmock.NewBytesResponder(http.StatusOK, fakeArchive))
|
||||
client := &piperhttp.Client{}
|
||||
client.SetOptions(piperhttp.ClientOptions{MaxRetries: -1, UseDefaultTransport: true})
|
||||
|
||||
err = runKanikoExecute(config, &telemetry.CustomData{}, &commonPipelineEnvironment, execRunner, client, fileUtils)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, 4, len(execRunner.Calls))
|
||||
assert.Equal(t, "/kaniko/executor", execRunner.Calls[0].Exec)
|
||||
assert.Equal(t, "/kaniko/executor", execRunner.Calls[1].Exec)
|
||||
|
||||
cwd, _ := fileUtils.Getwd()
|
||||
expectedParams := [][]string{
|
||||
{"--dockerfile", "Dockerfile", "--context", "dir://" + cwd, "--context-sub-path", "/test1", "--destination", "my.registry.com:50000/myImageOne:myTag"},
|
||||
{"--dockerfile", "Dockerfile", "--context", "dir://" + cwd, "--context-sub-path", "/test2", "--destination", "my.registry.com:50000/myImageTwo:myTagTwo"},
|
||||
{"packages", "registry:my.registry.com:50000/myImageOne:myTag", "-o", "cyclonedx-xml", "--file"},
|
||||
{"packages", "registry:my.registry.com:50000/myImageTwo:myTagTwo", "-o", "cyclonedx-xml", "--file"},
|
||||
}
|
||||
// need to go this way since we cannot count on the correct order
|
||||
for index, call := range execRunner.Calls {
|
||||
found := false
|
||||
for _, expected := range expectedParams {
|
||||
if expected[0] == "packages" {
|
||||
expected = append(expected, fmt.Sprintf("bom-docker-%d.xml", index-2), "-q")
|
||||
}
|
||||
if strings.Join(call.Params, " ") == strings.Join(expected, " ") {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.True(t, found, fmt.Sprintf("%v not found", call.Params))
|
||||
}
|
||||
|
||||
assert.Equal(t, "https://my.registry.com:50000", commonPipelineEnvironment.container.registryURL)
|
||||
assert.Equal(t, "myImage:myTag", commonPipelineEnvironment.container.imageNameTag)
|
||||
assert.Contains(t, commonPipelineEnvironment.container.imageNames, "myImageOne")
|
||||
assert.Contains(t, commonPipelineEnvironment.container.imageNames, "myImageTwo")
|
||||
assert.Contains(t, commonPipelineEnvironment.container.imageNameTags, "myImageOne:myTag")
|
||||
assert.Contains(t, commonPipelineEnvironment.container.imageNameTags, "myImageTwo:myTagTwo")
|
||||
|
||||
assert.Equal(t, "", commonPipelineEnvironment.container.imageDigest)
|
||||
assert.Empty(t, commonPipelineEnvironment.container.imageDigests)
|
||||
})
|
||||
|
||||
t.Run("error case - multi image build: no docker files", func(t *testing.T) {
|
||||
config := &kanikoExecuteOptions{
|
||||
ContainerImageName: "myImage",
|
||||
@ -762,4 +840,27 @@ func TestRunKanikoExecute(t *testing.T) {
|
||||
|
||||
assert.EqualError(t, err, "failed to write file '/kaniko/.docker/config.json': write error")
|
||||
})
|
||||
|
||||
t.Run("error case - multi context build: no subcontext provided", func(t *testing.T) {
|
||||
config := &kanikoExecuteOptions{
|
||||
ContainerImageName: "myImage",
|
||||
ContainerImageTag: "myTag",
|
||||
ContainerRegistryURL: "https://my.registry.com:50000",
|
||||
MultipleImages: []map[string]interface{}{
|
||||
{"containerImageName": "myImageOne"},
|
||||
{"containerImageName": "myImageTwo"},
|
||||
},
|
||||
}
|
||||
|
||||
cpe := kanikoExecuteCommonPipelineEnvironment{}
|
||||
execRunner := &mock.ExecMockRunner{}
|
||||
|
||||
fileUtils := &mock.FilesMock{}
|
||||
fileUtils.AddFile("Dockerfile", []byte("some content"))
|
||||
|
||||
err := runKanikoExecute(config, &telemetry.CustomData{}, &cpe, execRunner, nil, fileUtils)
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, fmt.Sprint(err), "multipleImages: empty contextSubPath")
|
||||
})
|
||||
}
|
||||
|
@ -144,6 +144,36 @@ spec:
|
||||
resourceRef:
|
||||
- name: commonPipelineEnvironment
|
||||
param: artifactVersion
|
||||
- name: multipleImages
|
||||
aliases:
|
||||
- name: images
|
||||
type: "[]map[string]interface{}"
|
||||
description: |
|
||||
This parameter is only needed if `kanikoExecute` should create multiple images using the same root Dockerfile, but with different sub-contexts.
|
||||
Otherwise it can be ignored!!!
|
||||
|
||||
In case of multiple images, this array contains one entry for each image.
|
||||
Either containerImageName OR containerImage MUST be provided for each entry.
|
||||
contextSubPath MUST be provided for each entry.
|
||||
|
||||
Array keys:
|
||||
contextSubPath - Set a context subpath.
|
||||
containerImageName - Name of the container which will be built.
|
||||
containerImageTag - Tag of the container which will be built. If empty - root containerImageTag will be used.
|
||||
containerImage - Defines the full name of the Docker image to be created including registry.
|
||||
|
||||
```yaml
|
||||
containerRegistryUrl: docker.io
|
||||
containerImageTag: latest
|
||||
multipleImages:
|
||||
- containerImageName: myImage1
|
||||
containerImageTag: v1.0.0
|
||||
contextSubPath: path/to/folder
|
||||
```
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
- name: containerMultiImageBuild
|
||||
type: bool
|
||||
description: Defines if multiple containers should be build. Dockerfiles are used using the pattern **/Dockerfile*. Excludes can be defined via [`containerMultiImageBuildExcludes`](#containermultiimagebuildexscludes).
|
||||
|
Loading…
x
Reference in New Issue
Block a user