System tests: add podman system check tests

Testing `podman system check` requires that we have a way to
intentionally introduce storage corruptions.  Add a hidden `podman
testing` command that provides the necessary internal logic in
subcommands.  Stub out the tunnel implementation for now.

Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
This commit is contained in:
Nalin Dahyabhai
2024-04-22 17:37:42 -04:00
parent fec58a4571
commit 2e70d4201f
21 changed files with 1813 additions and 4 deletions

View File

@ -238,7 +238,7 @@ binaries: podman podman-remote ## Build podman and podman-remote binaries
else ifneq (, $(findstring $(GOOS),darwin windows))
binaries: podman-remote ## Build podman-remote (client) only binaries
else
binaries: podman podman-remote podmansh rootlessport quadlet ## Build podman, podman-remote and rootlessport binaries quadlet
binaries: podman podman-remote podman-testing podmansh rootlessport quadlet ## Build podman, podman-remote and rootlessport binaries quadlet
endif
# Extract text following double-# for targets, as their description for
@ -457,6 +457,16 @@ rootlessport: bin/rootlessport
podmansh: bin/podman
if [ ! -f bin/podmansh ]; then ln -s podman bin/podmansh; fi
$(SRCBINDIR)/podman-testing: $(SOURCES) go.mod go.sum
$(GOCMD) build \
$(BUILDFLAGS) \
$(GO_LDFLAGS) '$(LDFLAGS_PODMAN)' \
-tags "${BUILDTAGS}" \
-o $@ ./cmd/podman-testing
.PHONY: podman-testing
podman-testing: bin/podman-testing
###
### Secondary binary-build targets
###
@ -877,6 +887,11 @@ ifneq ($(shell uname -s),FreeBSD)
install ${SELINUXOPT} -m 644 contrib/tmpfile/podman.conf $(DESTDIR)${TMPFILESDIR}/podman.conf
endif
.PHONY: install.testing
install.testing:
install ${SELINUXOPT} -d -m 755 $(DESTDIR)$(BINDIR)
install ${SELINUXOPT} -m 755 bin/podman-testing $(DESTDIR)$(BINDIR)/podman-testing
.PHONY: install.modules-load
install.modules-load: # This should only be used by distros which might use iptables-legacy, this is not needed on RHEL
install ${SELINUXOPT} -m 755 -d $(DESTDIR)${MODULESLOADDIR}

View File

@ -0,0 +1,127 @@
package main
import (
"fmt"
"github.com/containers/common/pkg/completion"
"github.com/containers/podman/v5/cmd/podman/validate"
"github.com/containers/podman/v5/internal/domain/entities"
"github.com/spf13/cobra"
)
var (
createStorageLayerDescription = `Create an unmanaged layer in local storage.`
createStorageLayerCmd = &cobra.Command{
Use: "create-storage-layer [options]",
Args: validate.NoArgs,
Short: "Create an unmanaged layer",
Long: createStorageLayerDescription,
RunE: createStorageLayer,
ValidArgsFunction: completion.AutocompleteNone,
Example: `podman testing create-storage-layer`,
}
createStorageLayerOpts entities.CreateStorageLayerOptions
createLayerDescription = `Create an unused layer in local storage.`
createLayerCmd = &cobra.Command{
Use: "create-layer [options]",
Args: validate.NoArgs,
Short: "Create an unused layer",
Long: createLayerDescription,
RunE: createLayer,
ValidArgsFunction: completion.AutocompleteNone,
Example: `podman testing create-layer`,
}
createLayerOpts entities.CreateLayerOptions
createImageDescription = `Create an image in local storage.`
createImageCmd = &cobra.Command{
Use: "create-image [options]",
Args: validate.NoArgs,
Short: "Create an image",
Long: createImageDescription,
RunE: createImage,
ValidArgsFunction: completion.AutocompleteNone,
Example: `podman testing create-image`,
}
createImageOpts entities.CreateImageOptions
createContainerDescription = `Create a container in local storage.`
createContainerCmd = &cobra.Command{
Use: "create-container [options]",
Args: validate.NoArgs,
Short: "Create a container",
Long: createContainerDescription,
RunE: createContainer,
ValidArgsFunction: completion.AutocompleteNone,
Example: `podman testing create-container`,
}
createContainerOpts entities.CreateContainerOptions
)
func init() {
mainCmd.AddCommand(createStorageLayerCmd)
flags := createStorageLayerCmd.Flags()
flags.StringVarP(&createStorageLayerOpts.ID, "id", "i", "", "ID to assign the new layer (default random)")
flags.StringVarP(&createStorageLayerOpts.Parent, "parent", "p", "", "ID of parent of new layer (default none)")
mainCmd.AddCommand(createLayerCmd)
flags = createLayerCmd.Flags()
flags.StringVarP(&createLayerOpts.ID, "id", "i", "", "ID to assign the new layer (default random)")
flags.StringVarP(&createLayerOpts.Parent, "parent", "p", "", "ID of parent of new layer (default none)")
mainCmd.AddCommand(createImageCmd)
flags = createImageCmd.Flags()
flags.StringVarP(&createImageOpts.ID, "id", "i", "", "ID to assign the new image (default random)")
flags.StringVarP(&createImageOpts.Layer, "layer", "l", "", "ID of image's main layer (default none)")
mainCmd.AddCommand(createContainerCmd)
flags = createContainerCmd.Flags()
flags.StringVarP(&createContainerOpts.ID, "id", "i", "", "ID to assign the new container (default random)")
flags.StringVarP(&createContainerOpts.Image, "image", "b", "", "ID of containers's base image (default none)")
flags.StringVarP(&createContainerOpts.Layer, "layer", "l", "", "ID of containers's read-write layer (default none)")
}
func createStorageLayer(cmd *cobra.Command, args []string) error {
results, err := testingEngine.CreateStorageLayer(mainContext, createStorageLayerOpts)
if err != nil {
return err
}
fmt.Println(results.ID)
return nil
}
func createLayer(cmd *cobra.Command, args []string) error {
results, err := testingEngine.CreateLayer(mainContext, createLayerOpts)
if err != nil {
return err
}
fmt.Println(results.ID)
return nil
}
func createImage(cmd *cobra.Command, args []string) error {
results, err := testingEngine.CreateImage(mainContext, createImageOpts)
if err != nil {
return err
}
fmt.Println(results.ID)
return nil
}
func createContainer(cmd *cobra.Command, args []string) error {
results, err := testingEngine.CreateContainer(mainContext, createContainerOpts)
if err != nil {
return err
}
fmt.Println(results.ID)
return nil
}

405
cmd/podman-testing/data.go Normal file
View File

