builder.go 7.3 KB

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