1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

Implement docker inspect with standalone client lib.

Signed-off-by: David Calavera <david.calavera@gmail.com>
This commit is contained in:
David Calavera 2015-12-06 18:34:25 -05:00
parent 42670e30ee
commit cf3efd05d4
6 changed files with 281 additions and 155 deletions

View file

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

View file

@ -1,16 +1,12 @@
package client package client
import ( import (
"bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"net/http"
"net/url"
"strings"
"text/template" "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" Cli "github.com/docker/docker/cli"
flag "github.com/docker/docker/pkg/mflag" flag "github.com/docker/docker/pkg/mflag"
) )
@ -34,10 +30,15 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
cmd.ParseFlags(args, true) cmd.ParseFlags(args, true)
var tmpl *template.Template if *inspectType != "" && *inspectType != "container" && *inspectType != "image" {
var err error return fmt.Errorf("%q is not a valid value for --type", *inspectType)
var obj []byte }
var statusCode int
var (
err error
tmpl *template.Template
elementInspector inspect.Inspector
)
if *tmplStr != "" { if *tmplStr != "" {
if tmpl, err = template.New("").Funcs(funcMap).Parse(*tmplStr); err != nil { 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" { if tmpl != nil {
return fmt.Errorf("%q is not a valid value for --type", *inspectType) elementInspector = inspect.NewTemplateInspector(cli.out, tmpl)
} else {
elementInspector = inspect.NewIndentedInspector(cli.out)
} }
indented := new(bytes.Buffer) switch *inspectType {
indented.WriteString("[\n") case "container":
status := 0 err = cli.inspectContainers(cmd.Args(), *size, elementInspector)
isImage := false case "images":
err = cli.inspectImages(cmd.Args(), *size, elementInspector)
v := url.Values{} default:
if *size { err = cli.inspectAll(cmd.Args(), *size, elementInspector)
v.Set("size", "1")
} }
for _, name := range cmd.Args() { if err := elementInspector.Flush(); err != nil {
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 return err
} }
if *inspectType == "container" { return err
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
}
}
}
if obj == nil && (*inspectType == "" || *inspectType == "image") { func (cli *DockerCli) inspectContainers(containerIDs []string, getSize bool, elementInspector inspect.Inspector) error {
obj, statusCode, err = readBody(cli.call("GET", "/images/"+name+"/json", nil, nil)) for _, containerID := range containerIDs {
isImage = true if err := cli.inspectContainer(containerID, getSize, elementInspector); err != nil {
if err != nil { if lib.IsErrContainerNotFound(err) {
if err == errConnectionFailed { return fmt.Errorf("Error: No such container: %s\n", containerID)
}
return err 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
}
}
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
}
}
}
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 return nil
} }
// decodeRawInspect executes the inspect template with a raw interface. func (cli *DockerCli) inspectImages(imageIDs []string, getSize bool, elementInspector inspect.Inspector) error {
// This allows docker cli to parse inspect structs injected with Swarm fields. for _, imageID := range imageIDs {
// Unfortunately, go 1.4 doesn't fail executing invalid templates when the input is an interface. if err := cli.inspectImage(imageID, getSize, elementInspector); err != nil {
// It doesn't allow to modify this behavior either, sending <no value> messages to the output. if lib.IsErrImageNotFound(err) {
// We assume that the template is invalid when there is a <no value>, if the template was valid return fmt.Errorf("Error: No such image: %s\n", imageID)
// 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
} }
return err
if rawErr := tmpl.Execute(buf, raw); rawErr != nil {
return buf, false
} }
if strings.Contains(buf.String(), "<no value>") {
return buf, false
} }
return buf, true return nil
}
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
}
return err
}
}
return nil
}
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
}
return elementInspector.Inspect(c, raw)
}
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 elementInspector.Inspect(i, raw)
} }

View file

@ -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
}

View file

@ -1,20 +1,64 @@
package lib package lib
import ( import (
"bytes"
"encoding/json" "encoding/json"
"io/ioutil"
"net/http"
"net/url"
"github.com/docker/docker/api/types" "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) { func (cli *Client) ContainerInspect(containerID string) (types.ContainerJSON, error) {
serverResp, err := cli.get("/containers/"+containerID+"/json", nil, nil) serverResp, err := cli.get("/containers/"+containerID+"/json", nil, nil)
if err != nil { if err != nil {
if serverResp.statusCode == http.StatusNotFound {
return types.ContainerJSON{}, containerNotFoundError{containerID}
}
return types.ContainerJSON{}, err return types.ContainerJSON{}, err
} }
defer ensureReaderClosed(serverResp) defer ensureReaderClosed(serverResp)
var response types.ContainerJSON var response types.ContainerJSON
json.NewDecoder(serverResp.body).Decode(&response) err = json.NewDecoder(serverResp.body).Decode(&response)
return response, err 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
}

View file

@ -25,6 +25,23 @@ func IsErrImageNotFound(err error) bool {
return ok 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. // unauthorizedError represents an authorization error in a remote registry.
type unauthorizedError struct { type unauthorizedError struct {
cause error cause error

View file

@ -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
}