Complete "pulling up" of images in updateNames()
When updateNames() copies an image's record from a read-only store into the read-write store, copy the accompanying data as well. Add fields for setting data items at creation-time to LayerOptions, ImageOptions, and ContainerOptions to make this easier for us and our consumers. Replace the store-specific Create() (and the one CreateWithFlags() and Put()) with private create() and put() methods, since they're not intended for consumption outside of this package, and add Flags to the options structures we pass into those methods. In create() methods, make copies of those passed-in options structures before modifying any of their contents. Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
This commit is contained in:
parent
1da746e63e
commit
0f2bccfa56
2
Makefile
2
Makefile
|
|
@ -26,7 +26,7 @@ NATIVETAGS :=
|
|||
AUTOTAGS := $(shell ./hack/btrfs_tag.sh) $(shell ./hack/libdm_tag.sh) $(shell ./hack/libsubid_tag.sh)
|
||||
BUILDFLAGS := -tags "$(AUTOTAGS) $(TAGS)" $(FLAGS)
|
||||
GO ?= go
|
||||
TESTFLAGS := $(shell go test -race $(BUILDFLAGS) ./pkg/stringutils 2>&1 > /dev/null && echo -race)
|
||||
TESTFLAGS := $(shell $(GO) test -race $(BUILDFLAGS) ./pkg/stringutils 2>&1 > /dev/null && echo -race)
|
||||
|
||||
# Go module support: set `-mod=vendor` to use the vendored sources
|
||||
ifeq ($(shell $(GO) help mod >/dev/null 2>&1 && echo true), true)
|
||||
|
|
|
|||
|
|
@ -37,18 +37,30 @@ func addNames(flags *mflag.FlagSet, action string, m storage.Store, args []strin
|
|||
if err != nil {
|
||||
return 1, err
|
||||
}
|
||||
oldnames, err := m.Names(id)
|
||||
if err := m.AddNames(id, paramNames); err != nil {
|
||||
return 1, err
|
||||
}
|
||||
names, err := m.Names(id)
|
||||
if err != nil {
|
||||
return 1, err
|
||||
}
|
||||
newNames := []string{}
|
||||
if oldnames != nil {
|
||||
newNames = append(newNames, oldnames...)
|
||||
if jsonOutput {
|
||||
if _, err := outputJSON(names); err != nil {
|
||||
return 1, err
|
||||
}
|
||||
}
|
||||
if paramNames != nil {
|
||||
newNames = append(newNames, paramNames...)
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func removeNames(flags *mflag.FlagSet, action string, m storage.Store, args []string) (int, error) {
|
||||
if len(args) < 1 {
|
||||
return 1, nil
|
||||
}
|
||||
if err := m.SetNames(id, newNames); err != nil {
|
||||
id, err := m.Lookup(args[0])
|
||||
if err != nil {
|
||||
return 1, err
|
||||
}
|
||||
if err := m.RemoveNames(id, paramNames); err != nil {
|
||||
return 1, err
|
||||
}
|
||||
names, err := m.Names(id)
|
||||
|
|
@ -106,7 +118,19 @@ func init() {
|
|||
maxArgs: -1,
|
||||
action: addNames,
|
||||
addFlags: func(flags *mflag.FlagSet, cmd *command) {
|
||||
flags.Var(opts.NewListOptsRef(¶mNames, nil), []string{"-name", "n"}, "New name")
|
||||
flags.Var(opts.NewListOptsRef(¶mNames, nil), []string{"-name", "n"}, "Name to add")
|
||||
flags.BoolVar(&jsonOutput, []string{"-json", "j"}, jsonOutput, "Prefer JSON output")
|
||||
},
|
||||
})
|
||||
commands = append(commands, command{
|
||||
names: []string{"remove-names", "removenames"},
|
||||
optionsHelp: "[options [...]] imageOrContainerNameOrID",
|
||||
usage: "Remove layer, image, or container name or names",
|
||||
minArgs: 1,
|
||||
maxArgs: -1,
|
||||
action: removeNames,
|
||||
addFlags: func(flags *mflag.FlagSet, cmd *command) {
|
||||
flags.Var(opts.NewListOptsRef(¶mNames, nil), []string{"-name", "n"}, "Name to remove")
|
||||
flags.BoolVar(&jsonOutput, []string{"-json", "j"}, jsonOutput, "Prefer JSON output")
|
||||
},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -107,13 +107,13 @@ type rwContainerStore interface {
|
|||
// stopReading releases locks obtained by startReading.
|
||||
stopReading()
|
||||
|
||||
// Create creates a container that has a specified ID (or generates a
|
||||
// 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.
|
||||
// The maps in the container's options structure are recorded for the
|
||||
// convenience of the caller, nothing more.
|
||||
Create(id string, names []string, image, layer, metadata string, options *ContainerOptions) (*Container, error)
|
||||
create(id string, names []string, image, layer string, options *ContainerOptions) (*Container, error)
|
||||
|
||||
// updateNames modifies names associated with a container based on (op, names).
|
||||
updateNames(id string, names []string, op updateNameOperation) error
|
||||
|
|
@ -651,7 +651,10 @@ func (r *containerStore) SetFlag(id string, flag string, value interface{}) erro
|
|||
}
|
||||
|
||||
// Requires startWriting.
|
||||
func (r *containerStore) Create(id string, names []string, image, layer, metadata string, options *ContainerOptions) (container *Container, err error) {
|
||||
func (r *containerStore) create(id string, names []string, image, layer string, options *ContainerOptions) (container *Container, err error) {
|
||||
if options == nil {
|
||||
options = &ContainerOptions{}
|
||||
}
|
||||
if id == "" {
|
||||
id = stringid.GenerateRandomID()
|
||||
_, idInUse := r.byid[id]
|
||||
|
|
@ -663,12 +666,6 @@ func (r *containerStore) Create(id string, names []string, image, layer, metadat
|
|||
if _, idInUse := r.byid[id]; idInUse {
|
||||
return nil, ErrDuplicateID
|
||||
}
|
||||
if options.MountOpts != nil {
|
||||
options.Flags[mountOptsFlag] = append([]string{}, options.MountOpts...)
|
||||
}
|
||||
if options.Volatile {
|
||||
options.Flags[volatileFlag] = true
|
||||
}
|
||||
names = dedupeNames(names)
|
||||
for _, name := range names {
|
||||
if _, nameInUse := r.byname[name]; nameInUse {
|
||||
|
|
@ -686,7 +683,7 @@ func (r *containerStore) Create(id string, names []string, image, layer, metadat
|
|||
Names: names,
|
||||
ImageID: image,
|
||||
LayerID: layer,
|
||||
Metadata: metadata,
|
||||
Metadata: options.Metadata,
|
||||
BigDataNames: []string{},
|
||||
BigDataSizes: make(map[string]int64),
|
||||
BigDataDigests: make(map[string]digest.Digest),
|
||||
|
|
@ -696,16 +693,42 @@ func (r *containerStore) Create(id string, names []string, image, layer, metadat
|
|||
GIDMap: copyIDMap(options.GIDMap),
|
||||
volatileStore: options.Volatile,
|
||||
}
|
||||
if options.MountOpts != nil {
|
||||
container.Flags[mountOptsFlag] = append([]string{}, options.MountOpts...)
|
||||
}
|
||||
if options.Volatile {
|
||||
container.Flags[volatileFlag] = true
|
||||
}
|
||||
r.containers = append(r.containers, container)
|
||||
r.byid[id] = container
|
||||
// This can only fail on duplicate IDs, which shouldn’t happen — and in that case the index is already in the desired state anyway.
|
||||
// Implementing recovery from an unlikely and unimportant failure here would be too risky.
|
||||
// This can only fail on duplicate IDs, which shouldn’t happen — and in
|
||||
// that case the index is already in the desired state anyway.
|
||||
// Implementing recovery from an unlikely and unimportant failure here
|
||||
// would be too risky.
|
||||
_ = r.idindex.Add(id)
|
||||
r.byid[id] = container
|
||||
r.bylayer[layer] = container
|
||||
for _, name := range names {
|
||||
r.byname[name] = container
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
// now that the in-memory structures know about the new
|
||||
// record, we can use regular Delete() to clean up if
|
||||
// anything breaks from here on out
|
||||
if e := r.Delete(id); e != nil {
|
||||
logrus.Debugf("while cleaning up partially-created container %q we failed to create: %v", id, e)
|
||||
}
|
||||
}
|
||||
}()
|
||||
err = r.saveFor(container)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, item := range options.BigData {
|
||||
if err = r.SetBigData(id, item.Key, item.Data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
container = copyContainer(container)
|
||||
return container, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
## containers-storage-add-names "August 2016"
|
||||
## containers-storage-add-names 1 "August 2016"
|
||||
|
||||
## NAME
|
||||
containers-storage add-names - Add names to a layer/image/container
|
||||
|
|
@ -23,4 +23,5 @@ other layer, image, or container.
|
|||
|
||||
## SEE ALSO
|
||||
containers-storage-get-names(1)
|
||||
containers-storage-remove-names(1)
|
||||
containers-storage-set-names(1)
|
||||
|
|
|
|||
|
|
@ -18,4 +18,5 @@ command can be used to read the list of names for any of them.
|
|||
|
||||
## SEE ALSO
|
||||
containers-storage-add-names(1)
|
||||
containers-storage-remove-names(1)
|
||||
containers-storage-set-names(1)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
## containers-storage-remove-names 1 "January 2023"
|
||||
|
||||
## NAME
|
||||
containers-storage remove-names - Remove names from a layer/image/container
|
||||
|
||||
## SYNOPSIS
|
||||
**containers-storage** **remove-names** [*options* [...]] *layerOrImageOrContainerNameOrID*
|
||||
|
||||
## DESCRIPTION
|
||||
In addition to IDs, *layers*, *images*, and *containers* can have
|
||||
human-readable names assigned to them in *containers-storage*. The *remove-names*
|
||||
command can be used to remove one or more names from them.
|
||||
|
||||
## OPTIONS
|
||||
**-n | --name** *name*
|
||||
|
||||
Specifies a name to remove from the layer, image, or container.
|
||||
|
||||
## EXAMPLE
|
||||
**containers-storage remove-names -n my-for-realsies-awesome-container f3be6c6134d0d980936b4c894f1613b69a62b79588fdeda744d0be3693bde8ec**
|
||||
|
||||
## SEE ALSO
|
||||
containers-storage-add-names(1)
|
||||
containers-storage-get-names(1)
|
||||
containers-storage-set-names(1)
|
||||
|
|
@ -26,3 +26,4 @@ will be removed from the layer, image, or container.
|
|||
## SEE ALSO
|
||||
containers-storage-add-names(1)
|
||||
containers-storage-get-names(1)
|
||||
containers-storage-remove-names(1)
|
||||
|
|
|
|||
71
images.go
71
images.go
|
|
@ -137,10 +137,10 @@ type rwImageStore interface {
|
|||
// stopWriting releases locks obtained by startWriting.
|
||||
stopWriting()
|
||||
|
||||
// Create creates an image that has a specified ID (or a random one) and
|
||||
// create creates an image that has a specified ID (or a random one) and
|
||||
// optional names, using the specified layer as its topmost (hopefully
|
||||
// read-only) layer. That layer can be referenced by multiple images.
|
||||
Create(id string, names []string, layer, metadata string, created time.Time, searchableDigest digest.Digest) (*Image, error)
|
||||
create(id string, names []string, layer string, options ImageOptions) (*Image, error)
|
||||
|
||||
// updateNames modifies names associated with an image based on (op, names).
|
||||
// The values are expected to be valid normalized
|
||||
|
|
@ -688,7 +688,7 @@ func (r *imageStore) SetFlag(id string, flag string, value interface{}) error {
|
|||
}
|
||||
|
||||
// Requires startWriting.
|
||||
func (r *imageStore) Create(id string, names []string, layer, metadata string, created time.Time, searchableDigest digest.Digest) (image *Image, err error) {
|
||||
func (r *imageStore) create(id string, names []string, layer string, options ImageOptions) (image *Image, err error) {
|
||||
if !r.lockfile.IsReadWrite() {
|
||||
return nil, fmt.Errorf("not allowed to create new images at %q: %w", r.imagespath(), ErrStoreIsReadOnly)
|
||||
}
|
||||
|
|
@ -709,30 +709,32 @@ func (r *imageStore) Create(id string, names []string, layer, metadata string, c
|
|||
return nil, fmt.Errorf("image name %q is already associated with image %q: %w", name, image.ID, ErrDuplicateName)
|
||||
}
|
||||
}
|
||||
if created.IsZero() {
|
||||
created = time.Now().UTC()
|
||||
}
|
||||
|
||||
image = &Image{
|
||||
ID: id,
|
||||
Digest: searchableDigest,
|
||||
Digests: nil,
|
||||
Digest: options.Digest,
|
||||
Digests: copyDigestSlice(options.Digests),
|
||||
Names: names,
|
||||
NamesHistory: copyStringSlice(options.NamesHistory),
|
||||
TopLayer: layer,
|
||||
Metadata: metadata,
|
||||
Metadata: options.Metadata,
|
||||
BigDataNames: []string{},
|
||||
BigDataSizes: make(map[string]int64),
|
||||
BigDataDigests: make(map[string]digest.Digest),
|
||||
Created: created,
|
||||
Flags: make(map[string]interface{}),
|
||||
Created: options.CreationDate,
|
||||
Flags: copyStringInterfaceMap(options.Flags),
|
||||
}
|
||||
if image.Created.IsZero() {
|
||||
image.Created = time.Now().UTC()
|
||||
}
|
||||
err = image.recomputeDigests()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("validating digests for new image: %w", err)
|
||||
}
|
||||
r.images = append(r.images, image)
|
||||
// This can only fail on duplicate IDs, which shouldn’t happen — and in that case the index is already in the desired state anyway.
|
||||
// Implementing recovery from an unlikely and unimportant failure here would be too risky.
|
||||
// This can only fail on duplicate IDs, which shouldn’t happen — and in
|
||||
// that case the index is already in the desired state anyway.
|
||||
// Implementing recovery from an unlikely and unimportant failure here
|
||||
// would be too risky.
|
||||
_ = r.idindex.Add(id)
|
||||
r.byid[id] = image
|
||||
for _, name := range names {
|
||||
|
|
@ -742,7 +744,28 @@ func (r *imageStore) Create(id string, names []string, layer, metadata string, c
|
|||
list := r.bydigest[digest]
|
||||
r.bydigest[digest] = append(list, image)
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
// now that the in-memory structures know about the new
|
||||
// record, we can use regular Delete() to clean up if
|
||||
// anything breaks from here on out
|
||||
if e := r.Delete(id); e != nil {
|
||||
logrus.Debugf("while cleaning up partially-created image %q we failed to create: %v", id, e)
|
||||
}
|
||||
}
|
||||
}()
|
||||
err = r.Save()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, item := range options.BigData {
|
||||
if item.Digest == "" {
|
||||
item.Digest = digest.Canonical.FromBytes(item.Data)
|
||||
}
|
||||
if err = r.setBigData(image, item.Key, item.Data, item.Digest); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
image = copyImage(image)
|
||||
return image, err
|
||||
}
|
||||
|
|
@ -965,9 +988,6 @@ func imageSliceWithoutValue(slice []*Image, value *Image) []*Image {
|
|||
|
||||
// Requires startWriting.
|
||||
func (r *imageStore) SetBigData(id, key string, data []byte, digestManifest func([]byte) (digest.Digest, error)) error {
|
||||
if key == "" {
|
||||
return fmt.Errorf("can't set empty name for image big data item: %w", ErrInvalidBigDataName)
|
||||
}
|
||||
if !r.lockfile.IsReadWrite() {
|
||||
return fmt.Errorf("not allowed to save data items associated with images at %q: %w", r.imagespath(), ErrStoreIsReadOnly)
|
||||
}
|
||||
|
|
@ -975,10 +995,7 @@ func (r *imageStore) SetBigData(id, key string, data []byte, digestManifest func
|
|||
if !ok {
|
||||
return fmt.Errorf("locating image with ID %q: %w", id, ErrImageUnknown)
|
||||
}
|
||||
err := os.MkdirAll(r.datadir(image.ID), 0700)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var err error
|
||||
var newDigest digest.Digest
|
||||
if bigDataNameIsManifest(key) {
|
||||
if digestManifest == nil {
|
||||
|
|
@ -990,6 +1007,18 @@ func (r *imageStore) SetBigData(id, key string, data []byte, digestManifest func
|
|||
} else {
|
||||
newDigest = digest.Canonical.FromBytes(data)
|
||||
}
|
||||
return r.setBigData(image, key, data, newDigest)
|
||||
}
|
||||
|
||||
// Requires startWriting.
|
||||
func (r *imageStore) setBigData(image *Image, key string, data []byte, newDigest digest.Digest) error {
|
||||
if key == "" {
|
||||
return fmt.Errorf("can't set empty name for image big data item: %w", ErrInvalidBigDataName)
|
||||
}
|
||||
err := os.MkdirAll(r.datadir(image.ID), 0700)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ioutils.AtomicWriteFile(r.datapath(image.ID, key), data, 0600)
|
||||
if err == nil {
|
||||
save := false
|
||||
|
|
|
|||
|
|
@ -20,8 +20,12 @@ func addTestImage(t *testing.T, store rwImageStore, id string, names []string) {
|
|||
require.NoError(t, err)
|
||||
defer store.stopWriting()
|
||||
|
||||
_, err = store.Create(
|
||||
id, []string{}, "", "", time.Now(), digest.FromString(""),
|
||||
options := ImageOptions{
|
||||
CreationDate: time.Now(),
|
||||
Digest: digest.FromString(""),
|
||||
}
|
||||
_, err = store.create(
|
||||
id, []string{}, "", options,
|
||||
)
|
||||
|
||||
require.Nil(t, err)
|
||||
|
|
|
|||
167
layers.go
167
layers.go
|
|
@ -112,33 +112,33 @@ type Layer struct {
|
|||
Created time.Time `json:"created,omitempty"`
|
||||
|
||||
// CompressedDigest is the digest of the blob that was last passed to
|
||||
// ApplyDiff() or Put(), as it was presented to us.
|
||||
// ApplyDiff() or create(), as it was presented to us.
|
||||
CompressedDigest digest.Digest `json:"compressed-diff-digest,omitempty"`
|
||||
|
||||
// CompressedSize is the length of the blob that was last passed to
|
||||
// ApplyDiff() or Put(), as it was presented to us. If
|
||||
// ApplyDiff() or create(), as it was presented to us. If
|
||||
// CompressedDigest is not set, this should be treated as if it were an
|
||||
// uninitialized value.
|
||||
CompressedSize int64 `json:"compressed-size,omitempty"`
|
||||
|
||||
// UncompressedDigest is the digest of the blob that was last passed to
|
||||
// ApplyDiff() or Put(), after we decompressed it. Often referred to
|
||||
// ApplyDiff() or create(), after we decompressed it. Often referred to
|
||||
// as a DiffID.
|
||||
UncompressedDigest digest.Digest `json:"diff-digest,omitempty"`
|
||||
|
||||
// UncompressedSize is the length of the blob that was last passed to
|
||||
// ApplyDiff() or Put(), after we decompressed it. If
|
||||
// ApplyDiff() or create(), after we decompressed it. If
|
||||
// UncompressedDigest is not set, this should be treated as if it were
|
||||
// an uninitialized value.
|
||||
UncompressedSize int64 `json:"diff-size,omitempty"`
|
||||
|
||||
// CompressionType is the type of compression which we detected on the blob
|
||||
// that was last passed to ApplyDiff() or Put().
|
||||
// that was last passed to ApplyDiff() or create().
|
||||
CompressionType archive.Compression `json:"compression,omitempty"`
|
||||
|
||||
// UIDs and GIDs are lists of UIDs and GIDs used in the layer. This
|
||||
// field is only populated (i.e., will only contain one or more
|
||||
// entries) if the layer was created using ApplyDiff() or Put().
|
||||
// entries) if the layer was created using ApplyDiff() or create().
|
||||
UIDs []uint32 `json:"uidset,omitempty"`
|
||||
GIDs []uint32 `json:"gidset,omitempty"`
|
||||
|
||||
|
|
@ -248,20 +248,15 @@ type rwLayerStore interface {
|
|||
// stopWriting releases locks obtained by startWriting.
|
||||
stopWriting()
|
||||
|
||||
// Create creates a new layer, optionally giving it a specified ID rather than
|
||||
// create creates a new layer, optionally giving it a specified ID rather than
|
||||
// a randomly-generated one, either inheriting data from another specified
|
||||
// layer or the empty base layer. The new layer can optionally be given names
|
||||
// and have an SELinux label specified for use when mounting it. Some
|
||||
// underlying drivers can accept a "size" option. At this time, most
|
||||
// underlying drivers do not themselves distinguish between writeable
|
||||
// and read-only layers.
|
||||
Create(id string, parent *Layer, names []string, mountLabel string, options map[string]string, moreOptions *LayerOptions, writeable bool) (*Layer, error)
|
||||
|
||||
// CreateWithFlags combines the functions of Create and SetFlag.
|
||||
CreateWithFlags(id string, parent *Layer, names []string, mountLabel string, options map[string]string, moreOptions *LayerOptions, writeable bool, flags map[string]interface{}) (layer *Layer, err error)
|
||||
|
||||
// Put combines the functions of CreateWithFlags and ApplyDiff.
|
||||
Put(id string, parent *Layer, names []string, mountLabel string, options map[string]string, moreOptions *LayerOptions, writeable bool, flags map[string]interface{}, diff io.Reader) (*Layer, int64, error)
|
||||
// and read-only layers. Returns the new layer structure and the size of the
|
||||
// diff which was applied to its parent to initialize its contents.
|
||||
create(id string, parent *Layer, names []string, mountLabel string, options map[string]string, moreOptions *LayerOptions, writeable bool, diff io.Reader) (*Layer, int64, error)
|
||||
|
||||
// updateNames modifies names associated with a layer based on (op, names).
|
||||
updateNames(id string, names []string, op updateNameOperation) error
|
||||
|
|
@ -1186,8 +1181,10 @@ func (r *layerStore) PutAdditionalLayer(id string, parentLayer *Layer, names []s
|
|||
|
||||
// TODO: check if necessary fields are filled
|
||||
r.layers = append(r.layers, layer)
|
||||
// This can only fail on duplicate IDs, which shouldn’t happen — and in that case the index is already in the desired state anyway.
|
||||
// Implementing recovery from an unlikely and unimportant failure here would be too risky.
|
||||
// This can only fail on duplicate IDs, which shouldn’t happen — and in
|
||||
// that case the index is already in the desired state anyway.
|
||||
// Implementing recovery from an unlikely and unimportant failure here
|
||||
// would be too risky.
|
||||
_ = r.idindex.Add(id)
|
||||
r.byid[id] = layer
|
||||
for _, name := range names { // names got from the additional layer store won't be used
|
||||
|
|
@ -1200,8 +1197,8 @@ func (r *layerStore) PutAdditionalLayer(id string, parentLayer *Layer, names []s
|
|||
r.byuncompressedsum[layer.UncompressedDigest] = append(r.byuncompressedsum[layer.UncompressedDigest], layer.ID)
|
||||
}
|
||||
if err := r.saveFor(layer); err != nil {
|
||||
if err2 := r.driver.Remove(id); err2 != nil {
|
||||
logrus.Errorf("While recovering from a failure to save layers, error deleting layer %#v: %v", id, err2)
|
||||
if e := r.Delete(layer.ID); e != nil {
|
||||
logrus.Errorf("While recovering from a failure to save layers, error deleting layer %#v: %v", id, e)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1209,7 +1206,10 @@ func (r *layerStore) PutAdditionalLayer(id string, parentLayer *Layer, names []s
|
|||
}
|
||||
|
||||
// Requires startWriting.
|
||||
func (r *layerStore) Put(id string, parentLayer *Layer, names []string, mountLabel string, options map[string]string, moreOptions *LayerOptions, writeable bool, flags map[string]interface{}, diff io.Reader) (*Layer, int64, error) {
|
||||
func (r *layerStore) create(id string, parentLayer *Layer, names []string, mountLabel string, options map[string]string, moreOptions *LayerOptions, writeable bool, diff io.Reader) (layer *Layer, size int64, err error) {
|
||||
if moreOptions == nil {
|
||||
moreOptions = &LayerOptions{}
|
||||
}
|
||||
if !r.lockfile.IsReadWrite() {
|
||||
return nil, -1, fmt.Errorf("not allowed to create new layers at %q: %w", r.layerdir, ErrStoreIsReadOnly)
|
||||
}
|
||||
|
|
@ -1252,7 +1252,6 @@ func (r *layerStore) Put(id string, parentLayer *Layer, names []string, mountLab
|
|||
templateTSdata []byte
|
||||
)
|
||||
if moreOptions.TemplateLayer != "" {
|
||||
var tserr error
|
||||
templateLayer, ok := r.lookup(moreOptions.TemplateLayer)
|
||||
if !ok {
|
||||
return nil, -1, ErrLayerUnknown
|
||||
|
|
@ -1263,9 +1262,9 @@ func (r *layerStore) Put(id string, parentLayer *Layer, names []string, mountLab
|
|||
templateUncompressedDigest, templateUncompressedSize = templateLayer.UncompressedDigest, templateLayer.UncompressedSize
|
||||
templateCompressionType = templateLayer.CompressionType
|
||||
templateUIDs, templateGIDs = append([]uint32{}, templateLayer.UIDs...), append([]uint32{}, templateLayer.GIDs...)
|
||||
templateTSdata, tserr = os.ReadFile(r.tspath(templateLayer.ID))
|
||||
if tserr != nil && !os.IsNotExist(tserr) {
|
||||
return nil, -1, tserr
|
||||
templateTSdata, err = os.ReadFile(r.tspath(templateLayer.ID))
|
||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return nil, -1, err
|
||||
}
|
||||
} else {
|
||||
templateIDMappings = &idtools.IDMappings{}
|
||||
|
|
@ -1279,9 +1278,10 @@ func (r *layerStore) Put(id string, parentLayer *Layer, names []string, mountLab
|
|||
selinux.ReserveLabel(mountLabel)
|
||||
}
|
||||
|
||||
// Before actually creating the layer, make a persistent record of it with incompleteFlag,
|
||||
// so that future processes have a chance to delete it.
|
||||
layer := &Layer{
|
||||
// Before actually creating the layer, make a persistent record of it
|
||||
// with the incomplete flag set, so that future processes have a chance
|
||||
// to clean up after it.
|
||||
layer = &Layer{
|
||||
ID: id,
|
||||
Parent: parent,
|
||||
Names: names,
|
||||
|
|
@ -1295,98 +1295,109 @@ func (r *layerStore) Put(id string, parentLayer *Layer, names []string, mountLab
|
|||
CompressionType: templateCompressionType,
|
||||
UIDs: templateUIDs,
|
||||
GIDs: templateGIDs,
|
||||
Flags: make(map[string]interface{}),
|
||||
Flags: copyStringInterfaceMap(moreOptions.Flags),
|
||||
UIDMap: copyIDMap(moreOptions.UIDMap),
|
||||
GIDMap: copyIDMap(moreOptions.GIDMap),
|
||||
BigDataNames: []string{},
|
||||
volatileStore: moreOptions.Volatile,
|
||||
}
|
||||
layer.Flags[incompleteFlag] = true
|
||||
|
||||
r.layers = append(r.layers, layer)
|
||||
// This can only fail if the ID is already missing, which shouldn’t happen — and in that case the index is already in the desired state anyway.
|
||||
// This is on various paths to recover from failures, so this should be robust against partially missing data.
|
||||
// This can only fail if the ID is already missing, which shouldn’t
|
||||
// happen — and in that case the index is already in the desired state
|
||||
// anyway. This is on various paths to recover from failures, so this
|
||||
// should be robust against partially missing data.
|
||||
_ = r.idindex.Add(id)
|
||||
r.byid[id] = layer
|
||||
for _, name := range names {
|
||||
r.byname[name] = layer
|
||||
}
|
||||
for flag, value := range flags {
|
||||
layer.Flags[flag] = value
|
||||
}
|
||||
layer.Flags[incompleteFlag] = true
|
||||
|
||||
succeeded := false
|
||||
cleanupFailureContext := ""
|
||||
defer func() {
|
||||
if !succeeded {
|
||||
// On any error, try both removing the driver's data as well
|
||||
// as the in-memory layer record.
|
||||
if err2 := r.Delete(layer.ID); err2 != nil {
|
||||
if cleanupFailureContext == "" {
|
||||
cleanupFailureContext = "unknown: cleanupFailureContext not set at the failure site"
|
||||
}
|
||||
logrus.Errorf("While recovering from a failure (%s), error deleting layer %#v: %v", cleanupFailureContext, layer.ID, err2)
|
||||
if err != nil {
|
||||
// now that the in-memory structures know about the new
|
||||
// record, we can use regular Delete() to clean up if
|
||||
// anything breaks from here on out
|
||||
if cleanupFailureContext == "" {
|
||||
cleanupFailureContext = "unknown: cleanupFailureContext not set at the failure site"
|
||||
}
|
||||
if e := r.Delete(id); e != nil {
|
||||
logrus.Errorf("While recovering from a failure (%s), error deleting layer %#v: %v", cleanupFailureContext, id, e)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
err := r.saveFor(layer)
|
||||
if err != nil {
|
||||
if err = r.saveFor(layer); err != nil {
|
||||
cleanupFailureContext = "saving incomplete layer metadata"
|
||||
return nil, -1, err
|
||||
}
|
||||
|
||||
for _, item := range moreOptions.BigData {
|
||||
if err = r.setBigData(layer, item.Key, item.Data); err != nil {
|
||||
cleanupFailureContext = fmt.Sprintf("saving big data item %q", item.Key)
|
||||
return nil, -1, err
|
||||
}
|
||||
}
|
||||
|
||||
idMappings := idtools.NewIDMappingsFromMaps(moreOptions.UIDMap, moreOptions.GIDMap)
|
||||
opts := drivers.CreateOpts{
|
||||
MountLabel: mountLabel,
|
||||
StorageOpt: options,
|
||||
IDMappings: idMappings,
|
||||
}
|
||||
|
||||
if moreOptions.TemplateLayer != "" {
|
||||
if err := r.driver.CreateFromTemplate(id, moreOptions.TemplateLayer, templateIDMappings, parent, parentMappings, &opts, writeable); err != nil {
|
||||
cleanupFailureContext = "creating a layer from template"
|
||||
if err = r.driver.CreateFromTemplate(id, moreOptions.TemplateLayer, templateIDMappings, parent, parentMappings, &opts, writeable); err != nil {
|
||||
cleanupFailureContext = fmt.Sprintf("creating a layer from template layer %q", moreOptions.TemplateLayer)
|
||||
return nil, -1, fmt.Errorf("creating copy of template layer %q with ID %q: %w", moreOptions.TemplateLayer, id, err)
|
||||
}
|
||||
oldMappings = templateIDMappings
|
||||
} else {
|
||||
if writeable {
|
||||
if err := r.driver.CreateReadWrite(id, parent, &opts); err != nil {
|
||||
if err = r.driver.CreateReadWrite(id, parent, &opts); err != nil {
|
||||
cleanupFailureContext = "creating a read-write layer"
|
||||
return nil, -1, fmt.Errorf("creating read-write layer with ID %q: %w", id, err)
|
||||
}
|
||||
} else {
|
||||
if err := r.driver.Create(id, parent, &opts); err != nil {
|
||||
if err = r.driver.Create(id, parent, &opts); err != nil {
|
||||
cleanupFailureContext = "creating a read-only layer"
|
||||
return nil, -1, fmt.Errorf("creating layer with ID %q: %w", id, err)
|
||||
return nil, -1, fmt.Errorf("creating read-only layer with ID %q: %w", id, err)
|
||||
}
|
||||
}
|
||||
oldMappings = parentMappings
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(oldMappings.UIDs(), idMappings.UIDs()) || !reflect.DeepEqual(oldMappings.GIDs(), idMappings.GIDs()) {
|
||||
if err := r.driver.UpdateLayerIDMap(id, oldMappings, idMappings, mountLabel); err != nil {
|
||||
if err = r.driver.UpdateLayerIDMap(id, oldMappings, idMappings, mountLabel); err != nil {
|
||||
cleanupFailureContext = "in UpdateLayerIDMap"
|
||||
return nil, -1, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(templateTSdata) > 0 {
|
||||
if err := os.MkdirAll(filepath.Dir(r.tspath(id)), 0o700); err != nil {
|
||||
if err = os.MkdirAll(filepath.Dir(r.tspath(id)), 0o700); err != nil {
|
||||
cleanupFailureContext = "creating tar-split parent directory for a copy from template"
|
||||
return nil, -1, err
|
||||
}
|
||||
if err := ioutils.AtomicWriteFile(r.tspath(id), templateTSdata, 0o600); err != nil {
|
||||
if err = ioutils.AtomicWriteFile(r.tspath(id), templateTSdata, 0o600); err != nil {
|
||||
cleanupFailureContext = "creating a tar-split copy from template"
|
||||
return nil, -1, err
|
||||
}
|
||||
}
|
||||
|
||||
var size int64 = -1
|
||||
size = -1
|
||||
if diff != nil {
|
||||
size, err = r.applyDiffWithOptions(layer.ID, moreOptions, diff)
|
||||
if err != nil {
|
||||
if size, err = r.applyDiffWithOptions(layer.ID, moreOptions, diff); err != nil {
|
||||
cleanupFailureContext = "applying layer diff"
|
||||
return nil, -1, err
|
||||
}
|
||||
} else {
|
||||
// applyDiffWithOptions in the `diff != nil` case handles this bit for us
|
||||
// applyDiffWithOptions() would have updated r.bycompressedsum
|
||||
// and r.byuncompressedsum for us, but if we used a template
|
||||
// layer, we didn't call it, so add the new layer as candidates
|
||||
// for searches for layers by checksum
|
||||
if layer.CompressedDigest != "" {
|
||||
r.bycompressedsum[layer.CompressedDigest] = append(r.bycompressedsum[layer.CompressedDigest], layer.ID)
|
||||
}
|
||||
|
|
@ -1394,29 +1405,17 @@ func (r *layerStore) Put(id string, parentLayer *Layer, names []string, mountLab
|
|||
r.byuncompressedsum[layer.UncompressedDigest] = append(r.byuncompressedsum[layer.UncompressedDigest], layer.ID)
|
||||
}
|
||||
}
|
||||
|
||||
delete(layer.Flags, incompleteFlag)
|
||||
err = r.saveFor(layer)
|
||||
if err != nil {
|
||||
if err = r.saveFor(layer); err != nil {
|
||||
cleanupFailureContext = "saving finished layer metadata"
|
||||
return nil, -1, err
|
||||
}
|
||||
|
||||
layer = copyLayer(layer)
|
||||
succeeded = true
|
||||
return layer, size, err
|
||||
}
|
||||
|
||||
// Requires startWriting.
|
||||
func (r *layerStore) CreateWithFlags(id string, parent *Layer, names []string, mountLabel string, options map[string]string, moreOptions *LayerOptions, writeable bool, flags map[string]interface{}) (layer *Layer, err error) {
|
||||
layer, _, err = r.Put(id, parent, names, mountLabel, options, moreOptions, writeable, flags, nil)
|
||||
return layer, err
|
||||
}
|
||||
|
||||
// Requires startWriting.
|
||||
func (r *layerStore) Create(id string, parent *Layer, names []string, mountLabel string, options map[string]string, moreOptions *LayerOptions, writeable bool) (layer *Layer, err error) {
|
||||
return r.CreateWithFlags(id, parent, names, mountLabel, options, moreOptions, writeable, nil)
|
||||
}
|
||||
|
||||
// Requires startReading or startWriting.
|
||||
func (r *layerStore) Mounted(id string) (int, error) {
|
||||
if !r.lockfile.IsReadWrite() {
|
||||
|
|
@ -1677,9 +1676,6 @@ func (r *layerStore) BigData(id, key string) (io.ReadCloser, error) {
|
|||
|
||||
// Requires startWriting.
|
||||
func (r *layerStore) SetBigData(id, key string, data io.Reader) error {
|
||||
if key == "" {
|
||||
return fmt.Errorf("can't set empty name for layer big data item: %w", ErrInvalidBigDataName)
|
||||
}
|
||||
if !r.lockfile.IsReadWrite() {
|
||||
return fmt.Errorf("not allowed to save data items associated with layers at %q: %w", r.layerdir, ErrStoreIsReadOnly)
|
||||
}
|
||||
|
|
@ -1687,6 +1683,13 @@ func (r *layerStore) SetBigData(id, key string, data io.Reader) error {
|
|||
if !ok {
|
||||
return fmt.Errorf("locating layer with ID %q to write bigdata: %w", id, ErrLayerUnknown)
|
||||
}
|
||||
return r.setBigData(layer, key, data)
|
||||
}
|
||||
|
||||
func (r *layerStore) setBigData(layer *Layer, key string, data io.Reader) error {
|
||||
if key == "" {
|
||||
return fmt.Errorf("can't set empty name for layer big data item: %w", ErrInvalidBigDataName)
|
||||
}
|
||||
err := os.MkdirAll(r.datadir(layer.ID), 0700)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -1759,7 +1762,9 @@ func (r *layerStore) tspath(id string) string {
|
|||
// layerHasIncompleteFlag returns true if layer.Flags contains an incompleteFlag set to true
|
||||
// The caller must hold r.inProcessLock for reading.
|
||||
func layerHasIncompleteFlag(layer *Layer) bool {
|
||||
// layer.Flags[…] is defined to succeed and return ok == false if Flags == nil
|
||||
if layer.Flags == nil {
|
||||
return false
|
||||
}
|
||||
if flagValue, ok := layer.Flags[incompleteFlag]; ok {
|
||||
if b, ok := flagValue.(bool); ok && b {
|
||||
return true
|
||||
|
|
@ -1788,20 +1793,21 @@ func (r *layerStore) deleteInternal(id string) error {
|
|||
}
|
||||
}
|
||||
// We never unset incompleteFlag; below, we remove the entire object from r.layers.
|
||||
|
||||
id = layer.ID
|
||||
if err := r.driver.Remove(id); err != nil {
|
||||
if err := r.driver.Remove(id); err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
|
||||
os.Remove(r.tspath(id))
|
||||
os.RemoveAll(r.datadir(id))
|
||||
delete(r.byid, id)
|
||||
for _, name := range layer.Names {
|
||||
delete(r.byname, name)
|
||||
}
|
||||
// This can only fail if the ID is already missing, which shouldn’t happen — and in that case the index is already in the desired state anyway.
|
||||
// The store’s Delete method is used on various paths to recover from failures, so this should be robust against partially missing data.
|
||||
// This can only fail if the ID is already missing, which shouldn’t
|
||||
// happen — and in that case the index is already in the desired state
|
||||
// anyway. The store’s Delete method is used on various paths to
|
||||
// recover from failures, so this should be robust against partially
|
||||
// missing data.
|
||||
_ = r.idindex.Delete(id)
|
||||
mountLabel := layer.MountLabel
|
||||
if layer.MountPoint != "" {
|
||||
|
|
@ -1835,7 +1841,6 @@ func (r *layerStore) deleteInternal(id string) error {
|
|||
selinux.ReleaseLabel(mountLabel)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
237
store.go
237
store.go
|
|
@ -506,10 +506,13 @@ type Store interface {
|
|||
// GetDigestLock returns digest-specific Locker.
|
||||
GetDigestLock(digest.Digest) (Locker, error)
|
||||
|
||||
// LayerFromAdditionalLayerStore searches layers from the additional layer store and
|
||||
// returns the object for handling this. Note that this hasn't been stored to this store
|
||||
// yet so this needs to be done through PutAs method.
|
||||
// Releasing AdditionalLayer handler is caller's responsibility.
|
||||
// LayerFromAdditionalLayerStore searches the additional layer store and returns an object
|
||||
// which can create a layer with the specified digest associated with the specified image
|
||||
// reference. Note that this hasn't been stored to this store yet: the actual creation of
|
||||
// a usable layer is done by calling the returned object's PutAs() method. After creating
|
||||
// a layer, the caller must then call the object's Release() method to free any temporary
|
||||
// resources which were allocated for the object by this method or the object's PutAs()
|
||||
// method.
|
||||
// This API is experimental and can be changed without bumping the major version number.
|
||||
LookupAdditionalLayer(d digest.Digest, imageref string) (AdditionalLayer, error)
|
||||
|
||||
|
|
@ -562,6 +565,17 @@ type LayerOptions struct {
|
|||
UncompressedDigest digest.Digest
|
||||
// True is the layer info can be treated as volatile
|
||||
Volatile bool
|
||||
// BigData is a set of items which should be stored with the layer.
|
||||
BigData []LayerBigDataOption
|
||||
// Flags is a set of named flags and their values to store with the layer.
|
||||
// Currently these can only be set when the layer record is created, but that
|
||||
// could change in the future.
|
||||
Flags map[string]interface{}
|
||||
}
|
||||
|
||||
type LayerBigDataOption struct {
|
||||
Key string
|
||||
Data io.Reader
|
||||
}
|
||||
|
||||
// ImageOptions is used for passing options to a Store's CreateImage() method.
|
||||
|
|
@ -571,6 +585,26 @@ type ImageOptions struct {
|
|||
CreationDate time.Time
|
||||
// Digest is a hard-coded digest value that we can use to look up the image. It is optional.
|
||||
Digest digest.Digest
|
||||
// Digests is a list of digest values of the image's manifests, and
|
||||
// possibly a manually-specified value, that we can use to locate the
|
||||
// image. If Digest is set, its value is also in this list.
|
||||
Digests []digest.Digest
|
||||
// Metadata is caller-specified metadata associated with the layer.
|
||||
Metadata string
|
||||
// BigData is a set of items which should be stored with the image.
|
||||
BigData []ImageBigDataOption
|
||||
// NamesHistory is used for guessing for what this image was named when a container was created based
|
||||
// on it, but it no longer has any names.
|
||||
NamesHistory []string
|
||||
// Flags is a set of named flags and their values to store with the image. Currently these can only
|
||||
// be set when the image record is created, but that could change in the future.
|
||||
Flags map[string]interface{}
|
||||
}
|
||||
|
||||
type ImageBigDataOption struct {
|
||||
Key string
|
||||
Data []byte
|
||||
Digest digest.Digest
|
||||
}
|
||||
|
||||
// ContainerOptions is used for passing options to a Store's CreateContainer() method.
|
||||
|
|
@ -580,11 +614,23 @@ type ContainerOptions struct {
|
|||
// container's layer will inherit settings from the image's top layer
|
||||
// or, if it is not being created based on an image, the Store object.
|
||||
types.IDMappingOptions
|
||||
LabelOpts []string
|
||||
LabelOpts []string
|
||||
// Flags is a set of named flags and their values to store with the container.
|
||||
// Currently these can only be set when the container record is created, but that
|
||||
// could change in the future.
|
||||
Flags map[string]interface{}
|
||||
MountOpts []string
|
||||
Volatile bool
|
||||
StorageOpt map[string]string
|
||||
// Metadata is caller-specified metadata associated with the container.
|
||||
Metadata string
|
||||
// BigData is a set of items which should be stored for the container.
|
||||
BigData []ContainerBigDataOption
|
||||
}
|
||||
|
||||
type ContainerBigDataOption struct {
|
||||
Key string
|
||||
Data []byte
|
||||
}
|
||||
|
||||
type store struct {
|
||||
|
|
@ -1221,7 +1267,7 @@ func canUseShifting(store rwLayerStore, uidmap, gidmap []idtools.IDMap) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func (s *store) PutLayer(id, parent string, names []string, mountLabel string, writeable bool, options *LayerOptions, diff io.Reader) (*Layer, int64, error) {
|
||||
func (s *store) PutLayer(id, parent string, names []string, mountLabel string, writeable bool, lOptions *LayerOptions, diff io.Reader) (*Layer, int64, error) {
|
||||
var parentLayer *Layer
|
||||
rlstore, rlstores, err := s.bothLayerStoreKinds()
|
||||
if err != nil {
|
||||
|
|
@ -1235,8 +1281,11 @@ func (s *store) PutLayer(id, parent string, names []string, mountLabel string, w
|
|||
return nil, -1, err
|
||||
}
|
||||
defer s.containerStore.stopWriting()
|
||||
if options == nil {
|
||||
options = &LayerOptions{}
|
||||
var options LayerOptions
|
||||
if lOptions != nil {
|
||||
options = *lOptions
|
||||
options.BigData = copyLayerBigDataOptionSlice(lOptions.BigData)
|
||||
options.Flags = copyStringInterfaceMap(lOptions.Flags)
|
||||
}
|
||||
if options.HostUIDMapping {
|
||||
options.UIDMap = nil
|
||||
|
|
@ -1303,7 +1352,7 @@ func (s *store) PutLayer(id, parent string, names []string, mountLabel string, w
|
|||
GIDMap: copyIDMap(gidMap),
|
||||
}
|
||||
}
|
||||
return rlstore.Put(id, parentLayer, names, mountLabel, nil, &layerOptions, writeable, nil, diff)
|
||||
return rlstore.create(id, parentLayer, names, mountLabel, nil, &layerOptions, writeable, diff)
|
||||
}
|
||||
|
||||
func (s *store) CreateLayer(id, parent string, names []string, mountLabel string, writeable bool, options *LayerOptions) (*Layer, error) {
|
||||
|
|
@ -1311,7 +1360,7 @@ func (s *store) CreateLayer(id, parent string, names []string, mountLabel string
|
|||
return layer, err
|
||||
}
|
||||
|
||||
func (s *store) CreateImage(id string, names []string, layer, metadata string, options *ImageOptions) (*Image, error) {
|
||||
func (s *store) CreateImage(id string, names []string, layer, metadata string, iOptions *ImageOptions) (*Image, error) {
|
||||
if layer != "" {
|
||||
layerStores, err := s.allLayerStores()
|
||||
if err != nil {
|
||||
|
|
@ -1337,13 +1386,22 @@ func (s *store) CreateImage(id string, names []string, layer, metadata string, o
|
|||
|
||||
var res *Image
|
||||
err := s.writeToImageStore(func() error {
|
||||
creationDate := time.Now().UTC()
|
||||
if options != nil && !options.CreationDate.IsZero() {
|
||||
creationDate = options.CreationDate
|
||||
var options ImageOptions
|
||||
|
||||
if iOptions != nil {
|
||||
options = *iOptions
|
||||
options.Digests = copyDigestSlice(iOptions.Digests)
|
||||
options.BigData = copyImageBigDataOptionSlice(iOptions.BigData)
|
||||
options.NamesHistory = copyStringSlice(iOptions.NamesHistory)
|
||||
options.Flags = copyStringInterfaceMap(iOptions.Flags)
|
||||
}
|
||||
if options.CreationDate.IsZero() {
|
||||
options.CreationDate = time.Now().UTC()
|
||||
}
|
||||
options.Metadata = metadata
|
||||
|
||||
var err error
|
||||
res, err = s.imageStore.Create(id, names, layer, metadata, creationDate, options.Digest)
|
||||
res, err = s.imageStore.create(id, names, layer, options)
|
||||
return err
|
||||
})
|
||||
return res, err
|
||||
|
|
@ -1426,26 +1484,22 @@ func (s *store) imageTopLayerForMapping(image *Image, ristore roImageStore, rlst
|
|||
// mappings, and register it as an alternate top layer in the image.
|
||||
var layerOptions LayerOptions
|
||||
if canUseShifting(rlstore, options.UIDMap, options.GIDMap) {
|
||||
layerOptions = LayerOptions{
|
||||
IDMappingOptions: types.IDMappingOptions{
|
||||
HostUIDMapping: true,
|
||||
HostGIDMapping: true,
|
||||
UIDMap: nil,
|
||||
GIDMap: nil,
|
||||
},
|
||||
layerOptions.IDMappingOptions = types.IDMappingOptions{
|
||||
HostUIDMapping: true,
|
||||
HostGIDMapping: true,
|
||||
UIDMap: nil,
|
||||
GIDMap: nil,
|
||||
}
|
||||
} else {
|
||||
layerOptions = LayerOptions{
|
||||
IDMappingOptions: types.IDMappingOptions{
|
||||
HostUIDMapping: options.HostUIDMapping,
|
||||
HostGIDMapping: options.HostGIDMapping,
|
||||
UIDMap: copyIDMap(options.UIDMap),
|
||||
GIDMap: copyIDMap(options.GIDMap),
|
||||
},
|
||||
layerOptions.IDMappingOptions = types.IDMappingOptions{
|
||||
HostUIDMapping: options.HostUIDMapping,
|
||||
HostGIDMapping: options.HostGIDMapping,
|
||||
UIDMap: copyIDMap(options.UIDMap),
|
||||
GIDMap: copyIDMap(options.GIDMap),
|
||||
}
|
||||
}
|
||||
layerOptions.TemplateLayer = layer.ID
|
||||
mappedLayer, _, err := rlstore.Put("", parentLayer, nil, layer.MountLabel, nil, &layerOptions, false, nil, nil)
|
||||
mappedLayer, _, err := rlstore.create("", parentLayer, nil, layer.MountLabel, nil, &layerOptions, false, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating an ID-mapped copy of layer %q: %w", layer.ID, err)
|
||||
}
|
||||
|
|
@ -1459,9 +1513,17 @@ func (s *store) imageTopLayerForMapping(image *Image, ristore roImageStore, rlst
|
|||
return mappedLayer, nil
|
||||
}
|
||||
|
||||
func (s *store) CreateContainer(id string, names []string, image, layer, metadata string, options *ContainerOptions) (*Container, error) {
|
||||
if options == nil {
|
||||
options = &ContainerOptions{}
|
||||
func (s *store) CreateContainer(id string, names []string, image, layer, metadata string, cOptions *ContainerOptions) (*Container, error) {
|
||||
var options ContainerOptions
|
||||
if cOptions != nil {
|
||||
options = *cOptions
|
||||
options.IDMappingOptions.UIDMap = copyIDMap(cOptions.IDMappingOptions.UIDMap)
|
||||
options.IDMappingOptions.GIDMap = copyIDMap(cOptions.IDMappingOptions.GIDMap)
|
||||
options.LabelOpts = copyStringSlice(cOptions.LabelOpts)
|
||||
options.Flags = copyStringInterfaceMap(cOptions.Flags)
|
||||
options.MountOpts = copyStringSlice(cOptions.MountOpts)
|
||||
options.StorageOpt = copyStringStringMap(cOptions.StorageOpt)
|
||||
options.BigData = copyContainerBigDataOptionSlice(cOptions.BigData)
|
||||
}
|
||||
if options.HostUIDMapping {
|
||||
options.UIDMap = nil
|
||||
|
|
@ -1469,6 +1531,7 @@ func (s *store) CreateContainer(id string, names []string, image, layer, metadat
|
|||
if options.HostGIDMapping {
|
||||
options.GIDMap = nil
|
||||
}
|
||||
options.Metadata = metadata
|
||||
rlstore, lstores, err := s.bothLayerStoreKinds() // lstores will be locked read-only if image != ""
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -1574,22 +1637,19 @@ func (s *store) CreateContainer(id string, names []string, image, layer, metadat
|
|||
Volatile: options.Volatile || s.transientStore,
|
||||
}
|
||||
if canUseShifting(rlstore, uidMap, gidMap) {
|
||||
layerOptions.IDMappingOptions =
|
||||
types.IDMappingOptions{
|
||||
HostUIDMapping: true,
|
||||
HostGIDMapping: true,
|
||||
UIDMap: nil,
|
||||
GIDMap: nil,
|
||||
}
|
||||
layerOptions.IDMappingOptions = types.IDMappingOptions{
|
||||
HostUIDMapping: true,
|
||||
HostGIDMapping: true,
|
||||
UIDMap: nil,
|
||||
GIDMap: nil,
|
||||
}
|
||||
} else {
|
||||
layerOptions.IDMappingOptions =
|
||||
types.IDMappingOptions{
|
||||
HostUIDMapping: idMappingsOptions.HostUIDMapping,
|
||||
HostGIDMapping: idMappingsOptions.HostGIDMapping,
|
||||
UIDMap: copyIDMap(uidMap),
|
||||
GIDMap: copyIDMap(gidMap),
|
||||
}
|
||||
|
||||
layerOptions.IDMappingOptions = types.IDMappingOptions{
|
||||
HostUIDMapping: idMappingsOptions.HostUIDMapping,
|
||||
HostGIDMapping: idMappingsOptions.HostGIDMapping,
|
||||
UIDMap: copyIDMap(uidMap),
|
||||
GIDMap: copyIDMap(gidMap),
|
||||
}
|
||||
}
|
||||
if options.Flags == nil {
|
||||
options.Flags = make(map[string]interface{})
|
||||
|
|
@ -1610,7 +1670,7 @@ func (s *store) CreateContainer(id string, names []string, image, layer, metadat
|
|||
options.Flags[mountLabelFlag] = mountLabel
|
||||
}
|
||||
|
||||
clayer, err := rlstore.Create(layer, imageTopLayer, nil, mlabel, options.StorageOpt, layerOptions, true)
|
||||
clayer, _, err := rlstore.create(layer, imageTopLayer, nil, mlabel, options.StorageOpt, layerOptions, true, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1630,7 +1690,7 @@ func (s *store) CreateContainer(id string, names []string, image, layer, metadat
|
|||
GIDMap: copyIDMap(options.GIDMap),
|
||||
}
|
||||
var err error
|
||||
container, err = s.containerStore.Create(id, names, imageID, layer, metadata, options)
|
||||
container, err = s.containerStore.create(id, names, imageID, layer, &options)
|
||||
if err != nil || container == nil {
|
||||
if err2 := rlstore.Delete(layer); err2 != nil {
|
||||
if err == nil {
|
||||
|
|
@ -2117,7 +2177,8 @@ func (s *store) updateNames(id string, names []string, op updateNameOperation) e
|
|||
return s.imageStore.updateNames(id, deduped, op)
|
||||
}
|
||||
|
||||
// Check is id refers to a RO Store
|
||||
// Check if the id refers to a read-only image store -- we want to allow images in
|
||||
// read-only stores to have their names changed.
|
||||
for _, is := range s.roImageStores {
|
||||
store := is
|
||||
if err := store.startReading(); err != nil {
|
||||
|
|
@ -2125,12 +2186,35 @@ func (s *store) updateNames(id string, names []string, op updateNameOperation) e
|
|||
}
|
||||
defer store.stopReading()
|
||||
if i, err := store.Get(id); err == nil {
|
||||
if len(deduped) > 1 {
|
||||
// Do not want to create image name in R/W storage
|
||||
deduped = deduped[1:]
|
||||
// "pull up" the image so that we can change its names list
|
||||
options := ImageOptions{
|
||||
Metadata: i.Metadata,
|
||||
CreationDate: i.Created,
|
||||
Digest: i.Digest,
|
||||
Digests: copyDigestSlice(i.Digests),
|
||||
NamesHistory: copyStringSlice(i.NamesHistory),
|
||||
}
|
||||
_, err := s.imageStore.Create(id, deduped, i.TopLayer, i.Metadata, i.Created, i.Digest)
|
||||
return err
|
||||
for _, key := range i.BigDataNames {
|
||||
data, err := store.BigData(id, key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dataDigest, err := store.BigDataDigest(id, key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
options.BigData = append(options.BigData, ImageBigDataOption{
|
||||
Key: key,
|
||||
Data: data,
|
||||
Digest: dataDigest,
|
||||
})
|
||||
}
|
||||
_, err = s.imageStore.create(id, i.Names, i.TopLayer, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// now make the changes to the writeable image record's names list
|
||||
return s.imageStore.updateNames(id, deduped, op)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2962,6 +3046,16 @@ func (s *store) Image(id string) (*Image, error) {
|
|||
if done, err := s.readAllImageStores(func(store roImageStore) (bool, error) {
|
||||
image, err := store.Get(id)
|
||||
if err == nil {
|
||||
if store != s.imageStore {
|
||||
// found it in a read-only store - readAllImageStores() still has the writeable store locked for reading
|
||||
if _, localErr := s.imageStore.Get(image.ID); localErr == nil {
|
||||
// if the lookup key was a name, and we found the image in a read-only
|
||||
// store, but we have an entry with the same ID in the read-write store,
|
||||
// then the name was removed when we duplicated the image's
|
||||
// record into writable storage, so we should ignore this entry
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
res = image
|
||||
return true, nil
|
||||
}
|
||||
|
|
@ -3247,6 +3341,14 @@ func copyStringDigestMap(m map[string]digest.Digest) map[string]digest.Digest {
|
|||
return ret
|
||||
}
|
||||
|
||||
func copyStringStringMap(m map[string]string) map[string]string {
|
||||
ret := make(map[string]string, len(m))
|
||||
for k, v := range m {
|
||||
ret[k] = v
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func copyDigestSlice(slice []digest.Digest) []digest.Digest {
|
||||
if len(slice) == 0 {
|
||||
return nil
|
||||
|
|
@ -3266,6 +3368,31 @@ func copyStringInterfaceMap(m map[string]interface{}) map[string]interface{} {
|
|||
return ret
|
||||
}
|
||||
|
||||
func copyLayerBigDataOptionSlice(slice []LayerBigDataOption) []LayerBigDataOption {
|
||||
ret := make([]LayerBigDataOption, len(slice))
|
||||
copy(ret, slice)
|
||||
return ret
|
||||
}
|
||||
|
||||
func copyImageBigDataOptionSlice(slice []ImageBigDataOption) []ImageBigDataOption {
|
||||
ret := make([]ImageBigDataOption, len(slice))
|
||||
for i := range slice {
|
||||
ret[i].Key = slice[i].Key
|
||||
ret[i].Data = append([]byte{}, slice[i].Data...)
|
||||
ret[i].Digest = slice[i].Digest
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func copyContainerBigDataOptionSlice(slice []ContainerBigDataOption) []ContainerBigDataOption {
|
||||
ret := make([]ContainerBigDataOption, len(slice))
|
||||
for i := range slice {
|
||||
ret[i].Key = slice[i].Key
|
||||
ret[i].Data = append([]byte{}, slice[i].Data...)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// AutoUserNsMinSize is the minimum size for automatically created user namespaces
|
||||
const AutoUserNsMinSize = 1024
|
||||
|
||||
|
|
|
|||
107
tests/names.bats
107
tests/names.bats
|
|
@ -404,6 +404,113 @@ check-for-name() {
|
|||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "add-names: ro-images" {
|
||||
case "$STORAGE_DRIVER" in
|
||||
overlay*|vfs)
|
||||
;;
|
||||
*)
|
||||
skip "not supported by driver $STORAGE_DRIVER"
|
||||
;;
|
||||
esac
|
||||
|
||||
mkdir ${TESTDIR}/{ro-root,ro-runroot}
|
||||
|
||||
# Create a layer.
|
||||
run storage --debug=false --graph ${TESTDIR}/ro-root --run ${TESTDIR}/ro-runroot create-layer
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" != "" ]
|
||||
layer=$output
|
||||
|
||||
# Create an image with names that uses that layer.
|
||||
run storage --debug=false --graph ${TESTDIR}/ro-root --run ${TESTDIR}/ro-runroot create-image -n fooimage -n barimage $layer
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" != "" ]
|
||||
image=${output%% *}
|
||||
|
||||
storage --debug=false --graph ${TESTDIR}/ro-root --run ${TESTDIR}/ro-runroot shutdown
|
||||
|
||||
# Check that we can find the image by ID and by its names.
|
||||
run storage --storage-opt ${STORAGE_DRIVER}.imagestore=${TESTDIR}/ro-root exists -i $image
|
||||
[ "$status" -eq 0 ]
|
||||
run storage --storage-opt ${STORAGE_DRIVER}.imagestore=${TESTDIR}/ro-root exists -i fooimage
|
||||
[ "$status" -eq 0 ]
|
||||
run storage --storage-opt ${STORAGE_DRIVER}.imagestore=${TESTDIR}/ro-root exists -i barimage
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
# Add a pair of names to the image.
|
||||
run storage --storage-opt ${STORAGE_DRIVER}.imagestore=${TESTDIR}/ro-root add-names -n newimage -n otherimage $image
|
||||
echo "add-names:" "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
# Check that all of the names are resolveable.
|
||||
run storage --storage-opt ${STORAGE_DRIVER}.imagestore=${TESTDIR}/ro-root images
|
||||
run storage --storage-opt ${STORAGE_DRIVER}.imagestore=${TESTDIR}/ro-root exists -i $image
|
||||
[ "$status" -eq 0 ]
|
||||
run storage --storage-opt ${STORAGE_DRIVER}.imagestore=${TESTDIR}/ro-root exists -i fooimage
|
||||
[ "$status" -eq 0 ]
|
||||
run storage --storage-opt ${STORAGE_DRIVER}.imagestore=${TESTDIR}/ro-root exists -i barimage
|
||||
[ "$status" -eq 0 ]
|
||||
run storage --storage-opt ${STORAGE_DRIVER}.imagestore=${TESTDIR}/ro-root exists -i newimage
|
||||
[ "$status" -eq 0 ]
|
||||
run storage --storage-opt ${STORAGE_DRIVER}.imagestore=${TESTDIR}/ro-root exists -i otherimage
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "remove-names: ro-images" {
|
||||
case "$STORAGE_DRIVER" in
|
||||
overlay*|vfs)
|
||||
;;
|
||||
*)
|
||||
skip "not supported by driver $STORAGE_DRIVER"
|
||||
;;
|
||||
esac
|
||||
|
||||
mkdir ${TESTDIR}/{ro-root,ro-runroot}
|
||||
|
||||
# Create a layer.
|
||||
run storage --debug=false --graph ${TESTDIR}/ro-root --run ${TESTDIR}/ro-runroot create-layer
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" != "" ]
|
||||
layer=$output
|
||||
|
||||
# Create an image with names that uses that layer.
|
||||
run storage --debug=false --graph ${TESTDIR}/ro-root --run ${TESTDIR}/ro-runroot create-image -n fooimage -n barimage -n newimage -n otherimage $layer
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" != "" ]
|
||||
image=${output%% *}
|
||||
|
||||
storage --debug=false --graph ${TESTDIR}/ro-root --run ${TESTDIR}/ro-runroot shutdown
|
||||
|
||||
# Check that we can find the image by ID and by its names.
|
||||
run storage --storage-opt ${STORAGE_DRIVER}.imagestore=${TESTDIR}/ro-root exists -i $image
|
||||
[ "$status" -eq 0 ]
|
||||
run storage --storage-opt ${STORAGE_DRIVER}.imagestore=${TESTDIR}/ro-root exists -i fooimage
|
||||
[ "$status" -eq 0 ]
|
||||
run storage --storage-opt ${STORAGE_DRIVER}.imagestore=${TESTDIR}/ro-root exists -i barimage
|
||||
[ "$status" -eq 0 ]
|
||||
run storage --storage-opt ${STORAGE_DRIVER}.imagestore=${TESTDIR}/ro-root exists -i newimage
|
||||
[ "$status" -eq 0 ]
|
||||
run storage --storage-opt ${STORAGE_DRIVER}.imagestore=${TESTDIR}/ro-root exists -i otherimage
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
# Remove one of the names from the image.
|
||||
run storage --storage-opt ${STORAGE_DRIVER}.imagestore=${TESTDIR}/ro-root remove-names -n newimage $image
|
||||
echo "remove-names:" "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
# Check that all of the names are still resolveable, except for the one we removed.
|
||||
run storage --storage-opt ${STORAGE_DRIVER}.imagestore=${TESTDIR}/ro-root exists -i $image
|
||||
[ "$status" -eq 0 ]
|
||||
run storage --storage-opt ${STORAGE_DRIVER}.imagestore=${TESTDIR}/ro-root exists -i fooimage
|
||||
[ "$status" -eq 0 ]
|
||||
run storage --storage-opt ${STORAGE_DRIVER}.imagestore=${TESTDIR}/ro-root exists -i barimage
|
||||
[ "$status" -eq 0 ]
|
||||
run storage --storage-opt ${STORAGE_DRIVER}.imagestore=${TESTDIR}/ro-root exists -i newimage
|
||||
[ "$status" -ne 0 ]
|
||||
run storage --storage-opt ${STORAGE_DRIVER}.imagestore=${TESTDIR}/ro-root exists -i otherimage
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "names at creation: containers" {
|
||||
# Create a layer.
|
||||
run storage --debug=false create-layer
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@ outer:
|
|||
|
||||
// We need to create a temporary layer so we can mount it and lookup the
|
||||
// maximum IDs used.
|
||||
clayer, err := rlstore.Create("", topLayer, nil, "", nil, layerOptions, false)
|
||||
clayer, _, err := rlstore.create("", topLayer, nil, "", nil, layerOptions, false, nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue