// Package common ... package common import ( "encoding/json" "io/ioutil" "os" "path" "strings" "time" "github.com/anuvu/zot/errors" "github.com/anuvu/zot/pkg/log" 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" ) // OciLayoutInfo ... type OciLayoutUtils struct { Log log.Logger } // NewOciLayoutUtils initializes a new OciLayoutUtils object. func NewOciLayoutUtils(log log.Logger) *OciLayoutUtils { return &OciLayoutUtils{Log: log} } // Below method will return image path including root dir, root dir is determined by splitting. func (olu OciLayoutUtils) GetImageManifests(imagePath string) ([]ispec.Descriptor, error) { buf, err := ioutil.ReadFile(path.Join(imagePath, "index.json")) if err != nil { if os.IsNotExist(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", imagePath).Msg("invalid JSON") return nil, errors.ErrRepoNotFound } return index.Manifests, nil } func (olu OciLayoutUtils) GetImageBlobManifest(imageDir string, digest godigest.Digest) (v1.Manifest, error) { var blobIndex v1.Manifest blobBuf, err := ioutil.ReadFile(path.Join(imageDir, "blobs", digest.Algorithm().String(), digest.Encoded())) 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 } func (olu OciLayoutUtils) GetImageInfo(imageDir string, hash v1.Hash) (ispec.Image, error) { var imageInfo ispec.Image blobBuf, err := ioutil.ReadFile(path.Join(imageDir, "blobs", hash.Algorithm, hash.Hex)) 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(imagePath string) (bool, error) { imageDir, inputTag := GetImageDirAndTag(imagePath) if !DirExists(imageDir) { olu.Log.Error().Msg("image directory doesn't exist") return false, errors.ErrRepoNotFound } manifests, err := olu.GetImageManifests(imageDir) if err != nil { return false, err } for _, m := range manifests { tag, ok := m.Annotations[ispec.AnnotationRefName] if ok && inputTag != "" && tag != inputTag { continue } blobManifest, err := olu.GetImageBlobManifest(imageDir, m.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 v, 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: v, Timestamp: timeStamp, Digest: digest.String()}) } } return tagsInfo, nil } func DirExists(d string) bool { fi, err := os.Stat(d) if err != nil && os.IsNotExist(err) { return false } return fi.IsDir() } 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 }