github.com/khulnasoft/cli@v0.0.0-20240402070845-01bcad7beefa/cli/manifest/store/store.go (about)

     1  package store
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  
    10  	"github.com/distribution/reference"
    11  	"github.com/khulnasoft-lab/distribution/manifest/manifestlist"
    12  	"github.com/khulnasoft/cli/cli/manifest/types"
    13  	"github.com/opencontainers/go-digest"
    14  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    15  	"github.com/pkg/errors"
    16  )
    17  
    18  // Store manages local storage of image distribution manifests
    19  type Store interface {
    20  	Remove(listRef reference.Reference) error
    21  	Get(listRef reference.Reference, manifest reference.Reference) (types.ImageManifest, error)
    22  	GetList(listRef reference.Reference) ([]types.ImageManifest, error)
    23  	Save(listRef reference.Reference, manifest reference.Reference, image types.ImageManifest) error
    24  }
    25  
    26  // fsStore manages manifest files stored on the local filesystem
    27  type fsStore struct {
    28  	root string
    29  }
    30  
    31  // NewStore returns a new store for a local file path
    32  func NewStore(root string) Store {
    33  	return &fsStore{root: root}
    34  }
    35  
    36  // Remove a manifest list from local storage
    37  func (s *fsStore) Remove(listRef reference.Reference) error {
    38  	path := filepath.Join(s.root, makeFilesafeName(listRef.String()))
    39  	return os.RemoveAll(path)
    40  }
    41  
    42  // Get returns the local manifest
    43  func (s *fsStore) Get(listRef reference.Reference, manifest reference.Reference) (types.ImageManifest, error) {
    44  	filename := manifestToFilename(s.root, listRef.String(), manifest.String())
    45  	return s.getFromFilename(manifest, filename)
    46  }
    47  
    48  func (s *fsStore) getFromFilename(ref reference.Reference, filename string) (types.ImageManifest, error) {
    49  	bytes, err := os.ReadFile(filename)
    50  	switch {
    51  	case os.IsNotExist(err):
    52  		return types.ImageManifest{}, newNotFoundError(ref.String())
    53  	case err != nil:
    54  		return types.ImageManifest{}, err
    55  	}
    56  	var manifestInfo struct {
    57  		types.ImageManifest
    58  
    59  		// Deprecated Fields, replaced by Descriptor
    60  		Digest   digest.Digest
    61  		Platform *manifestlist.PlatformSpec
    62  	}
    63  
    64  	if err := json.Unmarshal(bytes, &manifestInfo); err != nil {
    65  		return types.ImageManifest{}, err
    66  	}
    67  
    68  	// Compatibility with image manifests created before
    69  	// descriptor, newer versions omit Digest and Platform
    70  	if manifestInfo.Digest != "" {
    71  		mediaType, raw, err := manifestInfo.Payload()
    72  		if err != nil {
    73  			return types.ImageManifest{}, err
    74  		}
    75  		if dgst := digest.FromBytes(raw); dgst != manifestInfo.Digest {
    76  			return types.ImageManifest{}, errors.Errorf("invalid manifest file %v: image manifest digest mismatch (%v != %v)", filename, manifestInfo.Digest, dgst)
    77  		}
    78  		manifestInfo.ImageManifest.Descriptor = ocispec.Descriptor{
    79  			Digest:    manifestInfo.Digest,
    80  			Size:      int64(len(raw)),
    81  			MediaType: mediaType,
    82  			Platform:  types.OCIPlatform(manifestInfo.Platform),
    83  		}
    84  	}
    85  
    86  	return manifestInfo.ImageManifest, nil
    87  }
    88  
    89  // GetList returns all the local manifests for a transaction
    90  func (s *fsStore) GetList(listRef reference.Reference) ([]types.ImageManifest, error) {
    91  	filenames, err := s.listManifests(listRef.String())
    92  	switch {
    93  	case err != nil:
    94  		return nil, err
    95  	case filenames == nil:
    96  		return nil, newNotFoundError(listRef.String())
    97  	}
    98  
    99  	manifests := []types.ImageManifest{}
   100  	for _, filename := range filenames {
   101  		filename = filepath.Join(s.root, makeFilesafeName(listRef.String()), filename)
   102  		manifest, err := s.getFromFilename(listRef, filename)
   103  		if err != nil {
   104  			return nil, err
   105  		}
   106  		manifests = append(manifests, manifest)
   107  	}
   108  	return manifests, nil
   109  }
   110  
   111  // listManifests stored in a transaction
   112  func (s *fsStore) listManifests(transaction string) ([]string, error) {
   113  	transactionDir := filepath.Join(s.root, makeFilesafeName(transaction))
   114  	fileInfos, err := os.ReadDir(transactionDir)
   115  	switch {
   116  	case os.IsNotExist(err):
   117  		return nil, nil
   118  	case err != nil:
   119  		return nil, err
   120  	}
   121  
   122  	filenames := make([]string, 0, len(fileInfos))
   123  	for _, info := range fileInfos {
   124  		filenames = append(filenames, info.Name())
   125  	}
   126  	return filenames, nil
   127  }
   128  
   129  // Save a manifest as part of a local manifest list
   130  func (s *fsStore) Save(listRef reference.Reference, manifest reference.Reference, image types.ImageManifest) error {
   131  	if err := s.createManifestListDirectory(listRef.String()); err != nil {
   132  		return err
   133  	}
   134  	filename := manifestToFilename(s.root, listRef.String(), manifest.String())
   135  	bytes, err := json.Marshal(image)
   136  	if err != nil {
   137  		return err
   138  	}
   139  	return os.WriteFile(filename, bytes, 0o644)
   140  }
   141  
   142  func (s *fsStore) createManifestListDirectory(transaction string) error {
   143  	path := filepath.Join(s.root, makeFilesafeName(transaction))
   144  	return os.MkdirAll(path, 0o755)
   145  }
   146  
   147  func manifestToFilename(root, manifestList, manifest string) string {
   148  	return filepath.Join(root, makeFilesafeName(manifestList), makeFilesafeName(manifest))
   149  }
   150  
   151  func makeFilesafeName(ref string) string {
   152  	fileName := strings.ReplaceAll(ref, ":", "-")
   153  	return strings.ReplaceAll(fileName, "/", "_")
   154  }
   155  
   156  type notFoundError struct {
   157  	object string
   158  }
   159  
   160  func newNotFoundError(ref string) *notFoundError {
   161  	return &notFoundError{object: ref}
   162  }
   163  
   164  func (n *notFoundError) Error() string {
   165  	return fmt.Sprintf("No such manifest: %s", n.object)
   166  }
   167  
   168  // NotFound interface
   169  func (n *notFoundError) NotFound() {}
   170  
   171  // IsNotFound returns true if the error is a not found error
   172  func IsNotFound(err error) bool {
   173  	_, ok := err.(notFound)
   174  	return ok
   175  }
   176  
   177  type notFound interface {
   178  	NotFound()
   179  }