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