diff options
28 files changed, 417 insertions, 336 deletions
@@ -1591,6 +1591,8 @@ user [?string](#?string) workdir [?string](#?string) env [?[]string](#?[]string) + +detachKeys [?string](#?string) ### <a name="Image"></a>type Image diff --git a/cmd/podman/build.go b/cmd/podman/build.go index 19337985d..14ac51889 100644 --- a/cmd/podman/build.go +++ b/cmd/podman/build.go @@ -101,6 +101,10 @@ func getNsValues(c *cliconfig.BuildValues) ([]buildah.NamespaceOption, error) { Name: string(specs.NetworkNamespace), Host: true, }) + } else if c.Network == "container" { + ret = append(ret, buildah.NamespaceOption{ + Name: string(specs.NetworkNamespace), + }) } else if c.Network[0] == '/' { ret = append(ret, buildah.NamespaceOption{ Name: string(specs.NetworkNamespace), diff --git a/cmd/podman/exec.go b/cmd/podman/exec.go index 2e9b9e47e..649a7b0db 100644 --- a/cmd/podman/exec.go +++ b/cmd/podman/exec.go @@ -2,7 +2,6 @@ package main import ( "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/adapter" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -70,11 +69,5 @@ func execCmd(c *cliconfig.ExecValues) error { defer runtime.DeferredShutdown(false) exitCode, err = runtime.ExecContainer(getContext(), c) - if errors.Cause(err) == define.ErrOCIRuntimePermissionDenied { - exitCode = 126 - } - if errors.Cause(err) == define.ErrOCIRuntimeNotFound { - exitCode = 127 - } return err } diff --git a/cmd/podman/import.go b/cmd/podman/import.go index 70ea167cb..d49792f27 100644 --- a/cmd/podman/import.go +++ b/cmd/podman/import.go @@ -6,6 +6,7 @@ import ( "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/shared/parse" "github.com/containers/libpod/pkg/adapter" + multierror "github.com/hashicorp/go-multierror" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -69,8 +70,11 @@ func importCmd(c *cliconfig.ImportValues) error { return errors.Errorf("too many arguments. Usage TARBALL [REFERENCE]") } - if err := parse.ValidateFileName(source); err != nil { - return err + errFileName := parse.ValidateFileName(source) + errURL := parse.ValidURL(source) + + if errFileName != nil && errURL != nil { + return multierror.Append(errFileName, errURL) } quiet := c.Quiet diff --git a/cmd/podman/shared/container.go b/cmd/podman/shared/container.go index 5492f843f..7f53f5ec9 100644 --- a/cmd/podman/shared/container.go +++ b/cmd/podman/shared/container.go @@ -32,7 +32,7 @@ const ( cmdTruncLength = 17 ) -// PsOptions describes the struct being formed for ps +// PsOptions describes the struct being formed for ps. type PsOptions struct { All bool Format string @@ -47,8 +47,8 @@ type PsOptions struct { Sync bool } -// BatchContainerStruct is the return obkect from BatchContainer and contains -// container related information +// BatchContainerStruct is the return object from BatchContainer and contains +// container related information. type BatchContainerStruct struct { ConConfig *libpod.ContainerConfig ConState define.ContainerStatus @@ -61,7 +61,7 @@ type BatchContainerStruct struct { } // PsContainerOutput is the struct being returned from a parallel -// Batch operation +// batch operation. type PsContainerOutput struct { ID string Image string @@ -90,7 +90,7 @@ type PsContainerOutput struct { Mounts string } -// Namespace describes output for ps namespace +// Namespace describes output for ps namespace. type Namespace struct { PID string `json:"pid,omitempty"` Cgroup string `json:"cgroup,omitempty"` @@ -103,14 +103,14 @@ type Namespace struct { } // ContainerSize holds the size of the container's root filesystem and top -// read-write layer +// read-write layer. type ContainerSize struct { RootFsSize int64 `json:"rootFsSize"` RwSize int64 `json:"rwSize"` } // NewBatchContainer runs a batch process under one lock to get container information and only -// be called in PBatch +// be called in PBatch. func NewBatchContainer(ctr *libpod.Container, opts PsOptions) (PsContainerOutput, error) { var ( conState define.ContainerStatus @@ -257,15 +257,15 @@ type workerInput struct { job int } -// worker is a "threaded" worker that takes jobs from the channel "queue" +// worker is a "threaded" worker that takes jobs from the channel "queue". func worker(wg *sync.WaitGroup, jobs <-chan workerInput, results chan<- PsContainerOutput, errors chan<- error) { for j := range jobs { r, err := j.parallelFunc() - // If we find an error, we return just the error + // If we find an error, we return just the error. if err != nil { errors <- err } else { - // Return the result + // Return the result. results <- r } wg.Done() @@ -398,7 +398,7 @@ func generateContainerFilterFuncs(filter, filterValue string, r *libpod.Runtime) return nil, errors.Errorf("%s is an invalid filter", filter) } -// GetPsContainerOutput returns a slice of containers specifically for ps output +// GetPsContainerOutput returns a slice of containers specifically for ps output. func GetPsContainerOutput(r *libpod.Runtime, opts PsOptions, filters []string, maxWorkers int) ([]PsContainerOutput, error) { var ( filterFuncs []libpod.ContainerFilter @@ -419,21 +419,21 @@ func GetPsContainerOutput(r *libpod.Runtime, opts PsOptions, filters []string, m } } if !opts.Latest { - // Get all containers + // Get all containers. containers, err := r.GetContainers(filterFuncs...) if err != nil { return nil, err } - // We only want the last few containers + // We only want the last few containers. if opts.Last > 0 && opts.Last <= len(containers) { return nil, errors.Errorf("--last not yet supported") } else { outputContainers = containers } } else { - // Get just the latest container - // Ignore filters + // Get just the latest container. + // Ignore filters. latestCtr, err := r.GetLatestContainer() if err != nil { return nil, err @@ -446,8 +446,8 @@ func GetPsContainerOutput(r *libpod.Runtime, opts PsOptions, filters []string, m return pss, nil } -// PBatch is performs batch operations on a container in parallel. It spawns the number of workers -// relative to the the number of parallel operations desired. +// PBatch performs batch operations on a container in parallel. It spawns the +// number of workers relative to the number of parallel operations desired. func PBatch(containers []*libpod.Container, workers int, opts PsOptions) []PsContainerOutput { var ( wg sync.WaitGroup @@ -455,7 +455,7 @@ func PBatch(containers []*libpod.Container, workers int, opts PsOptions) []PsCon ) // If the number of containers in question is less than the number of - // proposed parallel operations, we shouldnt spawn so many workers + // proposed parallel operations, we shouldnt spawn so many workers. if workers > len(containers) { workers = len(containers) } @@ -464,12 +464,12 @@ func PBatch(containers []*libpod.Container, workers int, opts PsOptions) []PsCon results := make(chan PsContainerOutput, len(containers)) batchErrors := make(chan error, len(containers)) - // Create the workers + // Create the workers. for w := 1; w <= workers; w++ { go worker(&wg, jobs, results, batchErrors) } - // Add jobs to the workers + // Add jobs to the workers. for i, j := range containers { j := j wg.Add(1) @@ -504,7 +504,7 @@ func PBatch(containers []*libpod.Container, workers int, opts PsOptions) []PsCon return psResults } -// BatchContainer is used in ps to reduce performance hits by "batching" +// BatchContainerOp is used in ps to reduce performance hits by "batching" // locks. func BatchContainerOp(ctr *libpod.Container, opts PsOptions) (BatchContainerStruct, error) { var ( @@ -582,7 +582,7 @@ func BatchContainerOp(ctr *libpod.Container, opts PsOptions) (BatchContainerStru }, nil } -// GetNamespaces returns a populated namespace struct +// GetNamespaces returns a populated namespace struct. func GetNamespaces(pid int) *Namespace { ctrPID := strconv.Itoa(pid) cgroup, _ := getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "cgroup")) @@ -613,7 +613,7 @@ func getNamespaceInfo(path string) (string, error) { return getStrFromSquareBrackets(val), nil } -// getStrFromSquareBrackets gets the string inside [] from a string +// getStrFromSquareBrackets gets the string inside [] from a string. func getStrFromSquareBrackets(cmd string) string { reg, err := regexp.Compile(`.*\[|\].*`) if err != nil { @@ -639,8 +639,8 @@ func comparePorts(i, j ocicni.PortMapping) bool { return i.Protocol < j.Protocol } -// returns the group as <IP:startPort:lastPort->startPort:lastPort/Proto> -// e.g 0.0.0.0:1000-1006->1000-1006/tcp +// formatGroup returns the group as <IP:startPort:lastPort->startPort:lastPort/Proto> +// e.g 0.0.0.0:1000-1006->1000-1006/tcp. func formatGroup(key string, start, last int32) string { parts := strings.Split(key, "/") groupType := parts[0] @@ -660,7 +660,7 @@ func formatGroup(key string, start, last int32) string { } // portsToString converts the ports used to a string of the from "port1, port2" -// also groups continuous list of ports in readable format. +// and also groups continuous list of ports in readable format. func portsToString(ports []ocicni.PortMapping) string { type portGroup struct { first int32 @@ -675,7 +675,7 @@ func portsToString(ports []ocicni.PortMapping) string { return comparePorts(ports[i], ports[j]) }) - // portGroupMap is used for grouping continuous ports + // portGroupMap is used for grouping continuous ports. portGroupMap := make(map[string]*portGroup) var groupKeyList []string @@ -685,7 +685,7 @@ func portsToString(ports []ocicni.PortMapping) string { if hostIP == "" { hostIP = "0.0.0.0" } - // if hostPort and containerPort are not same, consider as individual port. + // If hostPort and containerPort are not same, consider as individual port. if v.ContainerPort != v.HostPort { portDisplay = append(portDisplay, fmt.Sprintf("%s:%d->%d/%s", hostIP, v.HostPort, v.ContainerPort, v.Protocol)) continue @@ -696,7 +696,7 @@ func portsToString(ports []ocicni.PortMapping) string { portgroup, ok := portGroupMap[portMapKey] if !ok { portGroupMap[portMapKey] = &portGroup{first: v.ContainerPort, last: v.ContainerPort} - // this list is required to travese portGroupMap + // This list is required to travese portGroupMap. groupKeyList = append(groupKeyList, portMapKey) continue } @@ -706,7 +706,7 @@ func portsToString(ports []ocicni.PortMapping) string { continue } } - // for each portMapKey, format group list and appned to output string + // For each portMapKey, format group list and appned to output string. for _, portKey := range groupKeyList { group := portGroupMap[portKey] portDisplay = append(portDisplay, formatGroup(portKey, group.first, group.last)) @@ -715,7 +715,7 @@ func portsToString(ports []ocicni.PortMapping) string { } // GetRunlabel is a helper function for runlabel; it gets the image if needed and begins the -// construction of the runlabel output and environment variables +// construction of the runlabel output and environment variables. func GetRunlabel(label string, runlabelImage string, ctx context.Context, runtime *libpod.Runtime, pull bool, inputCreds string, dockerRegistryOptions image.DockerRegistryOptions, authfile string, signaturePolicyPath string, output io.Writer) (string, string, error) { var ( newImage *image.Image @@ -750,9 +750,9 @@ func GetRunlabel(label string, runlabelImage string, ctx context.Context, runtim return runLabel, imageName, err } -// GenerateRunlabelCommand generates the command that will eventually be execucted by podman +// GenerateRunlabelCommand generates the command that will eventually be execucted by podman. func GenerateRunlabelCommand(runLabel, imageName, name string, opts map[string]string, extraArgs []string, globalOpts string) ([]string, []string, error) { - // If no name is provided, we use the image's basename instead + // If no name is provided, we use the image's basename instead. if name == "" { baseName, err := image.GetImageBaseName(imageName) if err != nil { @@ -760,7 +760,7 @@ func GenerateRunlabelCommand(runLabel, imageName, name string, opts map[string]s } name = baseName } - // The user provided extra arguments that need to be tacked onto the label's command + // The user provided extra arguments that need to be tacked onto the label's command. if len(extraArgs) > 0 { runLabel = fmt.Sprintf("%s %s", runLabel, strings.Join(extraArgs, " ")) } @@ -782,7 +782,7 @@ func GenerateRunlabelCommand(runLabel, imageName, name string, opts map[string]s case "OPT3": return envmap["OPT3"] case "PWD": - // I would prefer to use os.getenv but it appears PWD is not in the os env list + // I would prefer to use os.getenv but it appears PWD is not in the os env list. d, err := os.Getwd() if err != nil { logrus.Error("unable to determine current working directory") @@ -819,7 +819,7 @@ func GenerateKube(name string, service bool, r *libpod.Runtime) (*v1.Pod, *v1.Se servicePorts []v1.ServicePort serviceYAML v1.Service ) - // Get the container in question + // Get the container in question. container, err = r.LookupContainer(name) if err != nil { pod, err = r.LookupPod(name) diff --git a/cmd/podman/shared/parse/parse.go b/cmd/podman/shared/parse/parse.go index a77002235..9fbc92fc3 100644 --- a/cmd/podman/shared/parse/parse.go +++ b/cmd/podman/shared/parse/parse.go @@ -7,6 +7,7 @@ import ( "bufio" "fmt" "net" + "net/url" "os" "regexp" "strings" @@ -162,3 +163,12 @@ func ValidateFileName(filename string) error { } return nil } + +// ValidURL checks a string urlStr is a url or not +func ValidURL(urlStr string) error { + _, err := url.ParseRequestURI(urlStr) + if err != nil { + return errors.Wrapf(err, "invalid url path: %q", urlStr) + } + return nil +} diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink index cc66e7df0..f5f3250f7 100644 --- a/cmd/podman/varlink/io.podman.varlink +++ b/cmd/podman/varlink/io.podman.varlink @@ -514,7 +514,9 @@ type ExecOpts( # workdir to run command in container workdir: ?string, # slice of keyword=value environment variables - env: ?[]string + env: ?[]string, + # string of detach keys + detachKeys: ?string ) # GetVersion returns version and build information of the podman service diff --git a/docs/podman-run.1.md b/docs/podman-run.1.md index d6c7ae055..c4747d234 100644 --- a/docs/podman-run.1.md +++ b/docs/podman-run.1.md @@ -646,6 +646,9 @@ If specified, the first argument refers to an exploded container on the file sys This is useful to run a container without requiring any image management, the rootfs of the container is assumed to be managed externally. +Note: On `SELinux` systems, the rootfs needs the correct label, which is by default +`unconfined_u:object_r:container_file_t`. + **--security-opt**=*option* Security Options diff --git a/install.md b/install.md index 5583e9610..49a67f984 100644 --- a/install.md +++ b/install.md @@ -318,11 +318,19 @@ To add build tags to the make option the `BUILDTAGS` variable must be set, for e make BUILDTAGS='seccomp apparmor' ``` -| Build Tag | Feature | Dependency | -|-----------|------------------------------------|-------------| -| seccomp | syscall filtering | libseccomp | -| selinux | selinux process and mount labeling | libselinux | -| apparmor | apparmor profile support | libapparmor | +| Build Tag | Feature | Dependency | +|----------------------------------|------------------------------------|----------------------| +| apparmor | apparmor support | libapparmor | +| exclude_graphdriver_btrfs | exclude btrfs | libbtrfs | +| exclude_graphdriver_devicemapper | exclude device-mapper | libdm | +| libdm_no_deferred_remove | exclude deferred removal in libdm | libdm | +| ostree | ostree support (requires selinux) | ostree-1, libselinux | +| containers_image_ostree_stub | exclude ostree | | +| seccomp | syscall filtering | libseccomp | +| selinux | selinux process and mount labeling | | +| systemd | journald logging | libsystemd | + +Note that Podman does not officially support device-mapper. Thus, the `exclude_graphdriver_devicemapper` tag is mandatory. ### Vendoring - Dependency Management diff --git a/libpod/container_api.go b/libpod/container_api.go index 0cce6ca22..cd020e429 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -18,11 +18,6 @@ import ( "k8s.io/client-go/tools/remotecommand" ) -const ( - defaultExecExitCode = 125 - defaultExecExitCodeCannotInvoke = 126 -) - // Init creates a container in the OCI runtime func (c *Container) Init(ctx context.Context) (err error) { span, _ := opentracing.StartSpanFromContext(ctx, "containerInit") @@ -234,7 +229,7 @@ func (c *Container) Exec(tty, privileged bool, env, cmd []string, user, workDir defer c.lock.Unlock() if err := c.syncContainer(); err != nil { - return defaultExecExitCodeCannotInvoke, err + return define.ExecErrorCodeCannotInvoke, err } } @@ -242,7 +237,7 @@ func (c *Container) Exec(tty, privileged bool, env, cmd []string, user, workDir // TODO can probably relax this once we track exec sessions if conState != define.ContainerStateRunning { - return defaultExecExitCodeCannotInvoke, errors.Wrapf(define.ErrCtrStateInvalid, "cannot exec into container that is not running") + return define.ExecErrorCodeCannotInvoke, errors.Wrapf(define.ErrCtrStateInvalid, "cannot exec into container that is not running") } if privileged || c.config.Privileged { @@ -269,7 +264,7 @@ func (c *Container) Exec(tty, privileged bool, env, cmd []string, user, workDir logrus.Debugf("Creating new exec session in container %s with session id %s", c.ID(), sessionID) if err := c.createExecBundle(sessionID); err != nil { - return defaultExecExitCodeCannotInvoke, err + return define.ExecErrorCodeCannotInvoke, err } defer func() { @@ -281,7 +276,7 @@ func (c *Container) Exec(tty, privileged bool, env, cmd []string, user, workDir pid, attachChan, err := c.ociRuntime.execContainer(c, cmd, capList, env, tty, workDir, user, sessionID, streams, preserveFDs, resize, detachKeys) if err != nil { - ec := defaultExecExitCode + ec := define.ExecErrorCodeGeneric // Conmon will pass a non-zero exit code from the runtime as a pid here. // we differentiate a pid with an exit code by sending it as negative, so reverse // that change and return the exit code the runtime failed with. @@ -303,7 +298,7 @@ func (c *Container) Exec(tty, privileged bool, env, cmd []string, user, workDir if err := c.save(); err != nil { // Now we have a PID but we can't save it in the DB // TODO handle this better - return defaultExecExitCode, errors.Wrapf(err, "error saving exec sessions %s for container %s", sessionID, c.ID()) + return define.ExecErrorCodeGeneric, errors.Wrapf(err, "error saving exec sessions %s for container %s", sessionID, c.ID()) } c.newContainerEvent(events.Exec) logrus.Debugf("Successfully started exec session %s in container %s", sessionID, c.ID()) diff --git a/libpod/define/exec_codes.go b/libpod/define/exec_codes.go new file mode 100644 index 000000000..7184f1e59 --- /dev/null +++ b/libpod/define/exec_codes.go @@ -0,0 +1,30 @@ +package define + +import ( + "github.com/pkg/errors" +) + +const ( + // ExecErrorCodeGeneric is the default error code to return from an exec session if libpod failed + // prior to calling the runtime + ExecErrorCodeGeneric = 125 + // ExecErrorCodeCannotInvoke is the error code to return when the runtime fails to invoke a command + // an example of this can be found by trying to execute a directory: + // `podman exec -l /etc` + ExecErrorCodeCannotInvoke = 126 + // ExecErrorCodeNotFound is the error code to return when a command cannot be found + ExecErrorCodeNotFound = 127 +) + +// TranslateExecErrorToExitCode takes an error and checks whether it +// has a predefined exit code associated. If so, it returns that, otherwise it returns +// the exit code originally stated in libpod.Exec() +func TranslateExecErrorToExitCode(originalEC int, err error) int { + if errors.Cause(err) == ErrOCIRuntimePermissionDenied { + return ExecErrorCodeCannotInvoke + } + if errors.Cause(err) == ErrOCIRuntimeNotFound { + return ExecErrorCodeNotFound + } + return originalEC +} diff --git a/libpod/image/image.go b/libpod/image/image.go index db50e3dbd..068491f28 100644 --- a/libpod/image/image.go +++ b/libpod/image/image.go @@ -1298,7 +1298,10 @@ func (i *Image) Comment(ctx context.Context, manifestType string) (string, error if err != nil { return "", err } - return ociv1Img.History[0].Comment, nil + if len(ociv1Img.History) > 0 { + return ociv1Img.History[0].Comment, nil + } + return "", nil } // Save writes a container image to the filesystem diff --git a/libpod/runtime.go b/libpod/runtime.go index 08c6cb588..28958e932 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -879,14 +879,6 @@ func makeRuntime(ctx context.Context, runtime *Runtime) (err error) { runtime.imageRuntime.Eventer = eventer } - // Set up a storage service for creating container root filesystems from - // images - storageService, err := getStorageService(runtime.store) - if err != nil { - return err - } - runtime.storageService = storageService - // Set up containers/image runtime.imageContext = &types.SystemContext{ SignaturePolicyPath: runtime.config.SignaturePolicyPath, @@ -1330,6 +1322,14 @@ func (r *Runtime) configureStore() error { r.store = store is.Transport.SetStore(store) + // Set up a storage service for creating container root filesystems from + // images + storageService, err := getStorageService(r.store) + if err != nil { + return err + } + r.storageService = storageService + ir := image.NewImageRuntimeFromStore(r.store) ir.SignaturePolicyPath = r.config.SignaturePolicyPath ir.EventsLogFilePath = r.config.EventsLogFilePath diff --git a/libpod/runtime_volume.go b/libpod/runtime_volume.go index 5c087faca..d05db936b 100644 --- a/libpod/runtime_volume.go +++ b/libpod/runtime_volume.go @@ -2,7 +2,6 @@ package libpod import ( "context" - "strings" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/events" @@ -72,7 +71,7 @@ func (r *Runtime) RemoveVolumes(ctx context.Context, volumes []string, all, forc return deletedVols, nil } -// GetVolume retrieves a volume by its name +// GetVolume retrieves a volume given its full name. func (r *Runtime) GetVolume(name string) (*Volume, error) { r.lock.RLock() defer r.lock.RUnlock() @@ -82,20 +81,11 @@ func (r *Runtime) GetVolume(name string) (*Volume, error) { } vol, err := r.state.Volume(name) - if err == nil { - return vol, err - } - - vols, err := r.GetAllVolumes() if err != nil { return nil, err } - for _, v := range vols { - if strings.HasPrefix(v.Name(), name) { - return v, nil - } - } - return nil, errors.Errorf("unable to find volume %s", name) + + return vol, nil } // HasVolume checks to see if a volume with the given name exists diff --git a/pkg/adapter/containers.go b/pkg/adapter/containers.go index 47f1b091e..faaef3e60 100644 --- a/pkg/adapter/containers.go +++ b/pkg/adapter/containers.go @@ -1000,7 +1000,8 @@ func (r *LocalRuntime) ExecContainer(ctx context.Context, cli *cliconfig.ExecVal streams.AttachOutput = true streams.AttachError = true - return ExecAttachCtr(ctx, ctr.Container, cli.Tty, cli.Privileged, envs, cmd, cli.User, cli.Workdir, streams, cli.PreserveFDs, cli.DetachKeys) + ec, err = ExecAttachCtr(ctx, ctr.Container, cli.Tty, cli.Privileged, envs, cmd, cli.User, cli.Workdir, streams, cli.PreserveFDs, cli.DetachKeys) + return define.TranslateExecErrorToExitCode(ec, err), err } // Prune removes stopped containers diff --git a/pkg/adapter/containers_remote.go b/pkg/adapter/containers_remote.go index 6b9fc8ee7..5a26f537f 100644 --- a/pkg/adapter/containers_remote.go +++ b/pkg/adapter/containers_remote.go @@ -3,6 +3,7 @@ package adapter import ( + "bufio" "context" "encoding/json" "fmt" @@ -555,93 +556,6 @@ func (r *LocalRuntime) Ps(c *cliconfig.PsValues, opts shared.PsOptions) ([]share return psContainers, nil } -func (r *LocalRuntime) attach(ctx context.Context, stdin, stdout *os.File, cid string, start bool, detachKeys string) (chan error, error) { - var ( - oldTermState *term.State - ) - errChan := make(chan error) - spec, err := r.Spec(cid) - if err != nil { - return nil, err - } - resize := make(chan remotecommand.TerminalSize, 5) - haveTerminal := terminal.IsTerminal(int(os.Stdin.Fd())) - - // Check if we are attached to a terminal. If we are, generate resize - // events, and set the terminal to raw mode - if haveTerminal && spec.Process.Terminal { - logrus.Debugf("Handling terminal attach") - - subCtx, cancel := context.WithCancel(ctx) - defer cancel() - - resizeTty(subCtx, resize) - oldTermState, err = term.SaveState(os.Stdin.Fd()) - if err != nil { - return nil, errors.Wrapf(err, "unable to save terminal state") - } - - logrus.SetFormatter(&RawTtyFormatter{}) - term.SetRawTerminal(os.Stdin.Fd()) - - } - // TODO add detach keys support - reply, err := iopodman.Attach().Send(r.Conn, varlink.Upgrade, cid, detachKeys, start) - if err != nil { - restoreTerminal(oldTermState) - return nil, err - } - - // See if the server accepts the upgraded connection or returns an error - _, err = reply() - - if err != nil { - restoreTerminal(oldTermState) - return nil, err - } - - // These are the varlink sockets - reader := r.Conn.Reader - writer := r.Conn.Writer - - // These are the special writers that encode input from the client. - varlinkStdinWriter := virtwriter.NewVirtWriteCloser(writer, virtwriter.ToStdin) - varlinkResizeWriter := virtwriter.NewVirtWriteCloser(writer, virtwriter.TerminalResize) - - go func() { - // Read from the wire and direct to stdout or stderr - err := virtwriter.Reader(reader, stdout, os.Stderr, nil, nil) - defer restoreTerminal(oldTermState) - errChan <- err - }() - - go func() { - for termResize := range resize { - b, err := json.Marshal(termResize) - if err != nil { - defer restoreTerminal(oldTermState) - errChan <- err - } - _, err = varlinkResizeWriter.Write(b) - if err != nil { - defer restoreTerminal(oldTermState) - errChan <- err - } - } - }() - - // Takes stdinput and sends it over the wire after being encoded - go func() { - if _, err := io.Copy(varlinkStdinWriter, stdin); err != nil { - defer restoreTerminal(oldTermState) - errChan <- err - } - - }() - return errChan, nil - -} - // Attach to a remote terminal func (r *LocalRuntime) Attach(ctx context.Context, c *cliconfig.AttachValues) error { ctr, err := r.LookupContainer(c.InputArgs[0]) @@ -796,6 +710,49 @@ func (r *LocalRuntime) Start(ctx context.Context, c *cliconfig.StartValues, sigP return exitCode, finalErr } +func (r *LocalRuntime) attach(ctx context.Context, stdin, stdout *os.File, cid string, start bool, detachKeys string) (chan error, error) { + var ( + oldTermState *term.State + ) + spec, err := r.Spec(cid) + if err != nil { + return nil, err + } + resize := make(chan remotecommand.TerminalSize, 5) + haveTerminal := terminal.IsTerminal(int(os.Stdin.Fd())) + + // Check if we are attached to a terminal. If we are, generate resize + // events, and set the terminal to raw mode + if haveTerminal && spec.Process.Terminal { + cancel, oldTermState, err := handleTerminalAttach(ctx, resize) + if err != nil { + return nil, err + } + defer cancel() + defer restoreTerminal(oldTermState) + + logrus.SetFormatter(&RawTtyFormatter{}) + term.SetRawTerminal(os.Stdin.Fd()) + } + + reply, err := iopodman.Attach().Send(r.Conn, varlink.Upgrade, cid, detachKeys, start) + if err != nil { + restoreTerminal(oldTermState) + return nil, err + } + + // See if the server accepts the upgraded connection or returns an error + _, err = reply() + + if err != nil { + restoreTerminal(oldTermState) + return nil, err + } + + errChan := configureVarlinkAttachStdio(r.Conn.Reader, r.Conn.Writer, stdin, stdout, oldTermState, resize, nil) + return errChan, nil +} + // PauseContainers pauses container(s) based on CLI inputs. func (r *LocalRuntime) PauseContainers(ctx context.Context, cli *cliconfig.PauseValues) ([]string, map[string]error, error) { var ( @@ -1037,8 +994,11 @@ func (r *LocalRuntime) Commit(ctx context.Context, c *cliconfig.CommitValues, co // ExecContainer executes a command in the container func (r *LocalRuntime) ExecContainer(ctx context.Context, cli *cliconfig.ExecValues) (int, error) { + var ( + oldTermState *term.State + ec int = define.ExecErrorCodeGeneric + ) // default invalid command exit code - ec := 125 // Validate given environment variables env := map[string]string{} if err := parse.ReadKVStrings(env, []string{}, cli.Env); err != nil { @@ -1051,6 +1011,23 @@ func (r *LocalRuntime) ExecContainer(ctx context.Context, cli *cliconfig.ExecVal envs = append(envs, fmt.Sprintf("%s=%s", k, v)) } + resize := make(chan remotecommand.TerminalSize, 5) + haveTerminal := terminal.IsTerminal(int(os.Stdin.Fd())) + + // Check if we are attached to a terminal. If we are, generate resize + // events, and set the terminal to raw mode + if haveTerminal && cli.Tty { + cancel, oldTermState, err := handleTerminalAttach(ctx, resize) + if err != nil { + return ec, err + } + defer cancel() + defer restoreTerminal(oldTermState) + + logrus.SetFormatter(&RawTtyFormatter{}) + term.SetRawTerminal(os.Stdin.Fd()) + } + opts := iopodman.ExecOpts{ Name: cli.InputArgs[0], Tty: cli.Tty, @@ -1059,18 +1036,79 @@ func (r *LocalRuntime) ExecContainer(ctx context.Context, cli *cliconfig.ExecVal User: &cli.User, Workdir: &cli.Workdir, Env: &envs, + DetachKeys: &cli.DetachKeys, + } + + inputStream := os.Stdin + if !cli.Interactive { + inputStream = nil } - receive, err := iopodman.ExecContainer().Send(r.Conn, varlink.Upgrade, opts) + reply, err := iopodman.ExecContainer().Send(r.Conn, varlink.Upgrade, opts) if err != nil { return ec, errors.Wrapf(err, "Exec failed to contact service for %s", cli.InputArgs) } - _, err = receive() + _, err = reply() if err != nil { return ec, errors.Wrapf(err, "Exec operation failed for %s", cli.InputArgs) } + ecChan := make(chan int, 1) + errChan := configureVarlinkAttachStdio(r.Conn.Reader, r.Conn.Writer, inputStream, os.Stdout, oldTermState, resize, ecChan) + + ec = <-ecChan + err = <-errChan + + return ec, err +} + +func configureVarlinkAttachStdio(reader *bufio.Reader, writer *bufio.Writer, stdin *os.File, stdout *os.File, oldTermState *term.State, resize chan remotecommand.TerminalSize, ecChan chan int) chan error { + errChan := make(chan error, 1) + // These are the special writers that encode input from the client. + varlinkStdinWriter := virtwriter.NewVirtWriteCloser(writer, virtwriter.ToStdin) + varlinkResizeWriter := virtwriter.NewVirtWriteCloser(writer, virtwriter.TerminalResize) + + go func() { + // Read from the wire and direct to stdout or stderr + err := virtwriter.Reader(reader, stdout, os.Stderr, nil, nil, ecChan) + defer restoreTerminal(oldTermState) + sendGenericError(ecChan) + errChan <- err + }() + + go func() { + for termResize := range resize { + b, err := json.Marshal(termResize) + if err != nil { + defer restoreTerminal(oldTermState) + sendGenericError(ecChan) + errChan <- err + } + _, err = varlinkResizeWriter.Write(b) + if err != nil { + defer restoreTerminal(oldTermState) + sendGenericError(ecChan) + errChan <- err + } + } + }() + + if stdin != nil { + // Takes stdinput and sends it over the wire after being encoded + go func() { + if _, err := io.Copy(varlinkStdinWriter, stdin); err != nil { + defer restoreTerminal(oldTermState) + sendGenericError(ecChan) + errChan <- err + } + + }() + } + return errChan +} - // TODO return exit code from exec call - return 0, nil +func sendGenericError(ecChan chan int) { + if ecChan != nil { + ecChan <- define.ExecErrorCodeGeneric + } } diff --git a/pkg/adapter/terminal.go b/pkg/adapter/terminal.go index 373c78322..51b747d23 100644 --- a/pkg/adapter/terminal.go +++ b/pkg/adapter/terminal.go @@ -7,6 +7,7 @@ import ( "github.com/docker/docker/pkg/signal" "github.com/docker/docker/pkg/term" + "github.com/pkg/errors" "github.com/sirupsen/logrus" "k8s.io/client-go/tools/remotecommand" ) @@ -76,3 +77,25 @@ func (f *RawTtyFormatter) Format(entry *logrus.Entry) ([]byte, error) { return bytes, err } + +func handleTerminalAttach(ctx context.Context, resize chan remotecommand.TerminalSize) (context.CancelFunc, *term.State, error) { + logrus.Debugf("Handling terminal attach") + + subCtx, cancel := context.WithCancel(ctx) + + resizeTty(subCtx, resize) + + oldTermState, err := term.SaveState(os.Stdin.Fd()) + if err != nil { + // allow caller to not have to do any cleaning up if we error here + cancel() + return nil, nil, errors.Wrapf(err, "unable to save terminal state") + } + + logrus.SetFormatter(&RawTtyFormatter{}) + if _, err := term.SetRawTerminal(os.Stdin.Fd()); err != nil { + return cancel, nil, err + } + + return cancel, oldTermState, nil +} diff --git a/pkg/adapter/terminal_linux.go b/pkg/adapter/terminal_linux.go index de2600b75..26cfd7b5e 100644 --- a/pkg/adapter/terminal_linux.go +++ b/pkg/adapter/terminal_linux.go @@ -6,7 +6,6 @@ import ( "os" "github.com/containers/libpod/libpod" - "github.com/docker/docker/pkg/term" "github.com/pkg/errors" "github.com/sirupsen/logrus" "golang.org/x/crypto/ssh/terminal" @@ -108,25 +107,3 @@ func StartAttachCtr(ctx context.Context, ctr *libpod.Container, stdout, stderr, return nil } - -func handleTerminalAttach(ctx context.Context, resize chan remotecommand.TerminalSize) (context.CancelFunc, *term.State, error) { - logrus.Debugf("Handling terminal attach") - - subCtx, cancel := context.WithCancel(ctx) - - resizeTty(subCtx, resize) - - oldTermState, err := term.SaveState(os.Stdin.Fd()) - if err != nil { - // allow caller to not have to do any cleaning up if we error here - cancel() - return nil, nil, errors.Wrapf(err, "unable to save terminal state") - } - - logrus.SetFormatter(&RawTtyFormatter{}) - if _, err := term.SetRawTerminal(os.Stdin.Fd()); err != nil { - return cancel, nil, err - } - - return cancel, oldTermState, nil -} diff --git a/pkg/ctime/ctime_linux.go b/pkg/ctime/ctime_linux.go index 28ad959cf..113693e87 100644 --- a/pkg/ctime/ctime_linux.go +++ b/pkg/ctime/ctime_linux.go @@ -10,5 +10,6 @@ import ( func created(fi os.FileInfo) time.Time { st := fi.Sys().(*syscall.Stat_t) - return time.Unix(st.Ctim.Sec, st.Ctim.Nsec) + //nolint + return time.Unix(int64(st.Ctim.Sec), int64(st.Ctim.Nsec)) } diff --git a/pkg/hooks/hooks.go b/pkg/hooks/hooks.go index 5ed028b95..b962ffa5c 100644 --- a/pkg/hooks/hooks.go +++ b/pkg/hooks/hooks.go @@ -4,7 +4,6 @@ package hooks import ( "context" "fmt" - "path/filepath" "sort" "strings" "sync" @@ -138,26 +137,3 @@ func (m *Manager) Hooks(config *rspec.Spec, annotations map[string]string, hasBi return extensionStageHooks, nil } - -// remove remove a hook by name. -func (m *Manager) remove(hook string) (ok bool) { - m.lock.Lock() - defer m.lock.Unlock() - _, ok = m.hooks[hook] - if ok { - delete(m.hooks, hook) - } - return ok -} - -// add adds a hook by path -func (m *Manager) add(path string) (err error) { - m.lock.Lock() - defer m.lock.Unlock() - hook, err := Read(path, m.extensionStages) - if err != nil { - return err - } - m.hooks[filepath.Base(path)] = hook - return nil -} diff --git a/pkg/hooks/monitor.go b/pkg/hooks/monitor.go index febe3483f..c50b321f2 100644 --- a/pkg/hooks/monitor.go +++ b/pkg/hooks/monitor.go @@ -2,9 +2,8 @@ package hooks import ( "context" - "os" - "path/filepath" + current "github.com/containers/libpod/pkg/hooks/1.0.0" "github.com/fsnotify/fsnotify" "github.com/sirupsen/logrus" ) @@ -49,47 +48,11 @@ func (m *Manager) Monitor(ctx context.Context, sync chan<- error) { for { select { case event := <-watcher.Events: - filename := filepath.Base(event.Name) - if len(m.directories) <= 1 { - if event.Op&fsnotify.Remove == fsnotify.Remove { - ok := m.remove(filename) - if ok { - logrus.Debugf("removed hook %s", event.Name) - } - } else if event.Op&fsnotify.Create == fsnotify.Create || event.Op&fsnotify.Write == fsnotify.Write { - err = m.add(event.Name) - if err == nil { - logrus.Debugf("added hook %s", event.Name) - } else if err != ErrNoJSONSuffix { - logrus.Errorf("failed to add hook %s: %v", event.Name, err) - } - } - } else if event.Op&fsnotify.Create == fsnotify.Create || event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Remove == fsnotify.Remove { - err = nil - found := false - for i := len(m.directories) - 1; i >= 0; i-- { - path := filepath.Join(m.directories[i], filename) - err = m.add(path) - if err == nil { - found = true - logrus.Debugf("(re)added hook %s (triggered activity on %s)", path, event.Name) - break - } else if err == ErrNoJSONSuffix { - found = true - break // this is not going to change for fallback directories - } else if os.IsNotExist(err) { - continue // move on to the next fallback directory - } else { - found = true - logrus.Errorf("failed to (re)add hook %s (triggered by activity on %s): %v", path, event.Name, err) - break - } - } - if (found || event.Op&fsnotify.Remove == fsnotify.Remove) && err != nil { - ok := m.remove(filename) - if ok { - logrus.Debugf("removed hook %s (triggered by activity on %s)", filename, event.Name) - } + m.hooks = make(map[string]*current.Hook) + for _, dir := range m.directories { + err = ReadDir(dir, m.extensionStages, m.hooks) + if err != nil { + logrus.Errorf("failed loading hooks for %s: %v", event.Name, err) } } case <-ctx.Done(): diff --git a/pkg/hooks/monitor_test.go b/pkg/hooks/monitor_test.go index 31d7f9e39..dc67eaf83 100644 --- a/pkg/hooks/monitor_test.go +++ b/pkg/hooks/monitor_test.go @@ -226,7 +226,28 @@ func TestMonitorTwoDirGood(t *testing.T) { assert.Equal(t, primaryInjected, config.Hooks) // masked by primary }) - t.Run("bad-primary-addition", func(t *testing.T) { + primaryPath2 := filepath.Join(primaryDir, "0a.json") //0a because it will be before a.json alphabetically + + t.Run("bad-primary-new-addition", func(t *testing.T) { + err = ioutil.WriteFile(primaryPath2, []byte("{\"version\": \"-1\"}"), 0644) + if err != nil { + t.Fatal(err) + } + + time.Sleep(100 * time.Millisecond) // wait for monitor to notice + + config := &rspec.Spec{} + fmt.Println("expected: ", config.Hooks) + expected := primaryInjected // 0a.json is bad, a.json is still good + _, err = manager.Hooks(config, map[string]string{}, false) + fmt.Println("actual: ", config.Hooks) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, expected, config.Hooks) + }) + + t.Run("bad-primary-same-addition", func(t *testing.T) { err = ioutil.WriteFile(primaryPath, []byte("{\"version\": \"-1\"}"), 0644) if err != nil { t.Fatal(err) @@ -235,7 +256,7 @@ func TestMonitorTwoDirGood(t *testing.T) { time.Sleep(100 * time.Millisecond) // wait for monitor to notice config := &rspec.Spec{} - expected := config.Hooks + expected := fallbackInjected _, err = manager.Hooks(config, map[string]string{}, false) if err != nil { t.Fatal(err) diff --git a/pkg/hooks/read.go b/pkg/hooks/read.go index d3995a0be..560ff1899 100644 --- a/pkg/hooks/read.go +++ b/pkg/hooks/read.go @@ -67,7 +67,7 @@ func ReadDir(path string, extensionStages []string, hooks map[string]*current.Ho if err != nil { return err } - + res := err for _, file := range files { filePath := filepath.Join(path, file.Name()) hook, err := Read(filePath, extensionStages) @@ -80,12 +80,17 @@ func ReadDir(path string, extensionStages []string, hooks map[string]*current.Ho continue } } - return err + if res == nil { + res = err + } else { + res = errors.Wrapf(res, "%v", err) + } + continue } hooks[file.Name()] = hook logrus.Debugf("added hook %s", filePath) } - return nil + return res } func init() { diff --git a/pkg/varlinkapi/attach.go b/pkg/varlinkapi/attach.go index 97ba525a5..1f8d48eb9 100644 --- a/pkg/varlinkapi/attach.go +++ b/pkg/varlinkapi/attach.go @@ -68,7 +68,7 @@ func (i *LibpodAPI) Attach(call iopodman.VarlinkCall, name string, detachKeys st reader, writer, _, pw, streams := setupStreams(call) go func() { - if err := virtwriter.Reader(reader, nil, nil, pw, resize); err != nil { + if err := virtwriter.Reader(reader, nil, nil, pw, resize, nil); err != nil { errChan <- err } }() @@ -83,7 +83,7 @@ func (i *LibpodAPI) Attach(call iopodman.VarlinkCall, name string, detachKeys st logrus.Error(finalErr) } - if err = virtwriter.HangUp(writer); err != nil { + if err = virtwriter.HangUp(writer, 0); err != nil { logrus.Errorf("Failed to HANG-UP attach to %s: %s", ctr.ID(), err.Error()) } return call.Writer.Flush() diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go index 19a8bfd2e..cd5f305c9 100644 --- a/pkg/varlinkapi/containers.go +++ b/pkg/varlinkapi/containers.go @@ -782,6 +782,9 @@ func (i *LibpodAPI) ExecContainer(call iopodman.VarlinkCall, opts iopodman.ExecO fmt.Sprintf("exec requires a running container, %s is %s", ctr.ID(), state.String())) } + // ACK the client upgrade request + call.ReplyExecContainer() + envs := []string{} if opts.Env != nil { envs = *opts.Env @@ -797,44 +800,52 @@ func (i *LibpodAPI) ExecContainer(call iopodman.VarlinkCall, opts iopodman.ExecO workDir = *opts.Workdir } + var detachKeys string + if opts.DetachKeys != nil { + detachKeys = *opts.DetachKeys + } + resizeChan := make(chan remotecommand.TerminalSize) - errChan := make(chan error) reader, writer, _, pipeWriter, streams := setupStreams(call) + type ExitCodeError struct { + ExitCode uint32 + Error error + } + ecErrChan := make(chan ExitCodeError, 1) + go func() { - fmt.Printf("ExecContainer Start Reader\n") - if err := virtwriter.Reader(reader, nil, nil, pipeWriter, resizeChan); err != nil { - fmt.Printf("ExecContainer Reader err %s, %s\n", err.Error(), errors.Cause(err).Error()) - errChan <- err + if err := virtwriter.Reader(reader, nil, nil, pipeWriter, resizeChan, nil); err != nil { + ecErrChan <- ExitCodeError{ + define.ExecErrorCodeGeneric, + err, + } } }() - // Debugging... - time.Sleep(5 * time.Second) - go func() { - fmt.Printf("ExecContainer Start ctr.Exec\n") - // TODO detach keys and resize - // TODO add handling for exit code - // TODO capture exit code and return to main thread - _, err := ctr.Exec(opts.Tty, opts.Privileged, envs, opts.Cmd, user, workDir, streams, 0, nil, "") + ec, err := ctr.Exec(opts.Tty, opts.Privileged, envs, opts.Cmd, user, workDir, streams, 0, resizeChan, detachKeys) if err != nil { - fmt.Printf("ExecContainer Exec err %s, %s\n", err.Error(), errors.Cause(err).Error()) - errChan <- errors.Wrapf(err, "ExecContainer failed for container %s", ctr.ID()) + logrus.Errorf(err.Error()) + } + ecErrChan <- ExitCodeError{ + uint32(ec), + err, } }() - execErr := <-errChan + ecErr := <-ecErrChan - if execErr != nil && errors.Cause(execErr) != io.EOF { - fmt.Printf("ExecContainer err: %s\n", execErr.Error()) - return call.ReplyErrorOccurred(execErr.Error()) - } + exitCode := define.TranslateExecErrorToExitCode(int(ecErr.ExitCode), ecErr.Error) - if err = virtwriter.HangUp(writer); err != nil { - fmt.Printf("ExecContainer hangup err: %s\n", err.Error()) + if err = virtwriter.HangUp(writer, uint32(exitCode)); err != nil { logrus.Errorf("ExecContainer failed to HANG-UP on %s: %s", ctr.ID(), err.Error()) } - return call.Writer.Flush() + + if err := call.Writer.Flush(); err != nil { + logrus.Errorf("Exec Container err: %s", err.Error()) + } + + return ecErr.Error } diff --git a/pkg/varlinkapi/virtwriter/virtwriter.go b/pkg/varlinkapi/virtwriter/virtwriter.go index 0da2a91fc..27ecd1f52 100644 --- a/pkg/varlinkapi/virtwriter/virtwriter.go +++ b/pkg/varlinkapi/virtwriter/virtwriter.go @@ -89,10 +89,14 @@ func (v VirtWriteCloser) Write(input []byte) (int, error) { } // Reader decodes the content that comes over the wire and directs it to the proper destination. -func Reader(r *bufio.Reader, output io.Writer, errput io.Writer, input io.Writer, resize chan remotecommand.TerminalSize) error { +func Reader(r *bufio.Reader, output, errput, input io.Writer, resize chan remotecommand.TerminalSize, execEcChan chan int) error { var messageSize int64 headerBytes := make([]byte, 8) + if r == nil { + return errors.Errorf("Reader must not be nil") + } + for { n, err := io.ReadFull(r, headerBytes) if err != nil { @@ -106,35 +110,43 @@ func Reader(r *bufio.Reader, output io.Writer, errput io.Writer, input io.Writer switch IntToSocketDest(int(headerBytes[0])) { case ToStdout: - _, err := io.CopyN(output, r, messageSize) - if err != nil { - return err + if output != nil { + _, err := io.CopyN(output, r, messageSize) + if err != nil { + return err + } } case ToStderr: - _, err := io.CopyN(errput, r, messageSize) - if err != nil { - return err + if errput != nil { + _, err := io.CopyN(errput, r, messageSize) + if err != nil { + return err + } } case ToStdin: - _, err := io.CopyN(input, r, messageSize) - if err != nil { - return err - } - case TerminalResize: - out := make([]byte, messageSize) - if messageSize > 0 { - _, err = io.ReadFull(r, out) - + if input != nil { + _, err := io.CopyN(input, r, messageSize) if err != nil { return err } } - // Resize events come over in bytes, need to be reserialized - resizeEvent := remotecommand.TerminalSize{} - if err := json.Unmarshal(out, &resizeEvent); err != nil { - return err + case TerminalResize: + if resize != nil { + out := make([]byte, messageSize) + if messageSize > 0 { + _, err = io.ReadFull(r, out) + + if err != nil { + return err + } + } + // Resize events come over in bytes, need to be reserialized + resizeEvent := remotecommand.TerminalSize{} + if err := json.Unmarshal(out, &resizeEvent); err != nil { + return err + } + resize <- resizeEvent } - resize <- resizeEvent case Quit: out := make([]byte, messageSize) if messageSize > 0 { @@ -144,6 +156,10 @@ func Reader(r *bufio.Reader, output io.Writer, errput io.Writer, input io.Writer return err } } + if execEcChan != nil { + ecInt := binary.BigEndian.Uint32(out) + execEcChan <- int(ecInt) + } return nil default: @@ -154,9 +170,11 @@ func Reader(r *bufio.Reader, output io.Writer, errput io.Writer, input io.Writer } // HangUp sends message to peer to close connection -func HangUp(writer *bufio.Writer) (err error) { +func HangUp(writer *bufio.Writer, ec uint32) (err error) { n := 0 - msg := []byte("HANG-UP") + msg := make([]byte, 4) + + binary.BigEndian.PutUint32(msg, ec) writeQuit := NewVirtWriteCloser(writer, Quit) if n, err = writeQuit.Write(msg); err != nil { diff --git a/test/e2e/common_test.go b/test/e2e/common_test.go index ef1c85518..7e14f9e06 100644 --- a/test/e2e/common_test.go +++ b/test/e2e/common_test.go @@ -214,11 +214,6 @@ func PodmanTestCreateUtil(tempDir string, remote bool) *PodmanTestIntegration { cgroupManager = os.Getenv("CGROUP_MANAGER") } - // Ubuntu doesn't use systemd cgroups - if host.Distribution == "ubuntu" { - cgroupManager = "cgroupfs" - } - ociRuntime := os.Getenv("OCI_RUNTIME") if ociRuntime == "" { var err error diff --git a/test/e2e/exec_test.go b/test/e2e/exec_test.go index 76515cf3e..6cf78a25c 100644 --- a/test/e2e/exec_test.go +++ b/test/e2e/exec_test.go @@ -1,5 +1,3 @@ -// +build !remoteclient - package integration import ( @@ -67,6 +65,8 @@ var _ = Describe("Podman exec", func() { }) It("podman exec simple command using latest", func() { + // the remote client doesn't use latest + SkipIfRemote() setup := podmanTest.RunTopContainer("test1") setup.WaitWithDefaultTimeout() Expect(setup.ExitCode()).To(Equal(0)) @@ -81,27 +81,35 @@ var _ = Describe("Podman exec", func() { setup.WaitWithDefaultTimeout() Expect(setup.ExitCode()).To(Equal(0)) - session := podmanTest.Podman([]string{"exec", "-l", "--env", "FOO=BAR", "printenv", "FOO"}) + session := podmanTest.Podman([]string{"exec", "--env", "FOO=BAR", "test1", "printenv", "FOO"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) match, _ := session.GrepString("BAR") Expect(match).Should(BeTrue()) - session = podmanTest.Podman([]string{"exec", "-l", "--env", "PATH=/bin", "printenv", "PATH"}) + session = podmanTest.Podman([]string{"exec", "--env", "PATH=/bin", "test1", "printenv", "PATH"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) match, _ = session.GrepString("/bin") Expect(match).Should(BeTrue()) + }) + + It("podman exec os.Setenv env", func() { + // remote doesn't properly interpret os.Setenv + SkipIfRemote() + setup := podmanTest.RunTopContainer("test1") + setup.WaitWithDefaultTimeout() + Expect(setup.ExitCode()).To(Equal(0)) os.Setenv("FOO", "BAR") - session = podmanTest.Podman([]string{"exec", "-l", "--env", "FOO", "printenv", "FOO"}) + session := podmanTest.Podman([]string{"exec", "--env", "FOO", "test1", "printenv", "FOO"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - match, _ = session.GrepString("BAR") + match, _ := session.GrepString("BAR") Expect(match).Should(BeTrue()) os.Unsetenv("FOO") - }) + It("podman exec exit code", func() { setup := podmanTest.RunTopContainer("test1") setup.WaitWithDefaultTimeout() @@ -143,13 +151,13 @@ var _ = Describe("Podman exec", func() { setup.WaitWithDefaultTimeout() Expect(setup.ExitCode()).To(Equal(0)) - session := podmanTest.Podman([]string{"exec", "-l", "--workdir", "/tmp", "pwd"}) + session := podmanTest.Podman([]string{"exec", "--workdir", "/tmp", "test1", "pwd"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) match, _ := session.GrepString("/tmp") Expect(match).Should(BeTrue()) - session = podmanTest.Podman([]string{"exec", "-l", "-w", "/tmp", "pwd"}) + session = podmanTest.Podman([]string{"exec", "-w", "/tmp", "test1", "pwd"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) match, _ = session.GrepString("/tmp") @@ -161,11 +169,11 @@ var _ = Describe("Podman exec", func() { setup.WaitWithDefaultTimeout() Expect(setup.ExitCode()).To(Equal(0)) - session := podmanTest.Podman([]string{"exec", "-l", "--workdir", "/missing", "pwd"}) + session := podmanTest.Podman([]string{"exec", "--workdir", "/missing", "test1", "pwd"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(1)) - session = podmanTest.Podman([]string{"exec", "-l", "-w", "/missing", "pwd"}) + session = podmanTest.Podman([]string{"exec", "-w", "/missing", "test1", "pwd"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(1)) }) |