//go:build !remote // +build !remote package libimage import ( "context" "time" "github.com/containers/image/v5/manifest" "github.com/containers/image/v5/types" "github.com/opencontainers/go-digest" ociv1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/sirupsen/logrus" ) // ImageData contains the inspected data of an image. type ImageData struct { ID string `json:"Id"` Digest digest.Digest `json:"Digest"` RepoTags []string `json:"RepoTags"` RepoDigests []string `json:"RepoDigests"` Parent string `json:"Parent"` Comment string `json:"Comment"` Created *time.Time `json:"Created"` Config *ociv1.ImageConfig `json:"Config"` Version string `json:"Version"` Author string `json:"Author"` Architecture string `json:"Architecture"` Os string `json:"Os"` Size int64 `json:"Size"` VirtualSize int64 `json:"VirtualSize"` GraphDriver *DriverData `json:"GraphDriver"` RootFS *RootFS `json:"RootFS"` Labels map[string]string `json:"Labels"` Annotations map[string]string `json:"Annotations"` ManifestType string `json:"ManifestType"` User string `json:"User"` History []ociv1.History `json:"History"` NamesHistory []string `json:"NamesHistory"` HealthCheck *manifest.Schema2HealthConfig `json:"Healthcheck,omitempty"` } // DriverData includes data on the storage driver of the image. type DriverData struct { Name string `json:"Name"` Data map[string]string `json:"Data"` } // RootFS includes data on the root filesystem of the image. type RootFS struct { Type string `json:"Type"` Layers []digest.Digest `json:"Layers"` } // InspectOptions allow for customizing inspecting images. type InspectOptions struct { // Compute the size of the image (expensive). WithSize bool // Compute the parent of the image (expensive). WithParent bool } // Inspect inspects the image. func (i *Image) Inspect(ctx context.Context, options *InspectOptions) (*ImageData, error) { logrus.Debugf("Inspecting image %s", i.ID()) if options == nil { options = &InspectOptions{} } if i.cached.completeInspectData != nil { if options.WithSize && i.cached.completeInspectData.Size == int64(-1) { size, err := i.Size() if err != nil { return nil, err } i.cached.completeInspectData.Size = size } if options.WithParent && i.cached.completeInspectData.Parent == "" { parentImage, err := i.Parent(ctx) if err != nil { return nil, err } if parentImage != nil { i.cached.completeInspectData.Parent = parentImage.ID() } } return i.cached.completeInspectData, nil } // First assemble data that does not depend on the format of the image. info, err := i.inspectInfo(ctx) if err != nil { return nil, err } ociImage, err := i.toOCI(ctx) if err != nil { return nil, err } repoTags, err := i.RepoTags() if err != nil { return nil, err } repoDigests, err := i.RepoDigests() if err != nil { return nil, err } driverData, err := i.driverData() if err != nil { return nil, err } size := int64(-1) if options.WithSize { size, err = i.Size() if err != nil { return nil, err } } data := &ImageData{ ID: i.ID(), RepoTags: repoTags, RepoDigests: repoDigests, Created: ociImage.Created, Author: ociImage.Author, Architecture: ociImage.Architecture, Os: ociImage.OS, Config: &ociImage.Config, Version: info.DockerVersion, Size: size, VirtualSize: size, // NOTE: same as size. Inherited from Docker where it's scheduled for deprecation. Digest: i.Digest(), Labels: info.Labels, RootFS: &RootFS{ Type: ociImage.RootFS.Type, Layers: ociImage.RootFS.DiffIDs, }, GraphDriver: driverData, User: ociImage.Config.User, History: ociImage.History, NamesHistory: i.NamesHistory(), } if options.WithParent { parentImage, err := i.Parent(ctx) if err != nil { return nil, err } if parentImage != nil { data.Parent = parentImage.ID() } } // Determine the format of the image. How we determine certain data // depends on the format (e.g., Docker v2s2, OCI v1). src, err := i.source(ctx) if err != nil { return nil, err } manifestRaw, manifestType, err := src.GetManifest(ctx, nil) if err != nil { return nil, err } data.ManifestType = manifestType switch manifestType { // OCI image case ociv1.MediaTypeImageManifest: var ociManifest ociv1.Manifest if err := json.Unmarshal(manifestRaw, &ociManifest); err != nil { return nil, err } data.Annotations = ociManifest.Annotations if len(ociImage.History) > 0 { data.Comment = ociImage.History[0].Comment } // Docker image case manifest.DockerV2Schema1MediaType, manifest.DockerV2Schema2MediaType: rawConfig, err := i.rawConfigBlob(ctx) if err != nil { return nil, err } var dockerManifest manifest.Schema2V1Image if err := json.Unmarshal(rawConfig, &dockerManifest); err != nil { return nil, err } data.Comment = dockerManifest.Comment // NOTE: Health checks may be listed in the container config or // the config. data.HealthCheck = dockerManifest.ContainerConfig.Healthcheck if data.HealthCheck == nil && dockerManifest.Config != nil { data.HealthCheck = dockerManifest.Config.Healthcheck } } if data.Annotations == nil { // Podman compat data.Annotations = make(map[string]string) } i.cached.completeInspectData = data return data, nil } // inspectInfo returns the image inspect info. func (i *Image) inspectInfo(ctx context.Context) (*types.ImageInspectInfo, error) { if i.cached.partialInspectData != nil { return i.cached.partialInspectData, nil } ref, err := i.StorageReference() if err != nil { return nil, err } img, err := ref.NewImage(ctx, &i.runtime.systemContext) if err != nil { return nil, err } defer img.Close() data, err := img.Inspect(ctx) if err != nil { return nil, err } i.cached.partialInspectData = data return data, nil }