DockerCLI/cli/manifest/store/store.go

179 lines
5.1 KiB
Go
Raw Normal View History

package store
import (
"encoding/json"
"os"
"path/filepath"
"strings"
"github.com/distribution/reference"
"github.com/docker/cli/cli/manifest/types"
"github.com/docker/distribution/manifest/manifestlist"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
)
// 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) {
bytes, err := os.ReadFile(filename)
switch {
case os.IsNotExist(err):
return types.ImageManifest{}, newNotFoundError(ref.String())
case err != nil:
return types.ImageManifest{}, err
}
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
}
// 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))
fileInfos, err := os.ReadDir(transactionDir)
switch {
case os.IsNotExist(err):
return nil, nil
case err != nil:
return nil, err
}
filenames := make([]string, 0, len(fileInfos))
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
}
return os.WriteFile(filename, bytes, 0o644)
}
func (s *fsStore) createManifestListDirectory(transaction string) error {
path := filepath.Join(s.root, makeFilesafeName(transaction))
return os.MkdirAll(path, 0o755)
}
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, "/", "_")
}
type notFoundError struct {
object string
}
func newNotFoundError(ref string) *notFoundError {
return &notFoundError{object: ref}
}
func (n *notFoundError) Error() string {
linting: fmt.Sprintf can be replaced with string concatenation (perfsprint) cli/registry/client/endpoint.go:128:34: fmt.Sprintf can be replaced with string concatenation (perfsprint) req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", th.token)) ^ cli/command/telemetry_docker.go:88:14: fmt.Sprintf can be replaced with string concatenation (perfsprint) endpoint = fmt.Sprintf("unix://%s", path.Join(u.Host, u.Path)) ^ cli/command/cli_test.go:195:47: fmt.Sprintf can be replaced with string concatenation (perfsprint) opts := &flags.ClientOptions{Hosts: []string{fmt.Sprintf("unix://%s", socket)}} ^ cli/command/registry_test.go:59:24: fmt.Sprintf can be replaced with string concatenation (perfsprint) inputServerAddress: fmt.Sprintf("https://%s", testAuthConfigs[1].ServerAddress), ^ cli/command/container/opts_test.go:338:35: fmt.Sprintf can be replaced with string concatenation (perfsprint) if config, _, _ := mustParse(t, fmt.Sprintf("--hostname=%s", hostname)); config.Hostname != expectedHostname { ^ cli/command/context/options.go:79:24: fmt.Sprintf can be replaced with string concatenation (perfsprint) errs = append(errs, fmt.Sprintf("%s: unrecognized config key", k)) ^ cli/command/image/build.go:461:68: fmt.Sprintf can be replaced with string concatenation (perfsprint) line = dockerfileFromLinePattern.ReplaceAllLiteralString(line, fmt.Sprintf("FROM %s", reference.FamiliarString(trustedRef))) ^ cli/command/image/remove_test.go:21:9: fmt.Sprintf can be replaced with string concatenation (perfsprint) return fmt.Sprintf("Error: No such image: %s", n.imageID) ^ cli/command/image/build/context.go:229:102: fmt.Sprintf can be replaced with string concatenation (perfsprint) progReader := progress.NewProgressReader(response.Body, progressOutput, response.ContentLength, "", fmt.Sprintf("Downloading build context from remote url: %s", remoteURL)) ^ cli/command/service/logs.go:215:16: fmt.Sprintf can be replaced with string concatenation (perfsprint) taskName += fmt.Sprintf(".%s", task.ID) ^ cli/command/service/logs.go:217:16: fmt.Sprintf can be replaced with string concatenation (perfsprint) taskName += fmt.Sprintf(".%s", stringid.TruncateID(task.ID)) ^ cli/command/service/progress/progress_test.go:877:18: fmt.Sprintf can be replaced with string concatenation (perfsprint) ID: fmt.Sprintf("task%s", nodeID), ^ cli/command/stack/swarm/remove.go:61:24: fmt.Sprintf can be replaced with string concatenation (perfsprint) errs = append(errs, fmt.Sprintf("Failed to remove some resources from stack: %s", namespace)) ^ cli/command/swarm/ipnet_slice_test.go:32:9: fmt.Sprintf can be replaced with string concatenation (perfsprint) arg := fmt.Sprintf("--cidrs=%s", strings.Join(vals, ",")) ^ cli/command/swarm/ipnet_slice_test.go:137:30: fmt.Sprintf can be replaced with string concatenation (perfsprint) if err := f.Parse([]string{fmt.Sprintf("--cidrs=%s", strings.Join(test.FlagArg, ","))}); err != nil { ^ cli/compose/schema/schema.go:105:11: fmt.Sprintf can be replaced with string concatenation (perfsprint) return fmt.Sprintf("must be a %s", humanReadableType(expectedType)) ^ cli/manifest/store/store.go:165:9: fmt.Sprintf can be replaced with string concatenation (perfsprint) return fmt.Sprintf("No such manifest: %s", n.object) ^ e2e/image/push_test.go:340:4: fmt.Sprintf can be replaced with string concatenation (perfsprint) fmt.Sprintf("NOTARY_ROOT_PASSPHRASE=%s", pwd), ^ e2e/image/push_test.go:341:4: fmt.Sprintf can be replaced with string concatenation (perfsprint) fmt.Sprintf("NOTARY_TARGETS_PASSPHRASE=%s", pwd), ^ e2e/image/push_test.go:342:4: fmt.Sprintf can be replaced with string concatenation (perfsprint) fmt.Sprintf("NOTARY_SNAPSHOT_PASSPHRASE=%s", pwd), ^ e2e/image/push_test.go:343:4: fmt.Sprintf can be replaced with string concatenation (perfsprint) fmt.Sprintf("NOTARY_DELEGATION_PASSPHRASE=%s", pwd), ^ e2e/plugin/trust_test.go:23:16: fmt.Sprintf can be replaced with string concatenation (perfsprint) pluginName := fmt.Sprintf("%s/plugin-content-trust", registryPrefix) ^ e2e/plugin/trust_test.go:53:8: fmt.Sprintf can be replaced with string concatenation (perfsprint) Out: fmt.Sprintf("Installed plugin %s", pluginName), ^ e2e/trust/revoke_test.go:62:57: fmt.Sprintf can be replaced with string concatenation (perfsprint) icmd.RunCommand("docker", "tag", fixtures.AlpineImage, fmt.Sprintf("%s:v1", revokeRepo)).Assert(t, icmd.Success) ^ e2e/trust/revoke_test.go:64:49: fmt.Sprintf can be replaced with string concatenation (perfsprint) icmd.Command("docker", "-D", "trust", "sign", fmt.Sprintf("%s:v1", revokeRepo)), ^ e2e/trust/revoke_test.go:68:58: fmt.Sprintf can be replaced with string concatenation (perfsprint) icmd.RunCommand("docker", "tag", fixtures.BusyboxImage, fmt.Sprintf("%s:v2", revokeRepo)).Assert(t, icmd.Success) ^ e2e/trust/revoke_test.go:70:49: fmt.Sprintf can be replaced with string concatenation (perfsprint) icmd.Command("docker", "-D", "trust", "sign", fmt.Sprintf("%s:v2", revokeRepo)), ^ e2e/trust/sign_test.go:36:47: fmt.Sprintf can be replaced with string concatenation (perfsprint) assert.Check(t, is.Contains(result.Stdout(), fmt.Sprintf("v1: digest: sha256:%s", fixtures.AlpineSha))) ^ e2e/trust/sign_test.go:53:47: fmt.Sprintf can be replaced with string concatenation (perfsprint) assert.Check(t, is.Contains(result.Stdout(), fmt.Sprintf("v1: digest: sha256:%s", fixtures.BusyboxSha))) ^ e2e/trust/sign_test.go:65:47: fmt.Sprintf can be replaced with string concatenation (perfsprint) assert.Check(t, is.Contains(result.Stdout(), fmt.Sprintf("v1: digest: sha256:%s", fixtures.AlpineSha))) ^ opts/file.go:21:9: fmt.Sprintf can be replaced with string concatenation (perfsprint) return fmt.Sprintf("poorly formatted environment: %s", e.msg) ^ opts/hosts_test.go:26:31: fmt.Sprintf can be replaced with string concatenation (perfsprint) "tcp://host:": fmt.Sprintf("tcp://host:%s", defaultHTTPPort), ^ Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-06-10 15:07:37 -04:00
return "No such manifest: " + 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()
}