github.com/AliyunContainerService/cli@v0.0.0-20181009023821-814ced4b30d0/cli/command/image/build_session.go (about) 1 package image 2 3 import ( 4 "bytes" 5 "context" 6 "crypto/rand" 7 "crypto/sha256" 8 "encoding/hex" 9 "fmt" 10 "io" 11 "io/ioutil" 12 "os" 13 "path/filepath" 14 "sync" 15 "time" 16 17 "github.com/docker/cli/cli/command" 18 "github.com/docker/cli/cli/command/image/build" 19 cliconfig "github.com/docker/cli/cli/config" 20 "github.com/docker/docker/api/types/versions" 21 "github.com/docker/docker/pkg/progress" 22 "github.com/moby/buildkit/session" 23 "github.com/moby/buildkit/session/filesync" 24 "github.com/pkg/errors" 25 "golang.org/x/time/rate" 26 ) 27 28 const clientSessionRemote = "client-session" 29 30 func isSessionSupported(dockerCli command.Cli) bool { 31 if versions.GreaterThanOrEqualTo(dockerCli.Client().ClientVersion(), "1.39") { 32 return true 33 } 34 return dockerCli.ServerInfo().HasExperimental && versions.GreaterThanOrEqualTo(dockerCli.Client().ClientVersion(), "1.31") 35 } 36 37 func trySession(dockerCli command.Cli, contextDir string) (*session.Session, error) { 38 var s *session.Session 39 if isSessionSupported(dockerCli) { 40 sharedKey, err := getBuildSharedKey(contextDir) 41 if err != nil { 42 return nil, errors.Wrap(err, "failed to get build shared key") 43 } 44 s, err = session.NewSession(context.Background(), filepath.Base(contextDir), sharedKey) 45 if err != nil { 46 return nil, errors.Wrap(err, "failed to create session") 47 } 48 } 49 return s, nil 50 } 51 52 func addDirToSession(session *session.Session, contextDir string, progressOutput progress.Output, done chan error) error { 53 excludes, err := build.ReadDockerignore(contextDir) 54 if err != nil { 55 return err 56 } 57 58 p := &sizeProgress{out: progressOutput, action: "Streaming build context to Docker daemon"} 59 60 workdirProvider := filesync.NewFSSyncProvider([]filesync.SyncedDir{ 61 {Dir: contextDir, Excludes: excludes}, 62 }) 63 session.Allow(workdirProvider) 64 65 // this will be replaced on parallel build jobs. keep the current 66 // progressbar for now 67 if snpc, ok := workdirProvider.(interface { 68 SetNextProgressCallback(func(int, bool), chan error) 69 }); ok { 70 snpc.SetNextProgressCallback(p.update, done) 71 } 72 73 return nil 74 } 75 76 type sizeProgress struct { 77 out progress.Output 78 action string 79 limiter *rate.Limiter 80 } 81 82 func (sp *sizeProgress) update(size int, last bool) { 83 if sp.limiter == nil { 84 sp.limiter = rate.NewLimiter(rate.Every(100*time.Millisecond), 1) 85 } 86 if last || sp.limiter.Allow() { 87 sp.out.WriteProgress(progress.Progress{Action: sp.action, Current: int64(size), LastUpdate: last}) 88 } 89 } 90 91 type bufferedWriter struct { 92 done chan error 93 io.Writer 94 buf *bytes.Buffer 95 flushed chan struct{} 96 mu sync.Mutex 97 } 98 99 func newBufferedWriter(done chan error, w io.Writer) *bufferedWriter { 100 bw := &bufferedWriter{done: done, Writer: w, buf: new(bytes.Buffer), flushed: make(chan struct{})} 101 go func() { 102 <-done 103 bw.flushBuffer() 104 }() 105 return bw 106 } 107 108 func (bw *bufferedWriter) Write(dt []byte) (int, error) { 109 select { 110 case <-bw.done: 111 bw.flushBuffer() 112 return bw.Writer.Write(dt) 113 default: 114 return bw.buf.Write(dt) 115 } 116 } 117 118 func (bw *bufferedWriter) flushBuffer() { 119 bw.mu.Lock() 120 select { 121 case <-bw.flushed: 122 default: 123 bw.Writer.Write(bw.buf.Bytes()) 124 close(bw.flushed) 125 } 126 bw.mu.Unlock() 127 } 128 129 func (bw *bufferedWriter) String() string { 130 return fmt.Sprintf("%s", bw.Writer) 131 } 132 133 func getBuildSharedKey(dir string) (string, error) { 134 // build session is hash of build dir with node based randomness 135 s := sha256.Sum256([]byte(fmt.Sprintf("%s:%s", tryNodeIdentifier(), dir))) 136 return hex.EncodeToString(s[:]), nil 137 } 138 139 func tryNodeIdentifier() string { 140 out := cliconfig.Dir() // return config dir as default on permission error 141 if err := os.MkdirAll(cliconfig.Dir(), 0700); err == nil { 142 sessionFile := filepath.Join(cliconfig.Dir(), ".buildNodeID") 143 if _, err := os.Lstat(sessionFile); err != nil { 144 if os.IsNotExist(err) { // create a new file with stored randomness 145 b := make([]byte, 32) 146 if _, err := rand.Read(b); err != nil { 147 return out 148 } 149 if err := ioutil.WriteFile(sessionFile, []byte(hex.EncodeToString(b)), 0600); err != nil { 150 return out 151 } 152 } 153 } 154 155 dt, err := ioutil.ReadFile(sessionFile) 156 if err == nil { 157 return string(dt) 158 } 159 } 160 return out 161 }