浏览代码

Implement docker inspect with standalone client lib.

Signed-off-by: David Calavera <david.calavera@gmail.com>
David Calavera 9 年之前
父节点
当前提交
cf3efd05d4
共有 6 个文件被更改,包括 270 次插入144 次删除
  1. 2 0
      api/client/client.go
  2. 67 142
      api/client/inspect.go
  3. 101 0
      api/client/inspect/inspector.go
  4. 46 2
      api/client/lib/container_inspect.go
  5. 17 0
      api/client/lib/errors.go
  6. 37 0
      api/client/lib/image_inspect.go

+ 2 - 0
api/client/client.go

@@ -26,6 +26,7 @@ type apiClient interface {
 	ContainerExecStart(execID string, config types.ExecStartCheck) error
 	ContainerExport(containerID string) (io.ReadCloser, error)
 	ContainerInspect(containerID string) (types.ContainerJSON, error)
+	ContainerInspectWithRaw(containerID string, getSize bool) (types.ContainerJSON, []byte, error)
 	ContainerKill(containerID, signal string) error
 	ContainerList(options types.ContainerListOptions) ([]types.Container, error)
 	ContainerLogs(options types.ContainerLogsOptions) (io.ReadCloser, error)
@@ -47,6 +48,7 @@ type apiClient interface {
 	ImageCreate(options types.ImageCreateOptions) (io.ReadCloser, error)
 	ImageHistory(imageID string) ([]types.ImageHistory, error)
 	ImageImport(options types.ImageImportOptions) (io.ReadCloser, error)
+	ImageInspectWithRaw(imageID string, getSize bool) (types.ImageInspect, []byte, error)
 	ImageList(options types.ImageListOptions) ([]types.Image, error)
 	ImageLoad(input io.Reader) (io.ReadCloser, error)
 	ImagePull(options types.ImagePullOptions, privilegeFunc lib.RequestPrivilegeFunc) (io.ReadCloser, error)

+ 67 - 142
api/client/inspect.go

@@ -1,16 +1,12 @@
 package client
 
 import (
-	"bytes"
 	"encoding/json"
 	"fmt"
-	"io"
-	"net/http"
-	"net/url"
-	"strings"
 	"text/template"
 
-	"github.com/docker/docker/api/types"
+	"github.com/docker/docker/api/client/inspect"
+	"github.com/docker/docker/api/client/lib"
 	Cli "github.com/docker/docker/cli"
 	flag "github.com/docker/docker/pkg/mflag"
 )
@@ -34,10 +30,15 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
 
 	cmd.ParseFlags(args, true)
 
-	var tmpl *template.Template
-	var err error
-	var obj []byte
-	var statusCode int
+	if *inspectType != "" && *inspectType != "container" && *inspectType != "image" {
+		return fmt.Errorf("%q is not a valid value for --type", *inspectType)
+	}
+
+	var (
+		err              error
+		tmpl             *template.Template
+		elementInspector inspect.Inspector
+	)
 
 	if *tmplStr != "" {
 		if tmpl, err = template.New("").Funcs(funcMap).Parse(*tmplStr); err != nil {
@@ -46,160 +47,84 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
 		}
 	}
 
-	if *inspectType != "" && *inspectType != "container" && *inspectType != "image" {
-		return fmt.Errorf("%q is not a valid value for --type", *inspectType)
+	if tmpl != nil {
+		elementInspector = inspect.NewTemplateInspector(cli.out, tmpl)
+	} else {
+		elementInspector = inspect.NewIndentedInspector(cli.out)
 	}
 
-	indented := new(bytes.Buffer)
-	indented.WriteString("[\n")
-	status := 0
-	isImage := false
+	switch *inspectType {
+	case "container":
+		err = cli.inspectContainers(cmd.Args(), *size, elementInspector)
+	case "images":
+		err = cli.inspectImages(cmd.Args(), *size, elementInspector)
+	default:
+		err = cli.inspectAll(cmd.Args(), *size, elementInspector)
+	}
 
-	v := url.Values{}
-	if *size {
-		v.Set("size", "1")
+	if err := elementInspector.Flush(); err != nil {
+		return err
 	}
+	return err
+}
 
-	for _, name := range cmd.Args() {
-		if *inspectType == "" || *inspectType == "container" {
-			obj, statusCode, err = readBody(cli.call("GET", "/containers/"+name+"/json?"+v.Encode(), nil, nil))
-			if err != nil {
-				if err == errConnectionFailed {
-					return err
-				}
-				if *inspectType == "container" {
-					if statusCode == http.StatusNotFound {
-						fmt.Fprintf(cli.err, "Error: No such container: %s\n", name)
-					} else {
-						fmt.Fprintf(cli.err, "%s", err)
-					}
-					status = 1
-					continue
-				}
+func (cli *DockerCli) inspectContainers(containerIDs []string, getSize bool, elementInspector inspect.Inspector) error {
+	for _, containerID := range containerIDs {
+		if err := cli.inspectContainer(containerID, getSize, elementInspector); err != nil {
+			if lib.IsErrContainerNotFound(err) {
+				return fmt.Errorf("Error: No such container: %s\n", containerID)
 			}
+			return err
 		}
+	}
+	return nil
+}
 
-		if obj == nil && (*inspectType == "" || *inspectType == "image") {
-			obj, statusCode, err = readBody(cli.call("GET", "/images/"+name+"/json", nil, nil))
-			isImage = true
-			if err != nil {
-				if err == errConnectionFailed {
-					return err
-				}
-				if statusCode == http.StatusNotFound {
-					if *inspectType == "" {
-						fmt.Fprintf(cli.err, "Error: No such image or container: %s\n", name)
-					} else {
-						fmt.Fprintf(cli.err, "Error: No such image: %s\n", name)
-					}
-				} else {
-					fmt.Fprintf(cli.err, "%s", err)
-				}
-				status = 1
-				continue
+func (cli *DockerCli) inspectImages(imageIDs []string, getSize bool, elementInspector inspect.Inspector) error {
+	for _, imageID := range imageIDs {
+		if err := cli.inspectImage(imageID, getSize, elementInspector); err != nil {
+			if lib.IsErrImageNotFound(err) {
+				return fmt.Errorf("Error: No such image: %s\n", imageID)
 			}
+			return err
 		}
+	}
+	return nil
+}
 
-		if tmpl == nil {
-			if err := json.Indent(indented, obj, "", "    "); err != nil {
-				fmt.Fprintf(cli.err, "%s\n", err)
-				status = 1
-				continue
-			}
-		} else {
-			rdr := bytes.NewReader(obj)
-			dec := json.NewDecoder(rdr)
-			buf := bytes.NewBufferString("")
-
-			if isImage {
-				inspPtr := types.ImageInspect{}
-				if err := dec.Decode(&inspPtr); err != nil {
-					fmt.Fprintf(cli.err, "Unable to read inspect data: %v\n", err)
-					status = 1
-					break
-				}
-				if err := tmpl.Execute(buf, inspPtr); err != nil {
-					rdr.Seek(0, 0)
-					var ok bool
-
-					if buf, ok = cli.decodeRawInspect(tmpl, dec); !ok {
-						fmt.Fprintf(cli.err, "Template parsing error: %v\n", err)
-						status = 1
-						break
-					}
-				}
-			} else {
-				inspPtr := types.ContainerJSON{}
-				if err := dec.Decode(&inspPtr); err != nil {
-					fmt.Fprintf(cli.err, "Unable to read inspect data: %v\n", err)
-					status = 1
-					break
-				}
-				if err := tmpl.Execute(buf, inspPtr); err != nil {
-					rdr.Seek(0, 0)
-					var ok bool
-
-					if buf, ok = cli.decodeRawInspect(tmpl, dec); !ok {
-						fmt.Fprintf(cli.err, "Template parsing error: %v\n", err)
-						status = 1
-						break
+func (cli *DockerCli) inspectAll(ids []string, getSize bool, elementInspector inspect.Inspector) error {
+	for _, id := range ids {
+		if err := cli.inspectContainer(id, getSize, elementInspector); err != nil {
+			// Search for image with that id if a container doesn't exist.
+			if lib.IsErrContainerNotFound(err) {
+				if err := cli.inspectImage(id, getSize, elementInspector); err != nil {
+					if lib.IsErrImageNotFound(err) {
+						return fmt.Errorf("Error: No such image or container: %s", id)
 					}
+					return err
 				}
+				continue
 			}
-
-			cli.out.Write(buf.Bytes())
-			cli.out.Write([]byte{'\n'})
-		}
-		indented.WriteString(",")
-	}
-
-	if indented.Len() > 1 {
-		// Remove trailing ','
-		indented.Truncate(indented.Len() - 1)
-	}
-	indented.WriteString("]\n")
-
-	if tmpl == nil {
-		// Note that we will always write "[]" when "-f" isn't specified,
-		// to make sure the output would always be array, see
-		// https://github.com/docker/docker/pull/9500#issuecomment-65846734
-		if _, err := io.Copy(cli.out, indented); err != nil {
 			return err
 		}
 	}
-
-	if status != 0 {
-		return Cli.StatusError{StatusCode: status}
-	}
 	return nil
 }
 
-// decodeRawInspect executes the inspect template with a raw interface.
-// This allows docker cli to parse inspect structs injected with Swarm fields.
-// Unfortunately, go 1.4 doesn't fail executing invalid templates when the input is an interface.
-// It doesn't allow to modify this behavior either, sending <no value> messages to the output.
-// We assume that the template is invalid when there is a <no value>, if the template was valid
-// we'd get <nil> or "" values. In that case we fail with the original error raised executing the
-// template with the typed input.
-//
-// TODO: Go 1.5 allows to customize the error behavior, we can probably get rid of this as soon as
-// we build Docker with that version:
-// https://golang.org/pkg/text/template/#Template.Option
-func (cli *DockerCli) decodeRawInspect(tmpl *template.Template, dec *json.Decoder) (*bytes.Buffer, bool) {
-	var raw interface{}
-	buf := bytes.NewBufferString("")
-
-	if rawErr := dec.Decode(&raw); rawErr != nil {
-		fmt.Fprintf(cli.err, "Unable to read inspect data: %v\n", rawErr)
-		return buf, false
+func (cli *DockerCli) inspectContainer(containerID string, getSize bool, elementInspector inspect.Inspector) error {
+	c, raw, err := cli.client.ContainerInspectWithRaw(containerID, getSize)
+	if err != nil {
+		return err
 	}
 
-	if rawErr := tmpl.Execute(buf, raw); rawErr != nil {
-		return buf, false
-	}
+	return elementInspector.Inspect(c, raw)
+}
 
-	if strings.Contains(buf.String(), "<no value>") {
-		return buf, false
+func (cli *DockerCli) inspectImage(imageID string, getSize bool, elementInspector inspect.Inspector) error {
+	i, raw, err := cli.client.ImageInspectWithRaw(imageID, getSize)
+	if err != nil {
+		return err
 	}
-	return buf, true
+
+	return elementInspector.Inspect(i, raw)
 }

+ 101 - 0
api/client/inspect/inspector.go

@@ -0,0 +1,101 @@
+package inspect
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"io"
+	"text/template"
+)
+
+// Inspector defines an interface to implement to process elements
+type Inspector interface {
+	Inspect(typedElement interface{}, rawElement []byte) error
+	Flush() error
+}
+
+// TemplateInspector uses a text template to inspect elements.
+type TemplateInspector struct {
+	outputStream io.Writer
+	buffer       *bytes.Buffer
+	tmpl         *template.Template
+}
+
+// NewTemplateInspector creates a new inspector with a template.
+func NewTemplateInspector(outputStream io.Writer, tmpl *template.Template) Inspector {
+	return &TemplateInspector{
+		outputStream: outputStream,
+		buffer:       new(bytes.Buffer),
+		tmpl:         tmpl,
+	}
+}
+
+// Inspect executes the inspect template.
+// It decodes the raw element into a map if the initial execution fails.
+// This allows docker cli to parse inspect structs injected with Swarm fields.
+func (i TemplateInspector) Inspect(typedElement interface{}, rawElement []byte) error {
+	buffer := new(bytes.Buffer)
+	if err := i.tmpl.Execute(buffer, typedElement); err != nil {
+		var raw interface{}
+		rdr := bytes.NewReader(rawElement)
+		dec := json.NewDecoder(rdr)
+
+		if rawErr := dec.Decode(&raw); rawErr != nil {
+			return fmt.Errorf("unable to read inspect data: %v\n", rawErr)
+		}
+
+		tmplMissingKey := i.tmpl.Option("missingkey=error")
+		if rawErr := tmplMissingKey.Execute(buffer, raw); rawErr != nil {
+			return fmt.Errorf("Template parsing error: %v\n", err)
+		}
+	}
+	i.buffer.Write(buffer.Bytes())
+	i.buffer.WriteByte('\n')
+	return nil
+}
+
+// Flush write the result of inspecting all elements into the output stream.
+func (i TemplateInspector) Flush() error {
+	_, err := io.Copy(i.outputStream, i.buffer)
+	return err
+}
+
+// IndentedInspector uses a buffer to stop the indented representation of an element.
+type IndentedInspector struct {
+	outputStream io.Writer
+	indented     *bytes.Buffer
+}
+
+// NewIndentedInspector generates a new IndentedInspector.
+func NewIndentedInspector(outputStream io.Writer) Inspector {
+	indented := new(bytes.Buffer)
+	indented.WriteString("[\n")
+	return &IndentedInspector{
+		outputStream: outputStream,
+		indented:     indented,
+	}
+}
+
+// Inspect writes the raw element with an indented json format.
+func (i IndentedInspector) Inspect(_ interface{}, rawElement []byte) error {
+	if err := json.Indent(i.indented, rawElement, "", "    "); err != nil {
+		return err
+	}
+	i.indented.WriteByte(',')
+	return nil
+}
+
+// Flush write the result of inspecting all elements into the output stream.
+func (i IndentedInspector) Flush() error {
+	if i.indented.Len() > 1 {
+		// Remove trailing ','
+		i.indented.Truncate(i.indented.Len() - 1)
+	}
+	i.indented.WriteString("]\n")
+
+	// Note that we will always write "[]" when "-f" isn't specified,
+	// to make sure the output would always be array, see
+	// https://github.com/docker/docker/pull/9500#issuecomment-65846734
+	_, err := io.Copy(i.outputStream, i.indented)
+	return err
+}

+ 46 - 2
api/client/lib/container_inspect.go

@@ -1,20 +1,64 @@
 package lib
 
 import (
+	"bytes"
 	"encoding/json"
+	"io/ioutil"
+	"net/http"
+	"net/url"
 
 	"github.com/docker/docker/api/types"
 )
 
-// ContainerInspect returns the all the container information.
+// ContainerInspect returns the container information.
 func (cli *Client) ContainerInspect(containerID string) (types.ContainerJSON, error) {
 	serverResp, err := cli.get("/containers/"+containerID+"/json", nil, nil)
 	if err != nil {
+		if serverResp.statusCode == http.StatusNotFound {
+			return types.ContainerJSON{}, containerNotFoundError{containerID}
+		}
 		return types.ContainerJSON{}, err
 	}
 	defer ensureReaderClosed(serverResp)
 
 	var response types.ContainerJSON
-	json.NewDecoder(serverResp.body).Decode(&response)
+	err = json.NewDecoder(serverResp.body).Decode(&response)
 	return response, err
 }
+
+// ContainerInspectWithRaw returns the container information and it's raw representation.
+func (cli *Client) ContainerInspectWithRaw(containerID string, getSize bool) (types.ContainerJSON, []byte, error) {
+	query := url.Values{}
+	if getSize {
+		query.Set("size", "1")
+	}
+	serverResp, err := cli.get("/containers/"+containerID+"/json", query, nil)
+	if err != nil {
+		if serverResp.statusCode == http.StatusNotFound {
+			return types.ContainerJSON{}, nil, containerNotFoundError{containerID}
+		}
+		return types.ContainerJSON{}, nil, err
+	}
+	defer ensureReaderClosed(serverResp)
+
+	body, err := ioutil.ReadAll(serverResp.body)
+	if err != nil {
+		return types.ContainerJSON{}, nil, err
+	}
+
+	var response types.ContainerJSON
+	rdr := bytes.NewReader(body)
+	err = json.NewDecoder(rdr).Decode(&response)
+	return response, body, err
+}
+
+func (cli *Client) containerInspectWithResponse(containerID string, query url.Values) (types.ContainerJSON, *serverResponse, error) {
+	serverResp, err := cli.get("/containers/"+containerID+"/json", nil, nil)
+	if err != nil {
+		return types.ContainerJSON{}, serverResp, err
+	}
+
+	var response types.ContainerJSON
+	err = json.NewDecoder(serverResp.body).Decode(&response)
+	return response, serverResp, err
+}

+ 17 - 0
api/client/lib/errors.go

@@ -25,6 +25,23 @@ func IsErrImageNotFound(err error) bool {
 	return ok
 }
 
+// containerNotFoundError implements an error returned when a container is not in the docker host.
+type containerNotFoundError struct {
+	containerID string
+}
+
+// Error returns a string representation of an containerNotFoundError
+func (e containerNotFoundError) Error() string {
+	return fmt.Sprintf("Container not found: %s", e.containerID)
+}
+
+// IsErrContainerNotFound returns true if the error is caused
+// when a container is not found in the docker host.
+func IsErrContainerNotFound(err error) bool {
+	_, ok := err.(containerNotFoundError)
+	return ok
+}
+
 // unauthorizedError represents an authorization error in a remote registry.
 type unauthorizedError struct {
 	cause error

+ 37 - 0
api/client/lib/image_inspect.go

@@ -0,0 +1,37 @@
+package lib
+
+import (
+	"bytes"
+	"encoding/json"
+	"io/ioutil"
+	"net/http"
+	"net/url"
+
+	"github.com/docker/docker/api/types"
+)
+
+// ImageInspectWithRaw returns the image information and it's raw representation.
+func (cli *Client) ImageInspectWithRaw(imageID string, getSize bool) (types.ImageInspect, []byte, error) {
+	query := url.Values{}
+	if getSize {
+		query.Set("size", "1")
+	}
+	serverResp, err := cli.get("/images/"+imageID+"/json", query, nil)
+	if err != nil {
+		if serverResp.statusCode == http.StatusNotFound {
+			return types.ImageInspect{}, nil, imageNotFoundError{imageID}
+		}
+		return types.ImageInspect{}, nil, err
+	}
+	defer ensureReaderClosed(serverResp)
+
+	body, err := ioutil.ReadAll(serverResp.body)
+	if err != nil {
+		return types.ImageInspect{}, nil, err
+	}
+
+	var response types.ImageInspect
+	rdr := bytes.NewReader(body)
+	err = json.NewDecoder(rdr).Decode(&response)
+	return response, body, err
+}