@@ -4,22 +4,30 @@ import (
44 "archive/tar"
55 "bufio"
66 "bytes"
7+ "crypto/rand"
8+ "crypto/sha256"
9+ "encoding/hex"
710 "encoding/json"
811 "fmt"
912 "io"
1013 "io/ioutil"
1114 "os"
15+ "path/filepath"
1216 "regexp"
1317 "runtime"
1418
19+ "github.com/Sirupsen/logrus"
1520 "github.com/docker/cli/cli"
1621 "github.com/docker/cli/cli/command"
1722 "github.com/docker/cli/cli/command/image/build"
23+ cliconfig "github.com/docker/cli/cli/config"
1824 "github.com/docker/cli/opts"
1925 "github.com/docker/distribution/reference"
2026 "github.com/docker/docker/api"
2127 "github.com/docker/docker/api/types"
2228 "github.com/docker/docker/api/types/container"
29+ "github.com/docker/docker/api/types/versions"
30+ "github.com/docker/docker/client/session"
2331 "github.com/docker/docker/pkg/archive"
2432 "github.com/docker/docker/pkg/jsonmessage"
2533 "github.com/docker/docker/pkg/progress"
@@ -152,6 +160,10 @@ func (out *lastProgressOutput) WriteProgress(prog progress.Progress) error {
152160 return out .output .WriteProgress (prog )
153161}
154162
163+ func isSessionSupported (dockerCli * command.DockerCli ) bool {
164+ return dockerCli .ServerInfo ().HasExperimental && versions .GreaterThanOrEqualTo (dockerCli .Client ().ClientVersion (), "1.30" )
165+ }
166+
155167// nolint: gocyclo
156168func runBuild (dockerCli * command.DockerCli , options buildOptions ) error {
157169 var (
@@ -163,6 +175,7 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
163175 relDockerfile string
164176 progBuff io.Writer
165177 buildBuff io.Writer
178+ remote string
166179 )
167180
168181 if options .dockerfileFromStdin () {
@@ -250,7 +263,8 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
250263 }
251264 }
252265
253- ctx := context .Background ()
266+ ctx , cancel := context .WithCancel (context .Background ())
267+ defer cancel ()
254268
255269 var resolvedTags []* resolvedTag
256270 if command .IsTrusted () {
@@ -268,6 +282,21 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
268282 progressOutput = & lastProgressOutput {output : progressOutput }
269283 }
270284
285+ if dockerfileCtx != nil && buildCtx == nil {
286+ buildCtx = dockerfileCtx
287+ }
288+ var s * session.Session
289+ if isSessionSupported (dockerCli ) {
290+ sharedKey , err := getBuildSharedKey (contextDir )
291+ if err != nil {
292+ return errors .Wrap (err , "failed to get build shared key" )
293+ }
294+ s , err = session .NewSession (filepath .Base (contextDir ), sharedKey )
295+ if err != nil {
296+ return errors .Wrap (err , "failed to create session" )
297+ }
298+ }
299+
271300 var body io.Reader = progress .NewProgressReader (buildCtx , progressOutput , 0 , "" , "Sending build context to Docker daemon" )
272301
273302 authConfigs , _ := dockerCli .GetAllCredentials ()
@@ -299,6 +328,18 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
299328 Squash : options .squash ,
300329 ExtraHosts : options .extraHosts .GetAll (),
301330 Target : options .target ,
331+ RemoteContext : remote ,
332+ }
333+
334+ if s != nil {
335+ go func () {
336+ logrus .Debugf ("running session: %v" , s .UUID ())
337+ if err := s .Run (ctx , dockerCli .Client ().DialSession ); err != nil {
338+ logrus .Error (err )
339+ cancel ()
340+ }
341+ }()
342+ buildOptions .SessionID = s .UUID ()
302343 }
303344
304345 response , err := dockerCli .Client ().ImageBuild (ctx , body , buildOptions )
@@ -373,6 +414,36 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
373414 return nil
374415}
375416
417+ func getBuildSharedKey (dir string ) (string , error ) {
418+ // build session is hash of build dir with node based randomness
419+ sessionFile := filepath .Join (cliconfig .Dir (), ".buildsharedkey" )
420+ if _ , err := os .Lstat (sessionFile ); err != nil {
421+ if os .IsNotExist (err ) {
422+ b := make ([]byte , 32 )
423+ if _ , err := rand .Read (b ); err != nil {
424+ return "" , err
425+ }
426+ if err := os .MkdirAll (cliconfig .Dir (), 0600 ); err != nil {
427+ return "" , err
428+ }
429+ if err := ioutil .WriteFile (sessionFile , []byte (hex .EncodeToString (b )), 0600 ); err != nil {
430+ return "" , err
431+ }
432+ } else {
433+ return "" , err
434+ }
435+ }
436+
437+ dt , err := ioutil .ReadFile (sessionFile )
438+ if err != nil {
439+ return "" , errors .Wrapf (err , "failed to read %s" , sessionFile )
440+
441+ }
442+
443+ s := sha256 .Sum256 ([]byte (fmt .Sprintf ("%s:%s" , dt , dir )))
444+ return hex .EncodeToString (s [:]), nil // add randomness to force recheck
445+ }
446+
376447func isLocalDir (c string ) bool {
377448 _ , err := os .Stat (c )
378449 return err == nil
0 commit comments