@ -0,0 +1,405 @@
package main
import (
"errors"
"os"
"github.com/containers/common/pkg/completion"
"github.com/containers/podman/v5/cmd/podman/validate"
"github.com/containers/podman/v5/internal/domain/entities"
"github.com/spf13/cobra"
)
var (
createLayerDataDescription = `Create data for a layer in local storage.`
createLayerDataCmd = &cobra.Command{
Use: "create-layer-data [options]",
Args: validate.NoArgs,
Short: "Create data for a layer",
Long: createLayerDataDescription,
RunE: createLayerData,
ValidArgsFunction: completion.AutocompleteNone,
Example: `podman testing create-layer-data`,
}
createLayerDataOpts entities.CreateLayerDataOptions
createLayerDataKey string
createLayerDataValue string
createLayerDataFile string
createImageDataDescription = `Create data for an image in local storage.`
createImageDataCmd = &cobra.Command{
Use: "create-image-data [options]",
Args: validate.NoArgs,
Short: "Create data for an image",
Long: createImageDataDescription,
RunE: createImageData,
ValidArgsFunction: completion.AutocompleteNone,
Example: `podman testing create-image-data`,
}
createImageDataOpts entities.CreateImageDataOptions
createImageDataKey string
createImageDataValue string
createImageDataFile string
createContainerDataDescription = `Create data for a container in local storage.`
createContainerDataCmd = &cobra.Command{
Use: "create-container-data [options]",
Args: validate.NoArgs,
Short: "Create data for a container",
Long: createContainerDataDescription,
RunE: createContainerData,
ValidArgsFunction: completion.AutocompleteNone,
Example: `podman testing create-container-data`,
}
createContainerDataOpts entities.CreateContainerDataOptions
createContainerDataKey string
createContainerDataValue string
createContainerDataFile string
modifyLayerDataDescription = `Modify data for a layer in local storage, corrupting it.`
modifyLayerDataCmd = &cobra.Command{
Use: "modify-layer-data [options]",
Args: validate.NoArgs,
Short: "Modify data for a layer",
Long: modifyLayerDataDescription,
RunE: modifyLayerData,
ValidArgsFunction: completion.AutocompleteNone,
Example: `podman testing modify-layer-data`,
}
modifyLayerDataOpts entities.ModifyLayerDataOptions
modifyLayerDataValue string
modifyLayerDataFile string
modifyImageDataDescription = `Modify data for an image in local storage, corrupting it.`
modifyImageDataCmd = &cobra.Command{
Use: "modify-image-data [options]",
Args: validate.NoArgs,
Short: "Modify data for an image",
Long: modifyImageDataDescription,
RunE: modifyImageData,
ValidArgsFunction: completion.AutocompleteNone,
Example: `podman testing modify-image-data`,
}
modifyImageDataOpts entities.ModifyImageDataOptions
modifyImageDataValue string
modifyImageDataFile string
modifyContainerDataDescription = `Modify data for a container in local storage, corrupting it.`
modifyContainerDataCmd = &cobra.Command{
Use: "modify-container-data [options]",
Args: validate.NoArgs,
Short: "Modify data for a container",
Long: modifyContainerDataDescription,
RunE: modifyContainerData,
ValidArgsFunction: completion.AutocompleteNone,
Example: `podman testing modify-container-data`,
}
modifyContainerDataOpts entities.ModifyContainerDataOptions
modifyContainerDataValue string
modifyContainerDataFile string
removeLayerDataDescription = `Remove data from a layer in local storage, corrupting it.`
removeLayerDataCmd = &cobra.Command{
Use: "remove-layer-data [options]",
Args: validate.NoArgs,
Short: "Remove data for a layer",
Long: removeLayerDataDescription,
RunE: removeLayerData,
ValidArgsFunction: completion.AutocompleteNone,
Example: `podman testing remove-layer-data`,
}
removeLayerDataOpts entities.RemoveLayerDataOptions
removeImageDataDescription = `Remove data from an image in local storage, corrupting it.`
removeImageDataCmd = &cobra.Command{
Use: "remove-image-data [options]",
Args: validate.NoArgs,
Short: "Remove data from an image",
Long: removeImageDataDescription,
RunE: removeImageData,
ValidArgsFunction: completion.AutocompleteNone,
Example: `podman testing remove-image-data`,
}
removeImageDataOpts entities.RemoveImageDataOptions
removeContainerDataDescription = `Remove data from a container in local storage, corrupting it.`
removeContainerDataCmd = &cobra.Command{
Use: "remove-container-data [options]",
Args: validate.NoArgs,
Short: "Remove data from a container",
Long: removeContainerDataDescription,
RunE: removeContainerData,
ValidArgsFunction: completion.AutocompleteNone,
Example: `podman testing remove-container-data`,
}
removeContainerDataOpts entities.RemoveContainerDataOptions
)
func init() {
mainCmd.AddCommand(createLayerDataCmd)
flags := createLayerDataCmd.Flags()
flags.StringVarP(&createLayerDataOpts.ID, "layer", "i", "", "ID of the layer")
flags.StringVarP(&createLayerDataKey, "key", "k", "", "Name of the data item")
flags.StringVarP(&createLayerDataValue, "value", "v", "", "Value of the data item")
flags.StringVarP(&createLayerDataFile, "file", "f", "", "File containing the data item")
mainCmd.AddCommand(createImageDataCmd)
flags = createImageDataCmd.Flags()
flags.StringVarP(&createImageDataOpts.ID, "image", "i", "", "ID of the image")
flags.StringVarP(&createImageDataKey, "key", "k", "", "Name of the data item")
flags.StringVarP(&createImageDataValue, "value", "v", "", "Value of the data item")
flags.StringVarP(&createImageDataFile, "file", "f", "", "File containing the data item")
mainCmd.AddCommand(createContainerDataCmd)
flags = createContainerDataCmd.Flags()
flags.StringVarP(&createContainerDataOpts.ID, "container", "i", "", "ID of the container")
flags.StringVarP(&createContainerDataKey, "key", "k", "", "Name of the data item")
flags.StringVarP(&createContainerDataValue, "value", "v", "", "Value of the data item")
flags.StringVarP(&createContainerDataFile, "file", "f", "", "File containing the data item")
mainCmd.AddCommand(modifyLayerDataCmd)
flags = modifyLayerDataCmd.Flags()
flags.StringVarP(&modifyLayerDataOpts.ID, "layer", "i", "", "ID of the layer")
flags.StringVarP(&modifyLayerDataOpts.Key, "key", "k", "", "Name of the data item")
flags.StringVarP(&modifyLayerDataValue, "value", "v", "", "Value of the data item")
flags.StringVarP(&modifyLayerDataFile, "file", "f", "", "File containing the data item")
mainCmd.AddCommand(modifyImageDataCmd)
flags = modifyImageDataCmd.Flags()
flags.StringVarP(&modifyImageDataOpts.ID, "image", "i", "", "ID of the image")
flags.StringVarP(&modifyImageDataOpts.Key, "key", "k", "", "Name of the data item")
flags.StringVarP(&modifyImageDataValue, "value", "v", "", "Value of the data item")
flags.StringVarP(&modifyImageDataFile, "file", "f", "", "File containing the data item")
mainCmd.AddCommand(modifyContainerDataCmd)
flags = modifyContainerDataCmd.Flags()
flags.StringVarP(&modifyContainerDataOpts.ID, "container", "i", "", "ID of the container")
flags.StringVarP(&modifyContainerDataOpts.Key, "key", "k", "", "Name of the data item")
flags.StringVarP(&modifyContainerDataValue, "value", "v", "", "Value of the data item")
flags.StringVarP(&modifyContainerDataFile, "file", "f", "", "File containing the data item")
mainCmd.AddCommand(removeLayerDataCmd)
flags = removeLayerDataCmd.Flags()
flags.StringVarP(&removeLayerDataOpts.ID, "layer", "i", "", "ID of the layer")
flags.StringVarP(&removeLayerDataOpts.Key, "key", "k", "", "Name of the data item")
mainCmd.AddCommand(removeImageDataCmd)
flags = removeImageDataCmd.Flags()
flags.StringVarP(&removeImageDataOpts.ID, "image", "i", "", "ID of the image")
flags.StringVarP(&removeImageDataOpts.Key, "key", "k", "", "Name of the data item")
mainCmd.AddCommand(removeContainerDataCmd)
flags = removeContainerDataCmd.Flags()
flags.StringVarP(&removeContainerDataOpts.ID, "container", "i", "", "ID of the container")
flags.StringVarP(&removeContainerDataOpts.Key, "key", "k", "", "Name of the data item")
}
func createLayerData(cmd *cobra.Command, args []string) error {
if createLayerDataOpts.ID == "" {
return errors.New("layer ID not specified")
}
if createLayerDataKey == "" {
return errors.New("layer data name not specified")
}
if createLayerDataValue == "" && createLayerDataFile == "" {
return errors.New("neither layer data value nor file specified")
}
createLayerDataOpts.Data = make(map[string][]byte)
if createLayerDataValue != "" {
createLayerDataOpts.Data[createLayerDataKey] = []byte(createLayerDataValue)
}
if createLayerDataFile != "" {
buf, err := os.ReadFile(createLayerDataFile)
if err != nil {
return err
}
createLayerDataOpts.Data[createLayerDataKey] = buf
}
_, err := testingEngine.CreateLayerData(mainContext, createLayerDataOpts)
if err != nil {
return err
}
return nil
}
func createImageData(cmd *cobra.Command, args []string) error {
if createImageDataOpts.ID == "" {
return errors.New("image ID not specified")
}
if createImageDataKey == "" {
return errors.New("image data name not specified")
}
if createImageDataValue == "" && createImageDataFile == "" {
return errors.New("neither image data value nor file specified")
}
createImageDataOpts.Data = make(map[string][]byte)
if createImageDataValue != "" {
createImageDataOpts.Data[createImageDataKey] = []byte(createImageDataValue)
}
if createImageDataFile != "" {
d, err := os.ReadFile(createImageDataFile)
if err != nil {
return err
}
createImageDataOpts.Data[createImageDataKey] = d
}
_, err := testingEngine.CreateImageData(mainContext, createImageDataOpts)
if err != nil {
return err
}
return nil
}
func createContainerData(cmd *cobra.Command, args []string) error {
if createContainerDataOpts.ID == "" {
return errors.New("container ID not specified")
}
if createContainerDataKey == "" {
return errors.New("container data name not specified")
}
if createContainerDataValue == "" && createContainerDataFile == "" {
return errors.New("neither container data value nor file specified")
}
createContainerDataOpts.Data = make(map[string][]byte)
if createContainerDataValue != "" {
createContainerDataOpts.Data[createContainerDataKey] = []byte(createContainerDataValue)
}
if createContainerDataFile != "" {
d, err := os.ReadFile(createContainerDataFile)
if err != nil {
return err
}
createContainerDataOpts.Data[createContainerDataKey] = d
}
_, err := testingEngine.CreateContainerData(mainContext, createContainerDataOpts)
if err != nil {
return err
}
return nil
}
func modifyLayerData(cmd *cobra.Command, args []string) error {
if modifyLayerDataOpts.ID == "" {
return errors.New("layer ID not specified")
}
if modifyLayerDataOpts.Key == "" {
return errors.New("layer data name not specified")
}
if modifyLayerDataValue == "" && modifyLayerDataFile == "" {
return errors.New("neither layer data value nor file specified")
}
modifyLayerDataOpts.Data = []byte(modifyLayerDataValue)
if modifyLayerDataFile != "" {
d, err := os.ReadFile(modifyLayerDataFile)
if err != nil {
return err
}
modifyLayerDataOpts.Data = d
}
_, err := testingEngine.ModifyLayerData(mainContext, modifyLayerDataOpts)
if err != nil {
return err
}
return nil
}
func modifyImageData(cmd *cobra.Command, args []string) error {
if modifyImageDataOpts.ID == "" {
return errors.New("image ID not specified")
}
if modifyImageDataOpts.Key == "" {
return errors.New("image data name not specified")
}
if modifyImageDataValue == "" && modifyImageDataFile == "" {
return errors.New("neither image data value nor file specified")
}
modifyImageDataOpts.Data = []byte(modifyImageDataValue)
if modifyImageDataFile != "" {
d, err := os.ReadFile(modifyImageDataFile)
if err != nil {
return err
}
modifyImageDataOpts.Data = d
}
_, err := testingEngine.ModifyImageData(mainContext, modifyImageDataOpts)
if err != nil {
return err
}
return nil
}
func modifyContainerData(cmd *cobra.Command, args []string) error {
if modifyContainerDataOpts.ID == "" {
return errors.New("container ID not specified")
}
if modifyContainerDataOpts.Key == "" {
return errors.New("container data name not specified")
}
if modifyContainerDataValue == "" && modifyContainerDataFile == "" {
return errors.New("neither container data value nor file specified")
}
modifyContainerDataOpts.Data = []byte(modifyContainerDataValue)
if modifyContainerDataFile != "" {
d, err := os.ReadFile(modifyContainerDataFile)
if err != nil {
return err
}
modifyContainerDataOpts.Data = d
}
_, err := testingEngine.ModifyContainerData(mainContext, modifyContainerDataOpts)
if err != nil {
return err
}
return nil
}
func removeLayerData(cmd *cobra.Command, args []string) error {
if removeLayerDataOpts.ID == "" {
return errors.New("layer ID not specified")
}
if removeLayerDataOpts.Key == "" {
return errors.New("layer data name not specified")
}
_, err := testingEngine.RemoveLayerData(mainContext, removeLayerDataOpts)
if err != nil {
return err
}
return nil
}
func removeImageData(cmd *cobra.Command, args []string) error {
if removeImageDataOpts.ID == "" {
return errors.New("image ID not specified")
}
if removeImageDataOpts.Key == "" {
return errors.New("image data name not specified")
}
_, err := testingEngine.RemoveImageData(mainContext, removeImageDataOpts)
if err != nil {
return err
}
return nil
}
func removeContainerData(cmd *cobra.Command, args []string) error {
if removeContainerDataOpts.ID == "" {
return errors.New("container ID not specified")
}
if removeContainerDataOpts.Key == "" {
return errors.New("container data name not specified")
}
_, err := testingEngine.RemoveContainerData(mainContext, removeContainerDataOpts)
if err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,91 @@
package main
import (
"errors"
"os"
"github.com/containers/common/pkg/completion"
"github.com/containers/podman/v5/cmd/podman/validate"
"github.com/containers/podman/v5/internal/domain/entities"
"github.com/spf13/cobra"
)
var (
populateLayerDescription = `Populate a layer in local storage.`
populateLayerCmd = &cobra.Command{
Use: "populate-layer [options]",
Args: validate.NoArgs,
Short: "Populate a layer",
Long: populateLayerDescription,
RunE: populateLayer,
ValidArgsFunction: completion.AutocompleteNone,
Example: `podman testing populate-layer`,
}
populateLayerOpts entities.PopulateLayerOptions
populateLayerFile string
modifyLayerDescription = `Modify a layer in local storage, corrupting it.`
modifyLayerCmd = &cobra.Command{
Use: "modify-layer [options]",
Args: validate.NoArgs,
Short: "Modify the contents of a layer",
Long: modifyLayerDescription,
RunE: modifyLayer,
ValidArgsFunction: completion.AutocompleteNone,
Example: `podman testing modify-layer`,
}
modifyLayerOpts entities.ModifyLayerOptions
modifyLayerFile string
)
func init() {
mainCmd.AddCommand(populateLayerCmd)
flags := populateLayerCmd.Flags()
flags.StringVarP(&populateLayerOpts.ID, "layer", "l", "", "ID of layer to be populated")
flags.StringVarP(&populateLayerFile, "file", "f", "", "archive of contents to extract in layer")
mainCmd.AddCommand(modifyLayerCmd)
flags = modifyLayerCmd.Flags()
flags.StringVarP(&modifyLayerOpts.ID, "layer", "l", "", "ID of layer to be modified")
flags.StringVarP(&modifyLayerFile, "file", "f", "", "archive of contents to extract over layer")
}
func populateLayer(cmd *cobra.Command, args []string) error {
if populateLayerOpts.ID == "" {
return errors.New("layer ID not specified")
}
if populateLayerFile == "" {
return errors.New("layer contents file not specified")
}
buf, err := os.ReadFile(populateLayerFile)
if err != nil {
return err
}
populateLayerOpts.ContentsArchive = buf
_, err = testingEngine.PopulateLayer(mainContext, populateLayerOpts)
if err != nil {
return err
}
return nil
}
func modifyLayer(cmd *cobra.Command, args []string) error {
if modifyLayerOpts.ID == "" {
return errors.New("layer ID not specified")
}
if modifyLayerFile == "" {
return errors.New("layer contents file not specified")
}
buf, err := os.ReadFile(modifyLayerFile)
if err != nil {
return err
}
modifyLayerOpts.ContentsArchive = buf
_, err = testingEngine.ModifyLayer(mainContext, modifyLayerOpts)
if err != nil {
return err
}
return nil
}

128
cmd/podman-testing/main.go Normal file
View File

@ -0,0 +1,128 @@
package main
import (
"context"
"errors"
"fmt"
"os"
"os/exec"
"syscall"
"github.com/containers/common/pkg/config"
_ "github.com/containers/podman/v5/cmd/podman/completion"
ientities "github.com/containers/podman/v5/internal/domain/entities"
"github.com/containers/podman/v5/internal/domain/infra"
"github.com/containers/podman/v5/pkg/domain/entities"
"github.com/containers/storage"
"github.com/containers/storage/pkg/reexec"
"github.com/containers/storage/pkg/unshare"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
var (
mainCmd = &cobra.Command{
Use: "podman-testing",
Long: "Assorted tools for use in testing podman",
RunE: func(cmd *cobra.Command, args []string) error {
return cmd.Help()
},
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
return before()
},
PersistentPostRunE: func(cmd *cobra.Command, args []string) error {
return after()
},
SilenceUsage: true,
SilenceErrors: true,
}
mainContext = context.Background()
podmanConfig entities.PodmanConfig
globalStorageOptions storage.StoreOptions
globalLogLevel string
testingEngine ientities.TestingEngine
)
func init() {
podmanConfig.FlagSet = mainCmd.PersistentFlags()
fl := mainCmd.PersistentFlags()
fl.StringVar(&podmanConfig.DockerConfig, "docker-config", os.Getenv("DOCKER_CONFIG"), "path to .docker/config")
fl.StringVar(&globalLogLevel, "log-level", "warn", "logging level")
fl.StringVar(&podmanConfig.URI, "url", "", "URL to access Podman service")
fl.StringVar(&podmanConfig.RegistriesConf, "registries-conf", os.Getenv("REGISTRIES_CONF"), "path to registries.conf (REGISTRIES_CONF)")
}
func before() error {
if globalLogLevel != "" {
parsedLogLevel, err := logrus.ParseLevel(globalLogLevel)
if err != nil {
return fmt.Errorf("parsing log level %q: %w", globalLogLevel, err)
}
logrus.SetLevel(parsedLogLevel)
}
if err := storeBefore(); err != nil {
return fmt.Errorf("setting up storage: %w", err)
}
podmanConfig.EngineMode = engineMode
podmanConfig.Remote = podmanConfig.URI != ""
containersConf, err := config.Default()
if err != nil {
return fmt.Errorf("loading default configuration (may reference $CONTAINERS_CONF): %w", err)
}
podmanConfig.ContainersConfDefaultsRO = containersConf
containersConf, err = config.New(nil)
if err != nil {
return fmt.Errorf("loading default configuration (may reference $CONTAINERS_CONF): %w", err)
}
podmanConfig.ContainersConf = containersConf
podmanConfig.StorageDriver = globalStorageOptions.GraphDriverName
podmanConfig.GraphRoot = globalStorageOptions.GraphRoot
podmanConfig.Runroot = globalStorageOptions.RunRoot
podmanConfig.ImageStore = globalStorageOptions.ImageStore
podmanConfig.StorageOpts = globalStorageOptions.GraphDriverOptions
podmanConfig.TransientStore = globalStorageOptions.TransientStore
te, err := infra.NewTestingEngine(&podmanConfig)
if err != nil {
return fmt.Errorf("initializing libpod: %w", err)
}
testingEngine = te
return nil
}
func after() error {
if err := storeAfter(); err != nil {
return fmt.Errorf("shutting down storage: %w", err)
}
return nil
}
func main() {
if reexec.Init() {
// We were invoked with a different argv[0] indicating that we
// had a specific job to do as a subprocess, and it's done.
return
}
unshare.MaybeReexecUsingUserNamespace(false)
exitCode := 1
if err := mainCmd.Execute(); err != nil {
if logrus.IsLevelEnabled(logrus.TraceLevel) {
fmt.Fprintf(os.Stderr, "Error: %+v\n", err)
} else {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
}
var ee *exec.ExitError
if errors.As(err, &ee) {
if w, ok := ee.Sys().(syscall.WaitStatus); ok {
exitCode = w.ExitStatus()
}
}
} else {
exitCode = 0
}
os.Exit(exitCode)
}

View File

@ -0,0 +1,118 @@
package main
import (
"fmt"
"github.com/containers/common/pkg/completion"
"github.com/containers/podman/v5/cmd/podman/validate"
"github.com/containers/podman/v5/internal/domain/entities"
"github.com/spf13/cobra"
)
var (
removeStorageLayerDescription = `Remove an unmanaged layer in local storage, potentially corrupting it.`
removeStorageLayerCmd = &cobra.Command{
Use: "remove-storage-layer [options]",
Args: validate.NoArgs,
Short: "Remove an unmanaged layer",
Long: removeStorageLayerDescription,
RunE: removeStorageLayer,
ValidArgsFunction: completion.AutocompleteNone,
Example: `podman testing remove-storage-layer`,
}
removeStorageLayerOpts entities.RemoveStorageLayerOptions
removeLayerDescription = `Remove a layer in local storage, potentially corrupting it.`
removeLayerCmd = &cobra.Command{
Use: "remove-layer [options]",
Args: validate.NoArgs,
Short: "Remove a layer",
Long: removeLayerDescription,
RunE: removeLayer,
ValidArgsFunction: completion.AutocompleteNone,
Example: `podman testing remove-layer`,
}
removeLayerOpts entities.RemoveLayerOptions
removeImageDescription = `Remove an image in local storage, potentially corrupting it.`
removeImageCmd = &cobra.Command{
Use: "remove-image [options]",
Args: validate.NoArgs,
Short: "Remove an image",
Long: removeImageDescription,
RunE: removeImage,
ValidArgsFunction: completion.AutocompleteNone,
Example: `podman testing remove-image`,
}
removeImageOpts entities.RemoveImageOptions
removeContainerDescription = `Remove a container in local storage, potentially corrupting it.`
removeContainerCmd = &cobra.Command{
Use: "remove-container [options]",
Args: validate.NoArgs,
Short: "Remove an container",
Long: removeContainerDescription,
RunE: removeContainer,
ValidArgsFunction: completion.AutocompleteNone,
Example: `podman testing remove-container`,
}
removeContainerOpts entities.RemoveContainerOptions
)
func init() {
mainCmd.AddCommand(removeStorageLayerCmd)
flags := removeStorageLayerCmd.Flags()
flags.StringVarP(&removeStorageLayerOpts.ID, "layer", "i", "", "ID of the layer to remove")
mainCmd.AddCommand(removeLayerCmd)
flags = removeLayerCmd.Flags()
flags.StringVarP(&removeLayerOpts.ID, "layer", "i", "", "ID of the layer to remove")
mainCmd.AddCommand(removeImageCmd)
flags = removeImageCmd.Flags()
flags.StringVarP(&removeImageOpts.ID, "image", "i", "", "ID of the image to remove")
mainCmd.AddCommand(removeContainerCmd)
flags = removeContainerCmd.Flags()
flags.StringVarP(&removeContainerOpts.ID, "container", "i", "", "ID of the container to remove")
}
func removeStorageLayer(cmd *cobra.Command, args []string) error {
results, err := testingEngine.RemoveStorageLayer(mainContext, removeStorageLayerOpts)
if err != nil {
return err
}
fmt.Println(results.ID)
return nil
}
func removeLayer(cmd *cobra.Command, args []string) error {
results, err := testingEngine.RemoveLayer(mainContext, removeLayerOpts)
if err != nil {
return err
}
fmt.Println(results.ID)
return nil
}
func removeImage(cmd *cobra.Command, args []string) error {
results, err := testingEngine.RemoveImage(mainContext, removeImageOpts)
if err != nil {
return err
}
fmt.Println(results.ID)
return nil
}
func removeContainer(cmd *cobra.Command, args []string) error {
results, err := testingEngine.RemoveContainer(mainContext, removeContainerOpts)
if err != nil {
return err
}
fmt.Println(results.ID)
return nil
}

View File

@ -0,0 +1,65 @@
//go:build linux && !remote
// +build linux,!remote
package main
import (
"fmt"
"os"
"github.com/containers/podman/v5/pkg/domain/entities"
"github.com/containers/storage"
"github.com/containers/storage/types"
)
var (
globalStore storage.Store
engineMode = entities.ABIMode
)
func init() {
if defaultStoreOptions, err := storage.DefaultStoreOptions(); err == nil {
globalStorageOptions = defaultStoreOptions
}
if storageConf, ok := os.LookupEnv("CONTAINERS_STORAGE_CONF"); ok {
options := globalStorageOptions
if types.ReloadConfigurationFileIfNeeded(storageConf, &options) == nil {
globalStorageOptions = options
}
}
fl := mainCmd.PersistentFlags()
fl.StringVar(&globalStorageOptions.GraphDriverName, "storage-driver", "", "storage driver used to manage images and containers")
fl.StringVar(&globalStorageOptions.GraphRoot, "root", "", "where images and containers will be stored")
fl.StringVar(&globalStorageOptions.RunRoot, "runroot", "", "where volatile state information will be stored")
fl.StringArrayVar(&globalStorageOptions.GraphDriverOptions, "storage-opt", nil, "storage driver options")
fl.StringVar(&globalStorageOptions.ImageStore, "imagestore", "", "where to store just some parts of images")
fl.BoolVar(&globalStorageOptions.TransientStore, "transient-store", false, "enable transient container storage")
}
func storeBefore() error {
defaultStoreOptions, err := storage.DefaultStoreOptions()
if err != nil {
fmt.Fprintf(os.Stderr, "selecting storage options: %v", err)
return nil
}
globalStorageOptions = defaultStoreOptions
store, err := storage.GetStore(globalStorageOptions)
if err != nil {
return err
}
globalStore = store
if podmanConfig.URI != "" {
engineMode = entities.TunnelMode
} else {
engineMode = entities.ABIMode
}
return nil
}
func storeAfter() error {
if globalStore != nil {
_, err := globalStore.Shutdown(false)
return err
}
return nil
}

View File

@ -0,0 +1,16 @@
//go:build !linux || remote
// +build !linux remote
package main
import "github.com/containers/podman/v5/pkg/domain/entities"
const engineMode = entities.TunnelMode
func storeBefore() error {
return nil
}
func storeAfter() error {
return nil
}

View File

@ -18,8 +18,8 @@ BUILD_TAGS_TUNNEL="$BUILD_TAGS_DEFAULT,remote"
BUILD_TAGS_REMOTE="remote,containers_image_openpgp"
SKIP_DIRS_ABI=""
SKIP_DIRS_TUNNEL="pkg/api,pkg/domain/infra/abi"
SKIP_DIRS_REMOTE="libpod/events,pkg/api,pkg/domain/infra/abi,pkg/machine/qemu,pkg/trust,test"
SKIP_DIRS_TUNNEL="pkg/api,pkg/domain/infra/abi,internal/domain/infra/abi"
SKIP_DIRS_REMOTE="libpod/events,pkg/api,pkg/domain/infra/abi,internal/domain/infra/abi,pkg/machine/qemu,pkg/trust,test"
declare -a to_lint
to_lint=(ABI TUNNEL REMOTE)

View File

@ -0,0 +1,27 @@
package entities
import (
"context"
)
type TestingEngine interface { //nolint:interfacebloat
CreateStorageLayer(ctx context.Context, opts CreateStorageLayerOptions) (*CreateStorageLayerReport, error)
CreateLayer(ctx context.Context, opts CreateLayerOptions) (*CreateLayerReport, error)
CreateLayerData(ctx context.Context, opts CreateLayerDataOptions) (*CreateLayerDataReport, error)
CreateImage(ctx context.Context, opts CreateImageOptions) (*CreateImageReport, error)
CreateImageData(ctx context.Context, opts CreateImageDataOptions) (*CreateImageDataReport, error)
CreateContainer(ctx context.Context, opts CreateContainerOptions) (*CreateContainerReport, error)
CreateContainerData(ctx context.Context, opts CreateContainerDataOptions) (*CreateContainerDataReport, error)
ModifyLayer(ctx context.Context, opts ModifyLayerOptions) (*ModifyLayerReport, error)
PopulateLayer(ctx context.Context, opts PopulateLayerOptions) (*PopulateLayerReport, error)
RemoveStorageLayer(ctx context.Context, opts RemoveStorageLayerOptions) (*RemoveStorageLayerReport, error)
RemoveLayer(ctx context.Context, opts RemoveLayerOptions) (*RemoveLayerReport, error)
RemoveImage(ctx context.Context, opts RemoveImageOptions) (*RemoveImageReport, error)
RemoveContainer(ctx context.Context, opts RemoveContainerOptions) (*RemoveContainerReport, error)
RemoveLayerData(ctx context.Context, opts RemoveLayerDataOptions) (*RemoveLayerDataReport, error)
RemoveImageData(ctx context.Context, opts RemoveImageDataOptions) (*RemoveImageDataReport, error)
RemoveContainerData(ctx context.Context, opts RemoveContainerDataOptions) (*RemoveContainerDataReport, error)
ModifyLayerData(ctx context.Context, opts ModifyLayerDataOptions) (*ModifyLayerDataReport, error)
ModifyImageData(ctx context.Context, opts ModifyImageDataOptions) (*ModifyImageDataReport, error)
ModifyContainerData(ctx context.Context, opts ModifyContainerDataOptions) (*ModifyContainerDataReport, error)
}

View File

@ -0,0 +1,153 @@
package entities
type CreateStorageLayerOptions struct {
Parent string
ID string
ContentsArchive []byte
}
type CreateStorageLayerReport struct {
ID string
}
type CreateLayerOptions struct {
Parent string
ID string
}
type CreateLayerReport struct {
ID string
}
type CreateLayerDataOptions struct {
ID string
Data map[string][]byte
}
type CreateLayerDataReport struct{}
type CreateImageOptions struct {
Layer string
Names []string
ID string
}
type CreateImageReport struct {
ID string
}
type CreateImageDataOptions struct {
ID string
Data map[string][]byte
}
type CreateImageDataReport struct{}
type CreateContainerOptions struct {
Layer string
Image string
Names []string
ID string
}
type CreateContainerReport struct {
ID string
}
type CreateContainerDataOptions struct {
ID string
Data map[string][]byte
}
type CreateContainerDataReport struct{}
type ModifyLayerOptions struct {
ID string
ContentsArchive []byte
}
type ModifyLayerReport struct{}
type PopulateLayerOptions struct {
ID string
ContentsArchive []byte
}
type PopulateLayerReport struct{}
type RemoveStorageLayerOptions struct {
ID string
}
type RemoveStorageLayerReport struct {
ID string
}
type RemoveLayerOptions struct {
ID string
}
type RemoveLayerReport struct {
ID string
}
type RemoveImageOptions struct {
ID string
}
type RemoveImageReport struct {
ID string
}
type RemoveContainerOptions struct {
ID string
}
type RemoveContainerReport struct {
ID string
}
type RemoveLayerDataOptions struct {
ID string
Key string
}
type RemoveLayerDataReport struct{}
type RemoveImageDataOptions struct {
ID string
Key string
}
type RemoveImageDataReport struct{}
type RemoveContainerDataOptions struct {
ID string
Key string
}
type RemoveContainerDataReport struct{}
type ModifyLayerDataOptions struct {
ID string
Key string
Data []byte
}
type ModifyLayerDataReport struct{}
type ModifyImageDataOptions struct {
ID string
Key string
Data []byte
}
type ModifyImageDataReport struct{}
type ModifyContainerDataOptions struct {
ID string
Key string
Data []byte
}
type ModifyContainerDataReport struct{}

View File

@ -0,0 +1,220 @@
package abi
import (
"bytes"
"context"
"fmt"
"os"
"path/filepath"
"github.com/containers/image/v5/manifest"
"github.com/containers/podman/v5/internal/domain/entities"
"github.com/containers/podman/v5/libpod"
"github.com/containers/storage"
graphdriver "github.com/containers/storage/drivers"
"github.com/containers/storage/pkg/chrootarchive"
"github.com/containers/storage/pkg/stringid"
)
type TestingEngine struct {
Libpod *libpod.Runtime
Store storage.Store
}
func (te *TestingEngine) CreateStorageLayer(ctx context.Context, opts entities.CreateStorageLayerOptions) (*entities.CreateStorageLayerReport, error) {
driver, err := te.Store.GraphDriver()
if err != nil {
return nil, err
}
id := opts.ID
if id == "" {
id = stringid.GenerateNonCryptoID()
}
if err := driver.CreateReadWrite(id, opts.Parent, &graphdriver.CreateOpts{}); err != nil {
return nil, err
}
return &entities.CreateStorageLayerReport{ID: id}, nil
}
func (te *TestingEngine) CreateLayer(ctx context.Context, opts entities.CreateLayerOptions) (*entities.CreateLayerReport, error) {
layer, err := te.Store.CreateLayer(opts.ID, opts.Parent, nil, "", true, nil)
if err != nil {
return nil, err
}
return &entities.CreateLayerReport{ID: layer.ID}, nil
}
func (te *TestingEngine) CreateLayerData(ctx context.Context, opts entities.CreateLayerDataOptions) (*entities.CreateLayerDataReport, error) {
for key, data := range opts.Data {
if err := te.Store.SetLayerBigData(opts.ID, key, bytes.NewReader(data)); err != nil {
return nil, err
}
}
return &entities.CreateLayerDataReport{}, nil
}
func (te *TestingEngine) ModifyLayer(ctx context.Context, opts entities.ModifyLayerOptions) (*entities.ModifyLayerReport, error) {
mnt, err := te.Store.Mount(opts.ID, "")
if err != nil {
return nil, err
}
modifyError := chrootarchive.UntarWithRoot(bytes.NewReader(opts.ContentsArchive), mnt, nil, mnt)
if _, err := te.Store.Unmount(opts.ID, false); err != nil {
return nil, err
}
if modifyError != nil {
return nil, modifyError
}
return &entities.ModifyLayerReport{}, nil
}
func (te *TestingEngine) PopulateLayer(ctx context.Context, opts entities.PopulateLayerOptions) (*entities.PopulateLayerReport, error) {
if _, err := te.Store.ApplyDiff(opts.ID, bytes.NewReader(opts.ContentsArchive)); err != nil {
return nil, err
}
return &entities.PopulateLayerReport{}, nil
}
func (te *TestingEngine) CreateImage(ctx context.Context, opts entities.CreateImageOptions) (*entities.CreateImageReport, error) {
image, err := te.Store.CreateImage(opts.ID, opts.Names, opts.Layer, "", nil)
if err != nil {
return nil, err
}
return &entities.CreateImageReport{ID: image.ID}, nil
}
func (te *TestingEngine) CreateImageData(ctx context.Context, opts entities.CreateImageDataOptions) (*entities.CreateImageDataReport, error) {
for key, data := range opts.Data {
if err := te.Store.SetImageBigData(opts.ID, key, data, manifest.Digest); err != nil {
return nil, err
}
}
return &entities.CreateImageDataReport{}, nil
}
func (te *TestingEngine) CreateContainer(ctx context.Context, opts entities.CreateContainerOptions) (*entities.CreateContainerReport, error) {
image, err := te.Store.CreateContainer(opts.ID, opts.Names, opts.Image, opts.Layer, "", nil)
if err != nil {
return nil, err
}
return &entities.CreateContainerReport{ID: image.ID}, nil
}
func (te *TestingEngine) CreateContainerData(ctx context.Context, opts entities.CreateContainerDataOptions) (*entities.CreateContainerDataReport, error) {
for key, data := range opts.Data {
if err := te.Store.SetContainerBigData(opts.ID, key, data); err != nil {
return nil, err
}
}
return &entities.CreateContainerDataReport{}, nil
}
func (te *TestingEngine) RemoveStorageLayer(ctx context.Context, opts entities.RemoveStorageLayerOptions) (*entities.RemoveStorageLayerReport, error) {
driver, err := te.Store.GraphDriver()
if err != nil {
return nil, err
}
if err := driver.Remove(opts.ID); err != nil {
return nil, err
}
return &entities.RemoveStorageLayerReport{ID: opts.ID}, nil
}
func (te *TestingEngine) RemoveLayer(ctx context.Context, opts entities.RemoveLayerOptions) (*entities.RemoveLayerReport, error) {
if err := te.Store.Delete(opts.ID); err != nil {
return nil, err
}
return &entities.RemoveLayerReport{ID: opts.ID}, nil
}
func (te *TestingEngine) RemoveImage(ctx context.Context, opts entities.RemoveImageOptions) (*entities.RemoveImageReport, error) {
if err := te.Store.Delete(opts.ID); err != nil {
return nil, err
}
return &entities.RemoveImageReport{ID: opts.ID}, nil
}
func (te *TestingEngine) RemoveContainer(ctx context.Context, opts entities.RemoveContainerOptions) (*entities.RemoveContainerReport, error) {
if err := te.Store.Delete(opts.ID); err != nil {
return nil, err
}
return &entities.RemoveContainerReport{ID: opts.ID}, nil
}
func (te *TestingEngine) datapath(itemType, id, key string) (string, error) {
switch itemType {
default:
return "", fmt.Errorf("unknown item type %q", itemType)
case "layer", "image", "container":
}
driverName := te.Store.GraphDriverName()
graphRoot := te.Store.GraphRoot()
datapath := filepath.Join(graphRoot, driverName+"-"+itemType+"s", id, key) // more or less accurate for keys whose names are [.a-z0-9]+
return datapath, nil
}
func (te *TestingEngine) RemoveLayerData(ctx context.Context, opts entities.RemoveLayerDataOptions) (*entities.RemoveLayerDataReport, error) {
datapath, err := te.datapath("layer", opts.ID, opts.Key)
if err != nil {
return nil, err
}
if err = os.Remove(datapath); err != nil {
return nil, err
}
return &entities.RemoveLayerDataReport{}, nil
}
func (te *TestingEngine) RemoveImageData(ctx context.Context, opts entities.RemoveImageDataOptions) (*entities.RemoveImageDataReport, error) {
datapath, err := te.datapath("image", opts.ID, opts.Key)
if err != nil {
return nil, err
}
if err = os.Remove(datapath); err != nil {
return nil, err
}
return &entities.RemoveImageDataReport{}, nil
}
func (te *TestingEngine) RemoveContainerData(ctx context.Context, opts entities.RemoveContainerDataOptions) (*entities.RemoveContainerDataReport, error) {
datapath, err := te.datapath("container", opts.ID, opts.Key)
if err != nil {
return nil, err
}
if err = os.Remove(datapath); err != nil {
return nil, err
}
return &entities.RemoveContainerDataReport{}, nil
}
func (te *TestingEngine) ModifyLayerData(ctx context.Context, opts entities.ModifyLayerDataOptions) (*entities.ModifyLayerDataReport, error) {
datapath, err := te.datapath("layer", opts.ID, opts.Key)
if err != nil {
return nil, err
}
if err = os.WriteFile(datapath, opts.Data, 0o0600); err != nil {
return nil, err
}
return &entities.ModifyLayerDataReport{}, nil
}
func (te *TestingEngine) ModifyImageData(ctx context.Context, opts entities.ModifyImageDataOptions) (*entities.ModifyImageDataReport, error) {
datapath, err := te.datapath("image", opts.ID, opts.Key)
if err != nil {
return nil, err
}
if err = os.WriteFile(datapath, opts.Data, 0o0600); err != nil {
return nil, err
}
return &entities.ModifyImageDataReport{}, nil
}
func (te *TestingEngine) ModifyContainerData(ctx context.Context, opts entities.ModifyContainerDataOptions) (*entities.ModifyContainerDataReport, error) {
datapath, err := te.datapath("container", opts.ID, opts.Key)
if err != nil {
return nil, err
}
if err = os.WriteFile(datapath, opts.Data, 0o0600); err != nil {
return nil, err
}
return &entities.ModifyContainerDataReport{}, nil
}

View File

@ -0,0 +1,5 @@
package abi
import "github.com/containers/podman/v5/internal/domain/entities"
var _ entities.TestingEngine = &TestingEngine{}

View File

@ -0,0 +1,26 @@
//go:build !remote
package infra
import (
"context"
"fmt"
ientities "github.com/containers/podman/v5/internal/domain/entities"
"github.com/containers/podman/v5/internal/domain/infra/tunnel"
"github.com/containers/podman/v5/pkg/bindings"
"github.com/containers/podman/v5/pkg/domain/entities"
)
// NewTestingEngine factory provides a libpod runtime for testing-specific operations
func NewTestingEngine(facts *entities.PodmanConfig) (ientities.TestingEngine, error) {
switch facts.EngineMode {
case entities.ABIMode:
r, err := NewLibpodTestingRuntime(facts.FlagSet, facts)
return r, err
case entities.TunnelMode:
ctx, err := bindings.NewConnectionWithIdentity(context.Background(), facts.URI, facts.Identity, facts.MachineMode)
return &tunnel.TestingEngine{ClientCtx: ctx}, err
}
return nil, fmt.Errorf("runtime mode '%v' is not supported", facts.EngineMode)
}

View File

@ -0,0 +1,26 @@
//go:build !remote
package infra
import (
"context"
ientities "github.com/containers/podman/v5/internal/domain/entities"
"github.com/containers/podman/v5/internal/domain/infra/abi"
"github.com/containers/podman/v5/pkg/domain/entities"
"github.com/containers/podman/v5/pkg/domain/infra"
"github.com/containers/storage"
flag "github.com/spf13/pflag"
)
func NewLibpodTestingRuntime(flags *flag.FlagSet, opts *entities.PodmanConfig) (ientities.TestingEngine, error) {
r, err := infra.GetRuntime(context.Background(), flags, opts)
if err != nil {
return nil, err
}
store, err := storage.GetStore(r.StorageConfig())
if err != nil {
return nil, err
}
return &abi.TestingEngine{Libpod: r, Store: store}, nil
}

View File

@ -0,0 +1,25 @@
//go:build remote
package infra
import (
"context"
"fmt"
ientities "github.com/containers/podman/v5/internal/domain/entities"
"github.com/containers/podman/v5/internal/domain/infra/tunnel"
"github.com/containers/podman/v5/pkg/bindings"
"github.com/containers/podman/v5/pkg/domain/entities"
)
// NewTestingEngine factory provides a libpod runtime for testing-specific operations
func NewTestingEngine(facts *entities.PodmanConfig) (ientities.TestingEngine, error) {
switch facts.EngineMode {
case entities.ABIMode:
return nil, fmt.Errorf("direct image runtime not supported")
case entities.TunnelMode:
ctx, err := bindings.NewConnectionWithIdentity(context.Background(), facts.URI, facts.Identity, facts.MachineMode)
return &tunnel.TestingEngine{ClientCtx: ctx}, err
}
return nil, fmt.Errorf("runtime mode '%v' is not supported", facts.EngineMode)
}

View File

@ -0,0 +1,88 @@
package tunnel
import (
"context"
"syscall"
"github.com/containers/podman/v5/internal/domain/entities"
)
type TestingEngine struct {
ClientCtx context.Context
}
func (te *TestingEngine) CreateStorageLayer(ctx context.Context, opts entities.CreateStorageLayerOptions) (*entities.CreateStorageLayerReport, error) {
return nil, syscall.ENOSYS
}
func (te *TestingEngine) CreateLayer(ctx context.Context, opts entities.CreateLayerOptions) (*entities.CreateLayerReport, error) {
return nil, syscall.ENOSYS
}
func (te *TestingEngine) CreateLayerData(ctx context.Context, opts entities.CreateLayerDataOptions) (*entities.CreateLayerDataReport, error) {
return nil, syscall.ENOSYS
}
func (te *TestingEngine) ModifyLayer(ctx context.Context, opts entities.ModifyLayerOptions) (*entities.ModifyLayerReport, error) {
return nil, syscall.ENOSYS
}
func (te *TestingEngine) PopulateLayer(ctx context.Context, opts entities.PopulateLayerOptions) (*entities.PopulateLayerReport, error) {
return nil, syscall.ENOSYS
}
func (te *TestingEngine) RemoveStorageLayer(ctx context.Context, opts entities.RemoveStorageLayerOptions) (*entities.RemoveStorageLayerReport, error) {
return nil, syscall.ENOSYS
}
func (te *TestingEngine) CreateImage(ctx context.Context, opts entities.CreateImageOptions) (*entities.CreateImageReport, error) {
return nil, syscall.ENOSYS
}
func (te *TestingEngine) CreateImageData(ctx context.Context, opts entities.CreateImageDataOptions) (*entities.CreateImageDataReport, error) {
return nil, syscall.ENOSYS
}
func (te *TestingEngine) RemoveLayer(ctx context.Context, opts entities.RemoveLayerOptions) (*entities.RemoveLayerReport, error) {
return nil, syscall.ENOSYS
}
func (te *TestingEngine) RemoveImage(ctx context.Context, opts entities.RemoveImageOptions) (*entities.RemoveImageReport, error) {
return nil, syscall.ENOSYS
}
func (te *TestingEngine) RemoveContainer(ctx context.Context, opts entities.RemoveContainerOptions) (*entities.RemoveContainerReport, error) {
return nil, syscall.ENOSYS
}
func (te *TestingEngine) CreateContainer(ctx context.Context, opts entities.CreateContainerOptions) (*entities.CreateContainerReport, error) {
return nil, syscall.ENOSYS
}
func (te *TestingEngine) CreateContainerData(ctx context.Context, opts entities.CreateContainerDataOptions) (*entities.CreateContainerDataReport, error) {
return nil, syscall.ENOSYS
}
func (te *TestingEngine) RemoveLayerData(ctx context.Context, opts entities.RemoveLayerDataOptions) (*entities.RemoveLayerDataReport, error) {
return nil, syscall.ENOSYS
}
func (te *TestingEngine) RemoveImageData(ctx context.Context, opts entities.RemoveImageDataOptions) (*entities.RemoveImageDataReport, error) {
return nil, syscall.ENOSYS
}
func (te *TestingEngine) RemoveContainerData(ctx context.Context, opts entities.RemoveContainerDataOptions) (*entities.RemoveContainerDataReport, error) {
return nil, syscall.ENOSYS
}
func (te *TestingEngine) ModifyLayerData(ctx context.Context, opts entities.ModifyLayerDataOptions) (*entities.ModifyLayerDataReport, error) {
return nil, syscall.ENOSYS
}
func (te *TestingEngine) ModifyImageData(ctx context.Context, opts entities.ModifyImageDataOptions) (*entities.ModifyImageDataReport, error) {
return nil, syscall.ENOSYS
}
func (te *TestingEngine) ModifyContainerData(ctx context.Context, opts entities.ModifyContainerDataOptions) (*entities.ModifyContainerDataReport, error) {
return nil, syscall.ENOSYS
}

View File

@ -0,0 +1,5 @@
package tunnel
import "github.com/containers/podman/v5/internal/domain/entities"
var _ entities.TestingEngine = &TestingEngine{}

View File

@ -240,6 +240,10 @@ export BUILDTAGS="$BASEBUILDTAGS exclude_graphdriver_btrfs btrfs_noversion remot
export BUILDTAGS="$BASEBUILDTAGS $(hack/btrfs_installed_tag.sh) $(hack/btrfs_tag.sh)"
%gobuild -o bin/quadlet ./cmd/quadlet
# build %%{name}-testing
export BUILDTAGS="$BASEBUILDTAGS $(hack/btrfs_installed_tag.sh) $(hack/btrfs_tag.sh)"
%gobuild -o bin/podman-testing ./cmd/podman-testing
# reset LDFLAGS for plugins binaries
LDFLAGS=''
@ -255,8 +259,9 @@ PODMAN_VERSION=%{version} %{__make} DESTDIR=%{buildroot} PREFIX=%{_prefix} ETCDI
install.docker \
install.docker-docs \
install.remote \
install.testing \
%if %{defined _modulesloaddir}
install.modules-load
install.modules-load
%endif
sed -i 's;%{buildroot};;g' %{buildroot}%{_bindir}/docker
@ -314,6 +319,7 @@ cp -pav test/system %{buildroot}/%{_datadir}/%{name}/test/
%{_datadir}/zsh/site-functions/_%{name}-remote
%files tests
%{_bindir}/%{name}-testing
%{_datadir}/%{name}/test
%files -n %{name}sh

View File

@ -0,0 +1,248 @@
#!/usr/bin/env bats -*- bats -*-
#
# Creates errors that should be caught by `system check`, and verifies
# that they are caught and remedied, even if it requires discarding some
# data in read-write layers.
#
load helpers
@test "podman system check - unmanaged layers" {
run_podman_testing create-storage-layer
layerID="$output"
run_podman_testing create-storage-layer --parent=$layerID
run_podman 125 system check
assert "$output" =~ "layer in lower level storage driver not accounted for" "output from 'podman system check' with unmanaged layers"
run_podman system check -r
run_podman system check
}
@test "podman system check - unused layers" {
run_podman_testing create-layer
layerID="$output"
run_podman_testing create-layer --parent=$layerID
run_podman system check
run_podman 125 system check -m 0
assert "$output" =~ "layer not referenced" "output from 'podman system check' with unused layers"
run_podman system check -m 0 -r
run_podman system check -m 0
}
@test "podman system check - layer content digest changed" {
run_podman_testing create-layer
layerID="$output"
make_layer_blob 8 ${PODMAN_TMPDIR}/archive.tar
run_podman_testing populate-layer --layer=$layerID --file=${PODMAN_TMPDIR}/archive.tar
run_podman_testing create-image --layer=$layerID
imageID="$output"
testing_make_image_metadata_for_layer_blobs $imageID ${PODMAN_TMPDIR}/archive.tar
run_podman create $imageID
make_layer_blob 1 ${PODMAN_TMPDIR}/archive.tar
run_podman_testing modify-layer --layer=$layerID --file=${PODMAN_TMPDIR}/archive.tar
run_podman 125 system check
assert "$output" =~ "checksum failed" "output from 'podman system check' with modified layer contents"
run_podman 125 system check -r
run_podman 0+w system check -r -f
run_podman system check
}
@test "podman system check - layer content added" {
run_podman_testing create-layer
layerID="$output"
make_layer_blob 8 ${PODMAN_TMPDIR}/archive.tar
run_podman_testing populate-layer --layer=$layerID --file=${PODMAN_TMPDIR}/archive.tar
run_podman_testing create-image --layer=$layerID
imageID="$output"
testing_make_image_metadata_for_layer_blobs $imageID ${PODMAN_TMPDIR}/archive.tar
run_podman create $imageID
make_layer_blob 100 101 ${PODMAN_TMPDIR}/archive.tar
run_podman_testing modify-layer --layer=$layerID --file=${PODMAN_TMPDIR}/archive.tar
run_podman 125 system check
assert "$output" =~ "content modified" "output from 'podman system check' with unexpected content added to layer"
run_podman 125 system check -r
run_podman 0+w system check -r -f
run_podman system check
}
@test "podman system check - storage image layer missing" {
run_podman_testing create-layer
layerID="$output"
make_layer_blob 8 ${PODMAN_TMPDIR}/archive.tar
run_podman_testing populate-layer --layer=$layerID --file=${PODMAN_TMPDIR}/archive.tar
run_podman_testing create-image --layer=$layerID
imageID="$output"
testing_make_image_metadata_for_layer_blobs $imageID ${PODMAN_TMPDIR}/archive.tar
run_podman create $imageID
run_podman_testing remove-layer --layer=$layerID
run_podman 125 system check
assert "$output" =~ "image layer is missing" "output from 'podman system check' with missing layer"
run_podman 125 system check -r
run_podman 0+w system check -r -f
run_podman system check
}
@test "podman system check - storage container image missing" {
run_podman_testing create-layer
layerID="$output"
make_layer_blob 8 ${PODMAN_TMPDIR}/archive.tar
run_podman_testing populate-layer --layer=$layerID --file=${PODMAN_TMPDIR}/archive.tar
run_podman_testing create-image --layer=$layerID
imageID="$output"
testing_make_image_metadata_for_layer_blobs $imageID ${PODMAN_TMPDIR}/archive.tar
run_podman create $imageID
run_podman_testing remove-image --image=$imageID
run_podman 125 system check -m 0
assert "$output" =~ "image missing" "output from 'podman system check' with missing image"
run_podman 125 system check -r -m 0
run_podman 0+w system check -r -f -m 0
run_podman system check -m 0
}
@test "podman system check - storage layer data missing" {
run_podman_testing create-layer
layerID="$output"
make_layer_blob ${PODMAN_TMPDIR}/archive.tar
run_podman_testing populate-layer --layer=$layerID --file=${PODMAN_TMPDIR}/archive.tar
make_random_file ${PODMAN_TMPDIR}/random-data.bin
run_podman_testing create-layer-data --key=foo --file=${PODMAN_TMPDIR}/random-data.bin --layer=$layerID
run_podman_testing create-image --layer=$layerID
imageID="$output"
testing_make_image_metadata_for_layer_blobs $imageID ${PODMAN_TMPDIR}/archive.tar
run_podman create $imageID
run_podman_testing remove-layer-data --key=foo --layer=$layerID
run_podman 125 system check
assert "$output" =~ "layer data item is missing" "output from 'podman system check' with missing layer data"
run_podman 125 system check -r
run_podman 0+w system check -r -f
run_podman system check
}
@test "podman system check - storage image data missing" {
run_podman_testing create-layer
layerID="$output"
make_layer_blob 8 ${PODMAN_TMPDIR}/archive.tar
run_podman_testing populate-layer --layer=$layerID --file=${PODMAN_TMPDIR}/archive.tar
run_podman_testing create-image --layer=$layerID
imageID="$output"
testing_make_image_metadata_for_layer_blobs $imageID ${PODMAN_TMPDIR}/archive.tar
make_random_file ${PODMAN_TMPDIR}/random-data.bin
run_podman_testing create-image-data --key=foo --file=${PODMAN_TMPDIR}/random-data.bin --image=$imageID
run_podman create $imageID
run_podman_testing remove-image-data --key=foo --image=$imageID
run_podman 125 system check
assert "$output" =~ "image data item is missing" "output from 'podman system check' with missing image data"
run_podman 125 system check -r
run_podman 0+w system check -r -f
run_podman system check
}
@test "podman system check - storage image data modified" {
run_podman_testing create-layer
layerID="$output"
make_layer_blob 8 ${PODMAN_TMPDIR}/archive.tar
run_podman_testing populate-layer --layer=$layerID --file=${PODMAN_TMPDIR}/archive.tar
run_podman_testing create-image --layer=$layerID
imageID="$output"
testing_make_image_metadata_for_layer_blobs $imageID ${PODMAN_TMPDIR}/archive.tar
run_podman create $imageID
make_random_file ${PODMAN_TMPDIR}/random-data.bin
run_podman_testing create-image-data --key=foo --file=${PODMAN_TMPDIR}/random-data.bin --image=$imageID
make_random_file ${PODMAN_TMPDIR}/random-data.bin
run_podman_testing modify-image-data --key=foo --file=${PODMAN_TMPDIR}/random-data.bin --image=$imageID
run_podman 125 system check
assert "$output" =~ "image data item has incorrect" "output from 'podman system check' with modified image data"
run_podman 125 system check -r
run_podman 0+w system check -r -f
run_podman system check
}
@test "podman system check - container data missing" {
run_podman_testing create-layer
layerID="$output"
make_layer_blob 8 ${PODMAN_TMPDIR}/archive.tar
run_podman_testing populate-layer --layer=$layerID --file=${PODMAN_TMPDIR}/archive.tar
run_podman_testing create-image --layer=$layerID
imageID="$output"
testing_make_image_metadata_for_layer_blobs $imageID ${PODMAN_TMPDIR}/archive.tar
run_podman create $imageID
containerID="$output"
make_random_file ${PODMAN_TMPDIR}/random-data.bin
run_podman_testing create-container-data --key=foo --file=${PODMAN_TMPDIR}/random-data.bin --container=$containerID
run_podman_testing remove-container-data --key=foo --container=$containerID
run_podman 125 system check
assert "$output" =~ "container data item is missing" "output from 'podman system check' with missing container data"
run_podman 125 system check -r
run_podman 0+w system check -r -f
run_podman system check
run_podman rmi $imageID
}
@test "podman system check - container data modified" {
run_podman_testing create-layer
layerID="$output"
make_layer_blob 8 ${PODMAN_TMPDIR}/archive.tar
run_podman_testing populate-layer --layer=$layerID --file=${PODMAN_TMPDIR}/archive.tar
run_podman_testing create-image --layer=$layerID
imageID="$output"
testing_make_image_metadata_for_layer_blobs $imageID ${PODMAN_TMPDIR}/archive.tar
run_podman create $imageID
containerID="$output"
make_random_file ${PODMAN_TMPDIR}/random-data.bin
run_podman_testing create-container-data --key=foo --file=${PODMAN_TMPDIR}/random-data.bin --container=$containerID
make_random_file ${PODMAN_TMPDIR}/random-data.bin
run_podman_testing modify-container-data --key=foo --file=${PODMAN_TMPDIR}/random-data.bin --container=$containerID
run_podman 125 system check
assert "$output" =~ "container data item has incorrect" "output from 'podman system check' with modified container data"
run_podman 125 system check -r
run_podman 0+w system check -r -f
run_podman system check
run_podman rmi $imageID
}
function make_layer_blob() {
local tmpdir=$(mktemp -d --tmpdir=${PODMAN_TMPDIR} make_layer_blob.XXXXXX)
local blobfile
local seqargs
for arg in "${@}" ; do
seqargs="${blobfile:+$seqargs $blobfile}"
blobfile="$arg"
done
seqargs="${seqargs:-8}"
local filelist=
for file in $(seq ${seqargs}); do
dd if=/dev/urandom of="$tmpdir/file$file" bs=1 count=$((1024 + $file)) status=none
filelist="$filelist file$file"
done
tar -c --owner=root:0 --group=root:0 -f "$blobfile" -C "$tmpdir" $filelist
}
function testing_make_image_metadata_for_layer_blobs() {
local tmpdir=$(mktemp -d --tmpdir=${PODMAN_TMPDIR} make_image_metadata.XXXXXX)
local imageID=$1
shift
echo '{"config":{},"rootfs":{"type":"layers","diff_ids":[' > $tmpdir/config.json
echo '{"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","layers":[' > $tmpdir/manifest
local comma=
for blob in "$@" ; do
local sum=$(sha256sum $blob)
sum=${sum%% *}
local size=$(wc -c $blob)
size=${size%% *}
echo $comma '"sha256:'$sum'"' >> $tmpdir/config.json
echo $comma '{"digest":"sha256:'$sum'","size":'$size',"mediaType":"application/vnd.oci.image.layer.v1.tar"}' >> $tmpdir/manifest
comma=,
done
echo ']}}' >> $tmpdir/config.json
sum=$(sha256sum $tmpdir/config.json)
sum=${sum%% *}
size=$(wc -c $tmpdir/config.json)
size=${size%% *}
echo '],"config":{"digest":"sha256:'$sum'","size":'$size',"mediaType":"application/vnd.oci.image.config.v1+json"}}' >> $tmpdir/manifest
run_podman_testing create-image-data -i $imageID -k sha256:$sum -f $tmpdir/config.json
sum=$(sha256sum $tmpdir/manifest)
sum=${sum%% *}
run_podman_testing create-image-data -i $imageID -k manifest-sha256:$sum -f $tmpdir/manifest
run_podman_testing create-image-data -i $imageID -k manifest -f $tmpdir/manifest
}
# vim: filetype=sh

View File

@ -4,6 +4,9 @@
PODMAN=${PODMAN:-podman}
QUADLET=${QUADLET:-/usr/libexec/podman/quadlet}
# Podman testing helper used in 331-system-check tests
PODMAN_TESTING=${PODMAN_TESTING:-$(dirname ${BASH_SOURCE})/../../bin/podman-testing}
# crun or runc, unlikely to change. Cache, because it's expensive to determine.
PODMAN_RUNTIME=
@ -449,6 +452,14 @@ function run_podman() {
fi
}
function run_podman_testing() {
printf "\n%s %s %s %s\n" "$(timestamp)" "$_LOG_PROMPT" "$PODMAN_TESTING" "$*"
run $PODMAN_TESTING "$@"
if [[ $status -ne 0 ]]; then
echo "$output"
die "Unexpected error from testing helper, which should always always succeed"
fi
}
# Wait for certain output from a container, indicating that it's ready.
function wait_for_output {
@ -1178,5 +1189,9 @@ function wait_for_command_output() {
die "Timed out waiting for '$cmd' to return '$want'"
}
function make_random_file() {
dd if=/dev/urandom of="$1" bs=1 count=${2:-$((${RANDOM} % 8192 + 1024))} status=none
}
# END miscellaneous tools
###############################################################################