diff options
author | baude <bbaude@redhat.com> | 2019-03-20 13:00:34 -0500 |
---|---|---|
committer | baude <bbaude@redhat.com> | 2019-04-08 09:05:31 -0500 |
commit | ba65301c955454e47c3893ca548f18a845a4c4a9 (patch) | |
tree | 4704ac2ce61efd5790a0e4dc06560d6eda38bc51 /pkg/adapter | |
parent | d86729e743fb5a58b9364ee5e991b5db2e9dd600 (diff) | |
download | podman-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.go | 150 | ||||
-rw-r--r-- | pkg/adapter/containers_remote.go | 30 | ||||
-rw-r--r-- | pkg/adapter/sigproxy.go | 36 | ||||
-rw-r--r-- | pkg/adapter/terminal.go | 159 |
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 +} |