File: blobcache.go

package info (click to toggle)
golang-github-containers-image 5.28.0-4
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 5,104 kB
  • sloc: sh: 194; makefile: 73
file content (148 lines) | stat: -rw-r--r-- 4,420 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package blobcache

import (
	"context"
	"fmt"
	"os"
	"path/filepath"

	"github.com/containers/image/v5/docker/reference"
	"github.com/containers/image/v5/internal/image"
	"github.com/containers/image/v5/transports"
	"github.com/containers/image/v5/types"
	digest "github.com/opencontainers/go-digest"
)

const (
	compressedNote   = ".compressed"
	decompressedNote = ".decompressed"
)

// BlobCache is an object which saves copies of blobs that are written to it while passing them
// through to some real destination, and which can be queried directly in order to read them
// back.
//
// Implements types.ImageReference.
type BlobCache struct {
	reference types.ImageReference
	// WARNING: The contents of this directory may be accessed concurrently,
	// both within this process and by multiple different processes
	directory string
	compress  types.LayerCompression
}

// NewBlobCache creates a new blob cache that wraps an image reference.  Any blobs which are
// written to the destination image created from the resulting reference will also be stored
// as-is to the specified directory or a temporary directory.
// The compress argument controls whether or not the cache will try to substitute a compressed
// or different version of a blob when preparing the list of layers when reading an image.
func NewBlobCache(ref types.ImageReference, directory string, compress types.LayerCompression) (*BlobCache, error) {
	if directory == "" {
		return nil, fmt.Errorf("error creating cache around reference %q: no directory specified", transports.ImageName(ref))
	}
	switch compress {
	case types.Compress, types.Decompress, types.PreserveOriginal:
		// valid value, accept it
	default:
		return nil, fmt.Errorf("unhandled LayerCompression value %v", compress)
	}
	return &BlobCache{
		reference: ref,
		directory: directory,
		compress:  compress,
	}, nil
}

func (b *BlobCache) Transport() types.ImageTransport {
	return b.reference.Transport()
}

func (b *BlobCache) StringWithinTransport() string {
	return b.reference.StringWithinTransport()
}

func (b *BlobCache) DockerReference() reference.Named {
	return b.reference.DockerReference()
}

func (b *BlobCache) PolicyConfigurationIdentity() string {
	return b.reference.PolicyConfigurationIdentity()
}

func (b *BlobCache) PolicyConfigurationNamespaces() []string {
	return b.reference.PolicyConfigurationNamespaces()
}

func (b *BlobCache) DeleteImage(ctx context.Context, sys *types.SystemContext) error {
	return b.reference.DeleteImage(ctx, sys)
}

// blobPath returns the path appropriate for storing a blob with digest.
func (b *BlobCache) blobPath(digest digest.Digest, isConfig bool) string {
	baseName := digest.String()
	if isConfig {
		baseName += ".config"
	}
	return filepath.Join(b.directory, baseName)
}

// findBlob checks if we have a blob for info in cache (whether a config or not)
// and if so, returns it path and size, and whether it was stored as a config.
// It returns ("", -1, nil) if the blob is not
func (b *BlobCache) findBlob(info types.BlobInfo) (string, int64, bool, error) {
	if info.Digest == "" {
		return "", -1, false, nil
	}

	for _, isConfig := range []bool{false, true} {
		path := b.blobPath(info.Digest, isConfig)
		fileInfo, err := os.Stat(path)
		if err == nil && (info.Size == -1 || info.Size == fileInfo.Size()) {
			return path, fileInfo.Size(), isConfig, nil
		}
		if !os.IsNotExist(err) {
			return "", -1, false, fmt.Errorf("checking size: %w", err)
		}
	}

	return "", -1, false, nil

}

func (b *BlobCache) HasBlob(blobinfo types.BlobInfo) (bool, int64, error) {
	path, size, _, err := b.findBlob(blobinfo)
	if err != nil {
		return false, -1, err
	}
	if path != "" {
		return true, size, nil
	}
	return false, -1, nil
}

func (b *BlobCache) Directory() string {
	return b.directory
}

func (b *BlobCache) ClearCache() error {
	f, err := os.Open(b.directory)
	if err != nil {
		return err
	}
	defer f.Close()
	names, err := f.Readdirnames(-1)
	if err != nil {
		return fmt.Errorf("error reading directory %q: %w", b.directory, err)
	}
	for _, name := range names {
		pathname := filepath.Join(b.directory, name)
		if err = os.RemoveAll(pathname); err != nil {
			return fmt.Errorf("clearing cache for %q: %w", transports.ImageName(b), err)
		}
	}
	return nil
}

func (b *BlobCache) NewImage(ctx context.Context, sys *types.SystemContext) (types.ImageCloser, error) {
	return image.FromReference(ctx, sys, b)
}