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,128 @@ 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
+ child .Next .Value = replacement
419
+ }
420
+ names [stage .Name ] = image
421
+ case child .Value == dockercmd .Copy :
422
+ if ref , ok := nodeHasFromRef (child ); ok {
423
+ if len (ref ) > 0 {
424
+ if _ , ok := names [ref ]; ! ok {
425
+ if replacement , ok := replacements [ref ]; ok {
426
+ nodeReplaceFromRef (child , replacement )
427
+ }
428
+ }
429
+ }
430
+ }
431
+ }
432
+ }
433
+ }
434
+ }
435
+
436
+ // findReferencedImages returns all qualified images referenced by the Dockerfile, whether the
437
+ // build is a multi-stage build, or returns an error.
438
+ func findReferencedImages (dockerfilePath string ) ([]string , bool , error ) {
439
+ if len (dockerfilePath ) == 0 {
440
+ return nil , false , nil
441
+ }
442
+ node , err := imagebuilder .ParseFile (dockerfilePath )
443
+ if err != nil {
444
+ return nil , false , err
445
+ }
446
+ names := make (map [string ]string )
447
+ images := sets .NewString ()
448
+ stages := imagebuilder .NewStages (node , imagebuilder .NewBuilder (nil ))
449
+ for _ , stage := range stages {
450
+ for _ , child := range stage .Node .Children {
451
+ switch {
452
+ case child .Value == dockercmd .From && child .Next != nil :
453
+ image := child .Next .Value
454
+ names [stage .Name ] = image
455
+ images .Insert (image )
456
+ case child .Value == dockercmd .Copy :
457
+ if ref , ok := nodeHasFromRef (child ); ok {
458
+ if len (ref ) > 0 {
459
+ if _ , ok := names [ref ]; ! ok {
460
+ images .Insert (ref )
461
+ }
462
+ }
463
+ }
464
+ }
465
+ }
466
+ }
467
+ return images .List (), len (stages ) > 1 , nil
468
+ }
379
469
380
- // Overwrite the Dockerfile.
381
- fi , err := os .Stat ( dockerfilePath )
470
+ func overwriteFile ( name string , out [] byte ) error {
471
+ f , err := os .OpenFile ( name , os . O_TRUNC | os . O_WRONLY , 0 )
382
472
if err != nil {
383
473
return err
384
474
}
385
- return ioutil .WriteFile (dockerfilePath , instructions , fi .Mode ())
475
+ if _ , err := f .Write (out ); err != nil {
476
+ f .Close ()
477
+ return err
478
+ }
479
+ return f .Close ()
480
+ }
481
+
482
+ func nodeHasFromRef (node * parser.Node ) (string , bool ) {
483
+ for _ , arg := range node .Flags {
484
+ switch {
485
+ case strings .HasPrefix (arg , "--from=" ):
486
+ from := strings .TrimPrefix (arg , "--from=" )
487
+ return from , true
488
+ }
489
+ }
490
+ return "" , false
491
+ }
492
+
493
+ func nodeReplaceFromRef (node * parser.Node , name string ) {
494
+ for i , arg := range node .Flags {
495
+ switch {
496
+ case strings .HasPrefix (arg , "--from=" ):
497
+ node .Flags [i ] = fmt .Sprintf ("--from=%s" , name )
498
+ }
499
+ }
386
500
}
0 commit comments