moby/layer/filestore.go
Sebastiaan van Stijn 0f7c9cd27e
Remove uses of deprecated go-digest.NewDigestFromHex, go-digest.Digest.Hex
Both of these were deprecated in 55f675811a,
but the format of the GoDoc comments didn't follow the correct format, which
caused them not being picked up by tools as "deprecated".

This patch updates uses in the codebase to use the alternatives.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-11-08 16:42:13 +01:00

432 lines
11 KiB
Go

package layer // import "github.com/docker/docker/layer"
import (
"compress/gzip"
"encoding/json"
"io"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"github.com/docker/distribution"
"github.com/docker/docker/pkg/ioutils"
"github.com/opencontainers/go-digest"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
var (
stringIDRegexp = regexp.MustCompile(`^[a-f0-9]{64}(-init)?$`)
supportedAlgorithms = []digest.Algorithm{
digest.SHA256,
// digest.SHA384, // Currently not used
// digest.SHA512, // Currently not used
}
)
type fileMetadataStore struct {
root string
}
type fileMetadataTransaction struct {
store *fileMetadataStore
ws *ioutils.AtomicWriteSet
}
// newFSMetadataStore returns an instance of a metadata store
// which is backed by files on disk using the provided root
// as the root of metadata files.
func newFSMetadataStore(root string) (*fileMetadataStore, error) {
if err := os.MkdirAll(root, 0o700); err != nil {
return nil, err
}
return &fileMetadataStore{
root: root,
}, nil
}
func (fms *fileMetadataStore) getLayerDirectory(layer ChainID) string {
dgst := digest.Digest(layer)
return filepath.Join(fms.root, string(dgst.Algorithm()), dgst.Encoded())
}
func (fms *fileMetadataStore) getLayerFilename(layer ChainID, filename string) string {
return filepath.Join(fms.getLayerDirectory(layer), filename)
}
func (fms *fileMetadataStore) getMountDirectory(mount string) string {
return filepath.Join(fms.root, "mounts", mount)
}
func (fms *fileMetadataStore) getMountFilename(mount, filename string) string {
return filepath.Join(fms.getMountDirectory(mount), filename)
}
func (fms *fileMetadataStore) StartTransaction() (*fileMetadataTransaction, error) {
tmpDir := filepath.Join(fms.root, "tmp")
if err := os.MkdirAll(tmpDir, 0o755); err != nil {
return nil, err
}
ws, err := ioutils.NewAtomicWriteSet(tmpDir)
if err != nil {
return nil, err
}
return &fileMetadataTransaction{
store: fms,
ws: ws,
}, nil
}
func (fm *fileMetadataTransaction) SetSize(size int64) error {
return fm.ws.WriteFile("size", []byte(strconv.FormatInt(size, 10)), 0o644)
}
func (fm *fileMetadataTransaction) SetParent(parent ChainID) error {
return fm.ws.WriteFile("parent", []byte(digest.Digest(parent).String()), 0o644)
}
func (fm *fileMetadataTransaction) SetDiffID(diff DiffID) error {
return fm.ws.WriteFile("diff", []byte(digest.Digest(diff).String()), 0o644)
}
func (fm *fileMetadataTransaction) SetCacheID(cacheID string) error {
return fm.ws.WriteFile("cache-id", []byte(cacheID), 0o644)
}
func (fm *fileMetadataTransaction) SetDescriptor(ref distribution.Descriptor) error {
jsonRef, err := json.Marshal(ref)
if err != nil {
return err
}
return fm.ws.WriteFile("descriptor.json", jsonRef, 0o644)
}
func (fm *fileMetadataTransaction) TarSplitWriter(compressInput bool) (io.WriteCloser, error) {
f, err := fm.ws.FileWriter("tar-split.json.gz", os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return nil, err
}
var wc io.WriteCloser
if compressInput {
wc = gzip.NewWriter(f)
} else {
wc = f
}
return ioutils.NewWriteCloserWrapper(wc, func() error {
wc.Close()
return f.Close()
}), nil
}
func (fm *fileMetadataTransaction) Commit(layer ChainID) error {
finalDir := fm.store.getLayerDirectory(layer)
if err := os.MkdirAll(filepath.Dir(finalDir), 0o755); err != nil {
return err
}
return fm.ws.Commit(finalDir)
}
func (fm *fileMetadataTransaction) Cancel() error {
return fm.ws.Cancel()
}
func (fm *fileMetadataTransaction) String() string {
return fm.ws.String()
}
func (fms *fileMetadataStore) GetSize(layer ChainID) (int64, error) {
content, err := os.ReadFile(fms.getLayerFilename(layer, "size"))
if err != nil {
return 0, err
}
size, err := strconv.ParseInt(string(content), 10, 64)
if err != nil {
return 0, err
}
return size, nil
}
func (fms *fileMetadataStore) GetParent(layer ChainID) (ChainID, error) {
content, err := os.ReadFile(fms.getLayerFilename(layer, "parent"))
if err != nil {
if os.IsNotExist(err) {
return "", nil
}
return "", err
}
dgst, err := digest.Parse(strings.TrimSpace(string(content)))
if err != nil {
return "", err
}
return ChainID(dgst), nil
}
func (fms *fileMetadataStore) GetDiffID(layer ChainID) (DiffID, error) {
content, err := os.ReadFile(fms.getLayerFilename(layer, "diff"))
if err != nil {
return "", err
}
dgst, err := digest.Parse(strings.TrimSpace(string(content)))
if err != nil {
return "", err
}
return DiffID(dgst), nil
}
func (fms *fileMetadataStore) GetCacheID(layer ChainID) (string, error) {
contentBytes, err := os.ReadFile(fms.getLayerFilename(layer, "cache-id"))
if err != nil {
return "", err
}
content := strings.TrimSpace(string(contentBytes))
if content == "" {
return "", errors.Errorf("invalid cache id value")
}
return content, nil
}
func (fms *fileMetadataStore) GetDescriptor(layer ChainID) (distribution.Descriptor, error) {
content, err := os.ReadFile(fms.getLayerFilename(layer, "descriptor.json"))
if err != nil {
if os.IsNotExist(err) {
// only return empty descriptor to represent what is stored
return distribution.Descriptor{}, nil
}
return distribution.Descriptor{}, err
}
var ref distribution.Descriptor
err = json.Unmarshal(content, &ref)
if err != nil {
return distribution.Descriptor{}, err
}
return ref, err
}
func (fms *fileMetadataStore) TarSplitReader(layer ChainID) (io.ReadCloser, error) {
fz, err := os.Open(fms.getLayerFilename(layer, "tar-split.json.gz"))
if err != nil {
return nil, err
}
f, err := gzip.NewReader(fz)
if err != nil {
fz.Close()
return nil, err
}
return ioutils.NewReadCloserWrapper(f, func() error {
f.Close()
return fz.Close()
}), nil
}
func (fms *fileMetadataStore) SetMountID(mount string, mountID string) error {
if err := os.MkdirAll(fms.getMountDirectory(mount), 0o755); err != nil {
return err
}
return os.WriteFile(fms.getMountFilename(mount, "mount-id"), []byte(mountID), 0o644)
}
func (fms *fileMetadataStore) SetInitID(mount string, init string) error {
if err := os.MkdirAll(fms.getMountDirectory(mount), 0o755); err != nil {
return err
}
return os.WriteFile(fms.getMountFilename(mount, "init-id"), []byte(init), 0o644)
}
func (fms *fileMetadataStore) SetMountParent(mount string, parent ChainID) error {
if err := os.MkdirAll(fms.getMountDirectory(mount), 0o755); err != nil {
return err
}
return os.WriteFile(fms.getMountFilename(mount, "parent"), []byte(digest.Digest(parent).String()), 0o644)
}
func (fms *fileMetadataStore) GetMountID(mount string) (string, error) {
contentBytes, err := os.ReadFile(fms.getMountFilename(mount, "mount-id"))
if err != nil {
return "", err
}
content := strings.TrimSpace(string(contentBytes))
if !stringIDRegexp.MatchString(content) {
return "", errors.New("invalid mount id value")
}
return content, nil
}
func (fms *fileMetadataStore) GetInitID(mount string) (string, error) {
contentBytes, err := os.ReadFile(fms.getMountFilename(mount, "init-id"))
if err != nil {
if os.IsNotExist(err) {
return "", nil
}
return "", err
}
content := strings.TrimSpace(string(contentBytes))
if !stringIDRegexp.MatchString(content) {
return "", errors.New("invalid init id value")
}
return content, nil
}
func (fms *fileMetadataStore) GetMountParent(mount string) (ChainID, error) {
content, err := os.ReadFile(fms.getMountFilename(mount, "parent"))
if err != nil {
if os.IsNotExist(err) {
return "", nil
}
return "", err
}
dgst, err := digest.Parse(strings.TrimSpace(string(content)))
if err != nil {
return "", err
}
return ChainID(dgst), nil
}
func (fms *fileMetadataStore) getOrphan() ([]roLayer, error) {
var orphanLayers []roLayer
for _, algorithm := range supportedAlgorithms {
fileInfos, err := os.ReadDir(filepath.Join(fms.root, string(algorithm)))
if err != nil {
if os.IsNotExist(err) {
continue
}
return nil, err
}
for _, fi := range fileInfos {
if !fi.IsDir() || !strings.HasSuffix(fi.Name(), "-removing") {
continue
}
// At this stage, fi.Name value looks like <digest>-<random>-removing
// Split on '-' to get the digest value.
nameSplit := strings.Split(fi.Name(), "-")
dgst := digest.NewDigestFromEncoded(algorithm, nameSplit[0])
if err := dgst.Validate(); err != nil {
logrus.WithError(err).WithField("digest", string(algorithm)+":"+nameSplit[0]).Debug("ignoring invalid digest")
continue
}
chainFile := filepath.Join(fms.root, string(algorithm), fi.Name(), "cache-id")
contentBytes, err := os.ReadFile(chainFile)
if err != nil {
if !os.IsNotExist(err) {
logrus.WithError(err).WithField("digest", dgst).Error("failed to read cache ID")
}
continue
}
cacheID := strings.TrimSpace(string(contentBytes))
if cacheID == "" {
logrus.Error("invalid cache ID")
continue
}
l := &roLayer{
chainID: ChainID(dgst),
cacheID: cacheID,
}
orphanLayers = append(orphanLayers, *l)
}
}
return orphanLayers, nil
}
func (fms *fileMetadataStore) List() ([]ChainID, []string, error) {
var ids []ChainID
for _, algorithm := range supportedAlgorithms {
fileInfos, err := os.ReadDir(filepath.Join(fms.root, string(algorithm)))
if err != nil {
if os.IsNotExist(err) {
continue
}
return nil, nil, err
}
for _, fi := range fileInfos {
if fi.IsDir() && fi.Name() != "mounts" {
dgst := digest.NewDigestFromEncoded(algorithm, fi.Name())
if err := dgst.Validate(); err != nil {
logrus.Debugf("Ignoring invalid digest %s:%s", algorithm, fi.Name())
} else {
ids = append(ids, ChainID(dgst))
}
}
}
}
fileInfos, err := os.ReadDir(filepath.Join(fms.root, "mounts"))
if err != nil {
if os.IsNotExist(err) {
return ids, []string{}, nil
}
return nil, nil, err
}
var mounts []string
for _, fi := range fileInfos {
if fi.IsDir() {
mounts = append(mounts, fi.Name())
}
}
return ids, mounts, nil
}
// Remove layerdb folder if that is marked for removal
func (fms *fileMetadataStore) Remove(layer ChainID, cache string) error {
dgst := digest.Digest(layer)
files, err := os.ReadDir(filepath.Join(fms.root, string(dgst.Algorithm())))
if err != nil {
return err
}
for _, f := range files {
if !strings.HasSuffix(f.Name(), "-removing") || !strings.HasPrefix(f.Name(), dgst.Encoded()) {
continue
}
// Make sure that we only remove layerdb folder which points to
// requested cacheID
dir := filepath.Join(fms.root, string(dgst.Algorithm()), f.Name())
chainFile := filepath.Join(dir, "cache-id")
contentBytes, err := os.ReadFile(chainFile)
if err != nil {
logrus.WithError(err).WithField("file", chainFile).Error("cannot get cache ID")
continue
}
cacheID := strings.TrimSpace(string(contentBytes))
if cacheID != cache {
continue
}
logrus.Debugf("Removing folder: %s", dir)
err = os.RemoveAll(dir)
if err != nil && !os.IsNotExist(err) {
logrus.WithError(err).WithField("name", f.Name()).Error("cannot remove layer")
continue
}
}
return nil
}
func (fms *fileMetadataStore) RemoveMount(mount string) error {
return os.RemoveAll(fms.getMountDirectory(mount))
}