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