-
Notifications
You must be signed in to change notification settings - Fork 9
/
fscache.go
97 lines (76 loc) · 2.88 KB
/
fscache.go
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
// Copyright 2022 Namespace Labs Inc; All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
package fscache
import (
"context"
"io"
"io/fs"
v1 "github.com/google/go-containerregistry/pkg/v1"
"namespacelabs.dev/foundation/internal/artifacts/oci"
"namespacelabs.dev/foundation/internal/bytestream"
"namespacelabs.dev/foundation/internal/compute"
"namespacelabs.dev/foundation/internal/compute/cache"
"namespacelabs.dev/foundation/internal/fnerrors"
"namespacelabs.dev/foundation/internal/fnfs/tarfs"
"namespacelabs.dev/foundation/schema"
)
func RegisterFSCacheable() {
compute.RegisterCacheable[fs.FS](fsCacheable{})
}
// Implementations of a `fs.FS` cache where each FS is cached as a individual image layer.
type fsCacheable struct{}
func ComputeDigest(ctx context.Context, fsys fs.FS) (schema.Digest, error) {
layer, err := oci.LayerFromFS(ctx, fsys)
if err != nil {
return schema.Digest{}, err
}
_, d, err := oci.ComputeLayerCacheData(layer)
return d, err
}
func (fsCacheable) ComputeDigest(ctx context.Context, v interface{}) (schema.Digest, error) {
return ComputeDigest(ctx, v.(fs.FS)) // XXX don't pay the cost twice (see Cache below).
}
func (fsCacheable) LoadCached(ctx context.Context, c cache.Cache, t compute.CacheableInstance, h schema.Digest) (compute.Result[fs.FS], error) {
layer, digest, err := oci.LoadCachedLayer(ctx, c, h)
if err != nil {
return compute.Result[fs.FS]{}, err
}
return compute.Result[fs.FS]{
Digest: digest,
Value: layerBackedFS{layer},
}, nil
}
func (fsCacheable) Cache(ctx context.Context, c cache.Cache, vfs fs.FS) (schema.Digest, error) {
layer, err := oci.LayerFromFS(ctx, vfs)
if err != nil {
return schema.Digest{}, err
}
return oci.CacheLayer(ctx, c, layer)
}
// Implements a fs.ReadDirFS which is backed by a layer. We don't buffer the layer in memory though,
// its contents are read on demand.
type layerBackedFS struct{ l v1.Layer }
var _ fs.ReadDirFS = layerBackedFS{}
var _ oci.HasToLayer = layerBackedFS{}
func (l layerBackedFS) tarStream() (io.ReadCloser, error) {
digest, err := l.l.Digest()
if err != nil {
return nil, fnerrors.BadInputError("failed to get layer digest: %w", err)
}
r, err := l.l.Uncompressed()
if err != nil {
return nil, fnerrors.BadInputError("%s: failed to get layer contents", digest)
}
return r, nil
}
func (l layerBackedFS) Open(path string) (fs.File, error) {
return tarfs.FS{TarStream: l.tarStream}.Open(path)
}
func (l layerBackedFS) ReadDir(dir string) ([]fs.DirEntry, error) {
return tarfs.FS{TarStream: l.tarStream}.ReadDir(dir)
}
func (l layerBackedFS) VisitFiles(ctx context.Context, visitor func(string, bytestream.ByteStream, fs.DirEntry) error) error {
return tarfs.FS{TarStream: l.tarStream}.VisitFiles(ctx, visitor)
}
func (f layerBackedFS) AsLayer() (v1.Layer, error) { return f.l, nil }