@@ -2,6 +2,7 @@ package cmd
2
2
3
3
import (
4
4
"fmt"
5
+ "github.com/mitchellh/mapstructure"
5
6
"strings"
6
7
7
8
"github.com/SAP/jenkins-library/pkg/buildsettings"
@@ -134,117 +135,222 @@ func runKanikoExecute(config *kanikoExecuteOptions, telemetryData *telemetry.Cus
134
135
}
135
136
commonPipelineEnvironment .custom .buildSettingsInfo = buildSettingsInfo
136
137
137
- if ! piperutils .ContainsString (config .BuildOptions , "--destination" ) {
138
- dest := []string {"--no-push" }
139
- if len (config .ContainerRegistryURL ) > 0 && len (config .ContainerImageName ) > 0 && len (config .ContainerImageTag ) > 0 {
140
- containerRegistry , err := docker .ContainerRegistryFromURL (config .ContainerRegistryURL )
141
- if err != nil {
142
- log .SetErrorCategory (log .ErrorConfiguration )
143
- return errors .Wrapf (err , "failed to read registry url %v" , config .ContainerRegistryURL )
138
+ switch {
139
+ case config .ContainerMultiImageBuild :
140
+ log .Entry ().Debugf ("Multi-image build activated for image name '%v'" , config .ContainerImageName )
141
+
142
+ if config .ContainerRegistryURL == "" {
143
+ return fmt .Errorf ("empty ContainerRegistryURL" )
144
+ }
145
+ if config .ContainerImageName == "" {
146
+ return fmt .Errorf ("empty ContainerImageName" )
147
+ }
148
+ if config .ContainerImageTag == "" {
149
+ return fmt .Errorf ("empty ContainerImageTag" )
150
+ }
151
+
152
+ containerRegistry , err := docker .ContainerRegistryFromURL (config .ContainerRegistryURL )
153
+ if err != nil {
154
+ log .SetErrorCategory (log .ErrorConfiguration )
155
+ return errors .Wrapf (err , "failed to read registry url %v" , config .ContainerRegistryURL )
156
+ }
157
+
158
+ commonPipelineEnvironment .container .registryURL = config .ContainerRegistryURL
159
+
160
+ // Docker image tags don't allow plus signs in tags, thus replacing with dash
161
+ containerImageTag := strings .ReplaceAll (config .ContainerImageTag , "+" , "-" )
162
+
163
+ imageListWithFilePath , err := docker .ImageListWithFilePath (config .ContainerImageName , config .ContainerMultiImageBuildExcludes , config .ContainerMultiImageBuildTrimDir , fileUtils )
164
+ if err != nil {
165
+ return fmt .Errorf ("failed to identify image list for multi image build: %w" , err )
166
+ }
167
+ if len (imageListWithFilePath ) == 0 {
168
+ return fmt .Errorf ("no docker files to process, please check exclude list" )
169
+ }
170
+ for image , file := range imageListWithFilePath {
171
+ log .Entry ().Debugf ("Building image '%v' using file '%v'" , image , file )
172
+ containerImageNameAndTag := fmt .Sprintf ("%v:%v" , image , containerImageTag )
173
+ buildOpts := append (config .BuildOptions , "--destination" , fmt .Sprintf ("%v/%v" , containerRegistry , containerImageNameAndTag ))
174
+ if err = runKaniko (file , buildOpts , config .ReadImageDigest , execRunner , fileUtils , commonPipelineEnvironment ); err != nil {
175
+ return fmt .Errorf ("failed to build image '%v' using '%v': %w" , image , file , err )
144
176
}
177
+ commonPipelineEnvironment .container .imageNames = append (commonPipelineEnvironment .container .imageNames , image )
178
+ commonPipelineEnvironment .container .imageNameTags = append (commonPipelineEnvironment .container .imageNameTags , containerImageNameAndTag )
179
+ }
145
180
146
- commonPipelineEnvironment .container .registryURL = config .ContainerRegistryURL
181
+ // for compatibility reasons also fill single imageNameTag field with "root" image in commonPipelineEnvironment
182
+ // only consider if it has been built
183
+ // ToDo: reconsider and possibly remove at a later point
184
+ if len (imageListWithFilePath [config .ContainerImageName ]) > 0 {
185
+ containerImageNameAndTag := fmt .Sprintf ("%v:%v" , config .ContainerImageName , containerImageTag )
186
+ commonPipelineEnvironment .container .imageNameTag = containerImageNameAndTag
187
+ }
188
+ if config .CreateBOM {
189
+ // Syft for multi image, generates bom-docker-(1/2/3).xml
190
+ return syft .GenerateSBOM (config .SyftDownloadURL , "/kaniko/.docker" , execRunner , fileUtils , httpClient , commonPipelineEnvironment .container .registryURL , commonPipelineEnvironment .container .imageNameTags )
191
+ }
192
+ return nil
147
193
148
- // Docker image tags don't allow plus signs in tags, thus replacing with dash
149
- containerImageTag := strings .ReplaceAll (config .ContainerImageTag , "+" , "-" )
194
+ case config .MultipleImages != nil :
195
+ log .Entry ().Debugf ("multipleImages build activated" )
196
+ parsedMultipleImages , err := parseMultipleImages (config .MultipleImages )
197
+ if err != nil {
198
+ log .SetErrorCategory (log .ErrorConfiguration )
199
+ return errors .Wrap (err , "failed to parse multipleImages param" )
200
+ }
150
201
151
- if config .ContainerMultiImageBuild {
152
- log .Entry ().Debugf ("Multi-image build activated for image name '%v'" , config .ContainerImageName )
153
- imageListWithFilePath , err := docker .ImageListWithFilePath (config .ContainerImageName , config .ContainerMultiImageBuildExcludes , config .ContainerMultiImageBuildTrimDir , fileUtils )
202
+ for _ , entry := range parsedMultipleImages {
203
+ switch {
204
+ case entry .ContextSubPath == "" :
205
+ return fmt .Errorf ("multipleImages: empty contextSubPath" )
206
+ case entry .ContainerImageName != "" :
207
+ containerRegistry , err := docker .ContainerRegistryFromURL (config .ContainerRegistryURL )
154
208
if err != nil {
155
- return fmt .Errorf ("failed to identify image list for multi image build: %w" , err )
156
- }
157
- if len (imageListWithFilePath ) == 0 {
158
- return fmt .Errorf ("no docker files to process, please check exclude list" )
209
+ log .SetErrorCategory (log .ErrorConfiguration )
210
+ return errors .Wrapf (err , "multipleImages: failed to read registry url %v" , config .ContainerRegistryURL )
159
211
}
160
- for image , file := range imageListWithFilePath {
161
- log .Entry ().Debugf ("Building image '%v' using file '%v'" , image , file )
162
- containerImageNameAndTag := fmt .Sprintf ("%v:%v" , image , containerImageTag )
163
- dest = []string {"--destination" , fmt .Sprintf ("%v/%v" , containerRegistry , containerImageNameAndTag )}
164
- buildOpts := append (config .BuildOptions , dest ... )
165
- err = runKaniko (file , buildOpts , config .ReadImageDigest , execRunner , fileUtils , commonPipelineEnvironment )
166
- if err != nil {
167
- return fmt .Errorf ("failed to build image '%v' using '%v': %w" , image , file , err )
212
+
213
+ if entry .ContainerImageTag == "" {
214
+ if config .ContainerImageTag == "" {
215
+ return fmt .Errorf ("both multipleImages containerImageTag and config.containerImageTag are empty" )
168
216
}
169
- commonPipelineEnvironment .container .imageNames = append (commonPipelineEnvironment .container .imageNames , image )
170
- commonPipelineEnvironment .container .imageNameTags = append (commonPipelineEnvironment .container .imageNameTags , containerImageNameAndTag )
217
+ entry .ContainerImageTag = config .ContainerImageTag
218
+ }
219
+ // Docker image tags don't allow plus signs in tags, thus replacing with dash
220
+ containerImageTag := strings .ReplaceAll (entry .ContainerImageTag , "+" , "-" )
221
+ containerImageNameAndTag := fmt .Sprintf ("%v:%v" , entry .ContainerImageName , containerImageTag )
222
+
223
+ log .Entry ().Debugf ("multipleImages: image build '%v'" , entry .ContainerImageName )
224
+
225
+ buildOptions := append (config .BuildOptions ,
226
+ "--context-sub-path" , entry .ContextSubPath ,
227
+ "--destination" , fmt .Sprintf ("%v/%v" , containerRegistry , containerImageNameAndTag ),
228
+ )
229
+ if err = runKaniko (config .DockerfilePath , buildOptions , config .ReadImageDigest , execRunner , fileUtils , commonPipelineEnvironment ); err != nil {
230
+ return fmt .Errorf ("multipleImages: failed to build image '%v' using '%v': %w" , entry .ContainerImageName , config .DockerfilePath , err )
171
231
}
172
232
173
- // for compatibility reasons also fill single imageNameTag field with "root" image in commonPipelineEnvironment
174
- // only consider if it has been built
175
- // ToDo: reconsider and possibly remove at a later point
176
- if len (imageListWithFilePath [config .ContainerImageName ]) > 0 {
177
- containerImageNameAndTag := fmt .Sprintf ("%v:%v" , config .ContainerImageName , containerImageTag )
178
- commonPipelineEnvironment .container .imageNameTag = containerImageNameAndTag
233
+ commonPipelineEnvironment .container .imageNameTags = append (commonPipelineEnvironment .container .imageNameTags , containerImageNameAndTag )
234
+ commonPipelineEnvironment .container .imageNames = append (commonPipelineEnvironment .container .imageNames , entry .ContainerImageName )
235
+
236
+ case entry .ContainerImage != "" :
237
+ containerImageName , err := docker .ContainerImageNameFromImage (entry .ContainerImage )
238
+ if err != nil {
239
+ log .SetErrorCategory (log .ErrorConfiguration )
240
+ return errors .Wrapf (err , "invalid name part in image %v" , entry .ContainerImage )
179
241
}
180
- if config .CreateBOM {
181
- //Syft for multi image, generates bom-docker-(1/2/3).xml
182
- return syft .GenerateSBOM (config .SyftDownloadURL , "/kaniko/.docker" , execRunner , fileUtils , httpClient , commonPipelineEnvironment .container .registryURL , commonPipelineEnvironment .container .imageNameTags )
242
+ containerImageNameTag , err := docker .ContainerImageNameTagFromImage (entry .ContainerImage )
243
+ if err != nil {
244
+ log .SetErrorCategory (log .ErrorConfiguration )
245
+ return errors .Wrapf (err , "invalid tag part in image %v" , entry .ContainerImage )
183
246
}
184
- return nil
185
- } else {
186
- commonPipelineEnvironment .container .imageNames = append (commonPipelineEnvironment .container .imageNames , config .ContainerImageName )
187
- commonPipelineEnvironment .container .imageNameTags = append (commonPipelineEnvironment .container .imageNameTags , fmt .Sprintf ("%v:%v" , config .ContainerImageName , containerImageTag ))
188
- }
189
247
190
- log .Entry ().Debugf ("Single image build for image name '%v'" , config .ContainerImageName )
191
- containerImageNameAndTag := fmt .Sprintf ("%v:%v" , config .ContainerImageName , containerImageTag )
192
- dest = []string {"--destination" , fmt .Sprintf ("%v/%v" , containerRegistry , containerImageNameAndTag )}
193
- commonPipelineEnvironment .container .imageNameTag = containerImageNameAndTag
194
- } else if len (config .ContainerImage ) > 0 {
195
- log .Entry ().Debugf ("Single image build for image '%v'" , config .ContainerImage )
196
- containerRegistry , err := docker .ContainerRegistryFromImage (config .ContainerImage )
197
- if err != nil {
198
- log .SetErrorCategory (log .ErrorConfiguration )
199
- return errors .Wrapf (err , "invalid registry part in image %v" , config .ContainerImage )
248
+ log .Entry ().Debugf ("multipleImages: image build '%v'" , containerImageName )
249
+
250
+ buildOptions := append (config .BuildOptions ,
251
+ "--context-sub-path" , entry .ContextSubPath ,
252
+ "--destination" , entry .ContainerImage ,
253
+ )
254
+ if err = runKaniko (config .DockerfilePath , buildOptions , config .ReadImageDigest , execRunner , fileUtils , commonPipelineEnvironment ); err != nil {
255
+ return fmt .Errorf ("multipleImages: failed to build image '%v' using '%v': %w" , containerImageName , config .DockerfilePath , err )
256
+ }
257
+
258
+ commonPipelineEnvironment .container .imageNameTags = append (commonPipelineEnvironment .container .imageNameTags , containerImageNameTag )
259
+ commonPipelineEnvironment .container .imageNames = append (commonPipelineEnvironment .container .imageNames , containerImageName )
260
+ default :
261
+ return fmt .Errorf ("multipleImages: either containerImageName or containerImage must be filled" )
200
262
}
201
- // errors are already caught with previous call to docker.ContainerRegistryFromImage
202
- containerImageName , _ := docker .ContainerImageNameFromImage (config .ContainerImage )
203
- containerImageNameTag , _ := docker .ContainerImageNameTagFromImage (config .ContainerImage )
204
- dest = []string {"--destination" , config .ContainerImage }
205
- commonPipelineEnvironment .container .registryURL = fmt .Sprintf ("https://%v" , containerRegistry )
206
- commonPipelineEnvironment .container .imageNameTag = containerImageNameTag
207
- commonPipelineEnvironment .container .imageNameTags = append (commonPipelineEnvironment .container .imageNameTags , containerImageNameTag )
208
- commonPipelineEnvironment .container .imageNames = append (commonPipelineEnvironment .container .imageNames , containerImageName )
209
263
}
210
- config .BuildOptions = append (config .BuildOptions , dest ... )
211
- } else {
212
- log .Entry ().Infof ("Running Kaniko build with destination defined via buildOptions: %v" , config .BuildOptions )
213
264
214
- destination := ""
265
+ // for compatibility reasons also fill single imageNameTag field with "root" image in commonPipelineEnvironment
266
+ containerImageNameAndTag := fmt .Sprintf ("%v:%v" , config .ContainerImageName , config .ContainerImageTag )
267
+ commonPipelineEnvironment .container .imageNameTag = containerImageNameAndTag
268
+ commonPipelineEnvironment .container .registryURL = config .ContainerRegistryURL
269
+
270
+ if config .CreateBOM {
271
+ // Syft for multi image, generates bom-docker-(1/2/3).xml
272
+ return syft .GenerateSBOM (config .SyftDownloadURL , "/kaniko/.docker" , execRunner , fileUtils , httpClient , commonPipelineEnvironment .container .registryURL , commonPipelineEnvironment .container .imageNameTags )
273
+ }
274
+ return nil
275
+
276
+ case piperutils .ContainsString (config .BuildOptions , "--destination" ):
277
+ log .Entry ().Infof ("Running Kaniko build with destination defined via buildOptions: %v" , config .BuildOptions )
215
278
216
279
for i , o := range config .BuildOptions {
217
280
if o == "--destination" && i + 1 < len (config .BuildOptions ) {
218
- destination = config .BuildOptions [i + 1 ]
219
- break
281
+ destination := config .BuildOptions [i + 1 ]
282
+
283
+ containerRegistry , err := docker .ContainerRegistryFromImage (destination )
284
+ if err != nil {
285
+ log .SetErrorCategory (log .ErrorConfiguration )
286
+ return errors .Wrapf (err , "invalid registry part in image %v" , destination )
287
+ }
288
+ if commonPipelineEnvironment .container .registryURL == "" {
289
+ commonPipelineEnvironment .container .registryURL = fmt .Sprintf ("https://%v" , containerRegistry )
290
+ }
291
+
292
+ // errors are already caught with previous call to docker.ContainerRegistryFromImage
293
+ containerImageName , _ := docker .ContainerImageNameFromImage (destination )
294
+ containerImageNameTag , _ := docker .ContainerImageNameTagFromImage (destination )
295
+
296
+ if commonPipelineEnvironment .container .imageNameTag == "" {
297
+ commonPipelineEnvironment .container .imageNameTag = containerImageNameTag
298
+ }
299
+ commonPipelineEnvironment .container .imageNameTags = append (commonPipelineEnvironment .container .imageNameTags , containerImageNameTag )
300
+ commonPipelineEnvironment .container .imageNames = append (commonPipelineEnvironment .container .imageNames , containerImageName )
220
301
}
221
302
}
222
303
223
- containerRegistry , err := docker .ContainerRegistryFromImage (destination )
304
+ case config .ContainerRegistryURL != "" && config .ContainerImageName != "" && config .ContainerImageTag != "" :
305
+ log .Entry ().Debugf ("Single image build for image name '%v'" , config .ContainerImageName )
224
306
307
+ containerRegistry , err := docker .ContainerRegistryFromURL (config .ContainerRegistryURL )
225
308
if err != nil {
226
309
log .SetErrorCategory (log .ErrorConfiguration )
227
- return errors .Wrapf (err , "invalid registry part in image %v" , destination )
310
+ return errors .Wrapf (err , "failed to read registry url %v" , config . ContainerRegistryURL )
228
311
}
229
312
230
- containerImageName , _ := docker .ContainerImageNameFromImage (destination )
231
- containerImageNameTag , _ := docker .ContainerImageNameTagFromImage (destination )
313
+ // Docker image tags don't allow plus signs in tags, thus replacing with dash
314
+ containerImageTag := strings .ReplaceAll (config .ContainerImageTag , "+" , "-" )
315
+ containerImageNameAndTag := fmt .Sprintf ("%v:%v" , config .ContainerImageName , containerImageTag )
316
+
317
+ commonPipelineEnvironment .container .registryURL = config .ContainerRegistryURL
318
+ commonPipelineEnvironment .container .imageNameTag = containerImageNameAndTag
319
+ commonPipelineEnvironment .container .imageNameTags = append (commonPipelineEnvironment .container .imageNameTags , containerImageNameAndTag )
320
+ commonPipelineEnvironment .container .imageNames = append (commonPipelineEnvironment .container .imageNames , config .ContainerImageName )
321
+ config .BuildOptions = append (config .BuildOptions , "--destination" , fmt .Sprintf ("%v/%v" , containerRegistry , containerImageNameAndTag ))
322
+
323
+ case config .ContainerImage != "" :
324
+ log .Entry ().Debugf ("Single image build for image '%v'" , config .ContainerImage )
325
+
326
+ containerRegistry , err := docker .ContainerRegistryFromImage (config .ContainerImage )
327
+ if err != nil {
328
+ log .SetErrorCategory (log .ErrorConfiguration )
329
+ return errors .Wrapf (err , "invalid registry part in image %v" , config .ContainerImage )
330
+ }
331
+
332
+ // errors are already caught with previous call to docker.ContainerRegistryFromImage
333
+ containerImageName , _ := docker .ContainerImageNameFromImage (config .ContainerImage )
334
+ containerImageNameTag , _ := docker .ContainerImageNameTagFromImage (config .ContainerImage )
232
335
233
336
commonPipelineEnvironment .container .registryURL = fmt .Sprintf ("https://%v" , containerRegistry )
234
337
commonPipelineEnvironment .container .imageNameTag = containerImageNameTag
235
338
commonPipelineEnvironment .container .imageNameTags = append (commonPipelineEnvironment .container .imageNameTags , containerImageNameTag )
236
339
commonPipelineEnvironment .container .imageNames = append (commonPipelineEnvironment .container .imageNames , containerImageName )
340
+ config .BuildOptions = append (config .BuildOptions , "--destination" , config .ContainerImage )
341
+ default :
342
+ config .BuildOptions = append (config .BuildOptions , "--no-push" )
237
343
}
238
344
239
- // no support for building multiple containers
240
- kanikoErr := runKaniko (config .DockerfilePath , config .BuildOptions , config .ReadImageDigest , execRunner , fileUtils , commonPipelineEnvironment )
241
- if kanikoErr != nil {
242
- return kanikoErr
345
+ if err = runKaniko (config .DockerfilePath , config .BuildOptions , config .ReadImageDigest , execRunner , fileUtils , commonPipelineEnvironment ); err != nil {
346
+ return err
243
347
}
348
+
244
349
if config .CreateBOM {
245
350
// Syft for single image, generates bom-docker-0.xml
246
351
return syft .GenerateSBOM (config .SyftDownloadURL , "/kaniko/.docker" , execRunner , fileUtils , httpClient , commonPipelineEnvironment .container .registryURL , commonPipelineEnvironment .container .imageNameTags )
247
352
}
353
+
248
354
return nil
249
355
}
250
356
@@ -254,7 +360,9 @@ func runKaniko(dockerFilepath string, buildOptions []string, readDigest bool, ex
254
360
return fmt .Errorf ("failed to get current working directory: %w" , err )
255
361
}
256
362
257
- kanikoOpts := []string {"--dockerfile" , dockerFilepath , "--context" , cwd }
363
+ // kaniko build context needs a proper prefix, for local directory it is 'dir://'
364
+ // for more details see https://github.com/GoogleContainerTools/kaniko#kaniko-build-contexts
365
+ kanikoOpts := []string {"--dockerfile" , dockerFilepath , "--context" , "dir://" + cwd }
258
366
kanikoOpts = append (kanikoOpts , buildOptions ... )
259
367
260
368
tmpDir , err := fileUtils .TempDir ("" , "*-kanikoExecute" )
@@ -280,7 +388,6 @@ func runKaniko(dockerFilepath string, buildOptions []string, readDigest bool, ex
280
388
281
389
if b , err := fileUtils .FileExists (digestFilePath ); err == nil && b {
282
390
digest , err := fileUtils .FileRead (digestFilePath )
283
-
284
391
if err != nil {
285
392
return errors .Wrap (err , "error while reading image digest" )
286
393
}
@@ -289,9 +396,31 @@ func runKaniko(dockerFilepath string, buildOptions []string, readDigest bool, ex
289
396
290
397
log .Entry ().Debugf ("image digest: %s" , digestStr )
291
398
292
- commonPipelineEnvironment .container .imageDigest = string ( digestStr )
399
+ commonPipelineEnvironment .container .imageDigest = digestStr
293
400
commonPipelineEnvironment .container .imageDigests = append (commonPipelineEnvironment .container .imageDigests , digestStr )
294
401
}
295
402
296
403
return nil
297
404
}
405
+
406
+ type multipleImageConf struct {
407
+ ContextSubPath string `json:"contextSubPath,omitempty"`
408
+ ContainerImageName string `json:"containerImageName,omitempty"`
409
+ ContainerImageTag string `json:"containerImageTag,omitempty"`
410
+ ContainerImage string `json:"containerImage,omitempty"`
411
+ }
412
+
413
+ func parseMultipleImages (src []map [string ]interface {}) ([]multipleImageConf , error ) {
414
+ var result []multipleImageConf
415
+
416
+ for _ , conf := range src {
417
+ var structuredConf multipleImageConf
418
+ if err := mapstructure .Decode (conf , & structuredConf ); err != nil {
419
+ return nil , err
420
+ }
421
+
422
+ result = append (result , structuredConf )
423
+ }
424
+
425
+ return result , nil
426
+ }
0 commit comments