Skip to content

Commit

Permalink
enhance skopeo inspect
Browse files Browse the repository at this point in the history
Signed-off-by: ningmingxiao <[email protected]>
  • Loading branch information
ningmingxiao committed Jul 19, 2022
1 parent 2bb3f3e commit 831eccc
Show file tree
Hide file tree
Showing 10 changed files with 188 additions and 7 deletions.
4 changes: 4 additions & 0 deletions internal/image/docker_schema1.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ func (m *manifestSchema1) Inspect(context.Context) (*types.ImageInspectInfo, err
return m.m.Inspect(nil)
}

func (m *manifestSchema1) InspectV2(context.Context) (*types.ImageInspectInfoV2, error) {
return m.m.InspectV2(nil)
}

// UpdatedImageNeedsLayerDiffIDs returns true iff UpdatedImage(options) needs InformationOnly.LayerDiffIDs.
// This is a horribly specific interface, but computing InformationOnly.LayerDiffIDs can be very expensive to compute
// (most importantly it forces us to download the full layers even if they are already present at the destination).
Expand Down
16 changes: 15 additions & 1 deletion internal/image/docker_schema2.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,20 @@ func (m *manifestSchema2) Inspect(ctx context.Context) (*types.ImageInspectInfo,
}
return m.m.Inspect(getter)
}
func (m *manifestSchema2) InspectV2(ctx context.Context) (*types.ImageInspectInfoV2, error) {
getter := func(info types.BlobInfo) ([]byte, error) {
if info.Digest != m.ConfigInfo().Digest {
// Shouldn't ever happen
return nil, errors.New("asked for a different config blob")
}
config, err := m.ConfigBlob(ctx)
if err != nil {
return nil, err
}
return config, nil
}
return m.m.InspectV2(getter)
}

