2020-07-10 08:07:59 +02:00
package cmd
import (
2020-10-14 11:13:08 +02:00
"fmt"
2020-07-10 08:07:59 +02:00
"strings"
2021-12-01 08:46:18 +02:00
"github.com/SAP/jenkins-library/pkg/buildsettings"
2021-10-01 13:48:24 +02:00
"github.com/SAP/jenkins-library/pkg/certutils"
2020-07-10 08:07:59 +02:00
piperhttp "github.com/SAP/jenkins-library/pkg/http"
2022-12-13 11:51:14 +02:00
"github.com/SAP/jenkins-library/pkg/syft"
2020-07-10 08:07:59 +02:00
"github.com/pkg/errors"
"github.com/SAP/jenkins-library/pkg/command"
2020-10-14 11:13:08 +02:00
"github.com/SAP/jenkins-library/pkg/docker"
2020-07-10 08:07:59 +02:00
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/piperutils"
"github.com/SAP/jenkins-library/pkg/telemetry"
)
2020-10-14 11:13:08 +02:00
func kanikoExecute ( config kanikoExecuteOptions , telemetryData * telemetry . CustomData , commonPipelineEnvironment * kanikoExecuteCommonPipelineEnvironment ) {
2020-07-10 08:07:59 +02:00
// for command execution use Command
c := command . Command {
ErrorCategoryMapping : map [ string ] [ ] string {
2020-09-24 08:58:53 +02:00
log . ErrorConfiguration . String ( ) : {
2020-07-10 08:07:59 +02:00
"unsupported status code 401" ,
} ,
} ,
2023-06-26 08:47:11 +02:00
StepName : "kanikoExecute" ,
2020-07-10 08:07:59 +02:00
}
// reroute command output to logging framework
c . Stdout ( log . Writer ( ) )
c . Stderr ( log . Writer ( ) )
client := & piperhttp . Client { }
fileUtils := & piperutils . Files { }
2022-12-13 11:51:14 +02:00
err := runKanikoExecute ( & config , telemetryData , commonPipelineEnvironment , & c , client , fileUtils )
2020-07-10 08:07:59 +02:00
if err != nil {
log . Entry ( ) . WithError ( err ) . Fatal ( "Kaniko execution failed" )
}
}
2022-12-13 11:51:14 +02:00
func runKanikoExecute ( config * kanikoExecuteOptions , telemetryData * telemetry . CustomData , commonPipelineEnvironment * kanikoExecuteCommonPipelineEnvironment , execRunner command . ExecRunner , httpClient piperhttp . Sender , fileUtils piperutils . FileUtils ) error {
2022-02-09 11:17:34 +02:00
binfmtSupported , _ := docker . IsBinfmtMiscSupportedByHost ( fileUtils )
if ! binfmtSupported && len ( config . TargetArchitectures ) > 0 {
log . Entry ( ) . Warning ( "Be aware that the host doesn't support binfmt_misc and thus multi archtecture docker builds might not be possible" )
}
2020-07-10 08:07:59 +02:00
// backward compatibility for parameter ContainerBuildOptions
if len ( config . ContainerBuildOptions ) > 0 {
config . BuildOptions = strings . Split ( config . ContainerBuildOptions , " " )
log . Entry ( ) . Warning ( "Parameter containerBuildOptions is deprecated, please use buildOptions instead." )
telemetryData . Custom1Label = "ContainerBuildOptions"
telemetryData . Custom1 = config . ContainerBuildOptions
}
// prepare kaniko container for running with proper Docker config.json and custom certificates
// custom certificates will be downloaded and appended to ca-certificates.crt file used in container
2022-02-07 08:58:41 +02:00
if len ( config . ContainerPreparationCommand ) > 0 {
prepCommand := strings . Split ( config . ContainerPreparationCommand , " " )
if err := execRunner . RunExecutable ( prepCommand [ 0 ] , prepCommand [ 1 : ] ... ) ; err != nil {
return errors . Wrap ( err , "failed to initialize Kaniko container" )
}
2020-07-10 08:07:59 +02:00
}
2021-03-25 16:32:10 +02:00
if len ( config . CustomTLSCertificateLinks ) > 0 {
2021-10-01 13:48:24 +02:00
err := certutils . CertificateUpdate ( config . CustomTLSCertificateLinks , httpClient , fileUtils , "/kaniko/ssl/certs/ca-certificates.crt" )
2021-03-25 16:32:10 +02:00
if err != nil {
return errors . Wrap ( err , "failed to update certificates" )
}
} else {
log . Entry ( ) . Info ( "skipping updation of certificates" )
2020-07-10 08:07:59 +02:00
}
2022-02-07 08:58:41 +02:00
dockerConfig := [ ] byte ( ` { "auths": { }} ` )
2022-07-15 08:40:33 +02:00
// respect user provided docker config json file
2022-02-07 08:58:41 +02:00
if len ( config . DockerConfigJSON ) > 0 {
var err error
dockerConfig , err = fileUtils . FileRead ( config . DockerConfigJSON )
if err != nil {
2022-07-15 08:40:33 +02:00
return errors . Wrapf ( err , "failed to read existing docker config json at '%v'" , config . DockerConfigJSON )
}
}
// if : user provided docker config json and registry credentials present then enahance the user provided docker provided json with the registry credentials
// else if : no user provided docker config json then create a new docker config json for kaniko
if len ( config . DockerConfigJSON ) > 0 && len ( config . ContainerRegistryURL ) > 0 && len ( config . ContainerRegistryPassword ) > 0 && len ( config . ContainerRegistryUser ) > 0 {
targetConfigJson , err := docker . CreateDockerConfigJSON ( config . ContainerRegistryURL , config . ContainerRegistryUser , config . ContainerRegistryPassword , "" , config . DockerConfigJSON , fileUtils )
if err != nil {
return errors . Wrapf ( err , "failed to update existing docker config json file '%v'" , config . DockerConfigJSON )
}
dockerConfig , err = fileUtils . FileRead ( targetConfigJson )
if err != nil {
return errors . Wrapf ( err , "failed to read enhanced file '%v'" , config . DockerConfigJSON )
}
} else if len ( config . DockerConfigJSON ) == 0 && len ( config . ContainerRegistryURL ) > 0 && len ( config . ContainerRegistryPassword ) > 0 && len ( config . ContainerRegistryUser ) > 0 {
targetConfigJson , err := docker . CreateDockerConfigJSON ( config . ContainerRegistryURL , config . ContainerRegistryUser , config . ContainerRegistryPassword , "" , "/kaniko/.docker/config.json" , fileUtils )
if err != nil {
return errors . Wrap ( err , "failed to create new docker config json at /kaniko/.docker/config.json" )
}
dockerConfig , err = fileUtils . FileRead ( targetConfigJson )
if err != nil {
return errors . Wrapf ( err , "failed to read new docker config file at /kaniko/.docker/config.json" )
2022-02-07 08:58:41 +02:00
}
}
if err := fileUtils . FileWrite ( "/kaniko/.docker/config.json" , dockerConfig , 0644 ) ; err != nil {
return errors . Wrap ( err , "failed to write file '/kaniko/.docker/config.json'" )
}
log . Entry ( ) . Debugf ( "preparing build settings information..." )
stepName := "kanikoExecute"
// ToDo: better testability required. So far retrieval of config is rather non deterministic
2022-03-14 13:33:52 +02:00
dockerImage , err := GetDockerImageValue ( stepName )
2022-02-07 08:58:41 +02:00
if err != nil {
return fmt . Errorf ( "failed to retrieve dockerImage configuration: %w" , err )
}
kanikoConfig := buildsettings . BuildOptions {
DockerImage : dockerImage ,
BuildSettingsInfo : config . BuildSettingsInfo ,
}
log . Entry ( ) . Debugf ( "creating build settings information..." )
buildSettingsInfo , err := buildsettings . CreateBuildSettingsInfo ( & kanikoConfig , stepName )
if err != nil {
log . Entry ( ) . Warnf ( "failed to create build settings info: %v" , err )
}
commonPipelineEnvironment . custom . buildSettingsInfo = buildSettingsInfo
2020-07-10 08:07:59 +02:00
if ! piperutils . ContainsString ( config . BuildOptions , "--destination" ) {
dest := [ ] string { "--no-push" }
2020-10-14 11:13:08 +02:00
if len ( config . ContainerRegistryURL ) > 0 && len ( config . ContainerImageName ) > 0 && len ( config . ContainerImageTag ) > 0 {
containerRegistry , err := docker . ContainerRegistryFromURL ( config . ContainerRegistryURL )
if err != nil {
2021-05-10 17:44:28 +02:00
log . SetErrorCategory ( log . ErrorConfiguration )
2020-10-14 11:13:08 +02:00
return errors . Wrapf ( err , "failed to read registry url %v" , config . ContainerRegistryURL )
}
2022-02-07 08:58:41 +02:00
2020-10-14 11:13:08 +02:00
commonPipelineEnvironment . container . registryURL = config . ContainerRegistryURL
2022-02-07 08:58:41 +02:00
// 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 )
2022-03-11 10:47:44 +02:00
imageListWithFilePath , err := docker . ImageListWithFilePath ( config . ContainerImageName , config . ContainerMultiImageBuildExcludes , config . ContainerMultiImageBuildTrimDir , fileUtils )
2022-02-07 08:58:41 +02:00
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 ... )
2022-02-24 18:38:47 +02:00
err = runKaniko ( file , buildOpts , config . ReadImageDigest , execRunner , fileUtils , commonPipelineEnvironment )
2022-02-07 08:58:41 +02:00
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
}
2022-11-07 15:27:05 +02:00
if config . CreateBOM {
//Syft for multi image, generates bom-docker-(1/2/3).xml
2022-12-13 11:51:14 +02:00
return syft . GenerateSBOM ( config . SyftDownloadURL , "/kaniko/.docker" , execRunner , fileUtils , httpClient , commonPipelineEnvironment . container . registryURL , commonPipelineEnvironment . container . imageNameTags )
2022-11-07 15:27:05 +02:00
}
2022-02-07 08:58:41 +02:00
return nil
2022-02-17 09:20:30 +02:00
} else {
commonPipelineEnvironment . container . imageNames = append ( commonPipelineEnvironment . container . imageNames , config . ContainerImageName )
commonPipelineEnvironment . container . imageNameTags = append ( commonPipelineEnvironment . container . imageNameTags , fmt . Sprintf ( "%v:%v" , config . ContainerImageName , containerImageTag ) )
2022-02-07 08:58:41 +02:00
}
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
2020-10-27 14:45:34 +02:00
} else if len ( config . ContainerImage ) > 0 {
2022-02-07 08:58:41 +02:00
log . Entry ( ) . Debugf ( "Single image build for image '%v'" , config . ContainerImage )
2020-10-27 14:45:34 +02:00
containerRegistry , err := docker . ContainerRegistryFromImage ( config . ContainerImage )
if err != nil {
2021-05-10 17:44:28 +02:00
log . SetErrorCategory ( log . ErrorConfiguration )
2020-10-27 14:45:34 +02:00
return errors . Wrapf ( err , "invalid registry part in image %v" , config . ContainerImage )
}
// errors are already caught with previous call to docker.ContainerRegistryFromImage
2022-02-17 09:20:30 +02:00
containerImageName , _ := docker . ContainerImageNameFromImage ( config . ContainerImage )
2020-10-27 14:45:34 +02:00
containerImageNameTag , _ := docker . ContainerImageNameTagFromImage ( config . ContainerImage )
dest = [ ] string { "--destination" , config . ContainerImage }
commonPipelineEnvironment . container . registryURL = fmt . Sprintf ( "https://%v" , containerRegistry )
commonPipelineEnvironment . container . imageNameTag = containerImageNameTag
2022-02-17 09:20:30 +02:00
commonPipelineEnvironment . container . imageNameTags = append ( commonPipelineEnvironment . container . imageNameTags , containerImageNameTag )
commonPipelineEnvironment . container . imageNames = append ( commonPipelineEnvironment . container . imageNames , containerImageName )
2020-07-10 08:07:59 +02:00
}
config . BuildOptions = append ( config . BuildOptions , dest ... )
2022-02-07 08:58:41 +02:00
} else {
log . Entry ( ) . Infof ( "Running Kaniko build with destination defined via buildOptions: %v" , config . BuildOptions )
2022-02-17 09:20:30 +02:00
destination := ""
for i , o := range config . BuildOptions {
if o == "--destination" && i + 1 < len ( config . BuildOptions ) {
destination = config . BuildOptions [ i + 1 ]
break
}
}
containerRegistry , err := docker . ContainerRegistryFromImage ( destination )
if err != nil {
log . SetErrorCategory ( log . ErrorConfiguration )
return errors . Wrapf ( err , "invalid registry part in image %v" , destination )
}
containerImageName , _ := docker . ContainerImageNameFromImage ( destination )
containerImageNameTag , _ := docker . ContainerImageNameTagFromImage ( destination )
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 )
2020-07-10 08:07:59 +02:00
}
2022-02-07 08:58:41 +02:00
// no support for building multiple containers
2022-11-07 15:27:05 +02:00
kanikoErr := runKaniko ( config . DockerfilePath , config . BuildOptions , config . ReadImageDigest , execRunner , fileUtils , commonPipelineEnvironment )
if kanikoErr != nil {
return kanikoErr
}
if config . CreateBOM {
// Syft for single image, generates bom-docker-0.xml
2022-12-13 11:51:14 +02:00
return syft . GenerateSBOM ( config . SyftDownloadURL , "/kaniko/.docker" , execRunner , fileUtils , httpClient , commonPipelineEnvironment . container . registryURL , commonPipelineEnvironment . container . imageNameTags )
2022-11-07 15:27:05 +02:00
}
return nil
2022-02-07 08:58:41 +02:00
}
2020-07-10 08:07:59 +02:00
2022-02-24 18:38:47 +02:00
func runKaniko ( dockerFilepath string , buildOptions [ ] string , readDigest bool , execRunner command . ExecRunner , fileUtils piperutils . FileUtils , commonPipelineEnvironment * kanikoExecuteCommonPipelineEnvironment ) error {
2022-02-09 13:36:43 +02:00
cwd , err := fileUtils . Getwd ( )
if err != nil {
return fmt . Errorf ( "failed to get current working directory: %w" , err )
}
2022-02-23 12:41:26 +02:00
2022-03-21 10:14:36 +02:00
kanikoOpts := [ ] string { "--dockerfile" , dockerFilepath , "--context" , cwd }
2022-02-24 18:38:47 +02:00
kanikoOpts = append ( kanikoOpts , buildOptions ... )
2022-02-23 12:41:26 +02:00
tmpDir , err := fileUtils . TempDir ( "" , "*-kanikoExecute" )
if err != nil {
return fmt . Errorf ( "failed to create tmp dir for kanikoExecute: %w" , err )
}
digestFilePath := fmt . Sprintf ( "%s/digest.txt" , tmpDir )
2022-02-24 18:38:47 +02:00
if readDigest {
kanikoOpts = append ( kanikoOpts , "--digest-file" , digestFilePath )
}
2020-07-10 08:07:59 +02:00
2023-08-04 08:53:24 +02:00
if GeneralConfig . Verbose {
kanikoOpts = append ( kanikoOpts , "--verbosity=debug" )
}
2022-02-09 13:36:43 +02:00
err = execRunner . RunExecutable ( "/kaniko/executor" , kanikoOpts ... )
2020-07-10 08:07:59 +02:00
if err != nil {
2021-05-10 17:44:28 +02:00
log . SetErrorCategory ( log . ErrorBuild )
2020-07-10 08:07:59 +02:00
return errors . Wrap ( err , "execution of '/kaniko/executor' failed" )
}
2022-02-23 12:41:26 +02:00
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" )
}
digestStr := string ( digest )
log . Entry ( ) . Debugf ( "image digest: %s" , digestStr )
commonPipelineEnvironment . container . imageDigest = string ( digestStr )
commonPipelineEnvironment . container . imageDigests = append ( commonPipelineEnvironment . container . imageDigests , digestStr )
}
2020-07-10 08:07:59 +02:00
return nil
}