1
1
package builder
2
2
3
3
import (
4
+ "bytes"
4
5
"context"
5
6
"crypto/sha1"
6
7
"encoding/json"
@@ -9,28 +10,32 @@ import (
9
10
"math/rand"
10
11
"os"
11
12
"path/filepath"
13
+ "sort"
14
+ "strings"
12
15
"time"
13
16
14
17
"k8s.io/apimachinery/pkg/api/errors"
15
18
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
19
+ "k8s.io/apimachinery/pkg/util/sets"
16
20
"k8s.io/apimachinery/pkg/util/wait"
17
21
18
22
"github.com/docker/distribution/reference"
23
+ dockercmd "github.com/docker/docker/builder/dockerfile/command"
24
+ "github.com/docker/docker/builder/dockerfile/parser"
19
25
"github.com/fsouza/go-dockerclient"
20
-
26
+ "github.com/openshift/imagebuilder"
21
27
s2igit "github.com/openshift/source-to-image/pkg/scm/git"
22
28
"github.com/openshift/source-to-image/pkg/util"
23
29
24
30
buildapiv1 "github.com/openshift/api/build/v1"
31
+ buildclientv1 "github.com/openshift/client-go/build/clientset/versioned/typed/build/v1"
25
32
"github.com/openshift/origin/pkg/build/builder/timing"
26
33
builderutil "github.com/openshift/origin/pkg/build/builder/util"
27
34
"github.com/openshift/origin/pkg/build/builder/util/dockerfile"
28
35
buildutil "github.com/openshift/origin/pkg/build/util"
29
36
"github.com/openshift/origin/pkg/git"
30
37
imageapi "github.com/openshift/origin/pkg/image/apis/image"
31
38
utilglog "github.com/openshift/origin/pkg/util/glog"
32
-
33
- buildclientv1 "github.com/openshift/client-go/build/clientset/versioned/typed/build/v1"
34
39
)
35
40
36
41
// glog is a placeholder until the builders pass an output stream down
@@ -302,8 +307,13 @@ func buildLabels(build *buildapiv1.Build, sourceInfo *git.SourceInfo) []dockerfi
302
307
addBuildLabels (labels , build )
303
308
304
309
kv := make ([]dockerfile.KeyValue , 0 , len (labels )+ len (build .Spec .Output .ImageLabels ))
305
- for k , v := range labels {
306
- kv = append (kv , dockerfile.KeyValue {Key : k , Value : v })
310
+ keys := make ([]string , 0 , len (labels ))
311
+ for k := range labels {
312
+ keys = append (keys , k )
313
+ }
314
+ sort .Strings (keys )
315
+ for _ , k := range keys {
316
+ kv = append (kv , dockerfile.KeyValue {Key : k , Value : labels [k ]})
307
317
}
308
318
// override autogenerated labels with user provided labels
309
319
for _ , lbl := range build .Spec .Output .ImageLabels {
@@ -339,7 +349,12 @@ func readSourceInfo() (*git.SourceInfo, error) {
339
349
// Also append the environment variables and labels in the Dockerfile.
340
350
func addBuildParameters (dir string , build * buildapiv1.Build , sourceInfo * git.SourceInfo ) error {
341
351
dockerfilePath := getDockerfilePath (dir , build )
342
- node , err := parseDockerfile (dockerfilePath )
352
+
353
+ in , err := ioutil .ReadFile (dockerfilePath )
354
+ if err != nil {
355
+ return err
356
+ }
357
+ node , err := imagebuilder .ParseDockerfile (bytes .NewBuffer (in ))
343
358
if err != nil {
344
359
return err
345
360
}
@@ -358,29 +373,130 @@ func addBuildParameters(dir string, build *buildapiv1.Build, sourceInfo *git.Sou
358
373
}
359
374
360
375
// Append build info as environment variables.
361
- err = appendEnv (node , buildEnv (build , sourceInfo ))
362
- if err != nil {
376
+ if err := appendEnv (node , buildEnv (build , sourceInfo )); err != nil {
363
377
return err
364
378
}
365
379
366
380
// Append build labels.
367
- err = appendLabel (node , buildLabels (build , sourceInfo ))
368
- if err != nil {
381
+ if err := appendLabel (node , buildLabels (build , sourceInfo )); err != nil {
369
382
return err
370
383
}
371
384
372
385
// Insert environment variables defined in the build strategy.
373
- err = insertEnvAfterFrom (node , build .Spec .Strategy .DockerStrategy .Env )
374
- if err != nil {
386
+ if err := insertEnvAfterFrom (node , build .Spec .Strategy .DockerStrategy .Env ); err != nil {
375
387
return err
376
388
}
377
389
378
- instructions := dockerfile .ParseTreeToDockerfile (node )
390
+ replaceImagesFromSource (node , build .Spec .Source .Images )
391
+
392
+ out := dockerfile .Write (node )
393
+ glog .V (4 ).Infof ("Replacing dockerfile\n %s\n with:\n %s" , string (in ), string (out ))
394
+ return overwriteFile (dockerfilePath , out )
395
+ }
396
+
397
+ // replaceImagesFromSource updates a single or multi-stage Dockerfile with any replacement
398
+ // image sources ('FROM <name>' and 'COPY --from=<name>'). It operates on exact string matches
399
+ // and performs no interpretation of names from the Dockerfile.
400
+ func replaceImagesFromSource (node * parser.Node , imageSources []buildapiv1.ImageSource ) {
401
+ replacements := make (map [string ]string )
402
+ for _ , image := range imageSources {
403
+ if image .From .Kind != "DockerImage" || len (image .From .Name ) == 0 {
404
+ continue
405
+ }
406
+ for _ , name := range image .As {
407
+ replacements [name ] = image .From .Name
408
+ }
409
+ }
410
+ names := make (map [string ]string )
411
+ stages := imagebuilder .NewStages (node , imagebuilder .NewBuilder (nil ))
412
+ for _ , stage := range stages {
413
+ for _ , child := range stage .Node .Children {
414
+ switch {
415
+ case child .Value == dockercmd .From && child .Next != nil :
416
+ image := child .Next .Value
417
+ if replacement , ok := replacements [image ]; ok {
418
+ image = replacement
419
+ child .Next .Value = image
420
+ }
421
+ names [stage .Name ] = image
422
+ case child .Value == dockercmd .Copy :
423
+ if ref , ok := nodeHasFromRef (child ); ok {
424
+ if len (ref ) > 0 {
425
+ if _ , ok := names [ref ]; ! ok {
426
+ if replacement , ok := replacements [ref ]; ok {
427
+ ref = replacement
428
+ nodeReplaceFromRef (child , ref )
429
+ }
430
+ }
431
+ }
432
+ }
433
+ }
434
+ }
435
+ }
436
+ }
437
+
438
+ // findReferencedImages returns all qualified images referenced by the Dockerfile, whether the
439
+ // build is a multi-stage build, or returns an error.
440
+ func findReferencedImages (dockerfilePath string ) ([]string , bool , error ) {
441
+ if len (dockerfilePath ) == 0 {
442
+ return nil , false , nil
443
+ }
444
+ node , err := imagebuilder .ParseFile (dockerfilePath )
445
+ if err != nil {
446
+ return nil , false , err
447
+ }
448
+ names := make (map [string ]string )
449
+ images := sets .NewString ()
450
+ stages := imagebuilder .NewStages (node , imagebuilder .NewBuilder (nil ))
451
+ for _ , stage := range stages {
452
+ for _ , child := range stage .Node .Children {
453
+ switch {
454
+ case child .Value == dockercmd .From && child .Next != nil :
455
+ image := child .Next .Value
456
+ names [stage .Name ] = image
457
+ images .Insert (image )
458
+ case child .Value == dockercmd .Copy :
459
+ if ref , ok := nodeHasFromRef (child ); ok {
460
+ if len (ref ) > 0 {
461
+ if _ , ok := names [ref ]; ! ok {
462
+ images .Insert (ref )
463
+ }
464
+ }
465
+ }
466
+ }
467
+ }
468
+ }
469
+ return images .List (), len (stages ) > 1 , nil
470
+ }
379
471
380
- // Overwrite the Dockerfile.
381
- fi , err := os .Stat ( dockerfilePath )
472
+ func overwriteFile ( name string , out [] byte ) error {
473
+ f , err := os .OpenFile ( name , os . O_TRUNC | os . O_WRONLY , 0 )
382
474
if err != nil {
383
475
return err
384
476
}
385
- return ioutil .WriteFile (dockerfilePath , instructions , fi .Mode ())
477
+ if _ , err := f .Write (out ); err != nil {
478
+ f .Close ()
479
+ return err
480
+ }
481
+ return f .Close ()
482
+ }
483
+
484
+ func nodeHasFromRef (node * parser.Node ) (string , bool ) {
485
+ for _ , arg := range node .Flags {
486
+ switch {
487
+ case strings .HasPrefix (arg , "--from=" ):
488
+ from := strings .TrimPrefix (arg , "--from=" )
489
+ return from , true
490
+ }
491
+ }
492
+ return "" , false
493
+ }
494
+
495
+ func nodeReplaceFromRef (node * parser.Node , name string ) {
496
+ for i , arg := range node .Flags {
497
+ switch {
498
+ case strings .HasPrefix (arg , "--from=" ):
499
+ node .Flags [i ] = fmt .Sprintf ("--from=%s" , name )
500
+ }
501
+ }
386
502
}
0 commit comments