internals.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. package dockerfile
  2. // internals for handling commands. Covers many areas and a lot of
  3. // non-contiguous functionality. Please read the comments.
  4. import (
  5. "crypto/sha256"
  6. "encoding/hex"
  7. "fmt"
  8. "strings"
  9. "github.com/Sirupsen/logrus"
  10. "github.com/docker/docker/api/types"
  11. "github.com/docker/docker/api/types/backend"
  12. "github.com/docker/docker/api/types/container"
  13. containerpkg "github.com/docker/docker/container"
  14. "github.com/docker/docker/pkg/jsonmessage"
  15. "github.com/docker/docker/pkg/stringid"
  16. "github.com/pkg/errors"
  17. )
  18. func (b *Builder) commit(dispatchState *dispatchState, comment string) error {
  19. if b.disableCommit {
  20. return nil
  21. }
  22. if !dispatchState.hasFromImage() {
  23. return errors.New("Please provide a source image with `from` prior to commit")
  24. }
  25. runConfigWithCommentCmd := copyRunConfig(dispatchState.runConfig, withCmdComment(comment))
  26. hit, err := b.probeCache(dispatchState, runConfigWithCommentCmd)
  27. if err != nil || hit {
  28. return err
  29. }
  30. id, err := b.create(runConfigWithCommentCmd)
  31. if err != nil {
  32. return err
  33. }
  34. return b.commitContainer(dispatchState, id, runConfigWithCommentCmd)
  35. }
  36. // TODO: see if any args can be dropped
  37. func (b *Builder) commitContainer(dispatchState *dispatchState, id string, containerConfig *container.Config) error {
  38. if b.disableCommit {
  39. return nil
  40. }
  41. commitCfg := &backend.ContainerCommitConfig{
  42. ContainerCommitConfig: types.ContainerCommitConfig{
  43. Author: dispatchState.maintainer,
  44. Pause: true,
  45. // TODO: this should be done by Commit()
  46. Config: copyRunConfig(dispatchState.runConfig),
  47. },
  48. ContainerConfig: containerConfig,
  49. }
  50. // Commit the container
  51. imageID, err := b.docker.Commit(id, commitCfg)
  52. if err != nil {
  53. return err
  54. }
  55. dispatchState.imageID = imageID
  56. b.buildStages.update(imageID, dispatchState.runConfig)
  57. return nil
  58. }
  59. func (b *Builder) performCopy(state *dispatchState, inst copyInstruction) error {
  60. srcHash := getSourceHashFromInfos(inst.infos)
  61. // TODO: should this have been using origPaths instead of srcHash in the comment?
  62. runConfigWithCommentCmd := copyRunConfig(
  63. state.runConfig,
  64. withCmdCommentString(fmt.Sprintf("%s %s in %s ", inst.cmdName, srcHash, inst.dest)))
  65. if hit, err := b.probeCache(state, runConfigWithCommentCmd); err != nil || hit {
  66. return err
  67. }
  68. container, err := b.docker.ContainerCreate(types.ContainerCreateConfig{
  69. Config: runConfigWithCommentCmd,
  70. // Set a log config to override any default value set on the daemon
  71. HostConfig: &container.HostConfig{LogConfig: defaultLogConfig},
  72. })
  73. if err != nil {
  74. return err
  75. }
  76. b.tmpContainers[container.ID] = struct{}{}
  77. // Twiddle the destination when it's a relative path - meaning, make it
  78. // relative to the WORKINGDIR
  79. dest, err := normaliseDest(inst.cmdName, state.runConfig.WorkingDir, inst.dest)
  80. if err != nil {
  81. return err
  82. }
  83. for _, info := range inst.infos {
  84. if err := b.docker.CopyOnBuild(container.ID, dest, info.root, info.path, inst.allowLocalDecompression); err != nil {
  85. return err
  86. }
  87. }
  88. return b.commitContainer(state, container.ID, runConfigWithCommentCmd)
  89. }
  90. // For backwards compat, if there's just one info then use it as the
  91. // cache look-up string, otherwise hash 'em all into one
  92. func getSourceHashFromInfos(infos []copyInfo) string {
  93. if len(infos) == 1 {
  94. return infos[0].hash
  95. }
  96. var hashs []string
  97. for _, info := range infos {
  98. hashs = append(hashs, info.hash)
  99. }
  100. return hashStringSlice("multi", hashs)
  101. }
  102. func hashStringSlice(prefix string, slice []string) string {
  103. hasher := sha256.New()
  104. hasher.Write([]byte(strings.Join(slice, ",")))
  105. return prefix + ":" + hex.EncodeToString(hasher.Sum(nil))
  106. }
  107. type runConfigModifier func(*container.Config)
  108. func copyRunConfig(runConfig *container.Config, modifiers ...runConfigModifier) *container.Config {
  109. copy := *runConfig
  110. for _, modifier := range modifiers {
  111. modifier(&copy)
  112. }
  113. return &copy
  114. }
  115. func withCmd(cmd []string) runConfigModifier {
  116. return func(runConfig *container.Config) {
  117. runConfig.Cmd = cmd
  118. }
  119. }
  120. // withCmdComment sets Cmd to a nop comment string. See withCmdCommentString for
  121. // why there are two almost identical versions of this.
  122. func withCmdComment(comment string) runConfigModifier {
  123. return func(runConfig *container.Config) {
  124. runConfig.Cmd = append(getShell(runConfig), "#(nop) ", comment)
  125. }
  126. }
  127. // withCmdCommentString exists to maintain compatibility with older versions.
  128. // A few instructions (workdir, copy, add) used a nop comment that is a single arg
  129. // where as all the other instructions used a two arg comment string. This
  130. // function implements the single arg version.
  131. func withCmdCommentString(comment string) runConfigModifier {
  132. return func(runConfig *container.Config) {
  133. runConfig.Cmd = append(getShell(runConfig), "#(nop) "+comment)
  134. }
  135. }
  136. func withEnv(env []string) runConfigModifier {
  137. return func(runConfig *container.Config) {
  138. runConfig.Env = env
  139. }
  140. }
  141. // withEntrypointOverride sets an entrypoint on runConfig if the command is
  142. // not empty. The entrypoint is left unmodified if command is empty.
  143. //
  144. // The dockerfile RUN instruction expect to run without an entrypoint
  145. // so the runConfig entrypoint needs to be modified accordingly. ContainerCreate
  146. // will change a []string{""} entrypoint to nil, so we probe the cache with the
  147. // nil entrypoint.
  148. func withEntrypointOverride(cmd []string, entrypoint []string) runConfigModifier {
  149. return func(runConfig *container.Config) {
  150. if len(cmd) > 0 {
  151. runConfig.Entrypoint = entrypoint
  152. }
  153. }
  154. }
  155. // getShell is a helper function which gets the right shell for prefixing the
  156. // shell-form of RUN, ENTRYPOINT and CMD instructions
  157. func getShell(c *container.Config) []string {
  158. if 0 == len(c.Shell) {
  159. return append([]string{}, defaultShell[:]...)
  160. }
  161. return append([]string{}, c.Shell[:]...)
  162. }
  163. // probeCache checks if cache match can be found for current build instruction.
  164. // If an image is found, probeCache returns `(true, nil)`.
  165. // If no image is found, it returns `(false, nil)`.
  166. // If there is any error, it returns `(false, err)`.
  167. func (b *Builder) probeCache(dispatchState *dispatchState, runConfig *container.Config) (bool, error) {
  168. c := b.imageCache
  169. if c == nil || b.options.NoCache || b.cacheBusted {
  170. return false, nil
  171. }
  172. cache, err := c.GetCache(dispatchState.imageID, runConfig)
  173. if err != nil {
  174. return false, err
  175. }
  176. if len(cache) == 0 {
  177. logrus.Debugf("[BUILDER] Cache miss: %s", runConfig.Cmd)
  178. b.cacheBusted = true
  179. return false, nil
  180. }
  181. fmt.Fprint(b.Stdout, " ---> Using cache\n")
  182. logrus.Debugf("[BUILDER] Use cached version: %s", runConfig.Cmd)
  183. dispatchState.imageID = string(cache)
  184. b.buildStages.update(dispatchState.imageID, runConfig)
  185. return true, nil
  186. }
  187. func (b *Builder) create(runConfig *container.Config) (string, error) {
  188. resources := container.Resources{
  189. CgroupParent: b.options.CgroupParent,
  190. CPUShares: b.options.CPUShares,
  191. CPUPeriod: b.options.CPUPeriod,
  192. CPUQuota: b.options.CPUQuota,
  193. CpusetCpus: b.options.CPUSetCPUs,
  194. CpusetMems: b.options.CPUSetMems,
  195. Memory: b.options.Memory,
  196. MemorySwap: b.options.MemorySwap,
  197. Ulimits: b.options.Ulimits,
  198. }
  199. // TODO: why not embed a hostconfig in builder?
  200. hostConfig := &container.HostConfig{
  201. SecurityOpt: b.options.SecurityOpt,
  202. Isolation: b.options.Isolation,
  203. ShmSize: b.options.ShmSize,
  204. Resources: resources,
  205. NetworkMode: container.NetworkMode(b.options.NetworkMode),
  206. // Set a log config to override any default value set on the daemon
  207. LogConfig: defaultLogConfig,
  208. ExtraHosts: b.options.ExtraHosts,
  209. }
  210. // Create the container
  211. c, err := b.docker.ContainerCreate(types.ContainerCreateConfig{
  212. Config: runConfig,
  213. HostConfig: hostConfig,
  214. })
  215. if err != nil {
  216. return "", err
  217. }
  218. for _, warning := range c.Warnings {
  219. fmt.Fprintf(b.Stdout, " ---> [Warning] %s\n", warning)
  220. }
  221. b.tmpContainers[c.ID] = struct{}{}
  222. fmt.Fprintf(b.Stdout, " ---> Running in %s\n", stringid.TruncateID(c.ID))
  223. return c.ID, nil
  224. }
  225. var errCancelled = errors.New("build cancelled")
  226. func (b *Builder) run(cID string, cmd []string) (err error) {
  227. attached := make(chan struct{})
  228. errCh := make(chan error)
  229. go func() {
  230. errCh <- b.docker.ContainerAttachRaw(cID, nil, b.Stdout, b.Stderr, true, attached)
  231. }()
  232. select {
  233. case err := <-errCh:
  234. return err
  235. case <-attached:
  236. }
  237. finished := make(chan struct{})
  238. cancelErrCh := make(chan error, 1)
  239. go func() {
  240. select {
  241. case <-b.clientCtx.Done():
  242. logrus.Debugln("Build cancelled, killing and removing container:", cID)
  243. b.docker.ContainerKill(cID, 0)
  244. b.removeContainer(cID)
  245. cancelErrCh <- errCancelled
  246. case <-finished:
  247. cancelErrCh <- nil
  248. }
  249. }()
  250. if err := b.docker.ContainerStart(cID, nil, "", ""); err != nil {
  251. close(finished)
  252. if cancelErr := <-cancelErrCh; cancelErr != nil {
  253. logrus.Debugf("Build cancelled (%v) and got an error from ContainerStart: %v",
  254. cancelErr, err)
  255. }
  256. return err
  257. }
  258. // Block on reading output from container, stop on err or chan closed
  259. if err := <-errCh; err != nil {
  260. close(finished)
  261. if cancelErr := <-cancelErrCh; cancelErr != nil {
  262. logrus.Debugf("Build cancelled (%v) and got an error from errCh: %v",
  263. cancelErr, err)
  264. }
  265. return err
  266. }
  267. waitC, err := b.docker.ContainerWait(b.clientCtx, cID, containerpkg.WaitConditionNotRunning)
  268. if err != nil {
  269. // Unable to begin waiting for container.
  270. close(finished)
  271. if cancelErr := <-cancelErrCh; cancelErr != nil {
  272. logrus.Debugf("Build cancelled (%v) and unable to begin ContainerWait: %d", cancelErr, err)
  273. }
  274. return err
  275. }
  276. if status := <-waitC; status.ExitCode() != 0 {
  277. close(finished)
  278. if cancelErr := <-cancelErrCh; cancelErr != nil {
  279. logrus.Debugf("Build cancelled (%v) and got a non-zero code from ContainerWait: %d", cancelErr, status.ExitCode())
  280. }
  281. // TODO: change error type, because jsonmessage.JSONError assumes HTTP
  282. return &jsonmessage.JSONError{
  283. Message: fmt.Sprintf("The command '%s' returned a non-zero code: %d", strings.Join(cmd, " "), status.ExitCode()),
  284. Code: status.ExitCode(),
  285. }
  286. }
  287. close(finished)
  288. return <-cancelErrCh
  289. }
  290. func (b *Builder) removeContainer(c string) error {
  291. rmConfig := &types.ContainerRmConfig{
  292. ForceRemove: true,
  293. RemoveVolume: true,
  294. }
  295. if err := b.docker.ContainerRm(c, rmConfig); err != nil {
  296. fmt.Fprintf(b.Stdout, "Error removing intermediate container %s: %v\n", stringid.TruncateID(c), err)
  297. return err
  298. }
  299. return nil
  300. }
  301. func (b *Builder) clearTmp() {
  302. for c := range b.tmpContainers {
  303. if err := b.removeContainer(c); err != nil {
  304. return
  305. }
  306. delete(b.tmpContainers, c)
  307. fmt.Fprintf(b.Stdout, "Removing intermediate container %s\n", stringid.TruncateID(c))
  308. }
  309. }