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
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)

View File

@ -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
v := url.Values{}
if *size {
v.Set("size", "1")
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)
}
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
}
}
}
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
}
}
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 err := elementInspector.Flush(); err != nil {
return err
}
return err
}
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 {
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
}
}
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) 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
}
}
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
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
}

View File

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

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
}