image.go 15 KB

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