Skip to content

Commit

Permalink
Proposal: allow for remote repository inspection
Browse files Browse the repository at this point in the history
Added new flag to `docker inspect` command:

    $ docker inspect --remote <imageName>[:<tag>]...

Which inspects remote images. Additional `[:TAG]` suffix may further
specify desired `<image>`.

This allows for remote inspection without downloading image layers.

Log Tag, Digest and Registry to stderr as debug messages. Need to pass
`-D` flag to Docker client to see them.

Signed-off-by: Michal Minar <miminar@redhat.com>
  • Loading branch information
Michal Minar committed Jul 14, 2015
1 parent daffcc0 commit b2c99f5
Show file tree
Hide file tree
Showing 7 changed files with 576 additions and 37 deletions.
102 changes: 90 additions & 12 deletions api/client/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ import (
"encoding/json"
"fmt"
"io"
"net/url"
"strings"
"text/template"

"github.com/Sirupsen/logrus"
"github.com/docker/docker/api/types"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/parsers"
"github.com/docker/docker/registry"
)

// CmdInspect displays low-level information on one or more containers or images.
Expand All @@ -19,6 +23,8 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
cmd := cli.Subcmd("inspect", []string{"CONTAINER|IMAGE [CONTAINER|IMAGE...]"}, "Return low-level information on a container or image", true)
tmplStr := cmd.String([]string{"f", "#format", "-format"}, "", "Format the output using the given go template")
inspectType := cmd.String([]string{"-type"}, "", "Return JSON for specified type, (e.g image or container)")
remote := cmd.Bool([]string{"r", "-remote"}, false, "Inspect remote images")

cmd.Require(flag.Min, 1)

cmd.ParseFlags(args, true)
Expand All @@ -37,13 +43,23 @@ 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 *inspectType == "container" && *remote {
return fmt.Errorf("conflicting options: --remote && --type=container")
}
if *remote {
*inspectType = "image"
}

indented := new(bytes.Buffer)
indented.WriteString("[\n")
status := 0
isImage := false

