api/server: StatPath, ArchivePath, ExtractToDir

Adds http handlers for new API endpoints:

GET ContainersArchivePath
  Return a Tar Archive of the contents at the specified location in a
  container. Deprecates POST ContainersCopy. Use a HEAD request to stat
  the resource.

PUT ContainersExtractToDir
  Extract the Tar Archive from the request body to the directory at the
  specified location inside a container.

Docker-DCO-1.1-Signed-off-by: Josh Hawn <josh.hawn@docker.com> (github: jlhawn)
This commit is contained in:
Josh Hawn 2015-05-13 15:01:51 -07:00
parent c32dde5baa
commit db9cc91a9e
2 changed files with 110 additions and 2 deletions

View file

@ -1309,6 +1309,7 @@ func (s *Server) postBuild(version version.Version, w http.ResponseWriter, r *ht
return nil
}
// postContainersCopy is deprecated in favor of getContainersArchivePath.
func (s *Server) postContainersCopy(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if vars == nil {
return fmt.Errorf("Missing parameter")
@ -1348,6 +1349,104 @@ func (s *Server) postContainersCopy(version version.Version, w http.ResponseWrit
return nil
}
// // Encode the stat to JSON, base64 encode, and place in a header.
func setContainerPathStatHeader(stat *types.ContainerPathStat, header http.Header) error {
statJSON, err := json.Marshal(stat)
if err != nil {
return err
}
header.Set(
"X-Docker-Container-Path-Stat",
base64.StdEncoding.EncodeToString(statJSON),
)
return nil
}
func (s *Server) headContainersArchive(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if vars == nil {
return fmt.Errorf("Missing parameter")
}
if err := parseForm(r); err != nil {
return err
}
name := vars["name"]
path := r.Form.Get("path")
switch {
case name == "":
return fmt.Errorf("bad parameter: 'name' cannot be empty")
case path == "":
return fmt.Errorf("bad parameter: 'path' cannot be empty")
}
stat, err := s.daemon.ContainerStatPath(name, path)
if err != nil {
return err
}
return setContainerPathStatHeader(stat, w.Header())
}
func (s *Server) getContainersArchive(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if vars == nil {
return fmt.Errorf("Missing parameter")
}
if err := parseForm(r); err != nil {
return err
}
name := vars["name"]
path := r.Form.Get("path")
switch {
case name == "":
return fmt.Errorf("bad parameter: 'name' cannot be empty")
case path == "":
return fmt.Errorf("bad parameter: 'path' cannot be empty")
}
tarArchive, stat, err := s.daemon.ContainerArchivePath(name, path)
if err != nil {
return err
}
defer tarArchive.Close()
if err := setContainerPathStatHeader(stat, w.Header()); err != nil {
return err
}
w.Header().Set("Content-Type", "application/x-tar")
_, err = io.Copy(w, tarArchive)
return err
}
func (s *Server) putContainersArchive(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if vars == nil {
return fmt.Errorf("Missing parameter")
}
if err := parseForm(r); err != nil {
return err
}
name := vars["name"]
path := r.Form.Get("path")
noOverwriteDirNonDir := boolValue(r, "noOverwriteDirNonDir")
switch {
case name == "":
return fmt.Errorf("bad parameter: 'name' cannot be empty")
case path == "":
return fmt.Errorf("bad parameter: 'path' cannot be empty")
}
return s.daemon.ContainerExtractToDir(name, path, noOverwriteDirNonDir, r.Body)
}
func (s *Server) postContainerExecCreate(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := parseForm(r); err != nil {
return err
@ -1536,6 +1635,9 @@ func createRouter(s *Server) *mux.Router {
ProfilerSetup(r, "/debug/")
}
m := map[string]map[string]HttpApiFunc{
"HEAD": {
"/containers/{name:.*}/archive": s.headContainersArchive,
},
"GET": {
"/_ping": s.ping,
"/events": s.getEvents,
@ -1557,6 +1659,7 @@ func createRouter(s *Server) *mux.Router {
"/containers/{name:.*}/stats": s.getContainersStats,
"/containers/{name:.*}/attach/ws": s.wsContainersAttach,
"/exec/{id:.*}/json": s.getExecByID,
"/containers/{name:.*}/archive": s.getContainersArchive,
},
"POST": {
"/auth": s.postAuth,
@ -1582,6 +1685,9 @@ func createRouter(s *Server) *mux.Router {
"/exec/{name:.*}/resize": s.postContainerExecResize,
"/containers/{name:.*}/rename": s.postContainerRename,
},
"PUT": {
"/containers/{name:.*}/archive": s.putContainersArchive,
},
"DELETE": {
"/containers/{name:.*}": s.deleteContainers,
"/images/{name:.*}": s.deleteImages,

View file

@ -128,8 +128,10 @@ type CopyConfig struct {
Resource string
}
// ContainerPathStat is used to encode the response from
// GET /containers/{name:.*}/stat-path
// ContainerPathStat is used to encode the header from
// GET /containers/{name:.*}/archive
// "name" is the file or directory name.
// "path" is the absolute path to the resource in the container.
type ContainerPathStat struct {
Name string `json:"name"`
Path string `json:"path"`