Explorar o código

changes 2 endpoints to avoid confusion, changed some parameters, fix doc, add api unit tests

Victor Vieux %!s(int64=12) %!d(string=hai) anos
pai
achega
60ddcaa15d
Modificáronse 5 ficheiros con 396 adicións e 62 borrados
  1. 9 9
      api.go
  2. 240 0
      api_test.go
  3. 8 8
      commands.go
  4. 130 28
      docs/sources/remote-api/api.rst
  5. 9 17
      server.go

+ 9 - 9
api.go

@@ -129,9 +129,9 @@ func getImages(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, err
 
 	all := r.Form.Get("all") == "1"
 	filter := r.Form.Get("filter")
-	quiet := r.Form.Get("quiet") == "1"
+	only_ids := r.Form.Get("only_ids") == "1"
 
-	outs, err := srv.Images(all, quiet, filter)
+	outs, err := srv.Images(all, only_ids, filter)
 	if err != nil {
 		return nil, err
 	}
@@ -201,14 +201,14 @@ func getContainers(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte,
 		return nil, err
 	}
 	all := r.Form.Get("all") == "1"
-	notrunc := r.Form.Get("notrunc") == "1"
-	quiet := r.Form.Get("quiet") == "1"
-	n, err := strconv.Atoi(r.Form.Get("n"))
+	trunc_cmd := r.Form.Get("trunc_cmd") != "0"
+	only_ids := r.Form.Get("only_ids") == "1"
+	n, err := strconv.Atoi(r.Form.Get("limit"))
 	if err != nil {
 		n = -1
 	}
 
-	outs := srv.Containers(all, notrunc, quiet, n)
+	outs := srv.Containers(all, trunc_cmd, only_ids, n)
 	b, err := json.Marshal(outs)
 	if err != nil {
 		return nil, err
@@ -540,13 +540,13 @@ func ListenAndServe(addr string, srv *Server) error {
 			"/containers/{name:.*}/export":  getContainersExport,
 			"/images":                       getImages,
 			"/info":                         getInfo,
+			"/images/search":                getImagesSearch,
 			"/images/{name:.*}/history":     getImagesHistory,
 			"/containers/{name:.*}/changes": getContainersChanges,
 			"/containers/{name:.*}/port":    getContainersPort,
 			"/containers":                   getContainers,
-			"/images/search":                getImagesSearch,
-			"/containers/{name:.*}":         getContainersByName,
-			"/images/{name:.*}":             getImagesByName,
+			"/images/{name:.*}/json":        getImagesByName,
+			"/containers/{name:.*}/json":    getContainersByName,
 		},
 		"POST": {
 			"/auth": postAuth,

+ 240 - 0
api_test.go

@@ -0,0 +1,240 @@
+package docker
+
+import (
+	"encoding/json"
+	"github.com/dotcloud/docker/auth"
+	"testing"
+)
+
+func init() {
+	// Make it our Store root
+	runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, false)
+	if err != nil {
+		panic(err)
+	}
+
+	// Create the "Server"
+	srv := &Server{
+		runtime: runtime,
+	}
+	go ListenAndServe("0.0.0.0:4243", srv)
+
+}
+
+func TestAuth(t *testing.T) {
+	var out auth.AuthConfig
+
+	out.Username = "utest"
+	out.Password = "utest"
+	out.Email = "utest@yopmail.com"
+
+	_, _, err := call("POST", "/auth", out)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	out.Username = ""
+	out.Password = ""
+	out.Email = ""
+
+	body, _, err := call("GET", "/auth", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	err = json.Unmarshal(body, &out)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if out.Username != "utest" {
+		t.Errorf("Expected username to be utest, %s found", out.Username)
+	}
+}
+
+func TestVersion(t *testing.T) {
+	body, _, err := call("GET", "/version", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	var out ApiVersion
+	err = json.Unmarshal(body, &out)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if out.Version != VERSION {
+		t.Errorf("Excepted version %s, %s found", VERSION, out.Version)
+	}
+}
+
+func TestImages(t *testing.T) {
+	body, _, err := call("GET", "/images?quiet=0&all=0", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	var outs []ApiImages
+	err = json.Unmarshal(body, &outs)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if len(outs) != 1 {
+		t.Errorf("Excepted 1 image, %d found", len(outs))
+	}
+
+	if outs[0].Repository != "docker-ut" {
+		t.Errorf("Excepted image docker-ut, %s found", outs[0].Repository)
+	}
+}
+
+func TestInfo(t *testing.T) {
+	body, _, err := call("GET", "/info", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	var out ApiInfo
+	err = json.Unmarshal(body, &out)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if out.Version != VERSION {
+		t.Errorf("Excepted version %s, %s found", VERSION, out.Version)
+	}
+}
+
+func TestHistory(t *testing.T) {
+	body, _, err := call("GET", "/images/"+unitTestImageName+"/history", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	var outs []ApiHistory
+	err = json.Unmarshal(body, &outs)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if len(outs) != 1 {
+		t.Errorf("Excepted 1 line, %d found", len(outs))
+	}
+}
+
+func TestImagesSearch(t *testing.T) {
+	body, _, err := call("GET", "/images/search?term=redis", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	var outs []ApiSearch
+	err = json.Unmarshal(body, &outs)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if len(outs) < 2 {
+		t.Errorf("Excepted at least 2 lines, %d found", len(outs))
+	}
+}
+
+func TestGetImage(t *testing.T) {
+	obj, _, err := call("GET", "/images/"+unitTestImageName+"/json", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	var out Image
+	err = json.Unmarshal(obj, &out)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if out.Comment != "Imported from http://get.docker.io/images/busybox" {
+		t.Errorf("Error inspecting image")
+	}
+}
+
+func TestCreateListStartStopRestartKillWaitDelete(t *testing.T) {
+	containers := testListContainers(t, -1)
+	for _, container := range containers {
+		testDeleteContainer(t, container.Id)
+	}
+	testCreateContainer(t)
+	id := testListContainers(t, 1)[0].Id
+	testContainerStart(t, id)
+	testContainerStop(t, id)
+	testContainerRestart(t, id)
+	testContainerKill(t, id)
+	testContainerWait(t, id)
+	testDeleteContainer(t, id)
+	testListContainers(t, 0)
+}
+
+func testCreateContainer(t *testing.T) {
+	config, _, err := ParseRun([]string{unitTestImageName, "touch test"}, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	_, _, err = call("POST", "/containers", *config)
+	if err != nil {
+		t.Fatal(err)
+	}
+}
+
+func testListContainers(t *testing.T, expected int) []ApiContainers {
+	body, _, err := call("GET", "/containers?quiet=1&all=1", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	var outs []ApiContainers
+	err = json.Unmarshal(body, &outs)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if expected >= 0 && len(outs) != expected {
+		t.Errorf("Excepted %d container, %d found", expected, len(outs))
+	}
+	return outs
+}
+
+func testContainerStart(t *testing.T, id string) {
+	_, _, err := call("POST", "/containers/"+id+"/start", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+}
+
+func testContainerRestart(t *testing.T, id string) {
+	_, _, err := call("POST", "/containers/"+id+"/restart?t=1", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+}
+
+func testContainerStop(t *testing.T, id string) {
+	_, _, err := call("POST", "/containers/"+id+"/stop?t=1", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+}
+
+func testContainerKill(t *testing.T, id string) {
+	_, _, err := call("POST", "/containers/"+id+"/kill", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+}
+
+func testContainerWait(t *testing.T, id string) {
+	_, _, err := call("POST", "/containers/"+id+"/wait", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+}
+
+func testDeleteContainer(t *testing.T, id string) {
+	_, _, err := call("DELETE", "/containers/"+id, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+}
+
+func testContainerChanges(t *testing.T, id string) {
+	_, _, err := call("GET", "/containers/"+id+"/changes", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+}

+ 8 - 8
commands.go

@@ -449,9 +449,9 @@ func CmdInspect(args ...string) error {
 		cmd.Usage()
 		return nil
 	}
-	obj, _, err := call("GET", "/containers/"+cmd.Arg(0), nil)
+	obj, _, err := call("GET", "/containers/"+cmd.Arg(0)+"/json", nil)
 	if err != nil {
-		obj, _, err = call("GET", "/images/"+cmd.Arg(0), nil)
+		obj, _, err = call("GET", "/images/"+cmd.Arg(0)+"/json", nil)
 		if err != nil {
 			return err
 		}
@@ -720,7 +720,7 @@ func CmdImages(args ...string) error {
 			v.Set("filter", cmd.Arg(0))
 		}
 		if *quiet {
-			v.Set("quiet", "1")
+			v.Set("only_ids", "1")
 		}
 		if *all {
 			v.Set("all", "1")
@@ -773,16 +773,16 @@ func CmdPs(args ...string) error {
 		*last = 1
 	}
 	if *quiet {
-		v.Set("quiet", "1")
+		v.Set("only_ids", "1")
 	}
 	if *all {
 		v.Set("all", "1")
 	}
 	if *noTrunc {
-		v.Set("notrunc", "1")
+		v.Set("trunc_cmd", "0")
 	}
 	if *last != -1 {
-		v.Set("n", strconv.Itoa(*last))
+		v.Set("limit", strconv.Itoa(*last))
 	}
 
 	body, _, err := call("GET", "/containers?"+v.Encode(), nil)
@@ -888,13 +888,13 @@ func CmdDiff(args ...string) error {
 		return err
 	}
 
-	var changes []string
+	var changes []Change
 	err = json.Unmarshal(body, &changes)
 	if err != nil {
 		return err
 	}
 	for _, change := range changes {
-		fmt.Println(change)
+		fmt.Println(change.String())
 	}
 	return nil
 }

+ 130 - 28
docs/sources/remote-api/api.rst

@@ -28,7 +28,7 @@ List containers
 
 	.. sourcecode:: http
 
-	   GET /containers?notrunc=1&all=1&quiet=0 HTTP/1.1
+	   GET /containers?trunc_cmd=0&all=1&only_ids=0 HTTP/1.1
 	   
 	**Example response**:
 
@@ -47,30 +47,30 @@ List containers
 		{
 			"Id": "9cd87474be90",
 			"Image": "base:latest",
-			"Command": "echo 2",
+			"Command": "echo 222222",
 			"Created": 1367854155,
 			"Status": "Exit 0"
 		},
 		{
 			"Id": "3176a2479c92",
 			"Image": "base:latest",
-			"Command": "echo 3",
+			"Command": "echo 3333333333333333",
 			"Created": 1367854154,
 			"Status": "Exit 0"
 		},
 		{
 			"Id": "4cb07b47f9fb",
 			"Image": "base:latest",
-			"Command": "echo 4",
+			"Command": "echo 444444444444444444444444444444444",
 			"Created": 1367854152,
 			"Status": "Exit 0"
 		}
 	   ]
  
-	:query quiet: 1 or 0, Only display numeric IDs. Not quiet by default
+	:query only_ids: 1 or 0, Only display numeric IDs. Default 0
 	:query all: 1 or 0, Show all containers. Only running containers are shown by default
-	:query notrunc: 1 or 0, Don't truncate output. Output is truncated by default  
-	:query n: limit number, Show n last created containers, include non-running ones.
+	:query trunc_cmd: 1 or 0, Truncate output. Output is truncated by default  
+	:query limit: Show ``limit`` last created containers, include non-running ones.
 	:statuscode 200: no error
 	:statuscode 500: server error
 
@@ -123,14 +123,14 @@ Create a container
 	
 	:jsonparam config: the container's configuration
 	:statuscode 200: no error
-	:statuscode 400: no such container
+	:statuscode 404: no such container
 	:statuscode 500: server error
 
 
 Inspect a container
 *******************
 
-.. http:get:: /containers/(id)
+.. http:get:: /containers/(id)/json
 
 	Return low-level information on the container ``id``
 
@@ -138,7 +138,7 @@ Inspect a container
 
 	.. sourcecode:: http
 
-	   GET /containers/4fa6e0f0c678 HTTP/1.1
+	   GET /containers/4fa6e0f0c678/json HTTP/1.1
 	   
 	**Example response**:
 
@@ -193,7 +193,7 @@ Inspect a container
 	   }
 
 	:statuscode 200: no error
-	:statuscode 400: no such container
+	:statuscode 404: no such container
 	:statuscode 500: server error
 
 
@@ -218,12 +218,22 @@ Inspect changes on a container's filesystem
 	   HTTP/1.1 200 OK
 	   
 	   [
-			"C /dev",
-			"A /dev/kmsg"
+		{
+			"Path":"/dev",
+			"Kind":0
+		},
+		{
+			"Path":"/dev/kmsg",
+			"Kind":1
+		},
+		{
+			"Path":"/test",
+			"Kind":1
+		}
 	   ]
 
 	:statuscode 200: no error
-	:statuscode 400: no such container
+	:statuscode 404: no such container
 	:statuscode 500: server error
 
 
@@ -251,7 +261,7 @@ Export a container
 	   {{ STREAM }}
 
 	:statuscode 200: no error
-	:statuscode 400: no such container
+	:statuscode 404: no such container
 	:statuscode 500: server error
 
 
@@ -279,7 +289,7 @@ Map container's private ports
 	
 	:query port: the container private port you want to get
 	:statuscode 200: no error
-	:statuscode 400: no such container
+	:statuscode 404: no such container
 	:statuscode 500: server error
 
 
@@ -303,7 +313,7 @@ Start a container
 	   HTTP/1.1 200 OK
 	   	
 	:statuscode 200: no error
-	:statuscode 400: no such container
+	:statuscode 404: no such container
 	:statuscode 500: server error
 
 
@@ -328,7 +338,7 @@ Stop a contaier
 	   	
 	:query t: number of seconds to wait before killing the container
 	:statuscode 200: no error
-	:statuscode 400: no such container
+	:statuscode 404: no such container
 	:statuscode 500: server error
 
 
@@ -353,7 +363,7 @@ Restart a container
 	   	
 	:query t: number of seconds to wait before killing the container
 	:statuscode 200: no error
-	:statuscode 400: no such container
+	:statuscode 404: no such container
 	:statuscode 500: server error
 
 
@@ -377,7 +387,7 @@ Kill a container
 	   HTTP/1.1 200 OK
 	   	
 	:statuscode 200: no error
-	:statuscode 400: no such container
+	:statuscode 404: no such container
 	:statuscode 500: server error
 
 
@@ -409,7 +419,7 @@ Attach to a container
 	:query stdout: 1 or 0, if logs=1, return stdout log, if stream=1, attach to stdout. Default 0
 	:query stderr: 1 or 0, if logs=1, return stderr log, if stream=1, attach to stderr. Default 0
 	:statuscode 200: no error
-	:statuscode 400: no such container
+	:statuscode 404: no such container
 	:statuscode 500: server error
 
 
@@ -435,7 +445,7 @@ Wait a container
 	   {"StatusCode":0}
 	   	
 	:statuscode 200: no error
-	:statuscode 400: no such container
+	:statuscode 404: no such container
 	:statuscode 500: server error
 
 
@@ -460,7 +470,7 @@ Remove a container
 
 	:query v: 1 or 0, Remove the volumes associated to the container. Default 0
         :statuscode 200: no error
-        :statuscode 400: no such container
+        :statuscode 404: no such container
         :statuscode 500: server error
 
 
@@ -478,7 +488,7 @@ List Images
 
 	.. sourcecode:: http
 
-	   GET /images?all=0&quiet=0 HTTP/1.1
+	   GET /images?all=0&only_ids=0 HTTP/1.1
 	   
 	**Example response**:
 
@@ -501,7 +511,7 @@ List Images
 		}
 	   ]
  
-	:query quiet: 1 or 0, Only display numeric IDs. Not quiet by default
+	:query only_ids: 1 or 0, Only display numeric IDs. Default 0
 	:query all: 1 or 0, Show all containers. Only running containers are shown by default
 	:statuscode 200: no error
 	:statuscode 500: server error
@@ -537,10 +547,36 @@ Create an image
         :statuscode 200: no error
         :statuscode 500: server error
 
+
+Insert a file in a image
+************************
+
+.. http:post:: /images/(name)/insert
+
+	Insert a file from ``url`` in the image ``name`` at ``path``
+
+	**Example request**:
+
+        .. sourcecode:: http
+
+           POST /images/test/insert?path=/usr&url=myurl HTTP/1.1
+
+	**Example response**:
+
+        .. sourcecode:: http
+
+           HTTP/1.1 200 OK
+
+	   {{ STREAM }}
+
+	:statuscode 200: no error
+        :statuscode 500: server error
+
+
 Inspect an image
 ****************
 
-.. http:get:: /images/(name)
+.. http:get:: /images/(name)/json
 
 	Return low-level information on the image ``name``
 
@@ -548,7 +584,7 @@ Inspect an image
 
 	.. sourcecode:: http
 
-	   GET /images/base HTTP/1.1
+	   GET /images/base/json HTTP/1.1
 
 	**Example response**:
 
@@ -703,9 +739,73 @@ Remove an image
         :statuscode 500: server error
 
 
+Search images
+*************
+
+.. http:get:: /images/search
+
+	Search for an image in the docker index
+	
+	**Example request**:
+
+        .. sourcecode:: http
+
+           GET /images/search?term=sshd HTTP/1.1
+
+	**Example response**:
+
+	.. sourcecode:: http
+	   
+	   [
+		{
+			"Name":"cespare/sshd",
+			"Description":""
+		},
+		{
+			"Name":"johnfuller/sshd",
+			"Description":""
+		},
+		{
+			"Name":"dhrp/mongodb-sshd",
+			"Description":""
+		}
+	   ]
+
+	   :query term: term to search
+	   :statuscode 200: no error
+	   :statuscode 500: server error
+
+
 2.3 Misc
 --------
 
+Build an image from Dockerfile via stdin
+****************************************
+
+.. http:post:: /build
+
+	Build an image from Dockerfile via stdin
+
+	**Example request**:
+
+        .. sourcecode:: http
+
+           POST /build HTTP/1.1
+	   
+	   {{ STREAM }}
+
+	**Example response**:
+
+        .. sourcecode:: http
+
+           HTTP/1.1 200 OK
+	   
+	   {{ STREAM }}
+
+	:statuscode 200: no error
+        :statuscode 500: server error
+
+
 Get default username and email
 ******************************
 
@@ -792,6 +892,7 @@ Display system-wide information
         :statuscode 200: no error
         :statuscode 500: server error
 
+
 Show the docker version information
 ***********************************
 
@@ -870,7 +971,8 @@ Here are the steps of 'docker run' :
 * Start the container
 * If you are not in detached mode:
         * Attach to the container, using logs=1 (to have stdout and stderr from the container's start) and stream=1
-        * Call /wait to get the exit code and exit with it
+* If in detached mode or only stdin is attached:
+	* Display the container's id
 
 
 3.2 Hijacking

+ 9 - 17
server.go

@@ -136,7 +136,7 @@ func (srv *Server) ImagesViz(out io.Writer) error {
 	return nil
 }
 
-func (srv *Server) Images(all, quiet bool, filter string) ([]ApiImages, error) {
+func (srv *Server) Images(all, only_ids bool, filter string) ([]ApiImages, error) {
 	var allImages map[string]*Image
 	var err error
 	if all {
@@ -160,7 +160,7 @@ func (srv *Server) Images(all, quiet bool, filter string) ([]ApiImages, error) {
 				continue
 			}
 			delete(allImages, id)
-			if !quiet {
+			if !only_ids {
 				out.Repository = name
 				out.Tag = tag
 				out.Id = TruncateId(id)
@@ -175,7 +175,7 @@ func (srv *Server) Images(all, quiet bool, filter string) ([]ApiImages, error) {
 	if filter == "" {
 		for id, image := range allImages {
 			var out ApiImages
-			if !quiet {
+			if !only_ids {
 				out.Repository = "<none>"
 				out.Tag = "<none>"
 				out.Id = TruncateId(id)
@@ -228,17 +228,9 @@ func (srv *Server) ImageHistory(name string) ([]ApiHistory, error) {
 
 }
 
-func (srv *Server) ContainerChanges(name string) ([]string, error) {
+func (srv *Server) ContainerChanges(name string) ([]Change, error) {
 	if container := srv.runtime.Get(name); container != nil {
-		changes, err := container.Changes()
-		if err != nil {
-			return nil, err
-		}
-		var changesStr []string
-		for _, name := range changes {
-			changesStr = append(changesStr, name.String())
-		}
-		return changesStr, nil
+		return container.Changes()
 	}
 	return nil, fmt.Errorf("No such container: %s", name)
 }
@@ -253,7 +245,7 @@ func (srv *Server) ContainerPort(name, privatePort string) (string, error) {
 	return "", fmt.Errorf("No such container: %s", name)
 }
 
-func (srv *Server) Containers(all, notrunc, quiet bool, n int) []ApiContainers {
+func (srv *Server) Containers(all, trunc_cmd, only_ids bool, n int) []ApiContainers {
 	var outs []ApiContainers = []ApiContainers{} //produce [] when empty instead of 'null'
 	for i, container := range srv.runtime.List() {
 		if !container.State.Running && !all && n == -1 {
@@ -264,9 +256,9 @@ func (srv *Server) Containers(all, notrunc, quiet bool, n int) []ApiContainers {
 		}
 		var out ApiContainers
 		out.Id = container.ShortId()
-		if !quiet {
+		if !only_ids {
 			command := fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " "))
-			if !notrunc {
+			if trunc_cmd {
 				command = Trunc(command, 20)
 			}
 			out.Image = srv.runtime.repositories.ImageName(container.Image)
@@ -461,7 +453,7 @@ func (srv *Server) ImageDelete(name string) error {
 		return fmt.Errorf("No such image: %s", name)
 	} else {
 		if err := srv.runtime.graph.Delete(img.Id); err != nil {
-			return fmt.Errorf("Error deleteing image %s: %s", name, err.Error())
+			return fmt.Errorf("Error deleting image %s: %s", name, err.Error())
 		}
 	}
 	return nil