Test helper: add "unshare"/"create-storage-layer"/"storage-layers"

Add "unshare", "create-storage-layer", and "storage-layers" commands to
the test helper, along with a "-U" flag to have it unshare when handling
a given command.

Add "-o" as an alias for the "--owner" flag to "copy".

Add "-r" as an alias for the "--ro" flag to "mount".

Add a "-q" flag to "layers" so that we can list just the IDs.

Drop mention of a couple of not-implemented options from
docs/containers-storage-create-layer.md.

Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
This commit is contained in:
Nalin Dahyabhai 2023-04-10 09:31:38 -04:00
parent 9d49da5f09
commit 3007ac6efb
10 changed files with 205 additions and 12 deletions

View File

@ -104,7 +104,7 @@ func init() {
maxArgs: -1,
action: copyContent,
addFlags: func(flags *mflag.FlagSet, cmd *command) {
flags.StringVar(&chownOptions, []string{"-chown", ""}, chownOptions, "Set owner on new copies")
flags.StringVar(&chownOptions, []string{"-chown", "o"}, chownOptions, "Set owner on new copies")
flags.BoolVar(&jsonOutput, []string{"-json", "j"}, jsonOutput, "Prefer JSON output")
},
})

View File

@ -1,14 +1,17 @@
package main
import (
"encoding/json"
"fmt"
"io"
"os"
"github.com/containers/storage"
graphdriver "github.com/containers/storage/drivers"
"github.com/containers/storage/internal/opts"
"github.com/containers/storage/pkg/idtools"
"github.com/containers/storage/pkg/mflag"
"github.com/containers/storage/pkg/stringid"
"github.com/containers/storage/types"
digest "github.com/opencontainers/go-digest"
)
@ -71,6 +74,45 @@ func paramIDMapping() (*types.IDMappingOptions, error) {
return &options, nil
}
func createStorageLayer(flags *mflag.FlagSet, action string, m storage.Store, args []string) (int, error) {
parent := ""
if len(args) > 0 {
parent = args[0]
}
mappings, err := paramIDMapping()
if err != nil {
return 1, err
}
driver, err := m.GraphDriver()
if err != nil {
return 1, err
}
opts := graphdriver.CreateOpts{
MountLabel: paramMountLabel,
IDMappings: idtools.NewIDMappingsFromMaps(mappings.UIDMap, mappings.GIDMap),
}
if paramID == "" {
paramID = stringid.GenerateNonCryptoID()
}
if paramCreateRO {
if err := driver.Create(paramID, parent, &opts); err != nil {
return 1, err
}
} else {
if err := driver.CreateReadWrite(paramID, parent, &opts); err != nil {
return 1, err
}
}
if jsonOutput {
if err := json.NewEncoder(os.Stdout).Encode(paramID); err != nil {
return 1, err
}
} else {
fmt.Printf("%s\n", paramID)
}
return 0, nil
}
func createLayer(flags *mflag.FlagSet, action string, m storage.Store, args []string) (int, error) {
parent := ""
if len(args) > 0 {
@ -194,6 +236,26 @@ func createContainer(flags *mflag.FlagSet, action string, m storage.Store, args
}
func init() {
commands = append(commands, command{
names: []string{"create-storage-layer"},
optionsHelp: "[options [...]] [parentLayerNameOrID]",
usage: "Create a new layer only in the storage driver",
minArgs: 0,
maxArgs: 1,
action: createStorageLayer,
addFlags: func(flags *mflag.FlagSet, cmd *command) {
flags.StringVar(&paramMountLabel, []string{"-label", "l"}, "", "Mount Label")
flags.StringVar(&paramID, []string{"-id", "i"}, "", "Layer ID")
flags.BoolVar(&paramCreateRO, []string{"-readonly", "r"}, false, "Mark as read-only")
flags.BoolVar(&jsonOutput, []string{"-json", "j"}, jsonOutput, "Prefer JSON output")
flags.BoolVar(&paramHostUIDMap, []string{"-hostuidmap"}, paramHostUIDMap, "Force host UID map")
flags.BoolVar(&paramHostGIDMap, []string{"-hostgidmap"}, paramHostGIDMap, "Force host GID map")
flags.StringVar(&paramUIDMap, []string{"-uidmap"}, "", "UID map")
flags.StringVar(&paramGIDMap, []string{"-gidmap"}, "", "GID map")
flags.StringVar(&paramSubUIDMap, []string{"-subuidmap"}, "", "subuid UID map for a user")
flags.StringVar(&paramSubGIDMap, []string{"-subgidmap"}, "", "subgid GID map for a group")
},
})
commands = append(commands, command{
names: []string{"create-layer", "createlayer"},
optionsHelp: "[options [...]] [parentLayerNameOrID]",

View File

@ -1,13 +1,39 @@
package main
import (
"encoding/json"
"fmt"
"os"
"github.com/containers/storage"
"github.com/containers/storage/pkg/mflag"
)
var listLayersTree = false
var (
listLayersTree = false
listLayersQuick = false
)
func storageLayers(flags *mflag.FlagSet, action string, m storage.Store, args []string) (int, error) {
driver, err := m.GraphDriver()
if err != nil {
return 1, err
}
layers, err := driver.ListLayers()
if err != nil {
return 1, err
}
if jsonOutput {
if err := json.NewEncoder(os.Stdout).Encode(layers); err != nil {
return 1, err
}
return 0, nil
}
for _, layer := range layers {
fmt.Printf("%s\n", layer)
}
return 0, nil
}
func layers(flags *mflag.FlagSet, action string, m storage.Store, args []string) (int, error) {
layers, err := m.Layers()
@ -71,6 +97,9 @@ func layers(flags *mflag.FlagSet, action string, m storage.Store, args []string)
nodes = append(nodes, node)
} else {
fmt.Printf("%s\n", layer.ID)
if listLayersQuick {
continue
}
for _, name := range layer.Names {
fmt.Printf("\tname: %s\n", name)
}
@ -106,6 +135,18 @@ func init() {
maxArgs: 0,
addFlags: func(flags *mflag.FlagSet, cmd *command) {
flags.BoolVar(&listLayersTree, []string{"-tree", "t"}, listLayersTree, "Use a tree")
flags.BoolVar(&listLayersQuick, []string{"-quick", "q"}, listLayersTree, "Just the IDs")
flags.BoolVar(&jsonOutput, []string{"-json", "j"}, jsonOutput, "Prefer JSON output")
},
})
commands = append(commands, command{
names: []string{"storage-layers"},
optionsHelp: "[options [...]]",
usage: "List storage layers",
action: storageLayers,
minArgs: 0,
maxArgs: 0,
addFlags: func(flags *mflag.FlagSet, cmd *command) {
flags.BoolVar(&jsonOutput, []string{"-json", "j"}, jsonOutput, "Prefer JSON output")
},
})

View File

@ -9,6 +9,7 @@ import (
"github.com/containers/storage/internal/opts"
"github.com/containers/storage/pkg/mflag"
"github.com/containers/storage/pkg/reexec"
"github.com/containers/storage/pkg/unshare"
"github.com/containers/storage/types"
"github.com/sirupsen/logrus"
)
@ -36,6 +37,7 @@ func main() {
options := types.StoreOptions{}
debug := false
doUnshare := false
makeFlags := func(command string, eh mflag.ErrorHandling) *mflag.FlagSet {
flags := mflag.NewFlagSet(command, eh)
@ -45,6 +47,7 @@ func main() {
flags.StringVar(&options.GraphDriverName, []string{"-storage-driver", "s"}, options.GraphDriverName, "Storage driver to use ($STORAGE_DRIVER)")
flags.Var(opts.NewListOptsRef(&options.GraphDriverOptions, nil), []string{"-storage-opt"}, "Set storage driver options ($STORAGE_OPTS)")
flags.BoolVar(&debug, []string{"-debug", "D"}, debug, "Print debugging information")
flags.BoolVar(&doUnshare, []string{"-unshare", "U"}, unshare.IsRootless(), fmt.Sprintf("Run in a user namespace (default %t)", unshare.IsRootless()))
return flags
}
@ -68,7 +71,6 @@ func main() {
flags.Usage()
os.Exit(1)
}
if options.GraphRoot == "" && options.RunRoot == "" && options.GraphDriverName == "" && len(options.GraphDriverOptions) == 0 {
options, _ = types.DefaultStoreOptionsAutoDetectUID()
}
@ -112,6 +114,9 @@ func main() {
flags.Usage()
os.Exit(1)
}
if doUnshare {
unshare.MaybeReexecUsingUserNamespace(true)
}
if debug {
logrus.SetLevel(logrus.DebugLevel)
logrus.Debugf("Root: %s", options.GraphRoot)

View File

@ -138,7 +138,7 @@ func init() {
addFlags: func(flags *mflag.FlagSet, cmd *command) {
flags.StringVar(&paramMountOptions, []string{"-opt", "o"}, "", "Mount Options")
flags.StringVar(&paramMountLabel, []string{"-label", "l"}, "", "Mount Label")
flags.BoolVar(&paramReadOnly, []string{"-ro", "r"}, paramReadOnly, "Mount image readonly")
flags.BoolVar(&paramReadOnly, []string{"-read-only", "-ro", "r"}, paramReadOnly, "Mount image readonly")
flags.BoolVar(&jsonOutput, []string{"-json", "j"}, jsonOutput, "Prefer JSON output")
},
})

View File

@ -0,0 +1,38 @@
package main
import (
"os"
"os/exec"
"github.com/containers/storage"
"github.com/containers/storage/pkg/mflag"
"github.com/containers/storage/pkg/unshare"
)
func unshareFn(flags *mflag.FlagSet, action string, m storage.Store, args []string) (int, error) {
unshare.MaybeReexecUsingUserNamespace(true)
if len(args) == 0 {
shell := os.Getenv("SHELL")
if shell == "" {
shell = "/bin/sh"
}
args = []string{shell}
}
cmd := exec.Command(args[0], args[1:]...)
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
if err := cmd.Run(); err != nil {
return 1, err
}
return 0, nil
}
func init() {
commands = append(commands, command{
names: []string{"unshare"},
usage: "Run a command in a user namespace",
optionsHelp: "[command [...]]",
minArgs: 0,
maxArgs: -1,
action: unshareFn,
})
}

View File

@ -20,14 +20,6 @@ returned.
Sets the ID for the layer. If none is specified, one is generated.
**-m | --metadata** *metadata-value*
Sets the metadata for the layer to the specified value.
**-f | --metadata-file** *metadata-file*
Sets the metadata for the layer to the contents of the specified file.
**-l | --label** *mount-label*
Sets the label which should be assigned as an SELinux context when mounting the

View File

@ -0,0 +1,29 @@
## containers-storage-create-storage-layer 1 "September 2022"
## NAME
containers-storage create-storage-layer - Create a layer in a lower-level storage driver
## SYNOPSIS
**containers-storage** **create-storage-layer** [*options* [...]] [*parentLayerNameOrID*]
## DESCRIPTION
Creates a new layer using the lower-level storage driver which either has a
specified layer as its parent, or if no parent is specified, is empty.
## OPTIONS
**-i | --id** *ID*
Sets the ID for the layer. If none is specified, one is generated.
**-l | --label** *mount-label*
Sets the label which should be assigned as an SELinux context when mounting the
layer.
## EXAMPLE
**containers-storage create-storage-layer somelayer**
## SEE ALSO
containers-storage-create-container(1)
containers-storage-create-image(1)
containers-storage-delete-layer(1)

View File

@ -0,0 +1,17 @@
## containers-storage-unshare 1 "September 2022"
## NAME
containers-storage unshare - Run a command in a user namespace
## SYNOPSIS
**containers-storage** **unshare** [command [...]]
## DESCRIPTION
Sets up a user namespace using mappings configured for the current user and runs
either a specified command or the current user's shell.
## EXAMPLE
**containers-storage unshare id**
## SEE ALSO
containers-storage-status(1)

View File

@ -68,6 +68,8 @@ The *containers-storage* command's features are broken down into several subcomm
**containers-storage create-layer(1)** Create a new layer
**containers-storage create-storage-layer(1)** Create a new layer in the lower-level storage driver
**containers-storage delete(1)** Delete a layer or image or container, with no safety checks
**containers-storage delete-container(1)** Delete a container, with safety checks
@ -116,6 +118,8 @@ The *containers-storage* command's features are broken down into several subcomm
**containers-storage unmount(1)** Unmount a layer or container
**containers-storage unshare(1)** Run a command in a user namespace
**containers-storage version(1)** Return containers-storage version information
**containers-storage wipe(1)** Wipe all layers, images, and containers
@ -155,6 +159,11 @@ Set options which will be passed to the storage driver. If not set, but
comma-separated list and used instead. If the storage tree has previously been
initialized, these need not be provided.
**--unshare, -U**
When started by a non-root user, run inside of a new user namespace configured
using the system's default ID mappings for the non-root user.
## ENVIRONMENT OVERRIDES
**CONTAINERS_STORAGE_CONF**