forked from operator-framework/operator-controller
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcache.go
More file actions
186 lines (162 loc) · 6.06 KB
/
Copy pathcache.go
File metadata and controls
186 lines (162 loc) · 6.06 KB
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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
package image
import (
"context"
"errors"
"fmt"
"io"
"io/fs"
"iter"
"os"
"path/filepath"
"slices"
"time"
"github.com/containerd/containerd/archive"
"github.com/opencontainers/go-digest"
ocispecv1 "github.com/opencontainers/image-spec/specs-go/v1"
"go.podman.io/image/v5/docker/reference"
"sigs.k8s.io/controller-runtime/pkg/log"
errorutil "github.com/operator-framework/operator-controller/internal/shared/util/error"
fsutil "github.com/operator-framework/operator-controller/internal/shared/util/fs"
)
type LayerData struct {
MediaType string
Reader io.Reader
Index int
Err error
}
type Cache interface {
Fetch(context.Context, string, reference.Canonical) (fs.FS, time.Time, error)
Store(context.Context, string, reference.Named, reference.Canonical, ocispecv1.Image, iter.Seq[LayerData]) (fs.FS, time.Time, error)
Delete(context.Context, string) error
GarbageCollect(context.Context, string, reference.Canonical) error
}
const ConfigDirLabel = "operators.operatorframework.io.index.configs.v1"
func CatalogCache(basePath string) Cache {
return &diskCache{
basePath: basePath,
filterFunc: filterForCatalogImage(),
}
}
func filterForCatalogImage() func(ctx context.Context, srcRef reference.Named, image ocispecv1.Image) (archive.Filter, error) {
return func(ctx context.Context, srcRef reference.Named, image ocispecv1.Image) (archive.Filter, error) {
_, specIsCanonical := srcRef.(reference.Canonical)
dirToUnpack, ok := image.Config.Labels[ConfigDirLabel]
if !ok {
// If the spec is a tagged keep, retries could end up resolving a new digest, where the label
// might show up. If the spec is canonical, no amount of retries will make the label appear.
// Therefore, we treat the error as terminal if the reference from the spec is canonical.
return nil, errorutil.WrapTerminal(fmt.Errorf("catalog image is missing the required label %q", ConfigDirLabel), specIsCanonical)
}
return allFilters(
onlyPath(dirToUnpack),
forceOwnershipRWX(),
), nil
}
}
func BundleCache(basePath string) Cache {
return &diskCache{
basePath: basePath,
filterFunc: filterForBundleImage(),
}
}
func filterForBundleImage() func(ctx context.Context, srcRef reference.Named, image ocispecv1.Image) (archive.Filter, error) {
return func(ctx context.Context, srcRef reference.Named, image ocispecv1.Image) (archive.Filter, error) {
return forceOwnershipRWX(), nil
}
}
type diskCache struct {
basePath string
filterFunc func(context.Context, reference.Named, ocispecv1.Image) (archive.Filter, error)
}
func (a *diskCache) Fetch(ctx context.Context, ownerID string, canonicalRef reference.Canonical) (fs.FS, time.Time, error) {
l := log.FromContext(ctx)
unpackPath := a.unpackPath(ownerID, canonicalRef.Digest())
modTime, err := fsutil.GetDirectoryModTime(unpackPath)
switch {
case errors.Is(err, os.ErrNotExist):
return nil, time.Time{}, nil
case errors.Is(err, fsutil.ErrNotDirectory):
l.Info("unpack path is not a directory; attempting to delete", "path", unpackPath)
return nil, time.Time{}, fsutil.DeleteReadOnlyRecursive(unpackPath)
case err != nil:
return nil, time.Time{}, fmt.Errorf("error checking image content already unpacked: %w", err)
}
l.Info("image already unpacked")
return os.DirFS(a.unpackPath(ownerID, canonicalRef.Digest())), modTime, nil
}
func (a *diskCache) ownerIDPath(ownerID string) string {
return filepath.Join(a.basePath, ownerID)
}
func (a *diskCache) unpackPath(ownerID string, digest digest.Digest) string {
return filepath.Join(a.ownerIDPath(ownerID), digest.String())
}
func (a *diskCache) Store(ctx context.Context, ownerID string, srcRef reference.Named, canonicalRef reference.Canonical, imgCfg ocispecv1.Image, layers iter.Seq[LayerData]) (fs.FS, time.Time, error) {
var applyOpts []archive.ApplyOpt
if a.filterFunc != nil {
filter, err := a.filterFunc(ctx, srcRef, imgCfg)
if err != nil {
return nil, time.Time{}, err
}
applyOpts = append(applyOpts, archive.WithFilter(filter))
}
dest := a.unpackPath(ownerID, canonicalRef.Digest())
if err := fsutil.EnsureEmptyDirectory(dest, 0700); err != nil {
return nil, time.Time{}, fmt.Errorf("error ensuring empty unpack directory: %w", err)
}
if err := func() error {
l := log.FromContext(ctx)
l.Info("unpacking image", "path", dest)
for layer := range layers {
if layer.Err != nil {
return fmt.Errorf("error reading layer[%d]: %w", layer.Index, layer.Err)
}
if _, err := archive.Apply(ctx, dest, layer.Reader, applyOpts...); err != nil {
return fmt.Errorf("error applying layer[%d]: %w", layer.Index, err)
}
l.Info("applied layer", "layer", layer.Index)
}
if err := fsutil.SetReadOnlyRecursive(dest); err != nil {
return fmt.Errorf("error making unpack directory read-only: %w", err)
}
return nil
}(); err != nil {
return nil, time.Time{}, errors.Join(err, fsutil.DeleteReadOnlyRecursive(dest))
}
modTime, err := fsutil.GetDirectoryModTime(dest)
if err != nil {
return nil, time.Time{}, fmt.Errorf("error getting mod time of unpack directory: %w", err)
}
return os.DirFS(dest), modTime, nil
}
func (a *diskCache) Delete(_ context.Context, ownerID string) error {
return fsutil.DeleteReadOnlyRecursive(a.ownerIDPath(ownerID))
}
func (a *diskCache) GarbageCollect(_ context.Context, ownerID string, keep reference.Canonical) error {
ownerIDPath := a.ownerIDPath(ownerID)
dirEntries, err := os.ReadDir(ownerIDPath)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return fmt.Errorf("error reading owner directory: %w", err)
}
foundKeep := false
dirEntries = slices.DeleteFunc(dirEntries, func(entry os.DirEntry) bool {
found := entry.Name() == keep.Digest().String()
if found {
foundKeep = true
}
return found
})
for _, dirEntry := range dirEntries {
if err := fsutil.DeleteReadOnlyRecursive(filepath.Join(ownerIDPath, dirEntry.Name())); err != nil {
return fmt.Errorf("error removing entry %s: %w", dirEntry.Name(), err)
}
}
if !foundKeep {
if err := fsutil.DeleteReadOnlyRecursive(ownerIDPath); err != nil {
return fmt.Errorf("error deleting unused owner data: %w", err)
}
}
return nil
}