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 ¬FoundError{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 }