// UpdatedImageNeedsLayerDiffIDs returns true iff UpdatedImage(options) needs InformationOnly.LayerDiffIDs.
// This is a horribly specific interface, but computing InformationOnly.LayerDiffIDs can be very expensive to compute
Expand Down Expand Up @@ -326,7 +340,7 @@ func (m *manifestSchema2) convertToManifestSchema1(ctx context.Context, options
ID: v1ID,
Parent: parentV1ID,
Comment: historyEntry.Comment,
Created: historyEntry.Created,
Created: *historyEntry.Created,
Author: historyEntry.Author,
ThrowAway: historyEntry.EmptyLayer,
}
Expand Down
1 change: 1 addition & 0 deletions internal/image/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type genericManifest interface {
EmbeddedDockerReferenceConflicts(ref reference.Named) bool
// Inspect returns various information for (skopeo inspect) parsed from the manifest and configuration.
Inspect(context.Context) (*types.ImageInspectInfo, error)
InspectV2(context.Context) (*types.ImageInspectInfoV2, error)
// UpdatedImageNeedsLayerDiffIDs returns true iff UpdatedImage(options) needs InformationOnly.LayerDiffIDs.
// This is a horribly specific interface, but computing InformationOnly.LayerDiffIDs can be very expensive to compute
// (most importantly it forces us to download the full layers even if they are already present at the destination).
Expand Down
15 changes: 15 additions & 0 deletions internal/image/oci.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,21 @@ func (m *manifestOCI1) Inspect(ctx context.Context) (*types.ImageInspectInfo, er
return m.m.Inspect(getter)
}

func (m *manifestOCI1) InspectV2(ctx context.Context) (*types.ImageInspectInfoV2, error) {
getter := func(info types.BlobInfo) ([]byte, error) {
if info.Digest != m.ConfigInfo().Digest {
// Shouldn't ever happen
return nil, errors.New("asked for a different config blob")
}
config, err := m.ConfigBlob(ctx)
if err != nil {
return nil, err
}
return config, nil
}
return m.m.InspectV2(getter)
}

// UpdatedImageNeedsLayerDiffIDs returns true iff UpdatedImage(options) needs InformationOnly.LayerDiffIDs.
// This is a horribly specific interface, but computing InformationOnly.LayerDiffIDs can be very expensive to compute
// (most importantly it forces us to download the full layers even if they are already present at the destination).
Expand Down
13 changes: 13 additions & 0 deletions manifest/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,19 @@ func layerInfosToStrings(infos []LayerInfo) []string {
return layers
}

// imgInspectLayersFromLayerInfos converts a list of layer infos, presumably obtained from a Manifest.LayerInfos()
// method call, into a format suitable for inclusion in a types.ImageInspectInfo structure.
func imgInspectLayersFromLayerInfos(infos []LayerInfo) []types.Layer {
layers := make([]types.Layer, len(infos))
for i, info := range infos {
layers[i].MIMEType = info.MediaType
layers[i].Digest = info.Digest
layers[i].Size = info.Size
layers[i].Annotations = info.Annotations
}
return layers
}

// compressionMIMETypeSet describes a set of MIME type “variants” that represent differently-compressed
// versions of “the same kind of content”.
// The map key is the return value of compressiontypes.Algorithm.Name(), or mtsUncompressed;
Expand Down
33 changes: 33 additions & 0 deletions manifest/docker_schema1.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,39 @@ func (m *Schema1) Inspect(_ func(types.BlobInfo) ([]byte, error)) (*types.ImageI
return i, nil
}

func (m *Schema1) InspectV2(_ func(types.BlobInfo) ([]byte, error)) (*types.ImageInspectInfoV2, error) {
s1 := &Schema2V1Image{}
if err := json.Unmarshal([]byte(m.History[0].V1Compatibility), s1); err != nil {
return nil, err
}
i := &types.ImageInspectInfoV2{
Tag: m.Tag,
Created: &s1.Created,
DockerVersion: s1.DockerVersion,
Architecture: s1.Architecture,
Os: s1.OS,
Layers: imgInspectLayersFromLayerInfos(m.LayerInfos()),
History: nil,
Author: s1.Author,
Size: s1.Size,
}
if s1.Config != nil {
i.Config.Env = s1.Config.Env
i.Config.Labels = s1.Config.Labels
i.Config.User = s1.Config.User
i.Config.Volumes = s1.Config.Volumes
i.Config.Entrypoint = s1.Config.Entrypoint
for key, value := range s1.Config.ExposedPorts {
exposedPorts := make(map[string]struct{})
exposedPorts[string(key)] = value
i.Config.ExposedPorts = exposedPorts
}
i.Config.StopSignal = s1.Config.StopSignal
i.Config.WorkingDir = s1.Config.WorkingDir
}
return i, nil
}

// ToSchema2Config builds a schema2-style configuration blob using the supplied diffIDs.
func (m *Schema1) ToSchema2Config(diffIDs []digest.Digest) ([]byte, error) {
// Convert the schema 1 compat info into a schema 2 config, constructing some of the fields
Expand Down
50 changes: 45 additions & 5 deletions manifest/docker_schema2.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/containers/image/v5/pkg/strslice"
"github.com/containers/image/v5/types"
"github.com/opencontainers/go-digest"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
)

// Schema2Descriptor is a “descriptor” in docker/distribution schema 2.
Expand Down Expand Up @@ -151,11 +152,11 @@ type Schema2History struct {
// Schema2Image is an Image in docker/docker/image.
type Schema2Image struct {
Schema2V1Image
Parent digest.Digest `json:"parent,omitempty"`
RootFS *Schema2RootFS `json:"rootfs,omitempty"`
History []Schema2History `json:"history,omitempty"`
OSVersion string `json:"os.version,omitempty"`
OSFeatures []string `json:"os.features,omitempty"`
Parent digest.Digest `json:"parent,omitempty"`
RootFS *Schema2RootFS `json:"rootfs,omitempty"`
History []imgspecv1.History `json:"history,omitempty"`
OSVersion string `json:"os.version,omitempty"`
OSFeatures []string `json:"os.features,omitempty"`
}

// Schema2FromManifest creates a Schema2 manifest instance from a manifest blob.
Expand Down Expand Up @@ -287,6 +288,45 @@ func (m *Schema2) Inspect(configGetter func(types.BlobInfo) ([]byte, error)) (*t
return i, nil
}

func (m *Schema2) InspectV2(configGetter func(types.BlobInfo) ([]byte, error)) (*types.ImageInspectInfoV2, error) {
config, err := configGetter(m.ConfigInfo())
if err != nil {
return nil, err
}
s2 := &Schema2Image{}
if err := json.Unmarshal(config, s2); err != nil {
return nil, err
}

i := &types.ImageInspectInfoV2{
Tag: "",
Created: &s2.Created,
DockerVersion: s2.DockerVersion,
Architecture: s2.Architecture,
Variant: s2.Variant,
Os: s2.OS,
Layers: imgInspectLayersFromLayerInfos(m.LayerInfos()),
History: s2.History,
Author: s2.Author,
Size: s2.Size,
}
if s2.Config != nil {
i.Config.Env = s2.Config.Env
i.Config.Labels = s2.Config.Labels
i.Config.User = s2.Config.User
i.Config.Volumes = s2.Config.Volumes
i.Config.Entrypoint = s2.Config.Entrypoint
for key, value := range s2.Config.ExposedPorts {
exposedPorts := make(map[string]struct{})
exposedPorts[string(key)] = value
i.Config.ExposedPorts = exposedPorts
}
i.Config.StopSignal = s2.Config.StopSignal
i.Config.WorkingDir = s2.Config.WorkingDir
}
return i, nil
}

// ImageID computes an ID which can uniquely identify this image by its contents.
func (m *Schema2) ImageID([]digest.Digest) (string, error) {
if err := m.ConfigDescriptor.Digest.Validate(); err != nil {
Expand Down
2 changes: 1 addition & 1 deletion manifest/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ type Manifest interface {
// incorporating information from a configuration blob returned by configGetter, if
// the underlying image format is expected to include a configuration blob.
Inspect(configGetter func(types.BlobInfo) ([]byte, error)) (*types.ImageInspectInfo, error)

InspectV2(configGetter func(types.BlobInfo) ([]byte, error)) (*types.ImageInspectInfoV2, error)
// Serialize returns the manifest in a blob format.
// NOTE: Serialize() does not in general reproduce the original blob if this object was loaded from one, even if no modifications were made!
Serialize() ([]byte, error)
Expand Down
38 changes: 38 additions & 0 deletions manifest/oci.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,44 @@ func (m *OCI1) Inspect(configGetter func(types.BlobInfo) ([]byte, error)) (*type
return i, nil
}

// Inspect returns various information for (skopeo inspect) parsed from the manifest and configuration.
func (m *OCI1) InspectV2(configGetter func(types.BlobInfo) ([]byte, error)) (*types.ImageInspectInfoV2, error) {
if m.Config.MediaType != imgspecv1.MediaTypeImageConfig {
// We could return at least the layers, but that’s already available in a better format via types.Image.LayerInfos.
// Most software calling this without human intervention is going to expect the values to be realistic and relevant,
// and is probably better served by failing; we can always re-visit that later if we fail now, but
// if we started returning some data for OCI artifacts now, we couldn’t start failing in this function later.
return nil, internalManifest.NewNonImageArtifactError(m.Config.MediaType)
}

config, err := configGetter(m.ConfigInfo())
if err != nil {
return nil, err
}
v1 := &imgspecv1.Image{}
if err := json.Unmarshal(config, v1); err != nil {
return nil, err
}
d1 := &Schema2V1Image{}
if err := json.Unmarshal(config, d1); err != nil {
return nil, err
}

i := &types.ImageInspectInfoV2{
Tag: "",
Created: v1.Created,
DockerVersion: d1.DockerVersion,
Architecture: v1.Architecture,
Os: v1.OS,
Layers: imgInspectLayersFromLayerInfos(m.LayerInfos()),
History: v1.History,
Author: v1.Author,
Size: d1.Size,
Config: v1.Config,
}
return i, nil
}

// ImageID computes an ID which can uniquely identify this image by its contents.
func (m *OCI1) ImageID([]digest.Digest) (string, error) {
// The way m.Config.Digest “uniquely identifies” an image is
Expand Down
23 changes: 23 additions & 0 deletions types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,7 @@ type Image interface {
EmbeddedDockerReferenceConflicts(ref reference.Named) bool
// Inspect returns various information for (skopeo inspect) parsed from the manifest and configuration.
Inspect(context.Context) (*ImageInspectInfo, error)
InspectV2(context.Context) (*ImageInspectInfoV2, error)
// UpdatedImageNeedsLayerDiffIDs returns true iff UpdatedImage(options) needs InformationOnly.LayerDiffIDs.
// This is a horribly specific interface, but computing InformationOnly.LayerDiffIDs can be very expensive to compute
// (most importantly it forces us to download the full layers even if they are already present at the destination).
Expand Down Expand Up @@ -468,6 +469,28 @@ type ImageInspectInfo struct {
Env []string
}

type Layer struct {
MIMEType string
Digest digest.Digest
Size int64
Annotations map[string]string
}

type ImageInspectInfoV2 struct {
Tag string
Created *time.Time
DockerVersion string
Labels map[string]string
Architecture string
Variant string
Os string
Layers []Layer
History []v1.History
Author string
Size int64
Config v1.ImageConfig
}

// DockerAuthConfig contains authorization information for connecting to a registry.
// the value of Username and Password can be empty for accessing the registry anonymously
type DockerAuthConfig struct {
Expand Down

0 comments on commit 831eccc

Please sign in to comment.