for _, name := range cmd.Args() {
var (
err error
isImage = false
resp = &serverResponse{}
)

if *inspectType == "" || *inspectType == "container" {
obj, _, err = readBody(cli.call("GET", "/containers/"+name+"/json", nil, nil))
Expand All @@ -59,11 +75,31 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
}

if obj == nil && (*inspectType == "" || *inspectType == "image") {
obj, _, err = readBody(cli.call("GET", "/images/"+name+"/json", nil, nil))
if *remote {
var repoInfo *registry.RepositoryInfo
taglessRemote, _ := parsers.ParseRepositoryTag(name)
// Resolve the Repository name from fqn to RepositoryInfo
repoInfo, err = registry.ParseRepositoryInfo(taglessRemote)
if err != nil {
return err
}
v := url.Values{}
v.Set("remote", "1")
body, statusCode, respErr := cli.clientRequestAttemptLogin("GET", "/images/"+name+"/json?"+v.Encode(), nil, nil, repoInfo.Index, "inspect")
if respErr == nil {
resp = &serverResponse{body: body, statusCode: statusCode}
} else {
err = respErr
}
} else {
resp, err = cli.call("GET", "/images/"+name+"/json", nil, nil)
}
obj, _, err = readBody(resp, err)

isImage = true
if err != nil {
if strings.Contains(err.Error(), "No such") {
if *inspectType == "" {
if strings.Contains(err.Error(), "No such") || strings.Contains(strings.ToLower(err.Error()), "not found") {
if *inspectType == "" || !*remote {
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)
Expand All @@ -74,11 +110,34 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
status = 1
continue
}

}

if tmpl == nil {
if err := json.Indent(indented, obj, "", " "); err != nil {
if *remote {
rdr := bytes.NewReader(obj)
dec := json.NewDecoder(rdr)

remoteImage := types.RemoteImageInspect{}
if err := dec.Decode(&remoteImage); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
} else {
ref := name
if remoteImage.Tag != "" {
ref += ":" + remoteImage.Tag
}
if remoteImage.Digest != "" {
ref += "@" + remoteImage.Digest
}
logrus.Debugf("Inspecting image %s", ref)
encoded, err := json.Marshal(&remoteImage.ImageInspectBase)
if err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
} else {
obj = encoded
}
}
}
if err = json.Indent(indented, obj, "", " "); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
status = 1
continue
Expand All @@ -88,13 +147,32 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
dec := json.NewDecoder(rdr)

if isImage {
inspPtr := types.ImageInspect{}
if err := dec.Decode(&inspPtr); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
status = 1
continue
if *remote {
remoteImage := types.RemoteImageInspect{}
if err := dec.Decode(&remoteImage); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
status = 1
continue
}
ref := name
if remoteImage.Tag != "" {
ref += ":" + remoteImage.Tag
}
if remoteImage.Digest != "" {
ref += "@" + remoteImage.Digest
}
logrus.Debugf("Inspecting image %s", ref)
err = tmpl.Execute(cli.out, &remoteImage.ImageInspectBase)
} else {
inspPtr := types.ImageInspect{}
if err := dec.Decode(&inspPtr); err != nil {
fmt.Fprintf(cli.err, "%s\n", err)
status = 1
continue
}
err = tmpl.Execute(cli.out, inspPtr)
}
if err := tmpl.Execute(cli.out, inspPtr); err != nil {
if err != nil {
rdr.Seek(0, 0)
var raw interface{}
if err := dec.Decode(&raw); err != nil {
Expand Down
41 changes: 36 additions & 5 deletions api/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -1226,12 +1226,43 @@ func (s *Server) getImagesByName(version version.Version, w http.ResponseWriter,
return fmt.Errorf("Missing parameter")
}

imageInspect, err := s.daemon.Repositories().Lookup(vars["name"])
if err != nil {
return err
}
name := vars["name"]

if boolValue(r, "remote") {
authEncoded := r.Header.Get("X-Registry-Auth")
authConfig := &cliconfig.AuthConfig{}
if authEncoded != "" {
authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
if err := json.NewDecoder(authJson).Decode(authConfig); err != nil {
// for a pull it is not an error if no auth was given
// to increase compatibility with the existing api it is defaulting to be empty
authConfig = &cliconfig.AuthConfig{}
}
}

return writeJSON(w, http.StatusOK, imageInspect)
image, tag := parsers.ParseRepositoryTag(name)
metaHeaders := map[string][]string{}
for k, v := range r.Header {
if strings.HasPrefix(k, "X-Meta-") {
metaHeaders[k] = v
}
}
lookupRemoteConfig := &graph.LookupRemoteConfig{
MetaHeaders: metaHeaders,
AuthConfig: authConfig,
}
imageInspect, err := s.daemon.Repositories().LookupRemote(image, tag, lookupRemoteConfig)
if err != nil {
return err
}
return writeJSON(w, http.StatusOK, imageInspect)
} else {
imageInspect, err := s.daemon.Repositories().Lookup(name)
if err != nil {
return err
}
return writeJSON(w, http.StatusOK, imageInspect)
}
}

func (s *Server) postBuild(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
Expand Down
20 changes: 16 additions & 4 deletions api/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,7 @@ type GraphDriverData struct {
Data map[string]string
}

// GET "/images/{name:.*}/json"
type ImageInspect struct {
type ImageInspectBase struct {
Id string
Parent string
Comment string
Expand All @@ -94,8 +93,21 @@ type ImageInspect struct {
Architecture string
Os string
Size int64
VirtualSize int64
GraphDriver GraphDriverData
}

// GET "/images/{name:.*}/json"
type ImageInspect struct {
ImageInspectBase
VirtualSize int64
GraphDriver GraphDriverData
}

// GET "/images/{name:.*}/json?remote=1"
type RemoteImageInspect struct {
ImageInspectBase
Registry string
Digest string
Tag string
}

// GET "/containers/json"
Expand Down
1 change: 1 addition & 0 deletions contrib/completion/fish/docker.fish
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ complete -c docker -f -n '__fish_docker_no_subcommand' -a info -d 'Display syste
complete -c docker -f -n '__fish_docker_no_subcommand' -a inspect -d 'Return low-level information on a container or image'
complete -c docker -A -f -n '__fish_seen_subcommand_from inspect' -s f -l format -d 'Format the output using the given go template.'
complete -c docker -A -f -n '__fish_seen_subcommand_from inspect' -l help -d 'Print usage'
complete -c docker -A -f -n '__fish_seen_subcommand_from inspect' -s r -l remote -d 'Inspect remote images'
complete -c docker -A -f -n '__fish_seen_subcommand_from inspect' -a '(__fish_print_docker_images)' -d "Image"
complete -c docker -A -f -n '__fish_seen_subcommand_from inspect' -a '(__fish_print_docker_containers all)' -d "Container"

Expand Down

0 comments on commit b2c99f5

Please sign in to comment.