github.com/jaylevin/jenkins-library@v1.230.4/cmd/kanikoExecute.go (about) 1 package cmd 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/SAP/jenkins-library/pkg/buildsettings" 8 "github.com/SAP/jenkins-library/pkg/certutils" 9 piperhttp "github.com/SAP/jenkins-library/pkg/http" 10 "github.com/pkg/errors" 11 12 "github.com/SAP/jenkins-library/pkg/command" 13 "github.com/SAP/jenkins-library/pkg/docker" 14 "github.com/SAP/jenkins-library/pkg/log" 15 "github.com/SAP/jenkins-library/pkg/piperutils" 16 "github.com/SAP/jenkins-library/pkg/telemetry" 17 ) 18 19 func kanikoExecute(config kanikoExecuteOptions, telemetryData *telemetry.CustomData, commonPipelineEnvironment *kanikoExecuteCommonPipelineEnvironment) { 20 // for command execution use Command 21 c := command.Command{ 22 ErrorCategoryMapping: map[string][]string{ 23 log.ErrorConfiguration.String(): { 24 "unsupported status code 401", 25 }, 26 }, 27 } 28 29 // reroute command output to logging framework 30 c.Stdout(log.Writer()) 31 c.Stderr(log.Writer()) 32 33 client := &piperhttp.Client{} 34 35 fileUtils := &piperutils.Files{} 36 37 err := runKanikoExecute(&config, telemetryData, commonPipelineEnvironment, &c, client, fileUtils) 38 if err != nil { 39 log.Entry().WithError(err).Fatal("Kaniko execution failed") 40 } 41 } 42 43 func runKanikoExecute(config *kanikoExecuteOptions, telemetryData *telemetry.CustomData, commonPipelineEnvironment *kanikoExecuteCommonPipelineEnvironment, execRunner command.ExecRunner, httpClient piperhttp.Sender, fileUtils piperutils.FileUtils) error { 44 binfmtSupported, _ := docker.IsBinfmtMiscSupportedByHost(fileUtils) 45 46 if !binfmtSupported && len(config.TargetArchitectures) > 0 { 47 log.Entry().Warning("Be aware that the host doesn't support binfmt_misc and thus multi archtecture docker builds might not be possible") 48 } 49 50 // backward compatibility for parameter ContainerBuildOptions 51 if len(config.ContainerBuildOptions) > 0 { 52 config.BuildOptions = strings.Split(config.ContainerBuildOptions, " ") 53 log.Entry().Warning("Parameter containerBuildOptions is deprecated, please use buildOptions instead.") 54 telemetryData.Custom1Label = "ContainerBuildOptions" 55 telemetryData.Custom1 = config.ContainerBuildOptions 56 } 57 58 // prepare kaniko container for running with proper Docker config.json and custom certificates 59 // custom certificates will be downloaded and appended to ca-certificates.crt file used in container 60 if len(config.ContainerPreparationCommand) > 0 { 61 prepCommand := strings.Split(config.ContainerPreparationCommand, " ") 62 if err := execRunner.RunExecutable(prepCommand[0], prepCommand[1:]...); err != nil { 63 return errors.Wrap(err, "failed to initialize Kaniko container") 64 } 65 } 66 67 if len(config.CustomTLSCertificateLinks) > 0 { 68 err := certutils.CertificateUpdate(config.CustomTLSCertificateLinks, httpClient, fileUtils, "/kaniko/ssl/certs/ca-certificates.crt") 69 if err != nil { 70 return errors.Wrap(err, "failed to update certificates") 71 } 72 } else { 73 log.Entry().Info("skipping updation of certificates") 74 } 75 76 dockerConfig := []byte(`{"auths":{}}`) 77 if len(config.DockerConfigJSON) > 0 { 78 var err error 79 dockerConfig, err = fileUtils.FileRead(config.DockerConfigJSON) 80 if err != nil { 81 return errors.Wrapf(err, "failed to read file '%v'", config.DockerConfigJSON) 82 } 83 } 84 85 if err := fileUtils.FileWrite("/kaniko/.docker/config.json", dockerConfig, 0644); err != nil { 86 return errors.Wrap(err, "failed to write file '/kaniko/.docker/config.json'") 87 } 88 89 log.Entry().Debugf("preparing build settings information...") 90 stepName := "kanikoExecute" 91 // ToDo: better testability required. So far retrieval of config is rather non deterministic 92 dockerImage, err := GetDockerImageValue(stepName) 93 if err != nil { 94 return fmt.Errorf("failed to retrieve dockerImage configuration: %w", err) 95 } 96 97 kanikoConfig := buildsettings.BuildOptions{ 98 DockerImage: dockerImage, 99 BuildSettingsInfo: config.BuildSettingsInfo, 100 } 101 102 log.Entry().Debugf("creating build settings information...") 103 buildSettingsInfo, err := buildsettings.CreateBuildSettingsInfo(&kanikoConfig, stepName) 104 if err != nil { 105 log.Entry().Warnf("failed to create build settings info: %v", err) 106 } 107 commonPipelineEnvironment.custom.buildSettingsInfo = buildSettingsInfo 108 109 if !piperutils.ContainsString(config.BuildOptions, "--destination") { 110 dest := []string{"--no-push"} 111 if len(config.ContainerRegistryURL) > 0 && len(config.ContainerImageName) > 0 && len(config.ContainerImageTag) > 0 { 112 containerRegistry, err := docker.ContainerRegistryFromURL(config.ContainerRegistryURL) 113 if err != nil { 114 log.SetErrorCategory(log.ErrorConfiguration) 115 return errors.Wrapf(err, "failed to read registry url %v", config.ContainerRegistryURL) 116 } 117 118 commonPipelineEnvironment.container.registryURL = config.ContainerRegistryURL 119 120 // Docker image tags don't allow plus signs in tags, thus replacing with dash 121 containerImageTag := strings.ReplaceAll(config.ContainerImageTag, "+", "-") 122 123 if config.ContainerMultiImageBuild { 124 log.Entry().Debugf("Multi-image build activated for image name '%v'", config.ContainerImageName) 125 imageListWithFilePath, err := docker.ImageListWithFilePath(config.ContainerImageName, config.ContainerMultiImageBuildExcludes, config.ContainerMultiImageBuildTrimDir, fileUtils) 126 if err != nil { 127 return fmt.Errorf("failed to identify image list for multi image build: %w", err) 128 } 129 if len(imageListWithFilePath) == 0 { 130 return fmt.Errorf("no docker files to process, please check exclude list") 131 } 132 for image, file := range imageListWithFilePath { 133 log.Entry().Debugf("Building image '%v' using file '%v'", image, file) 134 containerImageNameAndTag := fmt.Sprintf("%v:%v", image, containerImageTag) 135 dest = []string{"--destination", fmt.Sprintf("%v/%v", containerRegistry, containerImageNameAndTag)} 136 buildOpts := append(config.BuildOptions, dest...) 137 err = runKaniko(file, buildOpts, config.ReadImageDigest, execRunner, fileUtils, commonPipelineEnvironment) 138 if err != nil { 139 return fmt.Errorf("failed to build image '%v' using '%v': %w", image, file, err) 140 } 141 commonPipelineEnvironment.container.imageNames = append(commonPipelineEnvironment.container.imageNames, image) 142 commonPipelineEnvironment.container.imageNameTags = append(commonPipelineEnvironment.container.imageNameTags, containerImageNameAndTag) 143 } 144 145 // for compatibility reasons also fill single imageNameTag field with "root" image in commonPipelineEnvironment 146 // only consider if it has been built 147 // ToDo: reconsider and possibly remove at a later point 148 if len(imageListWithFilePath[config.ContainerImageName]) > 0 { 149 containerImageNameAndTag := fmt.Sprintf("%v:%v", config.ContainerImageName, containerImageTag) 150 commonPipelineEnvironment.container.imageNameTag = containerImageNameAndTag 151 } 152 153 return nil 154 } else { 155 commonPipelineEnvironment.container.imageNames = append(commonPipelineEnvironment.container.imageNames, config.ContainerImageName) 156 commonPipelineEnvironment.container.imageNameTags = append(commonPipelineEnvironment.container.imageNameTags, fmt.Sprintf("%v:%v", config.ContainerImageName, containerImageTag)) 157 } 158 159 log.Entry().Debugf("Single image build for image name '%v'", config.ContainerImageName) 160 containerImageNameAndTag := fmt.Sprintf("%v:%v", config.ContainerImageName, containerImageTag) 161 dest = []string{"--destination", fmt.Sprintf("%v/%v", containerRegistry, containerImageNameAndTag)} 162 commonPipelineEnvironment.container.imageNameTag = containerImageNameAndTag 163 } else if len(config.ContainerImage) > 0 { 164 log.Entry().Debugf("Single image build for image '%v'", config.ContainerImage) 165 containerRegistry, err := docker.ContainerRegistryFromImage(config.ContainerImage) 166 if err != nil { 167 log.SetErrorCategory(log.ErrorConfiguration) 168 return errors.Wrapf(err, "invalid registry part in image %v", config.ContainerImage) 169 } 170 // errors are already caught with previous call to docker.ContainerRegistryFromImage 171 containerImageName, _ := docker.ContainerImageNameFromImage(config.ContainerImage) 172 containerImageNameTag, _ := docker.ContainerImageNameTagFromImage(config.ContainerImage) 173 dest = []string{"--destination", config.ContainerImage} 174 commonPipelineEnvironment.container.registryURL = fmt.Sprintf("https://%v", containerRegistry) 175 commonPipelineEnvironment.container.imageNameTag = containerImageNameTag 176 commonPipelineEnvironment.container.imageNameTags = append(commonPipelineEnvironment.container.imageNameTags, containerImageNameTag) 177 commonPipelineEnvironment.container.imageNames = append(commonPipelineEnvironment.container.imageNames, containerImageName) 178 } 179 config.BuildOptions = append(config.BuildOptions, dest...) 180 } else { 181 log.Entry().Infof("Running Kaniko build with destination defined via buildOptions: %v", config.BuildOptions) 182 183 destination := "" 184 185 for i, o := range config.BuildOptions { 186 if o == "--destination" && i+1 < len(config.BuildOptions) { 187 destination = config.BuildOptions[i+1] 188 break 189 } 190 } 191 192 containerRegistry, err := docker.ContainerRegistryFromImage(destination) 193 194 if err != nil { 195 log.SetErrorCategory(log.ErrorConfiguration) 196 return errors.Wrapf(err, "invalid registry part in image %v", destination) 197 } 198 199 containerImageName, _ := docker.ContainerImageNameFromImage(destination) 200 containerImageNameTag, _ := docker.ContainerImageNameTagFromImage(destination) 201 202 commonPipelineEnvironment.container.registryURL = fmt.Sprintf("https://%v", containerRegistry) 203 commonPipelineEnvironment.container.imageNameTag = containerImageNameTag 204 commonPipelineEnvironment.container.imageNameTags = append(commonPipelineEnvironment.container.imageNameTags, containerImageNameTag) 205 commonPipelineEnvironment.container.imageNames = append(commonPipelineEnvironment.container.imageNames, containerImageName) 206 } 207 208 // no support for building multiple containers 209 return runKaniko(config.DockerfilePath, config.BuildOptions, config.ReadImageDigest, execRunner, fileUtils, commonPipelineEnvironment) 210 } 211 212 func runKaniko(dockerFilepath string, buildOptions []string, readDigest bool, execRunner command.ExecRunner, fileUtils piperutils.FileUtils, commonPipelineEnvironment *kanikoExecuteCommonPipelineEnvironment) error { 213 cwd, err := fileUtils.Getwd() 214 if err != nil { 215 return fmt.Errorf("failed to get current working directory: %w", err) 216 } 217 218 kanikoOpts := []string{"--dockerfile", dockerFilepath, "--context", cwd} 219 kanikoOpts = append(kanikoOpts, buildOptions...) 220 221 tmpDir, err := fileUtils.TempDir("", "*-kanikoExecute") 222 if err != nil { 223 return fmt.Errorf("failed to create tmp dir for kanikoExecute: %w", err) 224 } 225 226 digestFilePath := fmt.Sprintf("%s/digest.txt", tmpDir) 227 228 if readDigest { 229 kanikoOpts = append(kanikoOpts, "--digest-file", digestFilePath) 230 } 231 232 err = execRunner.RunExecutable("/kaniko/executor", kanikoOpts...) 233 if err != nil { 234 log.SetErrorCategory(log.ErrorBuild) 235 return errors.Wrap(err, "execution of '/kaniko/executor' failed") 236 } 237 238 if b, err := fileUtils.FileExists(digestFilePath); err == nil && b { 239 digest, err := fileUtils.FileRead(digestFilePath) 240 241 if err != nil { 242 return errors.Wrap(err, "error while reading image digest") 243 } 244 245 digestStr := string(digest) 246 247 log.Entry().Debugf("image digest: %s", digestStr) 248 249 commonPipelineEnvironment.container.imageDigest = string(digestStr) 250 commonPipelineEnvironment.container.imageDigests = append(commonPipelineEnvironment.container.imageDigests, digestStr) 251 } 252 253 return nil 254 }