01ba0a93 |
package image
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sync"
|
ea3cbd32 |
"github.com/docker/docker/pkg/ioutils" |
7a855799 |
"github.com/opencontainers/go-digest" |
7abeb08a |
"github.com/pkg/errors" |
1009e6a4 |
"github.com/sirupsen/logrus" |
01ba0a93 |
)
|
80522398 |
// DigestWalkFunc is function called by StoreBackend.Walk
type DigestWalkFunc func(id digest.Digest) error |
01ba0a93 |
// StoreBackend provides interface for image.Store persistence
type StoreBackend interface { |
80522398 |
Walk(f DigestWalkFunc) error
Get(id digest.Digest) ([]byte, error)
Set(data []byte) (digest.Digest, error)
Delete(id digest.Digest) error
SetMetadata(id digest.Digest, key string, data []byte) error
GetMetadata(id digest.Digest, key string) ([]byte, error)
DeleteMetadata(id digest.Digest, key string) error |
01ba0a93 |
}
// fs implements StoreBackend using the filesystem.
type fs struct {
sync.RWMutex
root string
}
const (
contentDirName = "content"
metadataDirName = "metadata"
)
// NewFSStoreBackend returns new filesystem based backend for image.Store
func NewFSStoreBackend(root string) (StoreBackend, error) {
return newFSStore(root)
}
func newFSStore(root string) (*fs, error) {
s := &fs{
root: root,
}
if err := os.MkdirAll(filepath.Join(root, contentDirName, string(digest.Canonical)), 0700); err != nil { |
7abeb08a |
return nil, errors.Wrap(err, "failed to create storage backend") |
01ba0a93 |
}
if err := os.MkdirAll(filepath.Join(root, metadataDirName, string(digest.Canonical)), 0700); err != nil { |
7abeb08a |
return nil, errors.Wrap(err, "failed to create storage backend") |
01ba0a93 |
}
return s, nil
}
|
80522398 |
func (s *fs) contentFile(dgst digest.Digest) string { |
01ba0a93 |
return filepath.Join(s.root, contentDirName, string(dgst.Algorithm()), dgst.Hex())
}
|
80522398 |
func (s *fs) metadataDir(dgst digest.Digest) string { |
01ba0a93 |
return filepath.Join(s.root, metadataDirName, string(dgst.Algorithm()), dgst.Hex())
}
// Walk calls the supplied callback for each image ID in the storage backend. |
80522398 |
func (s *fs) Walk(f DigestWalkFunc) error { |
01ba0a93 |
// Only Canonical digest (sha256) is currently supported
s.RLock()
dir, err := ioutil.ReadDir(filepath.Join(s.root, contentDirName, string(digest.Canonical)))
s.RUnlock()
if err != nil {
return err
}
for _, v := range dir {
dgst := digest.NewDigestFromHex(string(digest.Canonical), v.Name())
if err := dgst.Validate(); err != nil { |
b2ec509a |
logrus.Debugf("skipping invalid digest %s: %s", dgst, err) |
01ba0a93 |
continue
} |
80522398 |
if err := f(dgst); err != nil { |
01ba0a93 |
return err
}
}
return nil
}
|
80522398 |
// Get returns the content stored under a given digest.
func (s *fs) Get(dgst digest.Digest) ([]byte, error) { |
01ba0a93 |
s.RLock()
defer s.RUnlock()
|
80522398 |
return s.get(dgst) |
01ba0a93 |
}
|
80522398 |
func (s *fs) get(dgst digest.Digest) ([]byte, error) {
content, err := ioutil.ReadFile(s.contentFile(dgst)) |
01ba0a93 |
if err != nil { |
7abeb08a |
return nil, errors.Wrapf(err, "failed to get digest %s", dgst) |
01ba0a93 |
}
// todo: maybe optional |
80522398 |
if digest.FromBytes(content) != dgst {
return nil, fmt.Errorf("failed to verify: %v", dgst) |
01ba0a93 |
}
return content, nil
}
|
80522398 |
// Set stores content by checksum.
func (s *fs) Set(data []byte) (digest.Digest, error) { |
01ba0a93 |
s.Lock()
defer s.Unlock()
if len(data) == 0 { |
b2ec509a |
return "", fmt.Errorf("invalid empty data") |
01ba0a93 |
}
|
80522398 |
dgst := digest.FromBytes(data)
if err := ioutils.AtomicWriteFile(s.contentFile(dgst), data, 0600); err != nil { |
7abeb08a |
return "", errors.Wrap(err, "failed to write digest data") |
01ba0a93 |
}
|
80522398 |
return dgst, nil |
01ba0a93 |
}
|
80522398 |
// Delete removes content and metadata files associated with the digest.
func (s *fs) Delete(dgst digest.Digest) error { |
01ba0a93 |
s.Lock()
defer s.Unlock()
|
80522398 |
if err := os.RemoveAll(s.metadataDir(dgst)); err != nil { |
01ba0a93 |
return err
} |
b4a63139 |
return os.Remove(s.contentFile(dgst)) |
01ba0a93 |
}
// SetMetadata sets metadata for a given ID. It fails if there's no base file. |
80522398 |
func (s *fs) SetMetadata(dgst digest.Digest, key string, data []byte) error { |
01ba0a93 |
s.Lock()
defer s.Unlock() |
80522398 |
if _, err := s.get(dgst); err != nil { |
01ba0a93 |
return err
}
|
80522398 |
baseDir := filepath.Join(s.metadataDir(dgst)) |
01ba0a93 |
if err := os.MkdirAll(baseDir, 0700); err != nil {
return err
} |
80522398 |
return ioutils.AtomicWriteFile(filepath.Join(s.metadataDir(dgst), key), data, 0600) |
01ba0a93 |
}
|
80522398 |
// GetMetadata returns metadata for a given digest.
func (s *fs) GetMetadata(dgst digest.Digest, key string) ([]byte, error) { |
01ba0a93 |
s.RLock()
defer s.RUnlock()
|
80522398 |
if _, err := s.get(dgst); err != nil { |
01ba0a93 |
return nil, err
} |
7abeb08a |
bytes, err := ioutil.ReadFile(filepath.Join(s.metadataDir(dgst), key))
if err != nil {
return nil, errors.Wrap(err, "failed to read metadata")
}
return bytes, nil |
01ba0a93 |
}
|
80522398 |
// DeleteMetadata removes the metadata associated with a digest.
func (s *fs) DeleteMetadata(dgst digest.Digest, key string) error { |
01ba0a93 |
s.Lock()
defer s.Unlock()
|
80522398 |
return os.RemoveAll(filepath.Join(s.metadataDir(dgst), key)) |
01ba0a93 |
} |