mirror of
https://github.com/project-zot/zot.git
synced 2024-12-23 22:27:35 -05:00
37d150e32f
DetailedRepoInfo graphql api returns detailed repo info given repo name repo contains its manifests info Each manifest entry contains digest,signed, tag and layers info Each layer info containes digest, size Signed-off-by: Shivam Mishra <shimish2@cisco.com>
278 lines
6.5 KiB
Go
278 lines
6.5 KiB
Go
// Package common ...
|
|
package common
|
|
|
|
import (
|
|
"encoding/json"
|
|
goerrors "errors"
|
|
"path"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
v1 "github.com/google/go-containerregistry/pkg/v1"
|
|
"github.com/google/go-containerregistry/pkg/v1/types"
|
|
godigest "github.com/opencontainers/go-digest"
|
|
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
"zotregistry.io/zot/errors"
|
|
"zotregistry.io/zot/pkg/log"
|
|
"zotregistry.io/zot/pkg/storage"
|
|
)
|
|
|
|
const cosignedAnnotation = "dev.cosign.signature.baseimage"
|
|
|
|
// OciLayoutInfo ...
|
|
type OciLayoutUtils struct {
|
|
Log log.Logger
|
|
StoreController storage.StoreController
|
|
}
|
|
|
|
type RepoInfo struct {
|
|
Manifests []Manifest `json:"manifests"`
|
|
}
|
|
|
|
type Manifest struct {
|
|
Tag string `json:"tag"`
|
|
Digest string `json:"digest"`
|
|
IsSigned bool `json:"isSigned"`
|
|
Layers []Layer `json:"layers"`
|
|
}
|
|
|
|
type Layer struct {
|
|
Size string `json:"size"`
|
|
Digest string `json:"digest"`
|
|
}
|
|
|
|
// NewOciLayoutUtils initializes a new OciLayoutUtils object.
|
|
func NewOciLayoutUtils(storeController storage.StoreController, log log.Logger) *OciLayoutUtils {
|
|
return &OciLayoutUtils{Log: log, StoreController: storeController}
|
|
}
|
|
|
|
// Below method will return image path including root dir, root dir is determined by splitting.
|
|
func (olu OciLayoutUtils) GetImageManifests(image string) ([]ispec.Descriptor, error) {
|
|
imageStore := olu.StoreController.GetImageStore(image)
|
|
|
|
buf, err := imageStore.GetIndexContent(image)
|
|
if err != nil {
|
|
if goerrors.Is(errors.ErrRepoNotFound, err) {
|
|
olu.Log.Error().Err(err).Msg("index.json doesn't exist")
|
|
|
|
return nil, errors.ErrRepoNotFound
|
|
}
|
|
|
|
olu.Log.Error().Err(err).Msg("unable to open index.json")
|
|
|
|
return nil, errors.ErrRepoNotFound
|
|
}
|
|
|
|
var index ispec.Index
|
|
|
|
if err := json.Unmarshal(buf, &index); err != nil {
|
|
olu.Log.Error().Err(err).Str("dir", path.Join(imageStore.RootDir(), image)).Msg("invalid JSON")
|
|
|
|
return nil, errors.ErrRepoNotFound
|
|
}
|
|
|
|
return index.Manifests, nil
|
|
}
|
|
|
|
//nolint: interfacer
|
|
func (olu OciLayoutUtils) GetImageBlobManifest(imageDir string, digest godigest.Digest) (v1.Manifest, error) {
|
|
var blobIndex v1.Manifest
|
|
|
|
imageStore := olu.StoreController.GetImageStore(imageDir)
|
|
|
|
blobBuf, err := imageStore.GetBlobContent(imageDir, digest.String())
|
|
if err != nil {
|
|
olu.Log.Error().Err(err).Msg("unable to open image metadata file")
|
|
|
|
return blobIndex, err
|
|
}
|
|
|
|
if err := json.Unmarshal(blobBuf, &blobIndex); err != nil {
|
|
olu.Log.Error().Err(err).Msg("unable to marshal blob index")
|
|
|
|
return blobIndex, err
|
|
}
|
|
|
|
return blobIndex, nil
|
|
}
|
|
|
|
//nolint: interfacer
|
|
func (olu OciLayoutUtils) GetImageInfo(imageDir string, hash v1.Hash) (ispec.Image, error) {
|
|
var imageInfo ispec.Image
|
|
|
|
imageStore := olu.StoreController.GetImageStore(imageDir)
|
|
|
|
blobBuf, err := imageStore.GetBlobContent(imageDir, hash.String())
|
|
if err != nil {
|
|
olu.Log.Error().Err(err).Msg("unable to open image layers file")
|
|
|
|
return imageInfo, err
|
|
}
|
|
|
|
if err := json.Unmarshal(blobBuf, &imageInfo); err != nil {
|
|
olu.Log.Error().Err(err).Msg("unable to marshal blob index")
|
|
|
|
return imageInfo, err
|
|
}
|
|
|
|
return imageInfo, err
|
|
}
|
|
|
|
func (olu OciLayoutUtils) IsValidImageFormat(image string) (bool, error) {
|
|
imageDir, inputTag := GetImageDirAndTag(image)
|
|
|
|
manifests, err := olu.GetImageManifests(imageDir)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
for _, manifest := range manifests {
|
|
tag, ok := manifest.Annotations[ispec.AnnotationRefName]
|
|
|
|
if ok && inputTag != "" && tag != inputTag {
|
|
continue
|
|
}
|
|
|
|
blobManifest, err := olu.GetImageBlobManifest(imageDir, manifest.Digest)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
imageLayers := blobManifest.Layers
|
|
|
|
for _, imageLayer := range imageLayers {
|
|
switch imageLayer.MediaType {
|
|
case types.OCILayer, types.DockerLayer:
|
|
return true, nil
|
|
|
|
default:
|
|
olu.Log.Debug().Msg("image media type not supported for scanning")
|
|
|
|
return false, errors.ErrScanNotSupported
|
|
}
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// GetImageTagsWithTimestamp returns a list of image tags with timestamp available in the specified repository.
|
|
func (olu OciLayoutUtils) GetImageTagsWithTimestamp(repo string) ([]TagInfo, error) {
|
|
tagsInfo := make([]TagInfo, 0)
|
|
|
|
manifests, err := olu.GetImageManifests(repo)
|
|
if err != nil {
|
|
olu.Log.Error().Err(err).Msg("unable to read image manifests")
|
|
|
|
return tagsInfo, err
|
|
}
|
|
|
|
for _, manifest := range manifests {
|
|
digest := manifest.Digest
|
|
|
|
val, ok := manifest.Annotations[ispec.AnnotationRefName]
|
|
if ok {
|
|
imageBlobManifest, err := olu.GetImageBlobManifest(repo, digest)
|
|
if err != nil {
|
|
olu.Log.Error().Err(err).Msg("unable to read image blob manifest")
|
|
|
|
return tagsInfo, err
|
|
}
|
|
|
|
imageInfo, err := olu.GetImageInfo(repo, imageBlobManifest.Config.Digest)
|
|
if err != nil {
|
|
olu.Log.Error().Err(err).Msg("unable to read image info")
|
|
|
|
return tagsInfo, err
|
|
}
|
|
|
|
var timeStamp time.Time
|
|
|
|
if len(imageInfo.History) != 0 {
|
|
timeStamp = *imageInfo.History[0].Created
|
|
} else {
|
|
timeStamp = time.Time{}
|
|
}
|
|
|
|
tagsInfo = append(tagsInfo, TagInfo{Name: val, Timestamp: timeStamp, Digest: digest.String()})
|
|
}
|
|
}
|
|
|
|
return tagsInfo, nil
|
|
}
|
|
|
|
func (olu OciLayoutUtils) GetExpandedRepoInfo(name string) (RepoInfo, error) {
|
|
repo := RepoInfo{}
|
|
|
|
manifests := make([]Manifest, 0)
|
|
|
|
manifestList, err := olu.GetImageManifests(name)
|
|
if err != nil {
|
|
olu.Log.Error().Err(err).Msg("error getting image manifests")
|
|
|
|
return RepoInfo{}, err
|
|
}
|
|
|
|
for _, manifest := range manifestList {
|
|
manifestInfo := Manifest{}
|
|
|
|
manifestInfo.Digest = manifest.Digest.Encoded()
|
|
|
|
manifestInfo.IsSigned = false
|
|
|
|
tag, ok := manifest.Annotations[ispec.AnnotationRefName]
|
|
if !ok {
|
|
tag = "latest"
|
|
}
|
|
|
|
manifestInfo.Tag = tag
|
|
|
|
manifest, err := olu.GetImageBlobManifest(name, manifest.Digest)
|
|
if err != nil {
|
|
olu.Log.Error().Err(err).Msg("error getting image manifest blob")
|
|
|
|
return RepoInfo{}, err
|
|
}
|
|
|
|
layers := make([]Layer, 0)
|
|
|
|
for _, layer := range manifest.Layers {
|
|
layerInfo := Layer{}
|
|
|
|
layerInfo.Digest = layer.Digest.Hex
|
|
|
|
layerInfo.Size = strconv.FormatInt(layer.Size, 10)
|
|
|
|
layers = append(layers, layerInfo)
|
|
|
|
if _, ok := layer.Annotations[cosignedAnnotation]; ok {
|
|
manifestInfo.IsSigned = true
|
|
}
|
|
}
|
|
|
|
manifestInfo.Layers = layers
|
|
|
|
manifests = append(manifests, manifestInfo)
|
|
}
|
|
|
|
repo.Manifests = manifests
|
|
|
|
return repo, nil
|
|
}
|
|
|
|
func GetImageDirAndTag(imageName string) (string, string) {
|
|
var imageDir string
|
|
|
|
var imageTag string
|
|
|
|
if strings.Contains(imageName, ":") {
|
|
splitImageName := strings.Split(imageName, ":")
|
|
imageDir = splitImageName[0]
|
|
imageTag = splitImageName[1]
|
|
} else {
|
|
imageDir = imageName
|
|
}
|
|
|
|
return imageDir, imageTag
|
|
}
|