github.com/ncdc/docker@v0.10.1-0.20160129113957-6c6729ef5b74/daemon/daemonbuilder/builder.go (about) 1 package daemonbuilder 2 3 import ( 4 "fmt" 5 "io" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "strings" 10 11 "github.com/Sirupsen/logrus" 12 "github.com/docker/docker/api" 13 "github.com/docker/docker/builder" 14 "github.com/docker/docker/daemon" 15 "github.com/docker/docker/image" 16 "github.com/docker/docker/pkg/archive" 17 "github.com/docker/docker/pkg/chrootarchive" 18 "github.com/docker/docker/pkg/httputils" 19 "github.com/docker/docker/pkg/idtools" 20 "github.com/docker/docker/pkg/ioutils" 21 "github.com/docker/docker/pkg/urlutil" 22 "github.com/docker/docker/reference" 23 "github.com/docker/docker/registry" 24 "github.com/docker/engine-api/types" 25 "github.com/docker/engine-api/types/container" 26 ) 27 28 // Docker implements builder.Backend for the docker Daemon object. 29 type Docker struct { 30 *daemon.Daemon 31 } 32 33 // ensure Docker implements builder.Backend 34 var _ builder.Backend = Docker{} 35 36 // Pull tells Docker to pull image referenced by `name`. 37 func (d Docker) Pull(name string, authConfigs map[string]types.AuthConfig, output io.Writer) (builder.Image, error) { 38 ref, err := reference.ParseNamed(name) 39 if err != nil { 40 return nil, err 41 } 42 ref = reference.WithDefaultTag(ref) 43 44 pullRegistryAuth := &types.AuthConfig{} 45 if len(authConfigs) > 0 { 46 // The request came with a full auth config file, we prefer to use that 47 repoInfo, err := d.Daemon.RegistryService.ResolveRepository(ref) 48 if err != nil { 49 return nil, err 50 } 51 52 resolvedConfig := registry.ResolveAuthConfig( 53 authConfigs, 54 repoInfo.Index, 55 ) 56 pullRegistryAuth = &resolvedConfig 57 } 58 59 if err := d.Daemon.PullImage(ref, nil, pullRegistryAuth, ioutils.NopWriteCloser(output)); err != nil { 60 return nil, err 61 } 62 return d.GetImage(name) 63 } 64 65 // GetImage looks up a Docker image referenced by `name`. 66 func (d Docker) GetImage(name string) (builder.Image, error) { 67 img, err := d.Daemon.GetImage(name) 68 if err != nil { 69 return nil, err 70 } 71 return imgWrap{img}, nil 72 } 73 74 // ContainerUpdateCmd updates Path and Args for the container with ID cID. 75 func (d Docker) ContainerUpdateCmd(cID string, cmd []string) error { 76 c, err := d.Daemon.GetContainer(cID) 77 if err != nil { 78 return err 79 } 80 c.Path = cmd[0] 81 c.Args = cmd[1:] 82 return nil 83 } 84 85 // ContainerAttach attaches streams to the container cID. If stream is true, it streams the output. 86 func (d Docker) ContainerAttach(cID string, stdin io.ReadCloser, stdout, stderr io.Writer, stream bool) error { 87 return d.Daemon.ContainerWsAttachWithLogs(cID, &daemon.ContainerWsAttachWithLogsConfig{ 88 InStream: stdin, 89 OutStream: stdout, 90 ErrStream: stderr, 91 Stream: stream, 92 }) 93 } 94 95 // BuilderCopy copies/extracts a source FileInfo to a destination path inside a container 96 // specified by a container object. 97 // TODO: make sure callers don't unnecessarily convert destPath with filepath.FromSlash (Copy does it already). 98 // BuilderCopy should take in abstract paths (with slashes) and the implementation should convert it to OS-specific paths. 99 func (d Docker) BuilderCopy(cID string, destPath string, src builder.FileInfo, decompress bool) error { 100 srcPath := src.Path() 101 destExists := true 102 destDir := false 103 rootUID, rootGID := d.Daemon.GetRemappedUIDGID() 104 105 // Work in daemon-local OS specific file paths 106 destPath = filepath.FromSlash(destPath) 107 108 c, err := d.Daemon.GetContainer(cID) 109 if err != nil { 110 return err 111 } 112 err = d.Daemon.Mount(c) 113 if err != nil { 114 return err 115 } 116 defer d.Daemon.Unmount(c) 117 118 dest, err := c.GetResourcePath(destPath) 119 if err != nil { 120 return err 121 } 122 123 // Preserve the trailing slash 124 // TODO: why are we appending another path separator if there was already one? 125 if strings.HasSuffix(destPath, string(os.PathSeparator)) || destPath == "." { 126 destDir = true 127 dest += string(os.PathSeparator) 128 } 129 130 destPath = dest 131 132 destStat, err := os.Stat(destPath) 133 if err != nil { 134 if !os.IsNotExist(err) { 135 logrus.Errorf("Error performing os.Stat on %s. %s", destPath, err) 136 return err 137 } 138 destExists = false 139 } 140 141 uidMaps, gidMaps := d.Daemon.GetUIDGIDMaps() 142 archiver := &archive.Archiver{ 143 Untar: chrootarchive.Untar, 144 UIDMaps: uidMaps, 145 GIDMaps: gidMaps, 146 } 147 148 if src.IsDir() { 149 // copy as directory 150 if err := archiver.CopyWithTar(srcPath, destPath); err != nil { 151 return err 152 } 153 return fixPermissions(srcPath, destPath, rootUID, rootGID, destExists) 154 } 155 if decompress && archive.IsArchivePath(srcPath) { 156 // Only try to untar if it is a file and that we've been told to decompress (when ADD-ing a remote file) 157 158 // First try to unpack the source as an archive 159 // to support the untar feature we need to clean up the path a little bit 160 // because tar is very forgiving. First we need to strip off the archive's 161 // filename from the path but this is only added if it does not end in slash 162 tarDest := destPath 163 if strings.HasSuffix(tarDest, string(os.PathSeparator)) { 164 tarDest = filepath.Dir(destPath) 165 } 166 167 // try to successfully untar the orig 168 err := archiver.UntarPath(srcPath, tarDest) 169 if err != nil { 170 logrus.Errorf("Couldn't untar to %s: %v", tarDest, err) 171 } 172 return err 173 } 174 175 // only needed for fixPermissions, but might as well put it before CopyFileWithTar 176 if destDir || (destExists && destStat.IsDir()) { 177 destPath = filepath.Join(destPath, src.Name()) 178 } 179 180 if err := idtools.MkdirAllNewAs(filepath.Dir(destPath), 0755, rootUID, rootGID); err != nil { 181 return err 182 } 183 if err := archiver.CopyFileWithTar(srcPath, destPath); err != nil { 184 return err 185 } 186 187 return fixPermissions(srcPath, destPath, rootUID, rootGID, destExists) 188 } 189 190 // GetCachedImage returns a reference to a cached image whose parent equals `parent` 191 // and runconfig equals `cfg`. A cache miss is expected to return an empty ID and a nil error. 192 func (d Docker) GetCachedImage(imgID string, cfg *container.Config) (string, error) { 193 cache, err := d.Daemon.ImageGetCached(image.ID(imgID), cfg) 194 if cache == nil || err != nil { 195 return "", err 196 } 197 return cache.ID().String(), nil 198 } 199 200 // Following is specific to builder contexts 201 202 // DetectContextFromRemoteURL returns a context and in certain cases the name of the dockerfile to be used 203 // irrespective of user input. 204 // progressReader is only used if remoteURL is actually a URL (not empty, and not a Git endpoint). 205 func DetectContextFromRemoteURL(r io.ReadCloser, remoteURL string, createProgressReader func(in io.ReadCloser) io.ReadCloser) (context builder.ModifiableContext, dockerfileName string, err error) { 206 switch { 207 case remoteURL == "": 208 context, err = builder.MakeTarSumContext(r) 209 case urlutil.IsGitURL(remoteURL): 210 context, err = builder.MakeGitContext(remoteURL) 211 case urlutil.IsURL(remoteURL): 212 context, err = builder.MakeRemoteContext(remoteURL, map[string]func(io.ReadCloser) (io.ReadCloser, error){ 213 httputils.MimeTypes.TextPlain: func(rc io.ReadCloser) (io.ReadCloser, error) { 214 dockerfile, err := ioutil.ReadAll(rc) 215 if err != nil { 216 return nil, err 217 } 218 219 // dockerfileName is set to signal that the remote was interpreted as a single Dockerfile, in which case the caller 220 // should use dockerfileName as the new name for the Dockerfile, irrespective of any other user input. 221 dockerfileName = api.DefaultDockerfileName 222 223 // TODO: return a context without tarsum 224 return archive.Generate(dockerfileName, string(dockerfile)) 225 }, 226 // fallback handler (tar context) 227 "": func(rc io.ReadCloser) (io.ReadCloser, error) { 228 return createProgressReader(rc), nil 229 }, 230 }) 231 default: 232 err = fmt.Errorf("remoteURL (%s) could not be recognized as URL", remoteURL) 233 } 234 return 235 }