summaryrefslogtreecommitdiff
path: root/pkg/adapter
diff options
context:
space:
mode:
authorbaude <bbaude@redhat.com>2019-03-20 13:00:34 -0500
committerbaude <bbaude@redhat.com>2019-04-08 09:05:31 -0500
commitba65301c955454e47c3893ca548f18a845a4c4a9 (patch)
tree4704ac2ce61efd5790a0e4dc06560d6eda38bc51 /pkg/adapter
parentd86729e743fb5a58b9364ee5e991b5db2e9dd600 (diff)
downloadpodman-ba65301c955454e47c3893ca548f18a845a4c4a9.tar.gz
podman-ba65301c955454e47c3893ca548f18a845a4c4a9.tar.bz2
podman-ba65301c955454e47c3893ca548f18a845a4c4a9.zip
podman-remote create|run
add the ability to create and run containers via the podman-remote client. we now create an intermediate layer from the the create/run cli flags. the intermediate layer can be converted into a createconfig or into a varlink struct. Once transported, the varlink struct can be converted back to an intermediate layer and then to a createconfig. remote terminals are not supported yet. Signed-off-by: baude <bbaude@redhat.com>
Diffstat (limited to 'pkg/adapter')
-rw-r--r--pkg/adapter/containers.go150
-rw-r--r--pkg/adapter/containers_remote.go30
-rw-r--r--pkg/adapter/sigproxy.go36
-rw-r--r--pkg/adapter/terminal.go159
4 files changed, 375 insertions, 0 deletions
diff --git a/pkg/adapter/containers.go b/pkg/adapter/containers.go
index 932d209cd..1bca99cec 100644
--- a/pkg/adapter/containers.go
+++ b/pkg/adapter/containers.go
@@ -5,12 +5,17 @@ package adapter
import (
"context"
"fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
"strconv"
+ "strings"
"sync"
"syscall"
"time"
"github.com/containers/libpod/cmd/podman/cliconfig"
+ "github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/adapter/shortcuts"
"github.com/pkg/errors"
@@ -154,3 +159,148 @@ func (r *LocalRuntime) Log(c *cliconfig.LogsValues, options *libpod.LogOptions)
}
return nil
}
+
+// CreateContainer creates a libpod container
+func (r *LocalRuntime) CreateContainer(ctx context.Context, c *cliconfig.CreateValues) (string, error) {
+ results := shared.NewIntermediateLayer(&c.PodmanCommand)
+ ctr, _, err := shared.CreateContainer(ctx, &results, r.Runtime)
+ return ctr.ID(), err
+}
+
+// Run a libpod container
+func (r *LocalRuntime) Run(ctx context.Context, c *cliconfig.RunValues, exitCode int) (int, error) {
+ results := shared.NewIntermediateLayer(&c.PodmanCommand)
+
+ ctr, createConfig, err := shared.CreateContainer(ctx, &results, r.Runtime)
+ if err != nil {
+ return exitCode, err
+ }
+
+ if logrus.GetLevel() == logrus.DebugLevel {
+ cgroupPath, err := ctr.CGroupPath()
+ if err == nil {
+ logrus.Debugf("container %q has CgroupParent %q", ctr.ID(), cgroupPath)
+ }
+ }
+
+ // Handle detached start
+ if createConfig.Detach {
+ // if the container was created as part of a pod, also start its dependencies, if any.
+ if err := ctr.Start(ctx, c.IsSet("pod")); err != nil {
+ // This means the command did not exist
+ exitCode = 127
+ if strings.Index(err.Error(), "permission denied") > -1 {
+ exitCode = 126
+ }
+ return exitCode, err
+ }
+
+ fmt.Printf("%s\n", ctr.ID())
+ exitCode = 0
+ return exitCode, nil
+ }
+
+ outputStream := os.Stdout
+ errorStream := os.Stderr
+ inputStream := os.Stdin
+
+ // If -i is not set, clear stdin
+ if !c.Bool("interactive") {
+ inputStream = nil
+ }
+
+ // If attach is set, clear stdin/stdout/stderr and only attach requested
+ if c.IsSet("attach") || c.IsSet("a") {
+ outputStream = nil
+ errorStream = nil
+ if !c.Bool("interactive") {
+ inputStream = nil
+ }
+
+ attachTo := c.StringSlice("attach")
+ for _, stream := range attachTo {
+ switch strings.ToLower(stream) {
+ case "stdout":
+ outputStream = os.Stdout
+ case "stderr":
+ errorStream = os.Stderr
+ case "stdin":
+ inputStream = os.Stdin
+ default:
+ return exitCode, errors.Wrapf(libpod.ErrInvalidArg, "invalid stream %q for --attach - must be one of stdin, stdout, or stderr", stream)
+ }
+ }
+ }
+ // if the container was created as part of a pod, also start its dependencies, if any.
+ if err := StartAttachCtr(ctx, ctr, outputStream, errorStream, inputStream, c.String("detach-keys"), c.Bool("sig-proxy"), true, c.IsSet("pod")); err != nil {
+ // We've manually detached from the container
+ // Do not perform cleanup, or wait for container exit code
+ // Just exit immediately
+ if errors.Cause(err) == libpod.ErrDetach {
+ exitCode = 0
+ return exitCode, nil
+ }
+ // This means the command did not exist
+ exitCode = 127
+ if strings.Index(err.Error(), "permission denied") > -1 {
+ exitCode = 126
+ }
+ if c.IsSet("rm") {
+ if deleteError := r.Runtime.RemoveContainer(ctx, ctr, true, false); deleteError != nil {
+ logrus.Errorf("unable to remove container %s after failing to start and attach to it", ctr.ID())
+ }
+ }
+ return exitCode, err
+ }
+
+ if ecode, err := ctr.Wait(); err != nil {
+ if errors.Cause(err) == libpod.ErrNoSuchCtr {
+ // The container may have been removed
+ // Go looking for an exit file
+ config, err := r.Runtime.GetConfig()
+ if err != nil {
+ return exitCode, err
+ }
+ ctrExitCode, err := ReadExitFile(config.TmpDir, ctr.ID())
+ if err != nil {
+ logrus.Errorf("Cannot get exit code: %v", err)
+ exitCode = 127
+ } else {
+ exitCode = ctrExitCode
+ }
+ }
+ } else {
+ exitCode = int(ecode)
+ }
+
+ if c.IsSet("rm") {
+ r.Runtime.RemoveContainer(ctx, ctr, false, true)
+ }
+
+ return exitCode, nil
+}
+
+// ReadExitFile reads a container's exit file
+func ReadExitFile(runtimeTmp, ctrID string) (int, error) {
+ exitFile := filepath.Join(runtimeTmp, "exits", fmt.Sprintf("%s-old", ctrID))
+
+ logrus.Debugf("Attempting to read container %s exit code from file %s", ctrID, exitFile)
+
+ // Check if it exists
+ if _, err := os.Stat(exitFile); err != nil {
+ return 0, errors.Wrapf(err, "error getting exit file for container %s", ctrID)
+ }
+
+ // File exists, read it in and convert to int
+ statusStr, err := ioutil.ReadFile(exitFile)
+ if err != nil {
+ return 0, errors.Wrapf(err, "error reading exit file for container %s", ctrID)
+ }
+
+ exitCode, err := strconv.Atoi(string(statusStr))
+ if err != nil {
+ return 0, errors.Wrapf(err, "error parsing exit code for container %s", ctrID)
+ }
+
+ return exitCode, nil
+}
diff --git a/pkg/adapter/containers_remote.go b/pkg/adapter/containers_remote.go
index 2982d6cbb..3730827c7 100644
--- a/pkg/adapter/containers_remote.go
+++ b/pkg/adapter/containers_remote.go
@@ -262,3 +262,33 @@ func (r *LocalRuntime) Log(c *cliconfig.LogsValues, options *libpod.LogOptions)
}
return nil
}
+
+// CreateContainer creates a container from the cli over varlink
+func (r *LocalRuntime) CreateContainer(ctx context.Context, c *cliconfig.CreateValues) (string, error) {
+ if !c.Bool("detach") {
+ // TODO need to add attach when that function becomes available
+ return "", errors.New("the remote client only supports detached containers")
+ }
+ results := shared.NewIntermediateLayer(&c.PodmanCommand)
+ return iopodman.CreateContainer().Call(r.Conn, results.MakeVarlink())
+}
+
+// Run creates a container overvarlink and then starts it
+func (r *LocalRuntime) Run(ctx context.Context, c *cliconfig.RunValues, exitCode int) (int, error) {
+ // TODO the exit codes for run need to be figured out for remote connections
+ if !c.Bool("detach") {
+ return 0, errors.New("the remote client only supports detached containers")
+ }
+ results := shared.NewIntermediateLayer(&c.PodmanCommand)
+ cid, err := iopodman.CreateContainer().Call(r.Conn, results.MakeVarlink())
+ if err != nil {
+ return 0, err
+ }
+ fmt.Println(cid)
+ _, err = iopodman.StartContainer().Call(r.Conn, cid)
+ return 0, err
+}
+
+func ReadExitFile(runtimeTmp, ctrID string) (int, error) {
+ return 0, libpod.ErrNotImplemented
+}
diff --git a/pkg/adapter/sigproxy.go b/pkg/adapter/sigproxy.go
new file mode 100644
index 000000000..af968cb89
--- /dev/null
+++ b/pkg/adapter/sigproxy.go
@@ -0,0 +1,36 @@
+package adapter
+
+import (
+ "os"
+ "syscall"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/docker/docker/pkg/signal"
+ "github.com/sirupsen/logrus"
+)
+
+// ProxySignals ...
+func ProxySignals(ctr *libpod.Container) {
+ sigBuffer := make(chan os.Signal, 128)
+ signal.CatchAll(sigBuffer)
+
+ logrus.Debugf("Enabling signal proxying")
+
+ go func() {
+ for s := range sigBuffer {
+ // Ignore SIGCHLD and SIGPIPE - these are mostly likely
+ // intended for the podman command itself.
+ if s == signal.SIGCHLD || s == signal.SIGPIPE {
+ continue
+ }
+
+ if err := ctr.Kill(uint(s.(syscall.Signal))); err != nil {
+ logrus.Errorf("Error forwarding signal %d to container %s: %v", s, ctr.ID(), err)
+ signal.StopCatch(sigBuffer)
+ syscall.Kill(syscall.Getpid(), s.(syscall.Signal))
+ }
+ }
+ }()
+
+ return
+}
diff --git a/pkg/adapter/terminal.go b/pkg/adapter/terminal.go
new file mode 100644
index 000000000..0b608decf
--- /dev/null
+++ b/pkg/adapter/terminal.go
@@ -0,0 +1,159 @@
+package adapter
+
+import (
+ "context"
+ "fmt"
+ "os"
+ gosignal "os/signal"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/docker/docker/pkg/signal"
+ "github.com/docker/docker/pkg/term"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "golang.org/x/crypto/ssh/terminal"
+ "k8s.io/client-go/tools/remotecommand"
+)
+
+// RawTtyFormatter ...
+type RawTtyFormatter struct {
+}
+
+// StartAttachCtr starts and (if required) attaches to a container
+func StartAttachCtr(ctx context.Context, ctr *libpod.Container, stdout, stderr, stdin *os.File, detachKeys string, sigProxy bool, startContainer bool, recursive bool) error {
+ resize := make(chan remotecommand.TerminalSize)
+
+ 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 && ctr.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 errors.Wrapf(err, "unable to save terminal state")
+ }
+
+ logrus.SetFormatter(&RawTtyFormatter{})
+ term.SetRawTerminal(os.Stdin.Fd())
+
+ defer restoreTerminal(oldTermState)
+ }
+
+ streams := new(libpod.AttachStreams)
+ streams.OutputStream = stdout
+ streams.ErrorStream = stderr
+ streams.InputStream = stdin
+ streams.AttachOutput = true
+ streams.AttachError = true
+ streams.AttachInput = true
+
+ if stdout == nil {
+ logrus.Debugf("Not attaching to stdout")
+ streams.AttachOutput = false
+ }
+ if stderr == nil {
+ logrus.Debugf("Not attaching to stderr")
+ streams.AttachError = false
+ }
+ if stdin == nil {
+ logrus.Debugf("Not attaching to stdin")
+ streams.AttachInput = false
+ }
+
+ if !startContainer {
+ if sigProxy {
+ ProxySignals(ctr)
+ }
+
+ return ctr.Attach(streams, detachKeys, resize)
+ }
+
+ attachChan, err := ctr.StartAndAttach(ctx, streams, detachKeys, resize, recursive)
+ if err != nil {
+ return err
+ }
+
+ if sigProxy {
+ ProxySignals(ctr)
+ }
+
+ if stdout == nil && stderr == nil {
+ fmt.Printf("%s\n", ctr.ID())
+ }
+
+ err = <-attachChan
+ if err != nil {
+ return errors.Wrapf(err, "error attaching to container %s", ctr.ID())
+ }
+
+ return nil
+}
+
+// getResize returns a TerminalSize command matching stdin's current
+// size on success, and nil on errors.
+func getResize() *remotecommand.TerminalSize {
+ winsize, err := term.GetWinsize(os.Stdin.Fd())
+ if err != nil {
+ logrus.Warnf("Could not get terminal size %v", err)
+ return nil
+ }
+ return &remotecommand.TerminalSize{
+ Width: winsize.Width,
+ Height: winsize.Height,
+ }
+}
+
+// Helper for prepareAttach - set up a goroutine to generate terminal resize events
+func resizeTty(ctx context.Context, resize chan remotecommand.TerminalSize) {
+ sigchan := make(chan os.Signal, 1)
+ gosignal.Notify(sigchan, signal.SIGWINCH)
+ go func() {
+ defer close(resize)
+ // Update the terminal size immediately without waiting
+ // for a SIGWINCH to get the correct initial size.
+ resizeEvent := getResize()
+ for {
+ if resizeEvent == nil {
+ select {
+ case <-ctx.Done():
+ return
+ case <-sigchan:
+ resizeEvent = getResize()
+ }
+ } else {
+ select {
+ case <-ctx.Done():
+ return
+ case <-sigchan:
+ resizeEvent = getResize()
+ case resize <- *resizeEvent:
+ resizeEvent = nil
+ }
+ }
+ }
+ }()
+}
+
+func restoreTerminal(state *term.State) error {
+ logrus.SetFormatter(&logrus.TextFormatter{})
+ return term.RestoreTerminal(os.Stdin.Fd(), state)
+}
+
+// Format ...
+func (f *RawTtyFormatter) Format(entry *logrus.Entry) ([]byte, error) {
+ textFormatter := logrus.TextFormatter{}
+ bytes, err := textFormatter.Format(entry)
+
+ if err == nil {
+ bytes = append(bytes, '\r')
+ }
+
+ return bytes, err
+}