2017-06-15 14:41:54 -04:00
|
|
|
package store
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
|
2023-08-30 18:36:58 -04:00
|
|
|
"github.com/distribution/reference"
|
2017-06-15 14:41:54 -04:00
|
|
|
"github.com/docker/cli/cli/manifest/types"
|
2018-06-28 20:41:47 -04:00
|
|
|
"github.com/docker/distribution/manifest/manifestlist"
|
2022-03-04 08:43:34 -05:00
|
|
|
"github.com/opencontainers/go-digest"
|
2018-06-28 20:41:47 -04:00
|
|
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
|
|
"github.com/pkg/errors"
|
2017-06-15 14:41:54 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
// Store manages local storage of image distribution manifests
|
|
|
|
type Store interface {
|
|
|
|
Remove(listRef reference.Reference) error
|
|
|
|
Get(listRef reference.Reference, manifest reference.Reference) (types.ImageManifest, error)
|
|
|
|
GetList(listRef reference.Reference) ([]types.ImageManifest, error)
|
|
|
|
Save(listRef reference.Reference, manifest reference.Reference, image types.ImageManifest) error
|
|
|
|
}
|
|
|
|
|
|
|
|
// fsStore manages manifest files stored on the local filesystem
|
|
|
|
type fsStore struct {
|
|
|
|
root string
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewStore returns a new store for a local file path
|
|
|
|
func NewStore(root string) Store {
|
|
|
|
return &fsStore{root: root}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove a manifest list from local storage
|
|
|
|
func (s *fsStore) Remove(listRef reference.Reference) error {
|
|
|
|
path := filepath.Join(s.root, makeFilesafeName(listRef.String()))
|
|
|
|
return os.RemoveAll(path)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get returns the local manifest
|
|
|
|
func (s *fsStore) Get(listRef reference.Reference, manifest reference.Reference) (types.ImageManifest, error) {
|
|
|
|
filename := manifestToFilename(s.root, listRef.String(), manifest.String())
|
|
|
|
return s.getFromFilename(manifest, filename)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *fsStore) getFromFilename(ref reference.Reference, filename string) (types.ImageManifest, error) {
|
2022-02-25 09:29:30 -05:00
|
|
|
bytes, err := os.ReadFile(filename)
|
2017-06-15 14:41:54 -04:00
|
|
|
switch {
|
|
|
|
case os.IsNotExist(err):
|
|
|
|
return types.ImageManifest{}, newNotFoundError(ref.String())
|
|
|
|
case err != nil:
|
|
|
|
return types.ImageManifest{}, err
|
|
|
|
}
|
2018-06-28 20:41:47 -04:00
|
|
|
var manifestInfo struct {
|
|
|
|
types.ImageManifest
|
|
|
|
|
|
|
|
// Deprecated Fields, replaced by Descriptor
|
|
|
|
Digest digest.Digest
|
|
|
|
Platform *manifestlist.PlatformSpec
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := json.Unmarshal(bytes, &manifestInfo); err != nil {
|
|
|
|
return types.ImageManifest{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Compatibility with image manifests created before
|
|
|
|
// descriptor, newer versions omit Digest and Platform
|
|
|
|
if manifestInfo.Digest != "" {
|
|
|
|
mediaType, raw, err := manifestInfo.Payload()
|
|
|
|
if err != nil {
|
|
|
|
return types.ImageManifest{}, err
|
|
|
|
}
|
|
|
|
if dgst := digest.FromBytes(raw); dgst != manifestInfo.Digest {
|
|
|
|
return types.ImageManifest{}, errors.Errorf("invalid manifest file %v: image manifest digest mismatch (%v != %v)", filename, manifestInfo.Digest, dgst)
|
|
|
|
}
|
|
|
|
manifestInfo.ImageManifest.Descriptor = ocispec.Descriptor{
|
|
|
|
Digest: manifestInfo.Digest,
|
|
|
|
Size: int64(len(raw)),
|
|
|
|
MediaType: mediaType,
|
|
|
|
Platform: types.OCIPlatform(manifestInfo.Platform),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return manifestInfo.ImageManifest, nil
|
2017-06-15 14:41:54 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetList returns all the local manifests for a transaction
|
|
|
|
func (s *fsStore) GetList(listRef reference.Reference) ([]types.ImageManifest, error) {
|
|
|
|
filenames, err := s.listManifests(listRef.String())
|
|
|
|
switch {
|
|
|
|
case err != nil:
|
|
|
|
return nil, err
|
|
|
|
case filenames == nil:
|
|
|
|
return nil, newNotFoundError(listRef.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
manifests := []types.ImageManifest{}
|
|
|
|
for _, filename := range filenames {
|
|
|
|
filename = filepath.Join(s.root, makeFilesafeName(listRef.String()), filename)
|
|
|
|
manifest, err := s.getFromFilename(listRef, filename)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
manifests = append(manifests, manifest)
|
|
|
|
}
|
|
|
|
return manifests, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// listManifests stored in a transaction
|
|
|
|
func (s *fsStore) listManifests(transaction string) ([]string, error) {
|
|
|
|
transactionDir := filepath.Join(s.root, makeFilesafeName(transaction))
|
2022-02-25 09:29:30 -05:00
|
|
|
fileInfos, err := os.ReadDir(transactionDir)
|
2017-06-15 14:41:54 -04:00
|
|
|
switch {
|
|
|
|
case os.IsNotExist(err):
|
|
|
|
return nil, nil
|
|
|
|
case err != nil:
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2022-02-25 09:29:30 -05:00
|
|
|
filenames := make([]string, 0, len(fileInfos))
|
2017-06-15 14:41:54 -04:00
|
|
|
for _, info := range fileInfos {
|
|
|
|
filenames = append(filenames, info.Name())
|
|
|
|
}
|
|
|
|
return filenames, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Save a manifest as part of a local manifest list
|
|
|
|
func (s *fsStore) Save(listRef reference.Reference, manifest reference.Reference, image types.ImageManifest) error {
|
|
|
|
if err := s.createManifestListDirectory(listRef.String()); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
filename := manifestToFilename(s.root, listRef.String(), manifest.String())
|
|
|
|
bytes, err := json.Marshal(image)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-09-30 13:13:22 -04:00
|
|
|
return os.WriteFile(filename, bytes, 0o644)
|
2017-06-15 14:41:54 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *fsStore) createManifestListDirectory(transaction string) error {
|
|
|
|
path := filepath.Join(s.root, makeFilesafeName(transaction))
|
2022-09-30 13:13:22 -04:00
|
|
|
return os.MkdirAll(path, 0o755)
|
2017-06-15 14:41:54 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func manifestToFilename(root, manifestList, manifest string) string {
|
|
|
|
return filepath.Join(root, makeFilesafeName(manifestList), makeFilesafeName(manifest))
|
|
|
|
}
|
|
|
|
|
|
|
|
func makeFilesafeName(ref string) string {
|
linting: address assorted issues found by gocritic
internal/test/builders/config.go:36:15: captLocal: `ID' should not be capitalized (gocritic)
func ConfigID(ID string) func(config *swarm.Config) {
^
internal/test/builders/secret.go:45:15: captLocal: `ID' should not be capitalized (gocritic)
func SecretID(ID string) func(secret *swarm.Secret) {
^
internal/test/builders/service.go:21:16: captLocal: `ID' should not be capitalized (gocritic)
func ServiceID(ID string) func(*swarm.Service) {
^
cli/command/image/formatter_history.go:100:15: wrapperFunc: use strings.ReplaceAll method in `strings.Replace(c.h.CreatedBy, "\t", " ", -1)` (gocritic)
createdBy := strings.Replace(c.h.CreatedBy, "\t", " ", -1)
^
e2e/image/push_test.go:246:34: badCall: suspicious Join on 1 argument (gocritic)
assert.NilError(t, os.RemoveAll(filepath.Join(dir.Join("trust"))))
^
e2e/image/push_test.go:313:34: badCall: suspicious Join on 1 argument (gocritic)
assert.NilError(t, os.RemoveAll(filepath.Join(dir.Join("trust"))))
^
cli/config/configfile/file_test.go:185:2: assignOp: replace `c.GetAllCallCount = c.GetAllCallCount + 1` with `c.GetAllCallCount++` (gocritic)
c.GetAllCallCount = c.GetAllCallCount + 1
^
cli/command/context/inspect_test.go:20:58: wrapperFunc: use strings.ReplaceAll method in `strings.Replace(si.MetadataPath, `\`, `\\`, -1)` (gocritic)
expected = strings.Replace(expected, "<METADATA_PATH>", strings.Replace(si.MetadataPath, `\`, `\\`, -1), 1)
^
cli/command/context/inspect_test.go:21:53: wrapperFunc: use strings.ReplaceAll method in `strings.Replace(si.TLSPath, `\`, `\\`, -1)` (gocritic)
expected = strings.Replace(expected, "<TLS_PATH>", strings.Replace(si.TLSPath, `\`, `\\`, -1), 1)
^
cli/command/container/formatter_stats.go:119:46: captLocal: `Stats' should not be capitalized (gocritic)
func statsFormatWrite(ctx formatter.Context, Stats []StatsEntry, osType string, trunc bool) error {
^
cli/command/container/stats_helpers.go:209:4: assignOp: replace `blkRead = blkRead + bioEntry.Value` with `blkRead += bioEntry.Value` (gocritic)
blkRead = blkRead + bioEntry.Value
^
cli/command/container/stats_helpers.go:211:4: assignOp: replace `blkWrite = blkWrite + bioEntry.Value` with `blkWrite += bioEntry.Value` (gocritic)
blkWrite = blkWrite + bioEntry.Value
^
cli/command/registry/formatter_search.go:67:10: wrapperFunc: use strings.ReplaceAll method in `strings.Replace(c.s.Description, "\n", " ", -1)` (gocritic)
desc := strings.Replace(c.s.Description, "\n", " ", -1)
^
cli/command/registry/formatter_search.go:68:9: wrapperFunc: use strings.ReplaceAll method in `strings.Replace(desc, "\r", " ", -1)` (gocritic)
desc = strings.Replace(desc, "\r", " ", -1)
^
cli/command/service/list_test.go:164:5: assignOp: replace `tc.doc = tc.doc + " with quiet"` with `tc.doc += " with quiet"` (gocritic)
tc.doc = tc.doc + " with quiet"
^
cli/command/service/progress/progress.go:274:11: wrapperFunc: use strings.ReplaceAll method in `strings.Replace(errMsg, "\n", " ", -1)` (gocritic)
errMsg = strings.Replace(errMsg, "\n", " ", -1)
^
cli/manifest/store/store.go:153:9: wrapperFunc: use strings.ReplaceAll method in `strings.Replace(fileName, "/", "_", -1)` (gocritic)
return strings.Replace(fileName, "/", "_", -1)
^
cli/manifest/store/store.go:152:14: wrapperFunc: use strings.ReplaceAll method in `strings.Replace(ref, ":", "-", -1)` (gocritic)
fileName := strings.Replace(ref, ":", "-", -1)
^
cli/command/plugin/formatter.go:79:10: wrapperFunc: use strings.ReplaceAll method in `strings.Replace(c.p.Config.Description, "\n", "", -1)` (gocritic)
desc := strings.Replace(c.p.Config.Description, "\n", "", -1)
^
cli/command/plugin/formatter.go:80:9: wrapperFunc: use strings.ReplaceAll method in `strings.Replace(desc, "\r", "", -1)` (gocritic)
desc = strings.Replace(desc, "\r", "", -1)
^
cli/compose/convert/service.go:642:23: captLocal: `DNS' should not be capitalized (gocritic)
func convertDNSConfig(DNS []string, DNSSearch []string) *swarm.DNSConfig {
^
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-11-20 07:30:05 -05:00
|
|
|
fileName := strings.ReplaceAll(ref, ":", "-")
|
|
|
|
return strings.ReplaceAll(fileName, "/", "_")
|
2017-06-15 14:41:54 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
type notFoundError struct {
|
|
|
|
object string
|
|
|
|
}
|
|
|
|
|
|
|
|
func newNotFoundError(ref string) *notFoundError {
|
|
|
|
return ¬FoundError{object: ref}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *notFoundError) Error() string {
|
|
|
|
return fmt.Sprintf("No such manifest: %s", n.object)
|
|
|
|
}
|
|
|
|
|
|
|
|
// NotFound interface
|
|
|
|
func (n *notFoundError) NotFound() {}
|
|
|
|
|
|
|
|
// IsNotFound returns true if the error is a not found error
|
|
|
|
func IsNotFound(err error) bool {
|
|
|
|
_, ok := err.(notFound)
|
|
|
|
return ok
|
|
|
|
}
|
|
|
|
|
|
|
|
type notFound interface {
|
|
|
|
NotFound()
|
|
|
|
}
|