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
|
package client
import (
"bytes"
"errors"
"fmt"
"io"
"io/fs"
)
// FileRemoteStore provides a RemoteStore interface compatible
// implementation that can be used where the RemoteStore is backed by a
// fs.FS. This is useful for example in air-gapped environments where there's no
// possibility to make outbound network connections.
// By having this be a fs.FS instead of directories allows the repository to
// be backed by something that's not persisted to disk.
func NewFileRemoteStore(fsys fs.FS, targetDir string) (*FileRemoteStore, error) {
if fsys == nil {
return nil, errors.New("nil fs.FS")
}
t := targetDir
if t == "" {
t = "targets"
}
// Make sure directory exists
d, err := fsys.Open(t)
if err != nil {
return nil, fmt.Errorf("failed to open targets directory %s: %w", t, err)
}
fi, err := d.Stat()
if err != nil {
return nil, fmt.Errorf("failed to stat targets directory %s: %w", t, err)
}
if !fi.IsDir() {
return nil, fmt.Errorf("targets directory not a directory %s", t)
}
fsysT, err := fs.Sub(fsys, t)
if err != nil {
return nil, fmt.Errorf("failed to open targets directory %s: %w", t, err)
}
return &FileRemoteStore{fsys: fsys, targetDir: fsysT}, nil
}
type FileRemoteStore struct {
// Meta directory fs
fsys fs.FS
// Target directory fs.
targetDir fs.FS
// In order to be able to make write operations (create, delete) we can't
// use fs.FS for it (it's read only), so we have to know the underlying
// directory that add/delete test methods can use. This is only necessary
// for testing purposes.
testDir string
}
func (f *FileRemoteStore) GetMeta(name string) (io.ReadCloser, int64, error) {
rc, b, err := f.get(f.fsys, name)
return handleErrors(name, rc, b, err)
}
func (f *FileRemoteStore) GetTarget(name string) (io.ReadCloser, int64, error) {
rc, b, err := f.get(f.targetDir, name)
return handleErrors(name, rc, b, err)
}
func (f *FileRemoteStore) get(fsys fs.FS, s string) (io.ReadCloser, int64, error) {
if !fs.ValidPath(s) {
return nil, 0, fmt.Errorf("invalid path %s", s)
}
b, err := fs.ReadFile(fsys, s)
if err != nil {
return nil, -1, err
}
return io.NopCloser(bytes.NewReader(b)), int64(len(b)), nil
}
// handleErrors converts NotFound errors to something that TUF knows how to
// handle properly. For example, when looking for n+1 root files, this is a
// signal that it will stop looking.
func handleErrors(name string, rc io.ReadCloser, b int64, err error) (io.ReadCloser, int64, error) {
if err == nil {
return rc, b, err
}
if errors.Is(err, fs.ErrNotExist) {
return rc, b, ErrNotFound{name}
}
return rc, b, err
}
|