mirror of https://github.com/docker/cli.git
223 lines
5.8 KiB
Go
223 lines
5.8 KiB
Go
|
package storage
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"io/ioutil"
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
"strings"
|
||
|
|
||
|
"github.com/docker/notary"
|
||
|
)
|
||
|
|
||
|
// NewFilesystemStore creates a new store in a directory tree
|
||
|
func NewFilesystemStore(baseDir, subDir, extension string) (*FilesystemStore, error) {
|
||
|
baseDir = filepath.Join(baseDir, subDir)
|
||
|
|
||
|
return NewFileStore(baseDir, extension, notary.PrivKeyPerms)
|
||
|
}
|
||
|
|
||
|
// NewFileStore creates a fully configurable file store
|
||
|
func NewFileStore(baseDir, fileExt string, perms os.FileMode) (*FilesystemStore, error) {
|
||
|
baseDir = filepath.Clean(baseDir)
|
||
|
if err := createDirectory(baseDir, perms); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
if !strings.HasPrefix(fileExt, ".") {
|
||
|
fileExt = "." + fileExt
|
||
|
}
|
||
|
|
||
|
return &FilesystemStore{
|
||
|
baseDir: baseDir,
|
||
|
ext: fileExt,
|
||
|
perms: perms,
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
// NewSimpleFileStore is a convenience wrapper to create a world readable,
|
||
|
// owner writeable filestore
|
||
|
func NewSimpleFileStore(baseDir, fileExt string) (*FilesystemStore, error) {
|
||
|
return NewFileStore(baseDir, fileExt, notary.PubCertPerms)
|
||
|
}
|
||
|
|
||
|
// NewPrivateKeyFileStorage initializes a new filestore for private keys, appending
|
||
|
// the notary.PrivDir to the baseDir.
|
||
|
func NewPrivateKeyFileStorage(baseDir, fileExt string) (*FilesystemStore, error) {
|
||
|
baseDir = filepath.Join(baseDir, notary.PrivDir)
|
||
|
return NewFileStore(baseDir, fileExt, notary.PrivKeyPerms)
|
||
|
}
|
||
|
|
||
|
// NewPrivateSimpleFileStore is a wrapper to create an owner readable/writeable
|
||
|
// _only_ filestore
|
||
|
func NewPrivateSimpleFileStore(baseDir, fileExt string) (*FilesystemStore, error) {
|
||
|
return NewFileStore(baseDir, fileExt, notary.PrivKeyPerms)
|
||
|
}
|
||
|
|
||
|
// FilesystemStore is a store in a locally accessible directory
|
||
|
type FilesystemStore struct {
|
||
|
baseDir string
|
||
|
ext string
|
||
|
perms os.FileMode
|
||
|
}
|
||
|
|
||
|
func (f *FilesystemStore) getPath(name string) (string, error) {
|
||
|
fileName := fmt.Sprintf("%s%s", name, f.ext)
|
||
|
fullPath := filepath.Join(f.baseDir, fileName)
|
||
|
|
||
|
if !strings.HasPrefix(fullPath, f.baseDir) {
|
||
|
return "", ErrPathOutsideStore
|
||
|
}
|
||
|
return fullPath, nil
|
||
|
}
|
||
|
|
||
|
// GetSized returns the meta for the given name (a role) up to size bytes
|
||
|
// If size is "NoSizeLimit", this corresponds to "infinite," but we cut off at a
|
||
|
// predefined threshold "notary.MaxDownloadSize". If the file is larger than size
|
||
|
// we return ErrMaliciousServer for consistency with the HTTPStore
|
||
|
func (f *FilesystemStore) GetSized(name string, size int64) ([]byte, error) {
|
||
|
p, err := f.getPath(name)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
file, err := os.OpenFile(p, os.O_RDONLY, f.perms)
|
||
|
if err != nil {
|
||
|
if os.IsNotExist(err) {
|
||
|
err = ErrMetaNotFound{Resource: name}
|
||
|
}
|
||
|
return nil, err
|
||
|
}
|
||
|
defer file.Close()
|
||
|
|
||
|
if size == NoSizeLimit {
|
||
|
size = notary.MaxDownloadSize
|
||
|
}
|
||
|
|
||
|
stat, err := file.Stat()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
if stat.Size() > size {
|
||
|
return nil, ErrMaliciousServer{}
|
||
|
}
|
||
|
|
||
|
l := io.LimitReader(file, size)
|
||
|
return ioutil.ReadAll(l)
|
||
|
}
|
||
|
|
||
|
// Get returns the meta for the given name.
|
||
|
func (f *FilesystemStore) Get(name string) ([]byte, error) {
|
||
|
p, err := f.getPath(name)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
meta, err := ioutil.ReadFile(p)
|
||
|
if err != nil {
|
||
|
if os.IsNotExist(err) {
|
||
|
err = ErrMetaNotFound{Resource: name}
|
||
|
}
|
||
|
return nil, err
|
||
|
}
|
||
|
return meta, nil
|
||
|
}
|
||
|
|
||
|
// SetMulti sets the metadata for multiple roles in one operation
|
||
|
func (f *FilesystemStore) SetMulti(metas map[string][]byte) error {
|
||
|
for role, blob := range metas {
|
||
|
err := f.Set(role, blob)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Set sets the meta for a single role
|
||
|
func (f *FilesystemStore) Set(name string, meta []byte) error {
|
||
|
fp, err := f.getPath(name)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// Ensures the parent directories of the file we are about to write exist
|
||
|
err = os.MkdirAll(filepath.Dir(fp), f.perms)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// if something already exists, just delete it and re-write it
|
||
|
os.RemoveAll(fp)
|
||
|
|
||
|
// Write the file to disk
|
||
|
if err = ioutil.WriteFile(fp, meta, f.perms); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// RemoveAll clears the existing filestore by removing its base directory
|
||
|
func (f *FilesystemStore) RemoveAll() error {
|
||
|
return os.RemoveAll(f.baseDir)
|
||
|
}
|
||
|
|
||
|
// Remove removes the metadata for a single role - if the metadata doesn't
|
||
|
// exist, no error is returned
|
||
|
func (f *FilesystemStore) Remove(name string) error {
|
||
|
p, err := f.getPath(name)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return os.RemoveAll(p) // RemoveAll succeeds if path doesn't exist
|
||
|
}
|
||
|
|
||
|
// Location returns a human readable name for the storage location
|
||
|
func (f FilesystemStore) Location() string {
|
||
|
return f.baseDir
|
||
|
}
|
||
|
|
||
|
// ListFiles returns a list of all the filenames that can be used with Get*
|
||
|
// to retrieve content from this filestore
|
||
|
func (f FilesystemStore) ListFiles() []string {
|
||
|
files := make([]string, 0, 0)
|
||
|
filepath.Walk(f.baseDir, func(fp string, fi os.FileInfo, err error) error {
|
||
|
// If there are errors, ignore this particular file
|
||
|
if err != nil {
|
||
|
return nil
|
||
|
}
|
||
|
// Ignore if it is a directory
|
||
|
if fi.IsDir() {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// If this is a symlink, ignore it
|
||
|
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Only allow matches that end with our certificate extension (e.g. *.crt)
|
||
|
matched, _ := filepath.Match("*"+f.ext, fi.Name())
|
||
|
|
||
|
if matched {
|
||
|
// Find the relative path for this file relative to the base path.
|
||
|
fp, err = filepath.Rel(f.baseDir, fp)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
trimmed := strings.TrimSuffix(fp, f.ext)
|
||
|
files = append(files, trimmed)
|
||
|
}
|
||
|
return nil
|
||
|
})
|
||
|
return files
|
||
|
}
|
||
|
|
||
|
// createDirectory receives a string of the path to a directory.
|
||
|
// It does not support passing files, so the caller has to remove
|
||
|
// the filename by doing filepath.Dir(full_path_to_file)
|
||
|
func createDirectory(dir string, perms os.FileMode) error {
|
||
|
// This prevents someone passing /path/to/dir and 'dir' not being created
|
||
|
// If two '//' exist, MkdirAll deals it with correctly
|
||
|
dir = dir + "/"
|
||
|
return os.MkdirAll(dir, perms)
|
||
|
}
|