diff options
35 files changed, 513 insertions, 136 deletions
diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go index e96b6a8d6..403a1065b 100644 --- a/cmd/podman/common/create.go +++ b/cmd/podman/common/create.go @@ -518,21 +518,3 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { ) return &createFlags } - -func AliasFlags(_ *pflag.FlagSet, name string) pflag.NormalizedName { - switch name { - case "healthcheck-command": - name = "health-cmd" - case "healthcheck-interval": - name = "health-interval" - case "healthcheck-retries": - name = "health-retries" - case "healthcheck-start-period": - name = "health-start-period" - case "healthcheck-timeout": - name = "health-timeout" - case "net": - name = "network" - } - return pflag.NormalizedName(name) -} diff --git a/cmd/podman/containers/create.go b/cmd/podman/containers/create.go index 1516d15e9..6eec93f98 100644 --- a/cmd/podman/containers/create.go +++ b/cmd/podman/containers/create.go @@ -12,6 +12,7 @@ import ( "github.com/containers/image/v5/transports/alltransports" "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" + "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/libpod/define" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/containers/podman/v2/pkg/errorhandling" @@ -58,7 +59,7 @@ func createFlags(flags *pflag.FlagSet) { flags.SetInterspersed(false) flags.AddFlagSet(common.GetCreateFlags(&cliVals)) flags.AddFlagSet(common.GetNetFlags()) - flags.SetNormalizeFunc(common.AliasFlags) + flags.SetNormalizeFunc(utils.AliasFlags) if registry.IsRemote() { _ = flags.MarkHidden("authfile") diff --git a/cmd/podman/containers/exec.go b/cmd/podman/containers/exec.go index da450054f..e301ca588 100644 --- a/cmd/podman/containers/exec.go +++ b/cmd/podman/containers/exec.go @@ -10,6 +10,7 @@ import ( "github.com/containers/podman/v2/libpod/define" "github.com/containers/podman/v2/pkg/domain/entities" envLib "github.com/containers/podman/v2/pkg/env" + "github.com/containers/podman/v2/pkg/rootless" "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -110,6 +111,12 @@ func exec(_ *cobra.Command, args []string) error { execOpts.Envs = envLib.Join(execOpts.Envs, cliEnv) + for fd := 3; fd < int(3+execOpts.PreserveFDs); fd++ { + if !rootless.IsFdInherited(fd) { + return errors.Errorf("file descriptor %d is not available - the preserve-fds option requires that file descriptors must be passed", fd) + } + } + if !execDetach { streams := define.AttachStreams{} streams.OutputStream = os.Stdout diff --git a/cmd/podman/containers/run.go b/cmd/podman/containers/run.go index d26aed826..a84cb6814 100644 --- a/cmd/podman/containers/run.go +++ b/cmd/podman/containers/run.go @@ -8,6 +8,7 @@ import ( "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" + "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/libpod/define" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/containers/podman/v2/pkg/errorhandling" @@ -58,7 +59,7 @@ func runFlags(flags *pflag.FlagSet) { flags.SetInterspersed(false) flags.AddFlagSet(common.GetCreateFlags(&cliVals)) flags.AddFlagSet(common.GetNetFlags()) - flags.SetNormalizeFunc(common.AliasFlags) + flags.SetNormalizeFunc(utils.AliasFlags) flags.BoolVar(&runOpts.SigProxy, "sig-proxy", true, "Proxy received signals to the process") flags.BoolVar(&runRmi, "rmi", false, "Remove container image unless used by other containers") flags.UintVar(&runOpts.PreserveFDs, "preserve-fds", 0, "Pass a number of additional file descriptors into the container") @@ -125,6 +126,11 @@ func run(cmd *cobra.Command, args []string) error { if err := createInit(cmd); err != nil { return err } + for fd := 3; fd < int(3+runOpts.PreserveFDs); fd++ { + if !rootless.IsFdInherited(fd) { + return errors.Errorf("file descriptor %d is not available - the preserve-fds option requires that file descriptors must be passed", fd) + } + } imageName := args[0] if !cliVals.RootFS { diff --git a/cmd/podman/registry/remote.go b/cmd/podman/registry/remote.go index 7dbdd3824..9b7523ac0 100644 --- a/cmd/podman/registry/remote.go +++ b/cmd/podman/registry/remote.go @@ -5,22 +5,24 @@ import ( "sync" "github.com/containers/podman/v2/pkg/domain/entities" - "github.com/spf13/cobra" + "github.com/spf13/pflag" ) -var ( - // Was --remote given on command line - remoteOverride bool - remoteSync sync.Once -) +// Value for --remote given on command line +var remoteFromCLI = struct { + Value bool + sync sync.Once +}{} -// IsRemote returns true if podman was built to run remote +// IsRemote returns true if podman was built to run remote or --remote flag given on CLI // Use in init() functions as a initialization check func IsRemote() bool { - remoteSync.Do(func() { - remote := &cobra.Command{} - remote.Flags().BoolVarP(&remoteOverride, "remote", "r", false, "") - _ = remote.ParseFlags(os.Args) + remoteFromCLI.sync.Do(func() { + fs := pflag.NewFlagSet("remote", pflag.ContinueOnError) + fs.BoolVarP(&remoteFromCLI.Value, "remote", "r", false, "") + fs.ParseErrorsWhitelist.UnknownFlags = true + fs.SetInterspersed(false) + _ = fs.Parse(os.Args[1:]) }) - return podmanOptions.EngineMode == entities.TunnelMode || remoteOverride + return podmanOptions.EngineMode == entities.TunnelMode || remoteFromCLI.Value } @@ -62,7 +62,6 @@ require ( golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 - gopkg.in/yaml.v2 v2.3.0 k8s.io/api v0.18.6 k8s.io/apimachinery v0.18.6 k8s.io/client-go v0.0.0-20190620085101-78d2af792bab diff --git a/libpod/container_config.go b/libpod/container_config.go index 301b867fc..5f89395c1 100644 --- a/libpod/container_config.go +++ b/libpod/container_config.go @@ -13,25 +13,52 @@ import ( // ContainerConfig contains all information that was used to create the // container. It may not be changed once created. -// It is stored, read-only, on disk +// It is stored, read-only, on disk in Libpod's State. +// Any changes will not be written back to the database, and will cause +// inconsistencies with other Libpod instances. type ContainerConfig struct { + // Spec is OCI runtime spec used to create the container. This is passed + // in when the container is created, but it is not the final spec used + // to run the container - it will be modified by Libpod to add things we + // manage (e.g. bind mounts for /etc/resolv.conf, named volumes, a + // network namespace prepared by CNI or slirp4netns) in the + // generateSpec() function. Spec *spec.Spec `json:"spec"` + // ID is a hex-encoded 256-bit pseudorandom integer used as a unique + // identifier for the container. IDs are globally unique in Libpod - + // once an ID is in use, no other container or pod will be created with + // the same one until the holder of the ID has been removed. + // ID is generated by Libpod, and cannot be chosen or influenced by the + // user (except when restoring a checkpointed container). + // ID is guaranteed to be 64 characters long. ID string `json:"id"` + // Name is a human-readable name for the container. All containers must + // have a non-empty name. Name may be provided when the container is + // created; if no name is chosen, a name will be auto-generated. Name string `json:"name"` - // Full ID of the pood the container belongs to + // Pod is the full ID of the pod the container belongs to. If the + // container does not belong to a pod, this will be empty. + // If this is not empty, a pod with this ID is guaranteed to exist in + // the state for the duration of this container's existence. Pod string `json:"pod,omitempty"` - // Namespace the container is in + // Namespace is the libpod Namespace the container is in. + // Namespaces are used to divide containers in the state. Namespace string `json:"namespace,omitempty"` - // ID of this container's lock + // LockID is the ID of this container's lock. Each container, pod, and + // volume is assigned a unique Lock (from one of several backends) by + // the libpod Runtime. This lock will belong only to this container for + // the duration of the container's lifetime. LockID uint32 `json:"lockID"` - // CreateCommand is the full command plus arguments of the process the - // container has been created with. + // CreateCommand is the full command plus arguments that were used to + // create the container. It is shown in the output of Inspect, and may + // be used to recreate an identical container for automatic updates or + // portable systemd unit files. CreateCommand []string `json:"CreateCommand,omitempty"` // RawImageName is the raw and unprocessed name of the image when creating @@ -40,10 +67,13 @@ type ContainerConfig struct { // name and not some normalized instance of it. RawImageName string `json:"RawImageName,omitempty"` - // UID/GID mappings used by the storage + // IDMappings are UID/GID mappings used by the container's user + // namespace. They are used by the OCI runtime when creating the + // container, and by c/storage to ensure that the container's files have + // the appropriate owner. IDMappings storage.IDMappingOptions `json:"idMappingsOptions,omitempty"` - // IDs of dependency containers. + // Dependencies are the IDs of dependency containers. // These containers must be started before this container is started. Dependencies []string @@ -59,45 +89,92 @@ type ContainerConfig struct { // ContainerRootFSConfig is an embedded sub-config providing config info // about the container's root fs. type ContainerRootFSConfig struct { - RootfsImageID string `json:"rootfsImageID,omitempty"` + // RootfsImageID is the ID of the image used to create the container. + // If the container was created from a Rootfs, this will be empty. + // If non-empty, Podman will create a root filesystem for the container + // based on an image with this ID. + // This conflicts with Rootfs. + RootfsImageID string `json:"rootfsImageID,omitempty"` + // RootfsImageName is the (normalized) name of the image used to create + // the container. If the container was created from a Rootfs, this will + // be empty. RootfsImageName string `json:"rootfsImageName,omitempty"` - // Rootfs to use for the container, this conflicts with RootfsImageID + // Rootfs is a directory to use as the container's root filesystem. + // If RootfsImageID is set, this will be empty. + // If this is set, Podman will not create a root filesystem for the + // container based on an image, and will instead use the given directory + // as the container's root. + // Conflicts with RootfsImageID. Rootfs string `json:"rootfs,omitempty"` - // Src path to be mounted on /dev/shm in container. + // ShmDir is the path to be mounted on /dev/shm in container. + // If not set manually at creation time, Libpod will create a tmpfs + // with the size specified in ShmSize and populate this with the path of + // said tmpfs. ShmDir string `json:"ShmDir,omitempty"` - // Size of the container's SHM. + // ShmSize is the size of the container's SHM. Only used if ShmDir was + // not set manually at time of creation. ShmSize int64 `json:"shmSize"` // Static directory for container content that will persist across // reboot. + // StaticDir is a persistent directory for Libpod files that will + // survive system reboot. It is not part of the container's rootfs and + // is not mounted into the container. It will be removed when the + // container is removed. + // Usually used to store container log files, files that will be bind + // mounted into the container (e.g. the resolv.conf we made for the + // container), and other per-container content. StaticDir string `json:"staticDir"` - // Mounts list contains all additional mounts into the container rootfs. - // These include the SHM mount. + // Mounts contains all additional mounts into the container rootfs. + // It is presently only used for the container's SHM directory. // These must be unmounted before the container's rootfs is unmounted. Mounts []string `json:"mounts,omitempty"` - // NamedVolumes lists the named volumes to mount into the container. + // NamedVolumes lists the Libpod named volumes to mount into the + // container. Each named volume is guaranteed to exist so long as this + // container exists. NamedVolumes []*ContainerNamedVolume `json:"namedVolumes,omitempty"` // OverlayVolumes lists the overlay volumes to mount into the container. OverlayVolumes []*ContainerOverlayVolume `json:"overlayVolumes,omitempty"` + // CreateWorkingDir indicates that Libpod should create the container's + // working directory if it does not exist. Some OCI runtimes do this by + // default, but others do not. + CreateWorkingDir bool `json:"createWorkingDir,omitempty"` } // ContainerSecurityConfig is an embedded sub-config providing security configuration // to the container. type ContainerSecurityConfig struct { - // Whether the container is privileged + // Pirivileged is whether the container is privileged. Privileged + // containers have lessened security and increased access to the system. + // Note that this does NOT directly correspond to Podman's --privileged + // flag - most of the work of that flag is done in creating the OCI spec + // given to Libpod. This only enables a small subset of the overall + // operation, mostly around mounting the container image with reduced + // security. Privileged bool `json:"privileged"` - // SELinux process label for container + // ProcessLabel is the SELinux process label for the container. ProcessLabel string `json:"ProcessLabel,omitempty"` - // SELinux mount label for root filesystem + // MountLabel is the SELinux mount label for the container's root + // filesystem. Only used if the container was created from an image. + // If not explicitly set, an unused random MLS label will be assigned by + // containers/storage (but only if SELinux is enabled). MountLabel string `json:"MountLabel,omitempty"` - // LabelOpts are options passed in by the user to setup SELinux labels + // LabelOpts are options passed in by the user to setup SELinux labels. + // These are used by the containers/storage library. LabelOpts []string `json:"labelopts,omitempty"` - // User and group to use in the container - // Can be specified by name or UID/GID + // User and group to use in the container. Can be specified as only user + // (in which case we will attempt to look up the user in the container + // to determine the appropriate group) or user and group separated by a + // colon. + // Can be specified by name or UID/GID. + // If unset, this will default to UID and GID 0 (root). User string `json:"user,omitempty"` - // Additional groups to add + // Groups are additional groups to add the container's user to. These + // are resolved within the container using the container's /etc/passwd. Groups []string `json:"groups,omitempty"` - // AddCurrentUserPasswdEntry indicates that the current user passwd entry - // should be added to the /etc/passwd within the container + // AddCurrentUserPasswdEntry indicates that Libpod should ensure that + // the container's /etc/passwd contains an entry for the user running + // Libpod - mostly used in rootless containers where the user running + // Libpod wants to retain their UID inside the container. AddCurrentUserPasswdEntry bool `json:"addCurrentUserPasswdEntry,omitempty"` } diff --git a/libpod/container_exec.go b/libpod/container_exec.go index 08e95e6dd..bfeae0a11 100644 --- a/libpod/container_exec.go +++ b/libpod/container_exec.go @@ -415,6 +415,13 @@ func (c *Container) ExecHTTPStartAndAttach(sessionID string, httpCon net.Conn, h execOpts, err := prepareForExec(c, session) if err != nil { + session.State = define.ExecStateStopped + session.ExitCode = define.ExecErrorCodeGeneric + + if err := c.save(); err != nil { + logrus.Errorf("Error saving container %s exec session %s after failure to prepare: %v", err, c.ID(), session.ID()) + } + return err } @@ -427,6 +434,13 @@ func (c *Container) ExecHTTPStartAndAttach(sessionID string, httpCon net.Conn, h pid, attachChan, err := c.ociRuntime.ExecContainerHTTP(c, session.ID(), execOpts, httpCon, httpBuf, streams, cancel) if err != nil { + session.State = define.ExecStateStopped + session.ExitCode = define.TranslateExecErrorToExitCode(define.ExecErrorCodeGeneric, err) + + if err := c.save(); err != nil { + logrus.Errorf("Error saving container %s exec session %s after failure to start: %v", err, c.ID(), session.ID()) + } + return err } diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 4cfe992ea..9fb9738dc 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -159,7 +159,32 @@ func (c *Container) prepare() error { } // Save changes to container state - return c.save() + if err := c.save(); err != nil { + return err + } + + // Ensure container entrypoint is created (if required) + if c.config.CreateWorkingDir { + workdir, err := securejoin.SecureJoin(c.state.Mountpoint, c.WorkingDir()) + if err != nil { + return errors.Wrapf(err, "error creating path to container %s working dir", c.ID()) + } + rootUID := c.RootUID() + rootGID := c.RootGID() + + if err := os.MkdirAll(workdir, 0755); err != nil { + if os.IsExist(err) { + return nil + } + return errors.Wrapf(err, "error creating container %s working dir", c.ID()) + } + + if err := os.Chown(workdir, rootUID, rootGID); err != nil { + return errors.Wrapf(err, "error chowning container %s working directory to container root", c.ID()) + } + } + + return nil } // cleanupNetwork unmounts and cleans up the container's network diff --git a/libpod/image/image.go b/libpod/image/image.go index 8b2aa318f..4664da63e 100644 --- a/libpod/image/image.go +++ b/libpod/image/image.go @@ -14,6 +14,7 @@ import ( "syscall" "time" + "github.com/containers/common/pkg/retry" cp "github.com/containers/image/v5/copy" "github.com/containers/image/v5/directory" dockerarchive "github.com/containers/image/v5/docker/archive" @@ -75,6 +76,8 @@ type InfoImage struct { Layers []LayerInfo } +const maxRetry = 3 + // ImageFilter is a function to determine whether a image is included // in command output. Images to be outputted are tested using the function. // A true return will include the image, a false return will exclude it. @@ -158,7 +161,7 @@ func (ir *Runtime) New(ctx context.Context, name, signaturePolicyPath, authfile if signaturePolicyPath == "" { signaturePolicyPath = ir.SignaturePolicyPath } - imageName, err := ir.pullImageFromHeuristicSource(ctx, name, writer, authfile, signaturePolicyPath, signingoptions, dockeroptions, label) + imageName, err := ir.pullImageFromHeuristicSource(ctx, name, writer, authfile, signaturePolicyPath, signingoptions, dockeroptions, &retry.RetryOptions{MaxRetry: maxRetry}, label) if err != nil { return nil, errors.Wrapf(err, "unable to pull %s", name) } @@ -176,7 +179,7 @@ func (ir *Runtime) LoadFromArchiveReference(ctx context.Context, srcRef types.Im if signaturePolicyPath == "" { signaturePolicyPath = ir.SignaturePolicyPath } - imageNames, err := ir.pullImageFromReference(ctx, srcRef, writer, "", signaturePolicyPath, SigningOptions{}, &DockerRegistryOptions{}) + imageNames, err := ir.pullImageFromReference(ctx, srcRef, writer, "", signaturePolicyPath, SigningOptions{}, &DockerRegistryOptions{}, &retry.RetryOptions{MaxRetry: maxRetry}) if err != nil { return nil, errors.Wrapf(err, "unable to pull %s", transports.ImageName(srcRef)) } diff --git a/libpod/image/pull.go b/libpod/image/pull.go index d31f0dbdc..641698d03 100644 --- a/libpod/image/pull.go +++ b/libpod/image/pull.go @@ -7,6 +7,7 @@ import ( "path/filepath" "strings" + "github.com/containers/common/pkg/retry" cp "github.com/containers/image/v5/copy" "github.com/containers/image/v5/directory" "github.com/containers/image/v5/docker" @@ -218,7 +219,7 @@ func toLocalImageName(imageName string) string { // pullImageFromHeuristicSource pulls an image based on inputName, which is heuristically parsed and may involve configured registries. // Use pullImageFromReference if the source is known precisely. -func (ir *Runtime) pullImageFromHeuristicSource(ctx context.Context, inputName string, writer io.Writer, authfile, signaturePolicyPath string, signingOptions SigningOptions, dockerOptions *DockerRegistryOptions, label *string) ([]string, error) { +func (ir *Runtime) pullImageFromHeuristicSource(ctx context.Context, inputName string, writer io.Writer, authfile, signaturePolicyPath string, signingOptions SigningOptions, dockerOptions *DockerRegistryOptions, retryOptions *retry.RetryOptions, label *string) ([]string, error) { span, _ := opentracing.StartSpanFromContext(ctx, "pullImageFromHeuristicSource") defer span.Finish() @@ -247,11 +248,11 @@ func (ir *Runtime) pullImageFromHeuristicSource(ctx context.Context, inputName s return nil, errors.Wrapf(err, "error determining pull goal for image %q", inputName) } } - return ir.doPullImage(ctx, sc, *goal, writer, signingOptions, dockerOptions, label) + return ir.doPullImage(ctx, sc, *goal, writer, signingOptions, dockerOptions, retryOptions, label) } // pullImageFromReference pulls an image from a types.imageReference. -func (ir *Runtime) pullImageFromReference(ctx context.Context, srcRef types.ImageReference, writer io.Writer, authfile, signaturePolicyPath string, signingOptions SigningOptions, dockerOptions *DockerRegistryOptions) ([]string, error) { +func (ir *Runtime) pullImageFromReference(ctx context.Context, srcRef types.ImageReference, writer io.Writer, authfile, signaturePolicyPath string, signingOptions SigningOptions, dockerOptions *DockerRegistryOptions, retryOptions *retry.RetryOptions) ([]string, error) { span, _ := opentracing.StartSpanFromContext(ctx, "pullImageFromReference") defer span.Finish() @@ -264,7 +265,7 @@ func (ir *Runtime) pullImageFromReference(ctx context.Context, srcRef types.Imag if err != nil { return nil, errors.Wrapf(err, "error determining pull goal for image %q", transports.ImageName(srcRef)) } - return ir.doPullImage(ctx, sc, *goal, writer, signingOptions, dockerOptions, nil) + return ir.doPullImage(ctx, sc, *goal, writer, signingOptions, dockerOptions, retryOptions, nil) } func cleanErrorMessage(err error) string { @@ -274,7 +275,7 @@ func cleanErrorMessage(err error) string { } // doPullImage is an internal helper interpreting pullGoal. Almost everyone should call one of the callers of doPullImage instead. -func (ir *Runtime) doPullImage(ctx context.Context, sc *types.SystemContext, goal pullGoal, writer io.Writer, signingOptions SigningOptions, dockerOptions *DockerRegistryOptions, label *string) ([]string, error) { +func (ir *Runtime) doPullImage(ctx context.Context, sc *types.SystemContext, goal pullGoal, writer io.Writer, signingOptions SigningOptions, dockerOptions *DockerRegistryOptions, retryOptions *retry.RetryOptions, label *string) ([]string, error) { span, _ := opentracing.StartSpanFromContext(ctx, "doPullImage") defer span.Finish() @@ -310,9 +311,11 @@ func (ir *Runtime) doPullImage(ctx context.Context, sc *types.SystemContext, goa return nil, err } } - - _, err = cp.Image(ctx, policyContext, imageInfo.dstRef, imageInfo.srcRef, copyOptions) - if err != nil { + imageInfo := imageInfo + if err = retry.RetryIfNecessary(ctx, func() error { + _, err = cp.Image(ctx, policyContext, imageInfo.dstRef, imageInfo.srcRef, copyOptions) + return err + }, retryOptions); err != nil { pullErrors = multierror.Append(pullErrors, err) logrus.Debugf("Error pulling image ref %s: %v", imageInfo.srcRef.StringWithinTransport(), err) if writer != nil { diff --git a/libpod/options.go b/libpod/options.go index b98ef2221..16b05d9b6 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -1451,6 +1451,19 @@ func WithCreateCommand(cmd []string) CtrCreateOption { } } +// WithCreateWorkingDir tells Podman to create the container's working directory +// if it does not exist. +func WithCreateWorkingDir() CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.CreateWorkingDir = true + return nil + } +} + // Volume Creation Options // WithVolumeName sets the name of the volume. diff --git a/pkg/bindings/containers/containers.go b/pkg/bindings/containers/containers.go index 9e6dbef19..9913b773b 100644 --- a/pkg/bindings/containers/containers.go +++ b/pkg/bindings/containers/containers.go @@ -98,7 +98,7 @@ func Remove(ctx context.Context, nameOrID string, force, volumes *bool) error { params.Set("force", strconv.FormatBool(*force)) } if volumes != nil { - params.Set("vols", strconv.FormatBool(*volumes)) + params.Set("v", strconv.FormatBool(*volumes)) } response, err := conn.DoRequest(nil, http.MethodDelete, "/containers/%s", params, nil, nameOrID) if err != nil { diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go index 05adc40fe..35675e1f3 100644 --- a/pkg/domain/infra/abi/images.go +++ b/pkg/domain/infra/abi/images.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "net/url" "os" + "path" "path/filepath" "strconv" "strings" @@ -682,10 +683,6 @@ func (ir *ImageEngine) Shutdown(_ context.Context) { } func (ir *ImageEngine) Sign(ctx context.Context, names []string, options entities.SignOptions) (*entities.SignReport, error) { - dockerRegistryOptions := image.DockerRegistryOptions{ - DockerCertPath: options.CertDir, - } - mech, err := signature.NewGPGSigningMechanism() if err != nil { return nil, errors.Wrap(err, "error initializing GPG") @@ -704,7 +701,6 @@ func (ir *ImageEngine) Sign(ctx context.Context, names []string, options entitie } for _, signimage := range names { - var sigStoreDir string srcRef, err := alltransports.ParseImageName(signimage) if err != nil { return nil, errors.Wrapf(err, "error parsing image name") @@ -725,40 +721,38 @@ func (ir *ImageEngine) Sign(ctx context.Context, names []string, options entitie if dockerReference == nil { return nil, errors.Errorf("cannot determine canonical Docker reference for destination %s", transports.ImageName(rawSource.Reference())) } - - // create the signstore file - rtc, err := ir.Libpod.GetConfig() - if err != nil { - return nil, err - } - newImage, err := ir.Libpod.ImageRuntime().New(ctx, signimage, rtc.Engine.SignaturePolicyPath, "", os.Stderr, &dockerRegistryOptions, image.SigningOptions{SignBy: options.SignBy}, nil, util.PullImageMissing) - if err != nil { - return nil, errors.Wrapf(err, "error pulling image %s", signimage) + var sigStoreDir string + if options.Directory != "" { + sigStoreDir = options.Directory } if sigStoreDir == "" { if rootless.IsRootless() { sigStoreDir = filepath.Join(filepath.Dir(ir.Libpod.StorageConfig().GraphRoot), "sigstore") } else { + var sigStoreURI string registryInfo := trust.HaveMatchRegistry(rawSource.Reference().DockerReference().String(), registryConfigs) if registryInfo != nil { - if sigStoreDir = registryInfo.SigStoreStaging; sigStoreDir == "" { - sigStoreDir = registryInfo.SigStore - + if sigStoreURI = registryInfo.SigStoreStaging; sigStoreURI == "" { + sigStoreURI = registryInfo.SigStore } } + if sigStoreURI == "" { + return nil, errors.Errorf("no signature storage configuration found for %s", rawSource.Reference().DockerReference().String()) + + } + sigStoreDir, err = localPathFromURI(sigStoreURI) + if err != nil { + return nil, errors.Wrapf(err, "invalid signature storage %s", sigStoreURI) + } } } - sigStoreDir, err = isValidSigStoreDir(sigStoreDir) + manifestDigest, err := manifest.Digest(getManifest) if err != nil { - return nil, errors.Wrapf(err, "invalid signature storage %s", sigStoreDir) - } - repos, err := newImage.RepoDigests() - if err != nil { - return nil, errors.Wrapf(err, "error calculating repo digests for %s", signimage) + return nil, err } - if len(repos) == 0 { - logrus.Errorf("no repodigests associated with the image %s", signimage) - continue + repo := reference.Path(dockerReference) + if path.Clean(repo) != repo { // Coverage: This should not be reachable because /./ and /../ components are not valid in docker references + return nil, errors.Errorf("Unexpected path elements in Docker reference %s for signature storage", dockerReference.String()) } // create signature @@ -766,22 +760,21 @@ func (ir *ImageEngine) Sign(ctx context.Context, names []string, options entitie if err != nil { return nil, errors.Wrapf(err, "error creating new signature") } - - trimmedDigest := strings.TrimPrefix(repos[0], strings.Split(repos[0], "/")[0]) - sigStoreDir = filepath.Join(sigStoreDir, strings.Replace(trimmedDigest, ":", "=", 1)) - if err := os.MkdirAll(sigStoreDir, 0751); err != nil { + // create the signstore file + signatureDir := fmt.Sprintf("%s@%s=%s", filepath.Join(sigStoreDir, repo), manifestDigest.Algorithm(), manifestDigest.Hex()) + if err := os.MkdirAll(signatureDir, 0751); err != nil { // The directory is allowed to exist if !os.IsExist(err) { - logrus.Errorf("error creating directory %s: %s", sigStoreDir, err) + logrus.Errorf("error creating directory %s: %s", signatureDir, err) continue } } - sigFilename, err := getSigFilename(sigStoreDir) + sigFilename, err := getSigFilename(signatureDir) if err != nil { logrus.Errorf("error creating sigstore file: %v", err) continue } - err = ioutil.WriteFile(filepath.Join(sigStoreDir, sigFilename), newSig, 0644) + err = ioutil.WriteFile(filepath.Join(signatureDir, sigFilename), newSig, 0644) if err != nil { logrus.Errorf("error storing signature for %s", rawSource.Reference().DockerReference().String()) continue @@ -809,14 +802,12 @@ func getSigFilename(sigStoreDirPath string) (string, error) { } } -func isValidSigStoreDir(sigStoreDir string) (string, error) { - writeURIs := map[string]bool{"file": true} +func localPathFromURI(sigStoreDir string) (string, error) { url, err := url.Parse(sigStoreDir) if err != nil { return sigStoreDir, errors.Wrapf(err, "invalid directory %s", sigStoreDir) } - _, exists := writeURIs[url.Scheme] - if !exists { + if url.Scheme != "file" { return sigStoreDir, errors.Errorf("writing to %s is not supported. Use a supported scheme", sigStoreDir) } sigStoreDir = url.Path diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index 1fad67b86..d2221ab7b 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -500,9 +500,6 @@ func (ic *ContainerEngine) ContainerList(ctx context.Context, options entities.C } func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.ContainerRunOptions) (*entities.ContainerRunReport, error) { - if opts.Rm { - logrus.Info("the remote client does not support --rm yet") - } con, err := containers.CreateWithSpec(ic.ClientCxt, opts.Spec) if err != nil { return nil, err @@ -526,6 +523,17 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta if err != nil { report.ExitCode = define.ExitCode(err) } + if opts.Rm { + if err := containers.Remove(ic.ClientCxt, con.ID, bindings.PFalse, bindings.PTrue); err != nil { + if errors.Cause(err) == define.ErrNoSuchCtr || + errors.Cause(err) == define.ErrCtrRemoved { + logrus.Warnf("Container %s does not exist: %v", con.ID, err) + } else { + logrus.Errorf("Error removing container %s: %v", con.ID, err) + } + } + } + return &report, err } diff --git a/pkg/rootless/rootless_linux.c b/pkg/rootless/rootless_linux.c index 0223c35ee..2e1fddc48 100644 --- a/pkg/rootless/rootless_linux.c +++ b/pkg/rootless/rootless_linux.c @@ -225,6 +225,16 @@ can_use_shortcut () return ret; } +int +is_fd_inherited(int fd) +{ + if (open_files_set == NULL || fd > open_files_max_fd || fd < 0) + { + return 0; + } + return FD_ISSET(fd % FD_SETSIZE, &(open_files_set[fd / FD_SETSIZE])) ? 1 : 0; +} + static void __attribute__((constructor)) init() { const char *xdg_runtime_dir; diff --git a/pkg/rootless/rootless_linux.go b/pkg/rootless/rootless_linux.go index ccc8a1d94..c3f1fc7fa 100644 --- a/pkg/rootless/rootless_linux.go +++ b/pkg/rootless/rootless_linux.go @@ -32,6 +32,7 @@ extern uid_t rootless_gid(); extern int reexec_in_user_namespace(int ready, char *pause_pid_file_path, char *file_to_read, int fd); extern int reexec_in_user_namespace_wait(int pid, int options); extern int reexec_userns_join(int pid, char *pause_pid_file_path); +extern int is_fd_inherited(int fd); */ import "C" @@ -520,3 +521,8 @@ func ConfigurationMatches() (bool, error) { return matches(GetRootlessGID(), gids, currentGIDs), nil } + +// IsFdInherited checks whether the fd is opened and valid to use +func IsFdInherited(fd int) bool { + return int(C.is_fd_inherited(C.int(fd))) > 0 +} diff --git a/pkg/rootless/rootless_unsupported.go b/pkg/rootless/rootless_unsupported.go index 1499b737f..7dfb4a4b2 100644 --- a/pkg/rootless/rootless_unsupported.go +++ b/pkg/rootless/rootless_unsupported.go @@ -64,3 +64,8 @@ func GetConfiguredMappings() ([]idtools.IDMap, []idtools.IDMap, error) { func ReadMappingsProc(path string) ([]idtools.IDMap, error) { return nil, nil } + +// IsFdInherited checks whether the fd is opened and valid to use +func IsFdInherited(fd int) bool { + return false +} diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index 9dfb35be3..b61ac2c30 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -238,6 +238,17 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen. if s.Entrypoint != nil { options = append(options, libpod.WithEntrypoint(s.Entrypoint)) } + // If the user did not set an workdir but the image did, ensure it is + // created. + if s.WorkDir == "" && img != nil { + newWD, err := img.WorkingDir(ctx) + if err != nil { + return nil, err + } + if newWD != "" { + options = append(options, libpod.WithCreateWorkingDir()) + } + } if s.StopSignal != nil { options = append(options, libpod.WithStopSignal(*s.StopSignal)) } diff --git a/pkg/trust/trust.go b/pkg/trust/trust.go index 60de099fa..2348bc410 100644 --- a/pkg/trust/trust.go +++ b/pkg/trust/trust.go @@ -12,9 +12,9 @@ import ( "strings" "github.com/containers/image/v5/types" + "github.com/ghodss/yaml" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "gopkg.in/yaml.v2" ) // PolicyContent struct for policy.json file @@ -157,7 +157,7 @@ func HaveMatchRegistry(key string, registryConfigs *RegistryConfiguration) *Regi searchKey = searchKey[:strings.LastIndex(searchKey, "/")] } } - return nil + return registryConfigs.DefaultDocker } // CreateTmpFile creates a temp file under dir and writes the content into it diff --git a/test/e2e/exec_test.go b/test/e2e/exec_test.go index f5d15d3bd..055546f88 100644 --- a/test/e2e/exec_test.go +++ b/test/e2e/exec_test.go @@ -210,7 +210,6 @@ var _ = Describe("Podman exec", func() { }) It("podman exec missing working directory test", func() { - Skip(v2remotefail) setup := podmanTest.RunTopContainer("test1") setup.WaitWithDefaultTimeout() Expect(setup.ExitCode()).To(Equal(0)) @@ -225,7 +224,6 @@ var _ = Describe("Podman exec", func() { }) It("podman exec cannot be invoked", func() { - Skip(v2remotefail) setup := podmanTest.RunTopContainer("test1") setup.WaitWithDefaultTimeout() Expect(setup.ExitCode()).To(Equal(0)) @@ -236,7 +234,6 @@ var _ = Describe("Podman exec", func() { }) It("podman exec command not found", func() { - Skip(v2remotefail) setup := podmanTest.RunTopContainer("test1") setup.WaitWithDefaultTimeout() Expect(setup.ExitCode()).To(Equal(0)) diff --git a/test/e2e/image_sign_test.go b/test/e2e/image_sign_test.go new file mode 100644 index 000000000..c54cf433d --- /dev/null +++ b/test/e2e/image_sign_test.go @@ -0,0 +1,62 @@ +// +build !remote + +package integration + +import ( + "os" + "os/exec" + "path/filepath" + + . "github.com/containers/podman/v2/test/utils" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Podman image sign", func() { + var ( + origGNUPGHOME string + tempdir string + err error + podmanTest *PodmanTestIntegration + ) + + BeforeEach(func() { + tempdir, err = CreateTempDirInTempDir() + if err != nil { + os.Exit(1) + } + podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() + podmanTest.SeedImages() + + tempGNUPGHOME := filepath.Join(podmanTest.TempDir, "tmpGPG") + err := os.Mkdir(tempGNUPGHOME, os.ModePerm) + Expect(err).To(BeNil()) + + origGNUPGHOME = os.Getenv("GNUPGHOME") + err = os.Setenv("GNUPGHOME", tempGNUPGHOME) + Expect(err).To(BeNil()) + + }) + + AfterEach(func() { + podmanTest.Cleanup() + f := CurrentGinkgoTestDescription() + processTestResult(f) + os.Setenv("GNUPGHOME", origGNUPGHOME) + }) + + It("podman sign image", func() { + cmd := exec.Command("gpg", "--import", "sign/secret-key.asc") + err := cmd.Run() + Expect(err).To(BeNil()) + sigDir := filepath.Join(podmanTest.TempDir, "test-sign") + err = os.MkdirAll(sigDir, os.ModePerm) + Expect(err).To(BeNil()) + session := podmanTest.Podman([]string{"image", "sign", "--directory", sigDir, "--sign-by", "foo@bar.com", "docker://library/alpine"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + _, err = os.Stat(filepath.Join(sigDir, "library")) + Expect(err).To(BeNil()) + }) +}) diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index f2a6d14eb..9cb76d1f6 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -1063,6 +1063,13 @@ USER mail` Expect(session.ExitCode()).To(Equal(0)) }) + It("podman run --preserve-fds invalid fd", func() { + session := podmanTest.Podman([]string{"run", "--preserve-fds", "2", ALPINE}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session.ErrorToString()).To(ContainSubstring("file descriptor 3 is not available")) + }) + It("podman run --privileged and --group-add", func() { groupName := "kvm" session := podmanTest.Podman([]string{"run", "-t", "-i", "--group-add", groupName, "--privileged", fedoraMinimal, "groups"}) @@ -1135,4 +1142,16 @@ USER mail` Expect(session.ExitCode()).To(Not(Equal(0))) Expect(session.ErrorToString()).To(ContainSubstring("Invalid umask")) }) + + It("podman run makes entrypoint from image", func() { + // BuildImage does not seem to work remote + SkipIfRemote() + dockerfile := `FROM busybox +WORKDIR /madethis` + podmanTest.BuildImage(dockerfile, "test", "false") + session := podmanTest.Podman([]string{"run", "--rm", "test", "pwd"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(ContainSubstring("/madethis")) + }) }) diff --git a/test/e2e/sign/secret-key.asc b/test/e2e/sign/secret-key.asc new file mode 100644 index 000000000..23c0d05c3 --- /dev/null +++ b/test/e2e/sign/secret-key.asc @@ -0,0 +1,57 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- + +lQOYBF8kNqwBCAC0x3Kog+WlDNwcR6rWIP8Gj2T6LrQ2/3knSyAWzTgC/OBB6Oh0 +KAokXLjy8J3diG3EaSltE7erGG/bZCz8jYvMiwDJScON4zzidotqjoY80E+NeRDg +CC0gqvqmh0ftJIjYNBHzSxqrGRQwzwZU+u6ezlE8+0dvsHcHY+MRnxXJQrdM07EP +Prp85kKckChDlJ1tyGUB/YHieFQmOW5+TERA7ZqQOAQ12Vviv6V4kNfEJJq3MS2c +csZpO323tcHt3oebqsZCIElhX7uVw6GAeCw1tm4NZXs4g1yIC21Of/hzPeC18F72 +splCgKaAOiE9w/nMGLNEYy2NzgEclZLs2Y7jABEBAAEAB/9VOcwHvvrWN3xThsP2 +5CJmxNZtjfQfE4zZ5fRwW3pjCjVtTTC9hhzV7LKysZYzGPzqwksp5chKjKA7VXxR +6ic0nHmX68MaEr2i5BExAJUveWNvxloazC/+PS0ishdKKNWs28t0n/0oGZAnvIn3 +KT+ytYCeF7ajZJWQ8dncdlvuf86I8GdmqP2Og9A67LUpJfH2rtpBjzH25nLSZ3Qz +QbHoUIv318Wwb1sIidkmPsZufZG3pMsYjtFtJjkWt0lRsJQnSE9AQOpQTkLsVsh2 +FYQZ2ODhH8+NE86UNAAr2JiMZHoTrEUL2SLwpXEthFIR78N009dOS4nw8CLB61BL +pr6lBADH6yoF95rI0LR3jphfr7e8K3BViTPK97wke6EqwTlVo0TCdR785sa8T8O8 +HvlYx4a+h3e6D4D0vjDPzxtevjdTjYmxqI3cwE2N3NFALgGBwHs01qRRIw4qxZlC +1L1byJ8rUVi5z3YMO7X4RcXSAtg3fM2fx2x+sdpyH30jafuKPwQA533PVRo/Rlgt +pOak9Fs+3KOIb+oO8ypy7RRQcnsTKajts0sUE9scBhT4tSDeSa/HaDvzLiE8KKCK +3rhn8ZKLTW1fvKNZBj6oGlIRyfOFjtN/jMRjo0WVHSUDJ59Zr8C0khpP5J73yhTr +fDhcuTPWiCjlDYeSFHV/a4Z45GG2Kl0EAL1I31kxnSQR9bN5ZmvV+aOhTRKOuHDm +6nISF/XnVwuGHCvMbFRKsTxGkGrPO5VQZflFOqVab9umIQkOIcrzeKj+slYlm5VA +zKfCQ1vZ2f74QYCNP8oeRa1r3D46fszcElZJQxtZZewYRKX63bvU4F+hql8dJTqe +e3wVq8QD657yRwC0FGZvb2JhciA8Zm9vQGJhci5jb20+iQFUBBMBCAA+FiEERyT4 +ac7LLibByeabqaoHAy6P2bIFAl8kNqwCGwMFCQPCZwAFCwkIBwIGFQoJCAsCBBYC +AwECHgECF4AACgkQqaoHAy6P2bKtuggAgv54/F8wgi+uMrtFr8rqNtZMDyXRxfXa +XUy5uGNfqHD83yqxweEqxiA8lmFkRHixPWtgZ2MniFXMVc9kVmg8GNIIuzewXrPq +tXztvuURQo9phK68v8fXEqqT6K25wtq8TiQZ0J3mQIJPPTMe3pCCOyR6+W3iMtQp +2AmitxKbzLP3J3GG2i0rG5S147A2rPnzTeMYhds819+JE7jNMD7FkV+TcQlOVl4w +yOQhNEJcjb6rA6EUe5+s85pIFTBSyPMJpJ03Y0dLdcSGpKdncGTK2X9+hS96G1+F +P/t8hRIDblqUHtBRXe3Ozz6zSqpqu1DbAQSMbIrLYxXfnZEN+ro0dJ0DmARfJDas +AQgAncvLLZUHZkJWDPka3ocysJ7+/lmrXyAjT3D4r7UM4oaLBOMKjvaKSDw1uW5q +YmTxnnsqFDI0O5+XJxD1/0qEf6l2oUpnILdxVruf28FuvymbsyhDgs+MBoHz0jLW +WPHUW2oWLIqcvaF0BePQ1GS6UoZlmZejsLwwcSpbaAHJng7An/iLuqOBr5EdUA5X +MXqmdMFDrjh0uZezImJ2Eacu/hshBdu3IY49J5XP18GWrSdUnP27cv3tOii9j5Lf +l8QAvCN89vkALIU3eZtnMlWZqLgl5o6COVFmzpyx+iHOoCznQBt0aGoSNmE/dAqW +IQS/xCSFqMHI6kNd9N0oR0rEHwARAQABAAf+L3mAhBLJ2qDLrfSGenv3qr7zXggR +cLnFFeIZ2BdjLIYpLku2wgN34DrJOSR4umi/bxyEMPZX07Z0rgrC0E+VpKkSKX2u +oF/AqEUj1+SPEtGMaC8NfL4/1Tdk6ZFk/vanGufEixsbBEyekSUVD8nMawbHa5n9 +ZC+CbZG+VYDwLW6u0Pb26CIhqpFNQL3E88uLeVNhnE+nNJfgB2Nyo8gUQszovUxk +hv64UlXYA3wt49mpc9ORs9qKMZkuKdJfYmJkmvqLE35YpRRz6i1+hg71doj7Sjel +naBV3qIrIbcN6I2/9ZUwVwCzttpeHDfKOxQk5szWFop6H79TZGRsrV6boQQAwnFO +v5pjsZLhqHIZPty3zXz7Tv3LmaatwA260n6NcLBuQFiuEoh2QsMfVtLU8bBzyuC8 +Znx3kPlGCCognSjkEis+aEjsZgvCzR3aP+FWejkhnZnFiSJDvgEftODLF3gSPVp3 +dhc6q5GLysc0iN/gkBZN8Qm1lL/kEyeri4mbWT8EAM/AczrXL0tPEV3YzyeyM972 +HP9OnIYoyIkCa4M0PA0qhUPJ+vBHl/1+p5WZD/eokXqJ2M8IqNSlinuou3azbg+r +N3xTaB0a+Vx6O/RRI73+4UK2fyN9gYRH437eliNBRTkZeZCQ6Dd5eYcABaL2DbSs +1dyGXzRWfzdvGVu/r/0hBACER5u/uac+y9sXr79imoLVya25XkjvswGrDxmrlmNg +cfn/bix9Z93TXScYPiyxzLwlDDd7ovlpv1+mbHNgj6krSGG+R+uLQ2+nm+9glMmz +KupEYF59lzOgEYScJaHQWBULPRGUy/7HmZGpsDmz8zpj8lHaFNLlqDzrxw3MNKxO +F0NFiQE8BBgBCAAmFiEERyT4ac7LLibByeabqaoHAy6P2bIFAl8kNqwCGwwFCQPC +ZwAACgkQqaoHAy6P2bJfjQgAje6YR+p1QaNlTN9l4t2kGzy9RhkfYMrTgI2fEqbS +9bFJUy3Y3mH+vj/r2gN/kaN8LHH4K1d7fAohBsFqSI0flzHHIx2rfti9zAlbXcAE +rbnG+f0fk0AaqU7KelU35vjPfNe6Vn7ky6G9CC6jW04NkLZDNFA2GusdYf1aM0LW +ew5t4WZaquLVFhL36q9eHaogO/fcPR/quvQefHokk+b541ytwMN9l/g43rTbCvAj +rUDHwipbGbw91Wg2XjbecRiCXDKWds2M149BpxUzY5xHFtD5t5WSEE/SkkryGTMm +TxS3tuQZ9PdtCPGrNDO6Ts/amORF04Tf+YMJgfv3IWxMeQ== +=6kcB +-----END PGP PRIVATE KEY BLOCK----- diff --git a/test/system/030-run.bats b/test/system/030-run.bats index e93a2efe2..41863ba04 100644 --- a/test/system/030-run.bats +++ b/test/system/030-run.bats @@ -63,7 +63,6 @@ echo $rand | 0 | $rand # 'run --preserve-fds' passes a number of additional file descriptors into the container @test "podman run --preserve-fds" { - skip "enable this once #6653 is fixed" skip_if_remote content=$(random_string 20) @@ -96,8 +95,6 @@ echo $rand | 0 | $rand # Believe it or not, 'sh -c' resulted in different behavior run_podman 0 run --rm $IMAGE sh -c /bin/true run_podman 1 run --rm $IMAGE sh -c /bin/false - - if is_remote; then sleep 2;fi # FIXME: pending #7119 } @test "podman run --name" { @@ -266,8 +263,6 @@ echo $rand | 0 | $rand done done done - - if is_remote; then sleep 2;fi # FIXME: pending #7119 } # #6829 : add username to /etc/passwd inside container if --userns=keep-id @@ -292,8 +287,6 @@ echo $rand | 0 | $rand run_podman run --rm --privileged --userns=keep-id --user=0 $IMAGE id -un remove_same_dev_warning # grumble is "$output" "root" "--user=0 overrides keep-id" - - if is_remote; then sleep 2;fi # FIXME: pending #7119 } # #6991 : /etc/passwd is modifiable diff --git a/test/system/070-build.bats b/test/system/070-build.bats index 481e1759b..d2ef9f0f9 100644 --- a/test/system/070-build.bats +++ b/test/system/070-build.bats @@ -91,7 +91,6 @@ ADD https://github.com/containers/podman/blob/master/README.md /tmp/ EOF run_podman build -t add_url $tmpdir run_podman run --rm add_url stat /tmp/README.md - if is_remote; then sleep 2;fi # FIXME: pending #7119 run_podman rmi -f add_url # Now test COPY. That should fail. diff --git a/test/system/075-exec.bats b/test/system/075-exec.bats index b2c49510a..aa6e2bd28 100644 --- a/test/system/075-exec.bats +++ b/test/system/075-exec.bats @@ -19,6 +19,15 @@ load helpers run_podman exec $cid sh -c "cat /$rand_filename" is "$output" "$rand_content" "Can exec and see file in running container" + + # Specially defined situations: exec a dir, or no such command. + # We don't check the full error message because runc & crun differ. + run_podman 126 exec $cid /etc + is "$output" ".*permission denied" "podman exec /etc" + run_podman 127 exec $cid /no/such/command + is "$output" ".*such file or dir" "podman exec /no/such/command" + + # Done run_podman exec $cid rm -f /$rand_filename run_podman wait $cid diff --git a/test/system/160-volumes.bats b/test/system/160-volumes.bats index ef38b2a68..3f50bd3c4 100644 --- a/test/system/160-volumes.bats +++ b/test/system/160-volumes.bats @@ -93,7 +93,6 @@ Labels.l | $mylabel is "$(<$mountpoint/myfile)" "$rand" "we see content created in container" # Clean up - if is_remote; then sleep 2;fi # FIXME: pending #7119 run_podman volume rm $myvolume } @@ -135,15 +134,12 @@ EOF is "$output" "got here -$rand-" "script in volume is runnable with default (exec)" # Clean up - if is_remote; then sleep 2;fi # FIXME: pending #7119 run_podman volume rm $myvolume } # Anonymous temporary volumes, and persistent autocreated named ones @test "podman volume, implicit creation with run" { - skip_if_remote "FIXME: pending #7128" - # No hostdir arg: create anonymous container with random name rand=$(random_string) run_podman run -v /myvol $IMAGE sh -c "echo $rand >/myvol/myfile" @@ -175,7 +171,6 @@ EOF run_podman run --rm -v $myvol:/myvol:z $IMAGE \ sh -c "cp /myvol/myfile /myvol/myfile2" - if is_remote; then sleep 2;fi # FIXME: pending #7119 run_podman volume rm $myvol # Autocreated volumes should also work with keep-id @@ -184,7 +179,6 @@ EOF run_podman run --rm -v $myvol:/myvol:z --userns=keep-id $IMAGE \ touch /myvol/myfile - if is_remote; then sleep 2;fi # FIXME: pending #7119 run_podman volume rm $myvol } diff --git a/test/system/200-pod.bats b/test/system/200-pod.bats index cbfd7fe03..f3ec8a67c 100644 --- a/test/system/200-pod.bats +++ b/test/system/200-pod.bats @@ -93,7 +93,6 @@ function teardown() { is "$output" "$message" "message sent from one container to another" # Clean up. First the nc -l container... - if is_remote; then sleep 2;fi # FIXME: pending #7119 run_podman rm $cid1 # ...then, from pause container, find the image ID of the pause image... @@ -104,7 +103,6 @@ function teardown() { pause_iid="$output" # ...then rm the pod, then rmi the pause image so we don't leave strays. - if is_remote; then sleep 2;fi # FIXME: pending #7119 run_podman pod rm $podname run_podman rmi $pause_iid @@ -215,7 +213,6 @@ function random_ip() { is "$output" ".*options $dns_opt" "--dns-opt was added" # pod inspect - if is_remote; then sleep 2;fi # FIXME: pending #7119 run_podman pod inspect --format '{{.Name}}: {{.ID}} : {{.NumContainers}} : {{.Labels}}' mypod is "$output" "mypod: $pod_id : 1 : map\[${labelname}:${labelvalue}]" \ "pod inspect --format ..." diff --git a/test/system/300-cli-parsing.bats b/test/system/300-cli-parsing.bats index 2abc01bb7..92c073102 100644 --- a/test/system/300-cli-parsing.bats +++ b/test/system/300-cli-parsing.bats @@ -10,8 +10,6 @@ load helpers # Error: invalid argument "true=\"false\"" for "-l, --label" \ # flag: parse error on line 1, column 5: bare " in non-quoted-field run_podman run --rm --label 'true="false"' $IMAGE true - - if is_remote; then sleep 2;fi # FIXME: pending #7119 } # vim: filetype=sh diff --git a/test/system/400-unprivileged-access.bats b/test/system/400-unprivileged-access.bats index ebca75f13..1b2d14554 100644 --- a/test/system/400-unprivileged-access.bats +++ b/test/system/400-unprivileged-access.bats @@ -165,8 +165,6 @@ EOF die "$path: Unknown file type '$type'" fi done - - if is_remote; then sleep 2;fi # FIXME: pending #7119 } # vim: filetype=sh diff --git a/test/system/410-selinux.bats b/test/system/410-selinux.bats index c85fb2563..497e29b3e 100644 --- a/test/system/410-selinux.bats +++ b/test/system/410-selinux.bats @@ -16,7 +16,6 @@ function check_label() { # FIXME: it'd be nice to specify the command to run, e.g. 'ls -dZ /', # but alpine ls (from busybox) doesn't support -Z run_podman run --rm $args $IMAGE cat -v /proc/self/attr/current - if is_remote; then sleep 2;fi # FIXME: pending #7119 # FIXME: on some CI systems, 'run --privileged' emits a spurious # warning line about dup devices. Ignore it. diff --git a/test/utils/utils.go b/test/utils/utils.go index 2c454f532..b279a7084 100644 --- a/test/utils/utils.go +++ b/test/utils/utils.go @@ -207,6 +207,10 @@ func WaitContainerReady(p PodmanTestCommon, id string, expStr string, timeout in // OutputToString formats session output to string func (s *PodmanSession) OutputToString() string { + if s == nil || s.Out == nil || s.Out.Contents() == nil { + return "" + } + fields := strings.Fields(string(s.Out.Contents())) return strings.Join(fields, " ") } diff --git a/vendor/github.com/containers/common/pkg/retry/retry.go b/vendor/github.com/containers/common/pkg/retry/retry.go new file mode 100644 index 000000000..c20f900d8 --- /dev/null +++ b/vendor/github.com/containers/common/pkg/retry/retry.go @@ -0,0 +1,87 @@ +package retry + +import ( + "context" + "math" + "net" + "net/url" + "syscall" + "time" + + "github.com/docker/distribution/registry/api/errcode" + errcodev2 "github.com/docker/distribution/registry/api/v2" + "github.com/hashicorp/go-multierror" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +// RetryOptions defines the option to retry +type RetryOptions struct { + MaxRetry int // The number of times to possibly retry +} + +// RetryIfNecessary retries the operation in exponential backoff with the retryOptions +func RetryIfNecessary(ctx context.Context, operation func() error, retryOptions *RetryOptions) error { + err := operation() + for attempt := 0; err != nil && isRetryable(err) && attempt < retryOptions.MaxRetry; attempt++ { + delay := time.Duration(int(math.Pow(2, float64(attempt)))) * time.Second + logrus.Infof("Warning: failed, retrying in %s ... (%d/%d)", delay, attempt+1, retryOptions.MaxRetry) + select { + case <-time.After(delay): + break + case <-ctx.Done(): + return err + } + err = operation() + } + return err +} + +func isRetryable(err error) bool { + err = errors.Cause(err) + + if err == context.Canceled || err == context.DeadlineExceeded { + return false + } + + type unwrapper interface { + Unwrap() error + } + + switch e := err.(type) { + + case errcode.Error: + switch e.Code { + case errcode.ErrorCodeUnauthorized, errcodev2.ErrorCodeNameUnknown, errcodev2.ErrorCodeManifestUnknown: + return false + } + return true + case *net.OpError: + return isRetryable(e.Err) + case *url.Error: + return isRetryable(e.Err) + case syscall.Errno: + return e != syscall.ECONNREFUSED + case errcode.Errors: + // if this error is a group of errors, process them all in turn + for i := range e { + if !isRetryable(e[i]) { + return false + } + } + return true + case *multierror.Error: + // if this error is a group of errors, process them all in turn + for i := range e.Errors { + if !isRetryable(e.Errors[i]) { + return false + } + } + return true + case unwrapper: + err = e.Unwrap() + return isRetryable(err) + } + + return false +} diff --git a/vendor/modules.txt b/vendor/modules.txt index ac1e5036c..3f490616a 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -90,6 +90,7 @@ github.com/containers/common/pkg/auth github.com/containers/common/pkg/capabilities github.com/containers/common/pkg/cgroupv2 github.com/containers/common/pkg/config +github.com/containers/common/pkg/retry github.com/containers/common/pkg/sysinfo github.com/containers/common/version # github.com/containers/conmon v2.0.19+incompatible |