image.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538
  1. package local
  2. import (
  3. "encoding/base64"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "io"
  8. "net/http"
  9. "strings"
  10. "github.com/Sirupsen/logrus"
  11. "github.com/docker/docker/api/server/httputils"
  12. "github.com/docker/docker/api/types"
  13. "github.com/docker/docker/builder"
  14. "github.com/docker/docker/builder/dockerfile"
  15. "github.com/docker/docker/cliconfig"
  16. "github.com/docker/docker/daemon/daemonbuilder"
  17. derr "github.com/docker/docker/errors"
  18. "github.com/docker/docker/graph"
  19. "github.com/docker/docker/graph/tags"
  20. "github.com/docker/docker/pkg/archive"
  21. "github.com/docker/docker/pkg/chrootarchive"
  22. "github.com/docker/docker/pkg/ioutils"
  23. "github.com/docker/docker/pkg/parsers"
  24. "github.com/docker/docker/pkg/progressreader"
  25. "github.com/docker/docker/pkg/streamformatter"
  26. "github.com/docker/docker/pkg/ulimit"
  27. "github.com/docker/docker/registry"
  28. "github.com/docker/docker/runconfig"
  29. "github.com/docker/docker/utils"
  30. "golang.org/x/net/context"
  31. )
  32. func (s *router) postCommit(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  33. if err := httputils.ParseForm(r); err != nil {
  34. return err
  35. }
  36. if err := httputils.CheckForJSON(r); err != nil {
  37. return err
  38. }
  39. cname := r.Form.Get("container")
  40. pause := httputils.BoolValue(r, "pause")
  41. version := httputils.VersionFromContext(ctx)
  42. if r.FormValue("pause") == "" && version.GreaterThanOrEqualTo("1.13") {
  43. pause = true
  44. }
  45. c, _, err := runconfig.DecodeContainerConfig(r.Body)
  46. if err != nil && err != io.EOF { //Do not fail if body is empty.
  47. return err
  48. }
  49. commitCfg := &dockerfile.CommitConfig{
  50. Pause: pause,
  51. Repo: r.Form.Get("repo"),
  52. Tag: r.Form.Get("tag"),
  53. Author: r.Form.Get("author"),
  54. Comment: r.Form.Get("comment"),
  55. Changes: r.Form["changes"],
  56. Config: c,
  57. }
  58. if !s.daemon.Exists(cname) {
  59. return derr.ErrorCodeNoSuchContainer.WithArgs(cname)
  60. }
  61. imgID, err := dockerfile.Commit(cname, s.daemon, commitCfg)
  62. if err != nil {
  63. return err
  64. }
  65. return httputils.WriteJSON(w, http.StatusCreated, &types.ContainerCommitResponse{
  66. ID: string(imgID),
  67. })
  68. }
  69. // Creates an image from Pull or from Import
  70. func (s *router) postImagesCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  71. if err := httputils.ParseForm(r); err != nil {
  72. return err
  73. }
  74. var (
  75. image = r.Form.Get("fromImage")
  76. repo = r.Form.Get("repo")
  77. tag = r.Form.Get("tag")
  78. message = r.Form.Get("message")
  79. )
  80. authEncoded := r.Header.Get("X-Registry-Auth")
  81. authConfig := &cliconfig.AuthConfig{}
  82. if authEncoded != "" {
  83. authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
  84. if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil {
  85. // for a pull it is not an error if no auth was given
  86. // to increase compatibility with the existing api it is defaulting to be empty
  87. authConfig = &cliconfig.AuthConfig{}
  88. }
  89. }
  90. var (
  91. err error
  92. output = ioutils.NewWriteFlusher(w)
  93. )
  94. w.Header().Set("Content-Type", "application/json")
  95. if image != "" { //pull
  96. if tag == "" {
  97. image, tag = parsers.ParseRepositoryTag(image)
  98. }
  99. metaHeaders := map[string][]string{}
  100. for k, v := range r.Header {
  101. if strings.HasPrefix(k, "X-Meta-") {
  102. metaHeaders[k] = v
  103. }
  104. }
  105. imagePullConfig := &graph.ImagePullConfig{
  106. MetaHeaders: metaHeaders,
  107. AuthConfig: authConfig,
  108. OutStream: output,
  109. }
  110. err = s.daemon.PullImage(image, tag, imagePullConfig)
  111. } else { //import
  112. if tag == "" {
  113. repo, tag = parsers.ParseRepositoryTag(repo)
  114. }
  115. src := r.Form.Get("fromSrc")
  116. // 'err' MUST NOT be defined within this block, we need any error
  117. // generated from the download to be available to the output
  118. // stream processing below
  119. var newConfig *runconfig.Config
  120. newConfig, err = dockerfile.BuildFromConfig(&runconfig.Config{}, r.Form["changes"])
  121. if err != nil {
  122. return err
  123. }
  124. err = s.daemon.ImportImage(src, repo, tag, message, r.Body, output, newConfig)
  125. }
  126. if err != nil {
  127. if !output.Flushed() {
  128. return err
  129. }
  130. sf := streamformatter.NewJSONStreamFormatter()
  131. output.Write(sf.FormatError(err))
  132. }
  133. return nil
  134. }
  135. func (s *router) postImagesPush(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  136. metaHeaders := map[string][]string{}
  137. for k, v := range r.Header {
  138. if strings.HasPrefix(k, "X-Meta-") {
  139. metaHeaders[k] = v
  140. }
  141. }
  142. if err := httputils.ParseForm(r); err != nil {
  143. return err
  144. }
  145. authConfig := &cliconfig.AuthConfig{}
  146. authEncoded := r.Header.Get("X-Registry-Auth")
  147. if authEncoded != "" {
  148. // the new format is to handle the authConfig as a header
  149. authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
  150. if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil {
  151. // to increase compatibility to existing api it is defaulting to be empty
  152. authConfig = &cliconfig.AuthConfig{}
  153. }
  154. } else {
  155. // the old format is supported for compatibility if there was no authConfig header
  156. if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil {
  157. return fmt.Errorf("Bad parameters and missing X-Registry-Auth: %v", err)
  158. }
  159. }
  160. name := vars["name"]
  161. output := ioutils.NewWriteFlusher(w)
  162. imagePushConfig := &graph.ImagePushConfig{
  163. MetaHeaders: metaHeaders,
  164. AuthConfig: authConfig,
  165. Tag: r.Form.Get("tag"),
  166. OutStream: output,
  167. }
  168. w.Header().Set("Content-Type", "application/json")
  169. if err := s.daemon.PushImage(name, imagePushConfig); err != nil {
  170. if !output.Flushed() {
  171. return err
  172. }
  173. sf := streamformatter.NewJSONStreamFormatter()
  174. output.Write(sf.FormatError(err))
  175. }
  176. return nil
  177. }
  178. func (s *router) getImagesGet(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  179. if err := httputils.ParseForm(r); err != nil {
  180. return err
  181. }
  182. w.Header().Set("Content-Type", "application/x-tar")
  183. output := ioutils.NewWriteFlusher(w)
  184. var names []string
  185. if name, ok := vars["name"]; ok {
  186. names = []string{name}
  187. } else {
  188. names = r.Form["names"]
  189. }
  190. if err := s.daemon.ExportImage(names, output); err != nil {
  191. if !output.Flushed() {
  192. return err
  193. }
  194. sf := streamformatter.NewJSONStreamFormatter()
  195. output.Write(sf.FormatError(err))
  196. }
  197. return nil
  198. }
  199. func (s *router) postImagesLoad(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  200. return s.daemon.LoadImage(r.Body, w)
  201. }
  202. func (s *router) deleteImages(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  203. if err := httputils.ParseForm(r); err != nil {
  204. return err
  205. }
  206. name := vars["name"]
  207. if name == "" {
  208. return fmt.Errorf("image name cannot be blank")
  209. }
  210. force := httputils.BoolValue(r, "force")
  211. prune := !httputils.BoolValue(r, "noprune")
  212. list, err := s.daemon.ImageDelete(name, force, prune)
  213. if err != nil {
  214. return err
  215. }
  216. return httputils.WriteJSON(w, http.StatusOK, list)
  217. }
  218. func (s *router) getImagesByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  219. imageInspect, err := s.daemon.LookupImage(vars["name"])
  220. if err != nil {
  221. return err
  222. }
  223. return httputils.WriteJSON(w, http.StatusOK, imageInspect)
  224. }
  225. func (s *router) postBuild(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  226. var (
  227. authConfigs = map[string]cliconfig.AuthConfig{}
  228. authConfigsEncoded = r.Header.Get("X-Registry-Config")
  229. buildConfig = &dockerfile.Config{}
  230. )
  231. if authConfigsEncoded != "" {
  232. authConfigsJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authConfigsEncoded))
  233. if err := json.NewDecoder(authConfigsJSON).Decode(&authConfigs); err != nil {
  234. // for a pull it is not an error if no auth was given
  235. // to increase compatibility with the existing api it is defaulting
  236. // to be empty.
  237. }
  238. }
  239. w.Header().Set("Content-Type", "application/json")
  240. version := httputils.VersionFromContext(ctx)
  241. output := ioutils.NewWriteFlusher(w)
  242. sf := streamformatter.NewJSONStreamFormatter()
  243. errf := func(err error) error {
  244. // Do not write the error in the http output if it's still empty.
  245. // This prevents from writing a 200(OK) when there is an interal error.
  246. if !output.Flushed() {
  247. return err
  248. }
  249. _, err = w.Write(sf.FormatError(errors.New(utils.GetErrorMessage(err))))
  250. if err != nil {
  251. logrus.Warnf("could not write error response: %v", err)
  252. }
  253. return nil
  254. }
  255. if httputils.BoolValue(r, "forcerm") && version.GreaterThanOrEqualTo("1.12") {
  256. buildConfig.Remove = true
  257. } else if r.FormValue("rm") == "" && version.GreaterThanOrEqualTo("1.12") {
  258. buildConfig.Remove = true
  259. } else {
  260. buildConfig.Remove = httputils.BoolValue(r, "rm")
  261. }
  262. if httputils.BoolValue(r, "pull") && version.GreaterThanOrEqualTo("1.16") {
  263. buildConfig.Pull = true
  264. }
  265. repoAndTags, err := sanitizeRepoAndTags(r.Form["t"])
  266. if err != nil {
  267. return errf(err)
  268. }
  269. buildConfig.DockerfileName = r.FormValue("dockerfile")
  270. buildConfig.Verbose = !httputils.BoolValue(r, "q")
  271. buildConfig.UseCache = !httputils.BoolValue(r, "nocache")
  272. buildConfig.ForceRemove = httputils.BoolValue(r, "forcerm")
  273. buildConfig.MemorySwap = httputils.Int64ValueOrZero(r, "memswap")
  274. buildConfig.Memory = httputils.Int64ValueOrZero(r, "memory")
  275. buildConfig.CPUShares = httputils.Int64ValueOrZero(r, "cpushares")
  276. buildConfig.CPUPeriod = httputils.Int64ValueOrZero(r, "cpuperiod")
  277. buildConfig.CPUQuota = httputils.Int64ValueOrZero(r, "cpuquota")
  278. buildConfig.CPUSetCpus = r.FormValue("cpusetcpus")
  279. buildConfig.CPUSetMems = r.FormValue("cpusetmems")
  280. buildConfig.CgroupParent = r.FormValue("cgroupparent")
  281. if i := runconfig.IsolationLevel(r.FormValue("isolation")); i != "" {
  282. if !runconfig.IsolationLevel.IsValid(i) {
  283. return errf(fmt.Errorf("Unsupported isolation: %q", i))
  284. }
  285. buildConfig.Isolation = i
  286. }
  287. var buildUlimits = []*ulimit.Ulimit{}
  288. ulimitsJSON := r.FormValue("ulimits")
  289. if ulimitsJSON != "" {
  290. if err := json.NewDecoder(strings.NewReader(ulimitsJSON)).Decode(&buildUlimits); err != nil {
  291. return errf(err)
  292. }
  293. buildConfig.Ulimits = buildUlimits
  294. }
  295. var buildArgs = map[string]string{}
  296. buildArgsJSON := r.FormValue("buildargs")
  297. if buildArgsJSON != "" {
  298. if err := json.NewDecoder(strings.NewReader(buildArgsJSON)).Decode(&buildArgs); err != nil {
  299. return errf(err)
  300. }
  301. buildConfig.BuildArgs = buildArgs
  302. }
  303. remoteURL := r.FormValue("remote")
  304. // Currently, only used if context is from a remote url.
  305. // The field `In` is set by DetectContextFromRemoteURL.
  306. // Look at code in DetectContextFromRemoteURL for more information.
  307. pReader := &progressreader.Config{
  308. // TODO: make progressreader streamformatter-agnostic
  309. Out: output,
  310. Formatter: sf,
  311. Size: r.ContentLength,
  312. NewLines: true,
  313. ID: "Downloading context",
  314. Action: remoteURL,
  315. }
  316. var (
  317. context builder.ModifiableContext
  318. dockerfileName string
  319. )
  320. context, dockerfileName, err = daemonbuilder.DetectContextFromRemoteURL(r.Body, remoteURL, pReader)
  321. if err != nil {
  322. return errf(err)
  323. }
  324. defer func() {
  325. if err := context.Close(); err != nil {
  326. logrus.Debugf("[BUILDER] failed to remove temporary context: %v", err)
  327. }
  328. }()
  329. uidMaps, gidMaps := s.daemon.GetUIDGIDMaps()
  330. defaultArchiver := &archive.Archiver{
  331. Untar: chrootarchive.Untar,
  332. UIDMaps: uidMaps,
  333. GIDMaps: gidMaps,
  334. }
  335. docker := daemonbuilder.Docker{s.daemon, output, authConfigs, defaultArchiver}
  336. b, err := dockerfile.NewBuilder(buildConfig, docker, builder.DockerIgnoreContext{context}, nil)
  337. if err != nil {
  338. return errf(err)
  339. }
  340. b.Stdout = &streamformatter.StdoutFormatter{Writer: output, StreamFormatter: sf}
  341. b.Stderr = &streamformatter.StderrFormatter{Writer: output, StreamFormatter: sf}
  342. if closeNotifier, ok := w.(http.CloseNotifier); ok {
  343. finished := make(chan struct{})
  344. defer close(finished)
  345. go func() {
  346. select {
  347. case <-finished:
  348. case <-closeNotifier.CloseNotify():
  349. logrus.Infof("Client disconnected, cancelling job: build")
  350. b.Cancel()
  351. }
  352. }()
  353. }
  354. if len(dockerfileName) > 0 {
  355. b.DockerfileName = dockerfileName
  356. }
  357. imgID, err := b.Build()
  358. if err != nil {
  359. return errf(err)
  360. }
  361. for _, rt := range repoAndTags {
  362. if err := s.daemon.TagImage(rt.repo, rt.tag, string(imgID), true); err != nil {
  363. return errf(err)
  364. }
  365. }
  366. return nil
  367. }
  368. // repoAndTag is a helper struct for holding the parsed repositories and tags of
  369. // the input "t" argument.
  370. type repoAndTag struct {
  371. repo, tag string
  372. }
  373. // sanitizeRepoAndTags parses the raw "t" parameter received from the client
  374. // to a slice of repoAndTag.
  375. // It also validates each repoName and tag.
  376. func sanitizeRepoAndTags(names []string) ([]repoAndTag, error) {
  377. var (
  378. repoAndTags []repoAndTag
  379. // This map is used for deduplicating the "-t" paramter.
  380. uniqNames = make(map[string]struct{})
  381. )
  382. for _, repo := range names {
  383. name, tag := parsers.ParseRepositoryTag(repo)
  384. if name == "" {
  385. continue
  386. }
  387. if err := registry.ValidateRepositoryName(name); err != nil {
  388. return nil, err
  389. }
  390. nameWithTag := name
  391. if len(tag) > 0 {
  392. if err := tags.ValidateTagName(tag); err != nil {
  393. return nil, err
  394. }
  395. nameWithTag += ":" + tag
  396. } else {
  397. nameWithTag += ":" + tags.DefaultTag
  398. }
  399. if _, exists := uniqNames[nameWithTag]; !exists {
  400. uniqNames[nameWithTag] = struct{}{}
  401. repoAndTags = append(repoAndTags, repoAndTag{repo: name, tag: tag})
  402. }
  403. }
  404. return repoAndTags, nil
  405. }
  406. func (s *router) getImagesJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  407. if err := httputils.ParseForm(r); err != nil {
  408. return err
  409. }
  410. // FIXME: The filter parameter could just be a match filter
  411. images, err := s.daemon.ListImages(r.Form.Get("filters"), r.Form.Get("filter"), httputils.BoolValue(r, "all"))
  412. if err != nil {
  413. return err
  414. }
  415. return httputils.WriteJSON(w, http.StatusOK, images)
  416. }
  417. func (s *router) getImagesHistory(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  418. name := vars["name"]
  419. history, err := s.daemon.ImageHistory(name)
  420. if err != nil {
  421. return err
  422. }
  423. return httputils.WriteJSON(w, http.StatusOK, history)
  424. }
  425. func (s *router) postImagesTag(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  426. if err := httputils.ParseForm(r); err != nil {
  427. return err
  428. }
  429. repo := r.Form.Get("repo")
  430. tag := r.Form.Get("tag")
  431. name := vars["name"]
  432. force := httputils.BoolValue(r, "force")
  433. if err := s.daemon.TagImage(repo, tag, name, force); err != nil {
  434. return err
  435. }
  436. w.WriteHeader(http.StatusCreated)
  437. return nil
  438. }
  439. func (s *router) getImagesSearch(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
  440. if err := httputils.ParseForm(r); err != nil {
  441. return err
  442. }
  443. var (
  444. config *cliconfig.AuthConfig
  445. authEncoded = r.Header.Get("X-Registry-Auth")
  446. headers = map[string][]string{}
  447. )
  448. if authEncoded != "" {
  449. authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
  450. if err := json.NewDecoder(authJSON).Decode(&config); err != nil {
  451. // for a search it is not an error if no auth was given
  452. // to increase compatibility with the existing api it is defaulting to be empty
  453. config = &cliconfig.AuthConfig{}
  454. }
  455. }
  456. for k, v := range r.Header {
  457. if strings.HasPrefix(k, "X-Meta-") {
  458. headers[k] = v
  459. }
  460. }
  461. query, err := s.daemon.SearchRegistryForImages(r.Form.Get("term"), config, headers)
  462. if err != nil {
  463. return err
  464. }
  465. return httputils.WriteJSON(w, http.StatusOK, query.Results)
  466. }