d76645680f
Bump containers/image (pulling in its new dependency on ostree-go), containers/storage, and updated image-spec. This pulls in the OCI v1.0 specifications and code that allows us to support 1.0 images. Signed-off-by: Dan Walsh <dwalsh@redhat.com> Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
464 lines
12 KiB
Go
464 lines
12 KiB
Go
package storage
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/containers/storage/pkg/ioutils"
|
|
"github.com/containers/storage/pkg/stringid"
|
|
"github.com/containers/storage/pkg/truncindex"
|
|
)
|
|
|
|
var (
|
|
// ErrContainerUnknown indicates that there was no container with the specified name or ID
|
|
ErrContainerUnknown = errors.New("container not known")
|
|
)
|
|
|
|
// A Container is a reference to a read-write layer with metadata.
|
|
type Container struct {
|
|
// ID is either one which was specified at create-time, or a random
|
|
// value which was generated by the library.
|
|
ID string `json:"id"`
|
|
|
|
// Names is an optional set of user-defined convenience values. The
|
|
// container can be referred to by its ID or any of its names. Names
|
|
// are unique among containers.
|
|
Names []string `json:"names,omitempty"`
|
|
|
|
// ImageID is the ID of the image which was used to create the container.
|
|
ImageID string `json:"image"`
|
|
|
|
// LayerID is the ID of the read-write layer for the container itself.
|
|
// It is assumed that the image's top layer is the parent of the container's
|
|
// read-write layer.
|
|
LayerID string `json:"layer"`
|
|
|
|
// Metadata is data we keep for the convenience of the caller. It is not
|
|
// expected to be large, since it is kept in memory.
|
|
Metadata string `json:"metadata,omitempty"`
|
|
|
|
// BigDataNames is a list of names of data items that we keep for the
|
|
// convenience of the caller. They can be large, and are only in
|
|
// memory when being read from or written to disk.
|
|
BigDataNames []string `json:"big-data-names,omitempty"`
|
|
|
|
// BigDataSizes maps the names in BigDataNames to the sizes of the data
|
|
// that has been stored, if they're known.
|
|
BigDataSizes map[string]int64 `json:"big-data-sizes,omitempty"`
|
|
|
|
// Created is the datestamp for when this container was created. Older
|
|
// versions of the library did not track this information, so callers
|
|
// will likely want to use the IsZero() method to verify that a value
|
|
// is set before using it.
|
|
Created time.Time `json:"created,omitempty"`
|
|
|
|
Flags map[string]interface{} `json:"flags,omitempty"`
|
|
}
|
|
|
|
// ContainerStore provides bookkeeping for information about Containers.
|
|
type ContainerStore interface {
|
|
FileBasedStore
|
|
MetadataStore
|
|
BigDataStore
|
|
FlaggableStore
|
|
|
|
// Create creates a container that has a specified ID (or generates a
|
|
// random one if an empty value is supplied) and optional names,
|
|
// based on the specified image, using the specified layer as its
|
|
// read-write layer.
|
|
Create(id string, names []string, image, layer, metadata string) (*Container, error)
|
|
|
|
// SetNames updates the list of names associated with the container
|
|
// with the specified ID.
|
|
SetNames(id string, names []string) error
|
|
|
|
// Get retrieves information about a container given an ID or name.
|
|
Get(id string) (*Container, error)
|
|
|
|
// Exists checks if there is a container with the given ID or name.
|
|
Exists(id string) bool
|
|
|
|
// Delete removes the record of the container.
|
|
Delete(id string) error
|
|
|
|
// Wipe removes records of all containers.
|
|
Wipe() error
|
|
|
|
// Lookup attempts to translate a name to an ID. Most methods do this
|
|
// implicitly.
|
|
Lookup(name string) (string, error)
|
|
|
|
// Containers returns a slice enumerating the known containers.
|
|
Containers() ([]Container, error)
|
|
}
|
|
|
|
type containerStore struct {
|
|
lockfile Locker
|
|
dir string
|
|
containers []*Container
|
|
idindex *truncindex.TruncIndex
|
|
byid map[string]*Container
|
|
bylayer map[string]*Container
|
|
byname map[string]*Container
|
|
}
|
|
|
|
func (r *containerStore) Containers() ([]Container, error) {
|
|
containers := make([]Container, len(r.containers))
|
|
for i := range r.containers {
|
|
containers[i] = *(r.containers[i])
|
|
}
|
|
return containers, nil
|
|
}
|
|
|
|
func (r *containerStore) containerspath() string {
|
|
return filepath.Join(r.dir, "containers.json")
|
|
}
|
|
|
|
func (r *containerStore) datadir(id string) string {
|
|
return filepath.Join(r.dir, id)
|
|
}
|
|
|
|
func (r *containerStore) datapath(id, key string) string {
|
|
return filepath.Join(r.datadir(id), makeBigDataBaseName(key))
|
|
}
|
|
|
|
func (r *containerStore) Load() error {
|
|
needSave := false
|
|
rpath := r.containerspath()
|
|
data, err := ioutil.ReadFile(rpath)
|
|
if err != nil && !os.IsNotExist(err) {
|
|
return err
|
|
}
|
|
containers := []*Container{}
|
|
layers := make(map[string]*Container)
|
|
idlist := []string{}
|
|
ids := make(map[string]*Container)
|
|
names := make(map[string]*Container)
|
|
if err = json.Unmarshal(data, &containers); len(data) == 0 || err == nil {
|
|
for n, container := range containers {
|
|
idlist = append(idlist, container.ID)
|
|
ids[container.ID] = containers[n]
|
|
layers[container.LayerID] = containers[n]
|
|
for _, name := range container.Names {
|
|
if conflict, ok := names[name]; ok {
|
|
r.removeName(conflict, name)
|
|
needSave = true
|
|
}
|
|
names[name] = containers[n]
|
|
}
|
|
}
|
|
}
|
|
r.containers = containers
|
|
r.idindex = truncindex.NewTruncIndex(idlist)
|
|
r.byid = ids
|
|
r.bylayer = layers
|
|
r.byname = names
|
|
if needSave {
|
|
return r.Save()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *containerStore) Save() error {
|
|
rpath := r.containerspath()
|
|
if err := os.MkdirAll(filepath.Dir(rpath), 0700); err != nil {
|
|
return err
|
|
}
|
|
jdata, err := json.Marshal(&r.containers)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer r.Touch()
|
|
return ioutils.AtomicWriteFile(rpath, jdata, 0600)
|
|
}
|
|
|
|
func newContainerStore(dir string) (ContainerStore, error) {
|
|
if err := os.MkdirAll(dir, 0700); err != nil {
|
|
return nil, err
|
|
}
|
|
lockfile, err := GetLockfile(filepath.Join(dir, "containers.lock"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
lockfile.Lock()
|
|
defer lockfile.Unlock()
|
|
cstore := containerStore{
|
|
lockfile: lockfile,
|
|
dir: dir,
|
|
containers: []*Container{},
|
|
byid: make(map[string]*Container),
|
|
bylayer: make(map[string]*Container),
|
|
byname: make(map[string]*Container),
|
|
}
|
|
if err := cstore.Load(); err != nil {
|
|
return nil, err
|
|
}
|
|
return &cstore, nil
|
|
}
|
|
|
|
func (r *containerStore) lookup(id string) (*Container, bool) {
|
|
if container, ok := r.byid[id]; ok {
|
|
return container, ok
|
|
} else if container, ok := r.byname[id]; ok {
|
|
return container, ok
|
|
} else if container, ok := r.bylayer[id]; ok {
|
|
return container, ok
|
|
} else if longid, err := r.idindex.Get(id); err == nil {
|
|
if container, ok := r.byid[longid]; ok {
|
|
return container, ok
|
|
}
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
func (r *containerStore) ClearFlag(id string, flag string) error {
|
|
container, ok := r.lookup(id)
|
|
if !ok {
|
|
return ErrContainerUnknown
|
|
}
|
|
delete(container.Flags, flag)
|
|
return r.Save()
|
|
}
|
|
|
|
func (r *containerStore) SetFlag(id string, flag string, value interface{}) error {
|
|
container, ok := r.lookup(id)
|
|
if !ok {
|
|
return ErrContainerUnknown
|
|
}
|
|
container.Flags[flag] = value
|
|
return r.Save()
|
|
}
|
|
|
|
func (r *containerStore) Create(id string, names []string, image, layer, metadata string) (container *Container, err error) {
|
|
if id == "" {
|
|
id = stringid.GenerateRandomID()
|
|
_, idInUse := r.byid[id]
|
|
for idInUse {
|
|
id = stringid.GenerateRandomID()
|
|
_, idInUse = r.byid[id]
|
|
}
|
|
}
|
|
if _, idInUse := r.byid[id]; idInUse {
|
|
return nil, ErrDuplicateID
|
|
}
|
|
for _, name := range names {
|
|
if _, nameInUse := r.byname[name]; nameInUse {
|
|
return nil, ErrDuplicateName
|
|
}
|
|
}
|
|
if err == nil {
|
|
container = &Container{
|
|
ID: id,
|
|
Names: names,
|
|
ImageID: image,
|
|
LayerID: layer,
|
|
Metadata: metadata,
|
|
BigDataNames: []string{},
|
|
BigDataSizes: make(map[string]int64),
|
|
Created: time.Now().UTC(),
|
|
Flags: make(map[string]interface{}),
|
|
}
|
|
r.containers = append(r.containers, container)
|
|
r.byid[id] = container
|
|
r.idindex.Add(id)
|
|
r.bylayer[layer] = container
|
|
for _, name := range names {
|
|
r.byname[name] = container
|
|
}
|
|
err = r.Save()
|
|
}
|
|
return container, err
|
|
}
|
|
|
|
func (r *containerStore) Metadata(id string) (string, error) {
|
|
if container, ok := r.lookup(id); ok {
|
|
return container.Metadata, nil
|
|
}
|
|
return "", ErrContainerUnknown
|
|
}
|
|
|
|
func (r *containerStore) SetMetadata(id, metadata string) error {
|
|
if container, ok := r.lookup(id); ok {
|
|
container.Metadata = metadata
|
|
return r.Save()
|
|
}
|
|
return ErrContainerUnknown
|
|
}
|
|
|
|
func (r *containerStore) removeName(container *Container, name string) {
|
|
container.Names = stringSliceWithoutValue(container.Names, name)
|
|
}
|
|
|
|
func (r *containerStore) SetNames(id string, names []string) error {
|
|
if container, ok := r.lookup(id); ok {
|
|
for _, name := range container.Names {
|
|
delete(r.byname, name)
|
|
}
|
|
for _, name := range names {
|
|
if otherContainer, ok := r.byname[name]; ok {
|
|
r.removeName(otherContainer, name)
|
|
}
|
|
r.byname[name] = container
|
|
}
|
|
container.Names = names
|
|
return r.Save()
|
|
}
|
|
return ErrContainerUnknown
|
|
}
|
|
|
|
func (r *containerStore) Delete(id string) error {
|
|
container, ok := r.lookup(id)
|
|
if !ok {
|
|
return ErrContainerUnknown
|
|
}
|
|
id = container.ID
|
|
toDeleteIndex := -1
|
|
for i, candidate := range r.containers {
|
|
if candidate.ID == id {
|
|
toDeleteIndex = i
|
|
break
|
|
}
|
|
}
|
|
delete(r.byid, id)
|
|
r.idindex.Delete(id)
|
|
delete(r.bylayer, container.LayerID)
|
|
for _, name := range container.Names {
|
|
delete(r.byname, name)
|
|
}
|
|
if toDeleteIndex != -1 {
|
|
// delete the container at toDeleteIndex
|
|
if toDeleteIndex == len(r.containers)-1 {
|
|
r.containers = r.containers[:len(r.containers)-1]
|
|
} else {
|
|
r.containers = append(r.containers[:toDeleteIndex], r.containers[toDeleteIndex+1:]...)
|
|
}
|
|
}
|
|
if err := r.Save(); err != nil {
|
|
return err
|
|
}
|
|
if err := os.RemoveAll(r.datadir(id)); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *containerStore) Get(id string) (*Container, error) {
|
|
if container, ok := r.lookup(id); ok {
|
|
return container, nil
|
|
}
|
|
return nil, ErrContainerUnknown
|
|
}
|
|
|
|
func (r *containerStore) Lookup(name string) (id string, err error) {
|
|
if container, ok := r.lookup(name); ok {
|
|
return container.ID, nil
|
|
}
|
|
return "", ErrContainerUnknown
|
|
}
|
|
|
|
func (r *containerStore) Exists(id string) bool {
|
|
_, ok := r.lookup(id)
|
|
return ok
|
|
}
|
|
|
|
func (r *containerStore) BigData(id, key string) ([]byte, error) {
|
|
c, ok := r.lookup(id)
|
|
if !ok {
|
|
return nil, ErrContainerUnknown
|
|
}
|
|
return ioutil.ReadFile(r.datapath(c.ID, key))
|
|
}
|
|
|
|
func (r *containerStore) BigDataSize(id, key string) (int64, error) {
|
|
c, ok := r.lookup(id)
|
|
if !ok {
|
|
return -1, ErrContainerUnknown
|
|
}
|
|
if size, ok := c.BigDataSizes[key]; ok {
|
|
return size, nil
|
|
}
|
|
return -1, ErrSizeUnknown
|
|
}
|
|
|
|
func (r *containerStore) BigDataNames(id string) ([]string, error) {
|
|
c, ok := r.lookup(id)
|
|
if !ok {
|
|
return nil, ErrContainerUnknown
|
|
}
|
|
return c.BigDataNames, nil
|
|
}
|
|
|
|
func (r *containerStore) SetBigData(id, key string, data []byte) error {
|
|
c, ok := r.lookup(id)
|
|
if !ok {
|
|
return ErrContainerUnknown
|
|
}
|
|
if err := os.MkdirAll(r.datadir(c.ID), 0700); err != nil {
|
|
return err
|
|
}
|
|
err := ioutils.AtomicWriteFile(r.datapath(c.ID, key), data, 0600)
|
|
if err == nil {
|
|
save := false
|
|
oldSize, ok := c.BigDataSizes[key]
|
|
c.BigDataSizes[key] = int64(len(data))
|
|
if !ok || oldSize != c.BigDataSizes[key] {
|
|
save = true
|
|
}
|
|
add := true
|
|
for _, name := range c.BigDataNames {
|
|
if name == key {
|
|
add = false
|
|
break
|
|
}
|
|
}
|
|
if add {
|
|
c.BigDataNames = append(c.BigDataNames, key)
|
|
save = true
|
|
}
|
|
if save {
|
|
err = r.Save()
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (r *containerStore) Wipe() error {
|
|
ids := []string{}
|
|
for id := range r.byid {
|
|
ids = append(ids, id)
|
|
}
|
|
for _, id := range ids {
|
|
if err := r.Delete(id); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *containerStore) Lock() {
|
|
r.lockfile.Lock()
|
|
}
|
|
|
|
func (r *containerStore) Unlock() {
|
|
r.lockfile.Unlock()
|
|
}
|
|
|
|
func (r *containerStore) Touch() error {
|
|
return r.lockfile.Touch()
|
|
}
|
|
|
|
func (r *containerStore) Modified() (bool, error) {
|
|
return r.lockfile.Modified()
|
|
}
|
|
|
|
func (r *containerStore) IsReadWrite() bool {
|
|
return r.lockfile.IsReadWrite()
|
|
}
|
|
|
|
func (r *containerStore) TouchedSince(when time.Time) bool {
|
|
return r.lockfile.TouchedSince(when)
|
|
}
|