automation-tests/common/pkg/configmaps/configmaps.go

283 lines
8.4 KiB
Go

package configmaps
import (
"os"
"path/filepath"
"regexp"
"strings"
"time"
"github.com/containers/common/pkg/configmaps/filedriver"
"github.com/containers/storage/pkg/lockfile"
"github.com/containers/storage/pkg/stringid"
"github.com/pkg/errors"
)
// maxConfigMapSize is the max size for configMap data - 512kB
const maxConfigMapSize = 512000
// configMapIDLength is the character length of a configMap ID - 25
const configMapIDLength = 25
// errInvalidPath indicates that the configMaps path is invalid
var errInvalidPath = errors.New("invalid configmaps path")
// ErrNoSuchConfigMap indicates that the configMap does not exist
var ErrNoSuchConfigMap = errors.New("no such configmap")
// errConfigMapNameInUse indicates that the configMap name is already in use
var errConfigMapNameInUse = errors.New("configmap name in use")
// errInvalidConfigMapName indicates that the configMap name is invalid
var errInvalidConfigMapName = errors.New("invalid configmap name")
// errInvalidDriver indicates that the driver type is invalid
var errInvalidDriver = errors.New("invalid driver")
// errInvalidDriverOpt indicates that a driver option is invalid
var errInvalidDriverOpt = errors.New("invalid driver option")
// errAmbiguous indicates that a configMap is ambiguous
var errAmbiguous = errors.New("configmap is ambiguous")
// errDataSize indicates that the configMap data is too large or too small
var errDataSize = errors.New("configmap data must be larger than 0 and less than 512000 bytes")
// configMapsFile is the name of the file that the configMaps database will be stored in
var configMapsFile = "configmaps.json"
// configMapNameRegexp matches valid configMap names
// Allowed: 64 [a-zA-Z0-9-_.] characters, and the start and end character must be [a-zA-Z0-9]
var configMapNameRegexp = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_.-]*$`)
// ConfigMapManager holds information on handling configmaps
type ConfigMapManager struct {
// configMapDBPath is the path to the db file where configmaps are stored
configMapDBPath string
// lockfile is the locker for the configmap file
lockfile lockfile.Locker
// db is an in-memory cache of the database of configMaps
db *db
}
// ConfigMap defines a configMap
type ConfigMap struct {
// Name is the name of the configmap
Name string `json:"name"`
// ID is the unique configMap ID
ID string `json:"id"`
// Metadata stores other metadata on the configMap
Metadata map[string]string `json:"metadata,omitempty"`
// CreatedAt is when the configMap was created
CreatedAt time.Time `json:"createdAt"`
// Driver is the driver used to store configMap data
Driver string `json:"driver"`
// DriverOptions is other metadata needed to use the driver
DriverOptions map[string]string `json:"driverOptions"`
}
// ConfigMapsDriver interfaces with the configMaps data store.
// The driver stores the actual bytes of configMap data, as opposed to
// the configMap metadata.
//
// revive does not like the name because the package is already called configmaps
//nolint:revive
type ConfigMapsDriver interface {
// List lists all configMap ids in the configMaps data store
List() ([]string, error)
// Lookup gets the configMap's data bytes
Lookup(id string) ([]byte, error)
// Store stores the configMap's data bytes
Store(id string, data []byte) error
// Delete deletes a configMap's data from the driver
Delete(id string) error
}
// NewManager creates a new configMaps manager
// rootPath is the directory where the configMaps data file resides
func NewManager(rootPath string) (*ConfigMapManager, error) {
manager := new(ConfigMapManager)
if !filepath.IsAbs(rootPath) {
return nil, errors.Wrapf(errInvalidPath, "path must be absolute: %s", rootPath)
}
// the lockfile functions require that the rootPath dir is executable
if err := os.MkdirAll(rootPath, 0o700); err != nil {
return nil, err
}
lock, err := lockfile.GetLockfile(filepath.Join(rootPath, "configMaps.lock"))
if err != nil {
return nil, err
}
manager.lockfile = lock
manager.configMapDBPath = filepath.Join(rootPath, configMapsFile)
manager.db = new(db)
manager.db.ConfigMaps = make(map[string]ConfigMap)
manager.db.NameToID = make(map[string]string)
manager.db.IDToName = make(map[string]string)
return manager, nil
}
// Store takes a name, creates a configMap and stores the configMap metadata and the configMap payload.
// It returns a generated ID that is associated with the configMap.
// The max size for configMap data is 512kB.
func (s *ConfigMapManager) Store(name string, data []byte, driverType string, driverOpts map[string]string) (string, error) {
err := validateConfigMapName(name)
if err != nil {
return "", err
}
if !(len(data) > 0 && len(data) < maxConfigMapSize) {
return "", errDataSize
}
s.lockfile.Lock()
defer s.lockfile.Unlock()
exist, err := s.exactConfigMapExists(name)
if err != nil {
return "", err
}
if exist {
return "", errors.Wrapf(errConfigMapNameInUse, name)
}
secr := new(ConfigMap)
secr.Name = name
for {
newID := stringid.GenerateNonCryptoID()
// GenerateNonCryptoID() gives 64 characters, so we truncate to correct length
newID = newID[0:configMapIDLength]
_, err := s.lookupConfigMap(newID)
if err != nil {
if errors.Cause(err) == ErrNoSuchConfigMap {
secr.ID = newID
break
} else {
return "", err
}
}
}
secr.Driver = driverType
secr.Metadata = make(map[string]string)
secr.CreatedAt = time.Now()
secr.DriverOptions = driverOpts
driver, err := getDriver(driverType, driverOpts)
if err != nil {
return "", err
}
err = driver.Store(secr.ID, data)
if err != nil {
return "", errors.Wrapf(err, "error creating configMap %s", name)
}
err = s.store(secr)
if err != nil {
return "", errors.Wrapf(err, "error creating configMap %s", name)
}
return secr.ID, nil
}
// Delete removes all configMap metadata and configMap data associated with the specified configMap.
// Delete takes a name, ID, or partial ID.
func (s *ConfigMapManager) Delete(nameOrID string) (string, error) {
err := validateConfigMapName(nameOrID)
if err != nil {
return "", err
}
s.lockfile.Lock()
defer s.lockfile.Unlock()
configMap, err := s.lookupConfigMap(nameOrID)
if err != nil {
return "", err
}
configMapID := configMap.ID
driver, err := getDriver(configMap.Driver, configMap.DriverOptions)
if err != nil {
return "", err
}
err = driver.Delete(configMapID)
if err != nil {
return "", errors.Wrapf(err, "error deleting configMap %s", nameOrID)
}
err = s.delete(configMapID)
if err != nil {
return "", errors.Wrapf(err, "error deleting configMap %s", nameOrID)
}
return configMapID, nil
}
// Lookup gives a configMap's metadata given its name, ID, or partial ID.
func (s *ConfigMapManager) Lookup(nameOrID string) (*ConfigMap, error) {
s.lockfile.Lock()
defer s.lockfile.Unlock()
return s.lookupConfigMap(nameOrID)
}
// List lists all configMaps.
func (s *ConfigMapManager) List() ([]ConfigMap, error) {
s.lockfile.Lock()
defer s.lockfile.Unlock()
configMaps, err := s.lookupAll()
if err != nil {
return nil, err
}
var ls []ConfigMap
for _, v := range configMaps {
ls = append(ls, v)
}
return ls, nil
}
// LookupConfigMapData returns configMap metadata as well as configMap data in bytes.
// The configMap data can be looked up using its name, ID, or partial ID.
func (s *ConfigMapManager) LookupConfigMapData(nameOrID string) (*ConfigMap, []byte, error) {
s.lockfile.Lock()
defer s.lockfile.Unlock()
configMap, err := s.lookupConfigMap(nameOrID)
if err != nil {
return nil, nil, err
}
driver, err := getDriver(configMap.Driver, configMap.DriverOptions)
if err != nil {
return nil, nil, err
}
data, err := driver.Lookup(configMap.ID)
if err != nil {
return nil, nil, err
}
return configMap, data, nil
}
// validateConfigMapName checks if the configMap name is valid.
func validateConfigMapName(name string) error {
if !configMapNameRegexp.MatchString(name) || len(name) > 64 || strings.HasSuffix(name, "-") || strings.HasSuffix(name, ".") {
return errors.Wrapf(errInvalidConfigMapName, "only 64 [a-zA-Z0-9-_.] characters allowed, and the start and end character must be [a-zA-Z0-9]: %s", name)
}
return nil
}
// getDriver creates a new driver.
func getDriver(name string, opts map[string]string) (ConfigMapsDriver, error) {
if name == "file" {
if path, ok := opts["path"]; ok {
return filedriver.NewDriver(path)
}
return nil, errors.Wrap(errInvalidDriverOpt, "need path for filedriver")
}
return nil, errInvalidDriver
}