Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/skopeo/copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func copyHandler(context *cli.Context) {
}
signBy := context.String("sign-by")

manifest, _, err := src.GetManifest()
manifest, err := src.GetManifest()
if err != nil {
logrus.Fatalf("Error reading manifest: %s", err.Error())
}
Expand Down
48 changes: 42 additions & 6 deletions cmd/skopeo/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,27 @@ package main
import (
"encoding/json"
"fmt"
"time"

"github.com/Sirupsen/logrus"
"github.com/codegangsta/cli"
"github.com/projectatomic/skopeo/dockerutils"
)

// inspectOutput is the output format of (skopeo inspect), primarily so that we can format it with a simple json.MarshalIndent.
type inspectOutput struct {
Name string
Tag string
Digest string
RepoTags []string
Created time.Time
DockerVersion string
Labels map[string]string
Architecture string
Os string
Layers []string
}

var inspectCmd = cli.Command{
Name: "inspect",
Usage: "inspect images on a registry",
Expand All @@ -22,19 +38,39 @@ var inspectCmd = cli.Command{
if err != nil {
logrus.Fatal(err)
}
rawManifest, err := img.Manifest()
if err != nil {
logrus.Fatal(err)
}
if c.Bool("raw") {
b, err := img.Manifest()
if err != nil {
logrus.Fatal(err)
}
fmt.Println(string(b))
fmt.Println(string(rawManifest))
return
}
imgInspect, err := img.Inspect()
if err != nil {
logrus.Fatal(err)
}
out, err := json.MarshalIndent(imgInspect, "", " ")
manifestDigest, err := dockerutils.ManifestDigest(rawManifest)
if err != nil {
logrus.Fatalf("Error computing manifest digest: %s", err.Error())
}
repoTags, err := img.GetRepositoryTags()
Copy link
Member

@runcom runcom May 16, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with you this does smell here... (I mean this tag method on the generic image interface)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This improves the interface at least a little. How to do it truly cleanly and in a generic way, without breaking existing users (if any?) is not clear to me yet; I was thinking

if di, ok := img.(DockerImage); ok {
   tags = di.GetDockerRepositoryTags()
} else {
  tags = nil;
}

as another small step towards kicking this out of types.Image. Eventually; this is not really hurting me :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That type assertion sounds fine to me and it's clean (tags can be omitemtpy when/if marshaling also)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will eventually need to generalize dockerImage to work with openshiftImageSource, at that point the type assertion will make sense, because we could test the tags = nil path.

Or do you want it done immediately so that the GetRepositoryTags method is never added to types.Image?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine doing it later (hopefully we would freeze the API in the coming weeks so we are still more time to think more about this :)

We can leave a comment though just in case we forgot about doing this larer

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a comment pointing to this thread, within the “Add comments on the use of the API and the general direction” commit.

if err != nil {
logrus.Fatalf("Error determining repository tags: %s", err.Error())
}
outputData := inspectOutput{
Name: imgInspect.Name,
Tag: imgInspect.Tag,
Digest: manifestDigest,
RepoTags: repoTags,
Created: imgInspect.Created,
DockerVersion: imgInspect.DockerVersion,
Labels: imgInspect.Labels,
Architecture: imgInspect.Architecture,
Os: imgInspect.Os,
Layers: imgInspect.Layers,
}
out, err := json.MarshalIndent(outputData, "", " ")
if err != nil {
logrus.Fatal(err)
}
Expand Down
9 changes: 2 additions & 7 deletions directory/directory.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,8 @@ func (s *dirImageSource) IntendedDockerReference() string {
return ""
}

func (s *dirImageSource) GetManifest() ([]byte, string, error) {
manifest, err := ioutil.ReadFile(manifestPath(s.dir))
if err != nil {
return nil, "", err
}

return manifest, "", nil // FIXME? unverifiedCanonicalDigest value - really primarily used by dockerImage
func (s *dirImageSource) GetManifest() ([]byte, error) {
return ioutil.ReadFile(manifestPath(s.dir))
}

func (s *dirImageSource) GetLayer(digest string) (io.ReadCloser, error) {
Expand Down
74 changes: 26 additions & 48 deletions docker/docker_image.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ var (

type dockerImage struct {
src *dockerImageSource
digest string
rawManifest []byte
cachedManifest []byte // Private cache for Manifest(); nil if not yet known.
cachedSignatures [][]byte // Private cache for Signatures(); nil if not yet known.
}

Expand All @@ -44,10 +43,14 @@ func (i *dockerImage) IntendedDockerReference() string {

// Manifest is like ImageSource.GetManifest, but the result is cached; it is OK to call this however often you need.
func (i *dockerImage) Manifest() ([]byte, error) {
if err := i.retrieveRawManifest(); err != nil {
return nil, err
if i.cachedManifest == nil {
m, err := i.src.GetManifest()
if err != nil {
return nil, err
}
i.cachedManifest = m
}
return i.rawManifest, nil
return i.cachedManifest, nil
}

// Signatures is like ImageSource.GetSignatures, but the result is cached; it is OK to call this however often you need.
Expand All @@ -62,7 +65,7 @@ func (i *dockerImage) Signatures() ([][]byte, error) {
return i.cachedSignatures, nil
}

func (i *dockerImage) Inspect() (types.ImageManifest, error) {
func (i *dockerImage) Inspect() (*types.ImageInspectInfo, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for pointer here since this is an interface

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original types.ImageManifest was an interface. types.ImageInspectInfo (formerly types.DockerImageManifest) is a struct.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aww gotcha sorry for the noise

// TODO(runcom): unused version param for now, default to docker v2-1
m, err := i.getSchema1Manifest()
if err != nil {
Expand All @@ -72,18 +75,24 @@ func (i *dockerImage) Inspect() (types.ImageManifest, error) {
if !ok {
return nil, fmt.Errorf("error retrivieng manifest schema1")
}
tags, err := i.getTags()
if err != nil {
return nil, err
}
imgManifest, err := makeImageManifest(i.src.ref.FullName(), ms1, i.digest, tags)
if err != nil {
v1 := &v1Image{}
if err := json.Unmarshal([]byte(ms1.History[0].V1Compatibility), v1); err != nil {
return nil, err
}
return imgManifest, nil
return &types.ImageInspectInfo{
Name: i.src.ref.FullName(),
Tag: ms1.Tag,
DockerVersion: v1.DockerVersion,
Created: v1.Created,
Labels: v1.Config.Labels,
Architecture: v1.Architecture,
Os: v1.OS,
Layers: ms1.GetLayers(),
}, nil
}

func (i *dockerImage) getTags() ([]string, error) {
// GetRepositoryTags list all tags available in the repository. Note that this has no connection with the tag(s) used for this specific image, if any.
func (i *dockerImage) GetRepositoryTags() ([]string, error) {
// FIXME? Breaking the abstraction.
url := fmt.Sprintf(tagsURL, i.src.ref.RemoteName())
res, err := i.src.c.makeRequest("GET", url, nil, nil)
Expand Down Expand Up @@ -122,25 +131,6 @@ type v1Image struct {
OS string `json:"os,omitempty"`
}

func makeImageManifest(name string, m *manifestSchema1, dgst string, tagList []string) (types.ImageManifest, error) {
v1 := &v1Image{}
if err := json.Unmarshal([]byte(m.History[0].V1Compatibility), v1); err != nil {
return nil, err
}
return &types.DockerImageManifest{
Name: name,
Tag: m.Tag,
Digest: dgst,
RepoTags: tagList,
DockerVersion: v1.DockerVersion,
Created: v1.Created,
Labels: v1.Config.Labels,
Architecture: v1.Architecture,
Os: v1.OS,
Layers: m.GetLayers(),
}, nil
}

// TODO(runcom)
func (i *dockerImage) DockerTar() ([]byte, error) {
return nil, nil
Expand Down Expand Up @@ -181,25 +171,13 @@ func sanitize(s string) string {
return strings.Replace(s, "/", "-", -1)
}

func (i *dockerImage) retrieveRawManifest() error {
if i.rawManifest != nil {
return nil
}
manblob, unverifiedCanonicalDigest, err := i.src.GetManifest()
if err != nil {
return err
}
i.rawManifest = manblob
i.digest = unverifiedCanonicalDigest
return nil
}

func (i *dockerImage) getSchema1Manifest() (manifest, error) {
if err := i.retrieveRawManifest(); err != nil {
manblob, err := i.Manifest()
if err != nil {
return nil, err
}
mschema1 := &manifestSchema1{}
if err := json.Unmarshal(i.rawManifest, mschema1); err != nil {
if err := json.Unmarshal(manblob, mschema1); err != nil {
return nil, err
}
if err := fixManifestLayers(mschema1); err != nil {
Expand Down
11 changes: 6 additions & 5 deletions docker/docker_image_src.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,23 +55,24 @@ func (s *dockerImageSource) IntendedDockerReference() string {
return fmt.Sprintf("%s:%s", s.ref.Name(), s.tag)
}

func (s *dockerImageSource) GetManifest() (manifest []byte, unverifiedCanonicalDigest string, err error) {
func (s *dockerImageSource) GetManifest() ([]byte, error) {
url := fmt.Sprintf(manifestURL, s.ref.RemoteName(), s.tag)
// TODO(runcom) set manifest version header! schema1 for now - then schema2 etc etc and v1
// TODO(runcom) NO, switch on the resulter manifest like Docker is doing
res, err := s.c.makeRequest("GET", url, nil, nil)
if err != nil {
return nil, "", err
return nil, err
}
defer res.Body.Close()
manblob, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, "", err
return nil, err
}
if res.StatusCode != http.StatusOK {
return nil, "", errFetchManifest{res.StatusCode, manblob}
return nil, errFetchManifest{res.StatusCode, manblob}
}
return manblob, res.Header.Get("Docker-Content-Digest"), nil
// We might validate manblob against the Docker-Content-Digest header here to protect against transport errors.
return manblob, nil
}

func (s *dockerImageSource) GetLayer(digest string) (io.ReadCloser, error) {
Expand Down
4 changes: 2 additions & 2 deletions openshift/openshift.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,9 +193,9 @@ func (s *openshiftImageSource) IntendedDockerReference() string {
return s.client.canonicalDockerReference()
}

func (s *openshiftImageSource) GetManifest() (manifest []byte, unverifiedCanonicalDigest string, err error) {
func (s *openshiftImageSource) GetManifest() ([]byte, error) {
if err := s.ensureImageIsResolved(); err != nil {
return nil, "", err
return nil, err
}
return s.docker.GetManifest()
}
Expand Down
32 changes: 17 additions & 15 deletions types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,17 @@ type Repository interface {
}

// ImageSource is a service, possibly remote (= slow), to download components of a single image.
// This is primarily useful for copying images around; for examining their properties, Image (below)
// is usually more useful.
type ImageSource interface {
// IntendedDockerReference returns the full, unambiguous, Docker reference for this image, _as specified by the user_
// (not as the image itself, or its underlying storage, claims). This can be used e.g. to determine which public keys are trusted for this image.
// May be "" if unknown.
IntendedDockerReference() string
// GetManifest returns the image's manifest. It may use a remote (= slow) service.
GetManifest() (manifest []byte, unverifiedCanonicalDigest string, err error)
// FIXME? This should also return a MIME type if known, to differentiate between schema versions.
GetManifest() ([]byte, error)
// Note: Calling GetLayer() may have ordering dependencies WRT other methods of this type. FIXME: How does this work with (docker save) on stdin?
GetLayer(digest string) (io.ReadCloser, error)
// GetSignatures returns the image's signatures. It may use a remote (= slow) service.
GetSignatures() ([][]byte, error)
Expand All @@ -45,40 +49,38 @@ type ImageSource interface {
type ImageDestination interface {
// CanonicalDockerReference returns the full, unambiguous, Docker reference for this image (even if the user referred to the image using some shorthand notation).
CanonicalDockerReference() (string, error)
// FIXME? This should also receive a MIME type if known, to differentiate between schema versions.
PutManifest([]byte) error
// Note: Calling PutLayer() and other methods may have ordering dependencies WRT other methods of this type. FIXME: Figure out and document.
PutLayer(digest string, stream io.Reader) error
PutSignatures(signatures [][]byte) error
}

// Image is a Docker image in a repository.
// Image is the primary API for inspecting properties of images.
type Image interface {
// ref to repository?
// IntendedDockerReference returns the full, unambiguous, Docker reference for this image, _as specified by the user_
// (not as the image itself, or its underlying storage, claims). This can be used e.g. to determine which public keys are trusted for this image.
// May be "" if unknown.
IntendedDockerReference() string
// Manifest is like ImageSource.GetManifest, but the result is cached; it is OK to call this however often you need.
// FIXME? This should also return a MIME type if known, to differentiate between schema versions.
Manifest() ([]byte, error)
// Signatures is like ImageSource.GetSignatures, but the result is cached; it is OK to call this however often you need.
Signatures() ([][]byte, error)
Layers(layers ...string) error // configure download directory? Call it DownloadLayers?
Inspect() (ImageManifest, error)
Inspect() (*ImageInspectInfo, error)
DockerTar() ([]byte, error) // ??? also, configure output directory
// GetRepositoryTags list all tags available in the repository. Note that this has no connection with the tag(s) used for this specific image, if any.
// Eventually we should move this away from the generic Image interface, and move it into a Docker-specific case within the (skopeo inspect) command,
// see https://github.com/projectatomic/skopeo/pull/58#discussion_r63411838 .
GetRepositoryTags() ([]string, error)
}

// ImageManifest is the interesting subset of metadata about an Image.
// TODO(runcom)
type ImageManifest interface {
String() string
}

// DockerImageManifest is a set of metadata describing Docker images and their manifest.json files.
// Note that this is not exactly manifest.json, e.g. some fields have been added.
type DockerImageManifest struct {
// ImageInspectInfo is a set of metadata describing Docker images, primarily their manifest and configuration.
type ImageInspectInfo struct {
Name string
Tag string
Digest string
RepoTags []string
Created time.Time
DockerVersion string
Labels map[string]string
Expand All @@ -87,6 +89,6 @@ type DockerImageManifest struct {
Layers []string
}

func (m *DockerImageManifest) String() string {
func (m *ImageInspectInfo) String() string {
return fmt.Sprintf("%s:%s", m.Name, m.Tag)
}