github.com/bdwilliams/libcompose@v0.3.1-0.20160826154243-d81a9bdacff0/docker/builder/builder.go (about) 1 package builder 2 3 import ( 4 "fmt" 5 "io" 6 "os" 7 "path" 8 "path/filepath" 9 "strings" 10 11 "golang.org/x/net/context" 12 13 "github.com/Sirupsen/logrus" 14 "github.com/docker/docker/builder" 15 "github.com/docker/docker/builder/dockerignore" 16 "github.com/docker/docker/pkg/archive" 17 "github.com/docker/docker/pkg/fileutils" 18 "github.com/docker/docker/pkg/jsonmessage" 19 "github.com/docker/docker/pkg/progress" 20 "github.com/docker/docker/pkg/streamformatter" 21 "github.com/docker/docker/pkg/term" 22 "github.com/docker/engine-api/client" 23 "github.com/docker/engine-api/types" 24 "github.com/docker/libcompose/logger" 25 ) 26 27 // DefaultDockerfileName is the default name of a Dockerfile 28 const DefaultDockerfileName = "Dockerfile" 29 30 // Builder defines methods to provide a docker builder. This makes libcompose 31 // not tied up to the docker daemon builder. 32 type Builder interface { 33 Build(imageName string) error 34 } 35 36 // DaemonBuilder is the daemon "docker build" Builder implementation. 37 type DaemonBuilder struct { 38 Client client.ImageAPIClient 39 ContextDirectory string 40 Dockerfile string 41 AuthConfigs map[string]types.AuthConfig 42 NoCache bool 43 ForceRemove bool 44 Pull bool 45 BuildArgs map[string]string 46 LoggerFactory logger.Factory 47 } 48 49 // Build implements Builder. It consumes the docker build API endpoint and sends 50 // a tar of the specified service build context. 51 func (d *DaemonBuilder) Build(ctx context.Context, imageName string) error { 52 buildCtx, err := CreateTar(d.ContextDirectory, d.Dockerfile) 53 if err != nil { 54 return err 55 } 56 defer buildCtx.Close() 57 if d.LoggerFactory == nil { 58 d.LoggerFactory = &logger.NullLogger{} 59 } 60 61 l := d.LoggerFactory.CreateBuildLogger(imageName) 62 63 progBuff := &logger.Wrapper{ 64 Err: false, 65 Logger: l, 66 } 67 68 buildBuff := &logger.Wrapper{ 69 Err: false, 70 Logger: l, 71 } 72 73 errBuff := &logger.Wrapper{ 74 Err: true, 75 Logger: l, 76 } 77 78 // Setup an upload progress bar 79 progressOutput := streamformatter.NewStreamFormatter().NewProgressOutput(progBuff, true) 80 81 var body io.Reader = progress.NewProgressReader(buildCtx, progressOutput, 0, "", "Sending build context to Docker daemon") 82 83 logrus.Infof("Building %s...", imageName) 84 85 outFd, isTerminalOut := term.GetFdInfo(os.Stdout) 86 w := l.OutWriter() 87 if w != nil { 88 outFd, isTerminalOut = term.GetFdInfo(w) 89 } 90 91 response, err := d.Client.ImageBuild(ctx, body, types.ImageBuildOptions{ 92 Tags: []string{imageName}, 93 NoCache: d.NoCache, 94 Remove: true, 95 ForceRemove: d.ForceRemove, 96 PullParent: d.Pull, 97 Dockerfile: d.Dockerfile, 98 AuthConfigs: d.AuthConfigs, 99 BuildArgs: d.BuildArgs, 100 }) 101 if err != nil { 102 return err 103 } 104 105 err = jsonmessage.DisplayJSONMessagesStream(response.Body, buildBuff, outFd, isTerminalOut, nil) 106 if err != nil { 107 if jerr, ok := err.(*jsonmessage.JSONError); ok { 108 // If no error code is set, default to 1 109 if jerr.Code == 0 { 110 jerr.Code = 1 111 } 112 errBuff.Write([]byte(jerr.Error())) 113 return fmt.Errorf("Status: %s, Code: %d", jerr.Message, jerr.Code) 114 } 115 } 116 return err 117 } 118 119 // CreateTar create a build context tar for the specified project and service name. 120 func CreateTar(contextDirectory, dockerfile string) (io.ReadCloser, error) { 121 // This code was ripped off from docker/api/client/build.go 122 dockerfileName := filepath.Join(contextDirectory, dockerfile) 123 124 absContextDirectory, err := filepath.Abs(contextDirectory) 125 if err != nil { 126 return nil, err 127 } 128 129 filename := dockerfileName 130 131 if dockerfile == "" { 132 // No -f/--file was specified so use the default 133 dockerfileName = DefaultDockerfileName 134 filename = filepath.Join(absContextDirectory, dockerfileName) 135 136 // Just to be nice ;-) look for 'dockerfile' too but only 137 // use it if we found it, otherwise ignore this check 138 if _, err = os.Lstat(filename); os.IsNotExist(err) { 139 tmpFN := path.Join(absContextDirectory, strings.ToLower(dockerfileName)) 140 if _, err = os.Lstat(tmpFN); err == nil { 141 dockerfileName = strings.ToLower(dockerfileName) 142 filename = tmpFN 143 } 144 } 145 } 146 147 origDockerfile := dockerfileName // used for error msg 148 if filename, err = filepath.Abs(filename); err != nil { 149 return nil, err 150 } 151 152 // Now reset the dockerfileName to be relative to the build context 153 dockerfileName, err = filepath.Rel(absContextDirectory, filename) 154 if err != nil { 155 return nil, err 156 } 157 158 // And canonicalize dockerfile name to a platform-independent one 159 dockerfileName, err = archive.CanonicalTarNameForPath(dockerfileName) 160 if err != nil { 161 return nil, fmt.Errorf("Cannot canonicalize dockerfile path %s: %v", dockerfileName, err) 162 } 163 164 if _, err = os.Lstat(filename); os.IsNotExist(err) { 165 return nil, fmt.Errorf("Cannot locate Dockerfile: %s", origDockerfile) 166 } 167 var includes = []string{"."} 168 var excludes []string 169 170 dockerIgnorePath := path.Join(contextDirectory, ".dockerignore") 171 dockerIgnore, err := os.Open(dockerIgnorePath) 172 if err != nil { 173 if !os.IsNotExist(err) { 174 return nil, err 175 } 176 logrus.Warnf("Error while reading .dockerignore (%s) : %s", dockerIgnorePath, err.Error()) 177 excludes = make([]string, 0) 178 } else { 179 excludes, err = dockerignore.ReadAll(dockerIgnore) 180 if err != nil { 181 return nil, err 182 } 183 } 184 185 // If .dockerignore mentions .dockerignore or the Dockerfile 186 // then make sure we send both files over to the daemon 187 // because Dockerfile is, obviously, needed no matter what, and 188 // .dockerignore is needed to know if either one needs to be 189 // removed. The deamon will remove them for us, if needed, after it 190 // parses the Dockerfile. 191 keepThem1, _ := fileutils.Matches(".dockerignore", excludes) 192 keepThem2, _ := fileutils.Matches(dockerfileName, excludes) 193 if keepThem1 || keepThem2 { 194 includes = append(includes, ".dockerignore", dockerfileName) 195 } 196 197 if err := builder.ValidateContextDirectory(contextDirectory, excludes); err != nil { 198 return nil, fmt.Errorf("Error checking context is accessible: '%s'. Please check permissions and try again.", err) 199 } 200 201 options := &archive.TarOptions{ 202 Compression: archive.Uncompressed, 203 ExcludePatterns: excludes, 204 IncludeFiles: includes, 205 } 206 207 return archive.TarWithOptions(contextDirectory, options) 208 }