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)
| ... | ... |
@@ -1309,6 +1309,7 @@ func (s *Server) postBuild(version version.Version, w http.ResponseWriter, r *ht |
| 1309 | 1309 |
return nil |
| 1310 | 1310 |
} |
| 1311 | 1311 |
|
| 1312 |
+// postContainersCopy is deprecated in favor of getContainersArchivePath. |
|
| 1312 | 1313 |
func (s *Server) postContainersCopy(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
| 1313 | 1314 |
if vars == nil {
|
| 1314 | 1315 |
return fmt.Errorf("Missing parameter")
|
| ... | ... |
@@ -1348,6 +1349,104 @@ func (s *Server) postContainersCopy(version version.Version, w http.ResponseWrit |
| 1348 | 1348 |
return nil |
| 1349 | 1349 |
} |
| 1350 | 1350 |
|
| 1351 |
+// // Encode the stat to JSON, base64 encode, and place in a header. |
|
| 1352 |
+func setContainerPathStatHeader(stat *types.ContainerPathStat, header http.Header) error {
|
|
| 1353 |
+ statJSON, err := json.Marshal(stat) |
|
| 1354 |
+ if err != nil {
|
|
| 1355 |
+ return err |
|
| 1356 |
+ } |
|
| 1357 |
+ |
|
| 1358 |
+ header.Set( |
|
| 1359 |
+ "X-Docker-Container-Path-Stat", |
|
| 1360 |
+ base64.StdEncoding.EncodeToString(statJSON), |
|
| 1361 |
+ ) |
|
| 1362 |
+ |
|
| 1363 |
+ return nil |
|
| 1364 |
+} |
|
| 1365 |
+ |
|
| 1366 |
+func (s *Server) headContainersArchive(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 1367 |
+ if vars == nil {
|
|
| 1368 |
+ return fmt.Errorf("Missing parameter")
|
|
| 1369 |
+ } |
|
| 1370 |
+ if err := parseForm(r); err != nil {
|
|
| 1371 |
+ return err |
|
| 1372 |
+ } |
|
| 1373 |
+ |
|
| 1374 |
+ name := vars["name"] |
|
| 1375 |
+ path := r.Form.Get("path")
|
|
| 1376 |
+ |
|
| 1377 |
+ switch {
|
|
| 1378 |
+ case name == "": |
|
| 1379 |
+ return fmt.Errorf("bad parameter: 'name' cannot be empty")
|
|
| 1380 |
+ case path == "": |
|
| 1381 |
+ return fmt.Errorf("bad parameter: 'path' cannot be empty")
|
|
| 1382 |
+ } |
|
| 1383 |
+ |
|
| 1384 |
+ stat, err := s.daemon.ContainerStatPath(name, path) |
|
| 1385 |
+ if err != nil {
|
|
| 1386 |
+ return err |
|
| 1387 |
+ } |
|
| 1388 |
+ |
|
| 1389 |
+ return setContainerPathStatHeader(stat, w.Header()) |
|
| 1390 |
+} |
|
| 1391 |
+ |
|
| 1392 |
+func (s *Server) getContainersArchive(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 1393 |
+ if vars == nil {
|
|
| 1394 |
+ return fmt.Errorf("Missing parameter")
|
|
| 1395 |
+ } |
|
| 1396 |
+ if err := parseForm(r); err != nil {
|
|
| 1397 |
+ return err |
|
| 1398 |
+ } |
|
| 1399 |
+ |
|
| 1400 |
+ name := vars["name"] |
|
| 1401 |
+ path := r.Form.Get("path")
|
|
| 1402 |
+ |
|
| 1403 |
+ switch {
|
|
| 1404 |
+ case name == "": |
|
| 1405 |
+ return fmt.Errorf("bad parameter: 'name' cannot be empty")
|
|
| 1406 |
+ case path == "": |
|
| 1407 |
+ return fmt.Errorf("bad parameter: 'path' cannot be empty")
|
|
| 1408 |
+ } |
|
| 1409 |
+ |
|
| 1410 |
+ tarArchive, stat, err := s.daemon.ContainerArchivePath(name, path) |
|
| 1411 |
+ if err != nil {
|
|
| 1412 |
+ return err |
|
| 1413 |
+ } |
|
| 1414 |
+ defer tarArchive.Close() |
|
| 1415 |
+ |
|
| 1416 |
+ if err := setContainerPathStatHeader(stat, w.Header()); err != nil {
|
|
| 1417 |
+ return err |
|
| 1418 |
+ } |
|
| 1419 |
+ |
|
| 1420 |
+ w.Header().Set("Content-Type", "application/x-tar")
|
|
| 1421 |
+ _, err = io.Copy(w, tarArchive) |
|
| 1422 |
+ |
|
| 1423 |
+ return err |
|
| 1424 |
+} |
|
| 1425 |
+ |
|
| 1426 |
+func (s *Server) putContainersArchive(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 1427 |
+ if vars == nil {
|
|
| 1428 |
+ return fmt.Errorf("Missing parameter")
|
|
| 1429 |
+ } |
|
| 1430 |
+ if err := parseForm(r); err != nil {
|
|
| 1431 |
+ return err |
|
| 1432 |
+ } |
|
| 1433 |
+ |
|
| 1434 |
+ name := vars["name"] |
|
| 1435 |
+ path := r.Form.Get("path")
|
|
| 1436 |
+ |
|
| 1437 |
+ noOverwriteDirNonDir := boolValue(r, "noOverwriteDirNonDir") |
|
| 1438 |
+ |
|
| 1439 |
+ switch {
|
|
| 1440 |
+ case name == "": |
|
| 1441 |
+ return fmt.Errorf("bad parameter: 'name' cannot be empty")
|
|
| 1442 |
+ case path == "": |
|
| 1443 |
+ return fmt.Errorf("bad parameter: 'path' cannot be empty")
|
|
| 1444 |
+ } |
|
| 1445 |
+ |
|
| 1446 |
+ return s.daemon.ContainerExtractToDir(name, path, noOverwriteDirNonDir, r.Body) |
|
| 1447 |
+} |
|
| 1448 |
+ |
|
| 1351 | 1449 |
func (s *Server) postContainerExecCreate(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
| 1352 | 1450 |
if err := parseForm(r); err != nil {
|
| 1353 | 1451 |
return err |
| ... | ... |
@@ -1536,6 +1635,9 @@ func createRouter(s *Server) *mux.Router {
|
| 1536 | 1536 |
ProfilerSetup(r, "/debug/") |
| 1537 | 1537 |
} |
| 1538 | 1538 |
m := map[string]map[string]HttpApiFunc{
|
| 1539 |
+ "HEAD": {
|
|
| 1540 |
+ "/containers/{name:.*}/archive": s.headContainersArchive,
|
|
| 1541 |
+ }, |
|
| 1539 | 1542 |
"GET": {
|
| 1540 | 1543 |
"/_ping": s.ping, |
| 1541 | 1544 |
"/events": s.getEvents, |
| ... | ... |
@@ -1557,6 +1659,7 @@ func createRouter(s *Server) *mux.Router {
|
| 1557 | 1557 |
"/containers/{name:.*}/stats": s.getContainersStats,
|
| 1558 | 1558 |
"/containers/{name:.*}/attach/ws": s.wsContainersAttach,
|
| 1559 | 1559 |
"/exec/{id:.*}/json": s.getExecByID,
|
| 1560 |
+ "/containers/{name:.*}/archive": s.getContainersArchive,
|
|
| 1560 | 1561 |
}, |
| 1561 | 1562 |
"POST": {
|
| 1562 | 1563 |
"/auth": s.postAuth, |
| ... | ... |
@@ -1582,6 +1685,9 @@ func createRouter(s *Server) *mux.Router {
|
| 1582 | 1582 |
"/exec/{name:.*}/resize": s.postContainerExecResize,
|
| 1583 | 1583 |
"/containers/{name:.*}/rename": s.postContainerRename,
|
| 1584 | 1584 |
}, |
| 1585 |
+ "PUT": {
|
|
| 1586 |
+ "/containers/{name:.*}/archive": s.putContainersArchive,
|
|
| 1587 |
+ }, |
|
| 1585 | 1588 |
"DELETE": {
|
| 1586 | 1589 |
"/containers/{name:.*}": s.deleteContainers,
|
| 1587 | 1590 |
"/images/{name:.*}": s.deleteImages,
|
| ... | ... |
@@ -128,8 +128,10 @@ type CopyConfig struct {
|
| 128 | 128 |
Resource string |
| 129 | 129 |
} |
| 130 | 130 |
|
| 131 |
-// ContainerPathStat is used to encode the response from |
|
| 132 |
-// GET /containers/{name:.*}/stat-path
|
|
| 131 |
+// ContainerPathStat is used to encode the header from |
|
| 132 |
+// GET /containers/{name:.*}/archive
|
|
| 133 |
+// "name" is the file or directory name. |
|
| 134 |
+// "path" is the absolute path to the resource in the container. |
|
| 133 | 135 |
type ContainerPathStat struct {
|
| 134 | 136 |
Name string `json:"name"` |
| 135 | 137 |
Path string `json:"path"` |