mirror of
https://github.com/containers/podman.git
synced 2025-06-27 13:38:49 +08:00
Add the ability to attach remotely to a container
Also, you can now podman-remote run -it. There are some bugs that need to be ironed out but I would prefer to merge this so we can make both progress on start and exec as well as the bugs. * when doing podman-remote run -it foo /bin/bash, you have to press enter to get the prompt to display. with the localized podman, we had to teach it connect to the console first and then start the container so we did not miss anything. * when executing "exit" in the console, we get a hard lockup likely because nobody knows what to do. * custom detach keys are not supported * podman-remote run -it alpine ls does not currently work. only dropping to a shell works. Signed-off-by: baude <bbaude@redhat.com>
This commit is contained in:
21
API.md
21
API.md
@ -3,6 +3,10 @@ Podman Service Interface and API description. The master version of this docume
|
|||||||
in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in the upstream libpod repository.
|
in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in the upstream libpod repository.
|
||||||
## Index
|
## Index
|
||||||
|
|
||||||
|
[func Attach(name: string) ](#Attach)
|
||||||
|
|
||||||
|
[func AttachControl(name: string) ](#AttachControl)
|
||||||
|
|
||||||
[func BuildImage(build: BuildInfo) MoreResponse](#BuildImage)
|
[func BuildImage(build: BuildInfo) MoreResponse](#BuildImage)
|
||||||
|
|
||||||
[func BuildImageHierarchyMap(name: string) string](#BuildImageHierarchyMap)
|
[func BuildImageHierarchyMap(name: string) string](#BuildImageHierarchyMap)
|
||||||
@ -135,6 +139,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in
|
|||||||
|
|
||||||
[func SendFile(type: string, length: int) string](#SendFile)
|
[func SendFile(type: string, length: int) string](#SendFile)
|
||||||
|
|
||||||
|
[func Spec(name: string) string](#Spec)
|
||||||
|
|
||||||
[func StartContainer(name: string) string](#StartContainer)
|
[func StartContainer(name: string) string](#StartContainer)
|
||||||
|
|
||||||
[func StartPod(name: string) string](#StartPod)
|
[func StartPod(name: string) string](#StartPod)
|
||||||
@ -252,6 +258,16 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in
|
|||||||
[error WantsMoreRequired](#WantsMoreRequired)
|
[error WantsMoreRequired](#WantsMoreRequired)
|
||||||
|
|
||||||
## Methods
|
## Methods
|
||||||
|
### <a name="Attach"></a>func Attach
|
||||||
|
<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;">
|
||||||
|
|
||||||
|
method Attach(name: [string](https://godoc.org/builtin#string)) </div>
|
||||||
|
|
||||||
|
### <a name="AttachControl"></a>func AttachControl
|
||||||
|
<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;">
|
||||||
|
|
||||||
|
method AttachControl(name: [string](https://godoc.org/builtin#string)) </div>
|
||||||
|
|
||||||
### <a name="BuildImage"></a>func BuildImage
|
### <a name="BuildImage"></a>func BuildImage
|
||||||
<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;">
|
<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;">
|
||||||
|
|
||||||
@ -945,6 +961,11 @@ search results per registry.
|
|||||||
|
|
||||||
method SendFile(type: [string](https://godoc.org/builtin#string), length: [int](https://godoc.org/builtin#int)) [string](https://godoc.org/builtin#string)</div>
|
method SendFile(type: [string](https://godoc.org/builtin#string), length: [int](https://godoc.org/builtin#int)) [string](https://godoc.org/builtin#string)</div>
|
||||||
Sendfile allows a remote client to send a file to the host
|
Sendfile allows a remote client to send a file to the host
|
||||||
|
### <a name="Spec"></a>func Spec
|
||||||
|
<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;">
|
||||||
|
|
||||||
|
method Spec(name: [string](https://godoc.org/builtin#string)) [string](https://godoc.org/builtin#string)</div>
|
||||||
|
Spec returns the oci spec for a container. This call is for development of Podman only and generally should not be used.
|
||||||
### <a name="StartContainer"></a>func StartContainer
|
### <a name="StartContainer"></a>func StartContainer
|
||||||
<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;">
|
<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;">
|
||||||
|
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/containers/libpod/cmd/podman/cliconfig"
|
"github.com/containers/libpod/cmd/podman/cliconfig"
|
||||||
"github.com/containers/libpod/cmd/podman/libpodruntime"
|
|
||||||
"github.com/containers/libpod/libpod"
|
|
||||||
"github.com/containers/libpod/pkg/adapter"
|
"github.com/containers/libpod/pkg/adapter"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@ -39,49 +35,21 @@ func init() {
|
|||||||
flags.BoolVar(&attachCommand.SigProxy, "sig-proxy", true, "Proxy received signals to the process")
|
flags.BoolVar(&attachCommand.SigProxy, "sig-proxy", true, "Proxy received signals to the process")
|
||||||
flags.BoolVarP(&attachCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of")
|
flags.BoolVarP(&attachCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of")
|
||||||
markFlagHiddenForRemoteClient("latest", flags)
|
markFlagHiddenForRemoteClient("latest", flags)
|
||||||
|
// TODO allow for passing of a new deatch keys
|
||||||
|
markFlagHiddenForRemoteClient("detach-keys", flags)
|
||||||
}
|
}
|
||||||
|
|
||||||
func attachCmd(c *cliconfig.AttachValues) error {
|
func attachCmd(c *cliconfig.AttachValues) error {
|
||||||
args := c.InputArgs
|
|
||||||
var ctr *libpod.Container
|
|
||||||
|
|
||||||
if len(c.InputArgs) > 1 || (len(c.InputArgs) == 0 && !c.Latest) {
|
if len(c.InputArgs) > 1 || (len(c.InputArgs) == 0 && !c.Latest) {
|
||||||
return errors.Errorf("attach requires the name or id of one running container or the latest flag")
|
return errors.Errorf("attach requires the name or id of one running container or the latest flag")
|
||||||
}
|
}
|
||||||
|
if remoteclient && len(c.InputArgs) != 1 {
|
||||||
runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand)
|
return errors.Errorf("attach requires the name or id of one running container")
|
||||||
|
}
|
||||||
|
runtime, err := adapter.GetRuntime(&c.PodmanCommand)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "error creating libpod runtime")
|
return errors.Wrapf(err, "error creating runtime")
|
||||||
}
|
}
|
||||||
defer runtime.Shutdown(false)
|
defer runtime.Shutdown(false)
|
||||||
|
return runtime.Attach(getContext(), c)
|
||||||
if c.Latest {
|
|
||||||
ctr, err = runtime.GetLatestContainer()
|
|
||||||
} else {
|
|
||||||
ctr, err = runtime.LookupContainer(args[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "unable to exec into %s", args[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
conState, err := ctr.State()
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "unable to determine state of %s", args[0])
|
|
||||||
}
|
|
||||||
if conState != libpod.ContainerStateRunning {
|
|
||||||
return errors.Errorf("you can only attach to running containers")
|
|
||||||
}
|
|
||||||
|
|
||||||
inputStream := os.Stdin
|
|
||||||
if c.NoStdin {
|
|
||||||
inputStream = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the container is in a pod, also set to recursively start dependencies
|
|
||||||
if err := adapter.StartAttachCtr(getContext(), ctr, os.Stdout, os.Stderr, inputStream, c.DetachKeys, c.SigProxy, false, ctr.PodID() != ""); err != nil && errors.Cause(err) != libpod.ErrDetach {
|
|
||||||
return errors.Wrapf(err, "error attaching to container %s", ctr.ID())
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,6 @@ const remoteclient = false
|
|||||||
// Commands that the local client implements
|
// Commands that the local client implements
|
||||||
func getMainCommands() []*cobra.Command {
|
func getMainCommands() []*cobra.Command {
|
||||||
rootCommands := []*cobra.Command{
|
rootCommands := []*cobra.Command{
|
||||||
_attachCommand,
|
|
||||||
_commitCommand,
|
_commitCommand,
|
||||||
_execCommand,
|
_execCommand,
|
||||||
_generateCommand,
|
_generateCommand,
|
||||||
@ -47,7 +46,6 @@ func getImageSubCommands() []*cobra.Command {
|
|||||||
func getContainerSubCommands() []*cobra.Command {
|
func getContainerSubCommands() []*cobra.Command {
|
||||||
|
|
||||||
return []*cobra.Command{
|
return []*cobra.Command{
|
||||||
_attachCommand,
|
|
||||||
_checkpointCommand,
|
_checkpointCommand,
|
||||||
_cleanupCommand,
|
_cleanupCommand,
|
||||||
_commitCommand,
|
_commitCommand,
|
||||||
|
@ -50,6 +50,7 @@ var (
|
|||||||
|
|
||||||
// Commands that are universally implemented.
|
// Commands that are universally implemented.
|
||||||
containerCommands = []*cobra.Command{
|
containerCommands = []*cobra.Command{
|
||||||
|
_attachCommand,
|
||||||
_containerExistsCommand,
|
_containerExistsCommand,
|
||||||
_contInspectSubCommand,
|
_contInspectSubCommand,
|
||||||
_diffCommand,
|
_diffCommand,
|
||||||
|
@ -38,6 +38,7 @@ var (
|
|||||||
// Commands that the remote and local client have
|
// Commands that the remote and local client have
|
||||||
// implemented.
|
// implemented.
|
||||||
var mainCommands = []*cobra.Command{
|
var mainCommands = []*cobra.Command{
|
||||||
|
_attachCommand,
|
||||||
_buildCommand,
|
_buildCommand,
|
||||||
_diffCommand,
|
_diffCommand,
|
||||||
_createCommand,
|
_createCommand,
|
||||||
|
@ -658,8 +658,9 @@ method PauseContainer(name: string) -> (container: string)
|
|||||||
# See also [PauseContainer](#PauseContainer).
|
# See also [PauseContainer](#PauseContainer).
|
||||||
method UnpauseContainer(name: string) -> (container: string)
|
method UnpauseContainer(name: string) -> (container: string)
|
||||||
|
|
||||||
# This method has not be implemented yet.
|
method Attach(name: string) -> ()
|
||||||
# method AttachToContainer() -> (notimplemented: NotImplemented)
|
|
||||||
|
method AttachControl(name: string) -> ()
|
||||||
|
|
||||||
# GetAttachSockets takes the name or ID of an existing container. It returns file paths for two sockets needed
|
# GetAttachSockets takes the name or ID of an existing container. It returns file paths for two sockets needed
|
||||||
# to properly communicate with a container. The first is the actual I/O socket that the container uses. The
|
# to properly communicate with a container. The first is the actual I/O socket that the container uses. The
|
||||||
@ -1154,6 +1155,9 @@ method PodStateData(name: string) -> (config: string)
|
|||||||
# This call is for the development of Podman only and should not be used.
|
# This call is for the development of Podman only and should not be used.
|
||||||
method CreateFromCC(in: []string) -> (id: string)
|
method CreateFromCC(in: []string) -> (id: string)
|
||||||
|
|
||||||
|
# Spec returns the oci spec for a container. This call is for development of Podman only and generally should not be used.
|
||||||
|
method Spec(name: string) -> (config: string)
|
||||||
|
|
||||||
# Sendfile allows a remote client to send a file to the host
|
# Sendfile allows a remote client to send a file to the host
|
||||||
method SendFile(type: string, length: int) -> (file_handle: string)
|
method SendFile(type: string, length: int) -> (file_handle: string)
|
||||||
|
|
||||||
|
@ -407,3 +407,39 @@ func (r *LocalRuntime) Ps(c *cliconfig.PsValues, opts shared.PsOptions) ([]share
|
|||||||
logrus.Debugf("Setting maximum workers to %d", maxWorkers)
|
logrus.Debugf("Setting maximum workers to %d", maxWorkers)
|
||||||
return shared.GetPsContainerOutput(r.Runtime, opts, c.Filter, maxWorkers)
|
return shared.GetPsContainerOutput(r.Runtime, opts, c.Filter, maxWorkers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Attach ...
|
||||||
|
func (r *LocalRuntime) Attach(ctx context.Context, c *cliconfig.AttachValues) error {
|
||||||
|
var (
|
||||||
|
ctr *libpod.Container
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
if c.Latest {
|
||||||
|
ctr, err = r.Runtime.GetLatestContainer()
|
||||||
|
} else {
|
||||||
|
ctr, err = r.Runtime.LookupContainer(c.InputArgs[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "unable to exec into %s", c.InputArgs[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
conState, err := ctr.State()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "unable to determine state of %s", ctr.ID())
|
||||||
|
}
|
||||||
|
if conState != libpod.ContainerStateRunning {
|
||||||
|
return errors.Errorf("you can only attach to running containers")
|
||||||
|
}
|
||||||
|
|
||||||
|
inputStream := os.Stdin
|
||||||
|
if c.NoStdin {
|
||||||
|
inputStream = nil
|
||||||
|
}
|
||||||
|
// If the container is in a pod, also set to recursively start dependencies
|
||||||
|
if err := StartAttachCtr(ctx, ctr, os.Stdout, os.Stderr, inputStream, c.DetachKeys, c.SigProxy, false, ctr.PodID() != ""); err != nil && errors.Cause(err) != libpod.ErrDetach {
|
||||||
|
return errors.Wrapf(err, "error attaching to container %s", ctr.ID())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -6,19 +6,25 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containers/libpod/cmd/podman/cliconfig"
|
"github.com/containers/libpod/cmd/podman/cliconfig"
|
||||||
"github.com/containers/libpod/cmd/podman/shared"
|
"github.com/containers/libpod/cmd/podman/shared"
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
iopodman "github.com/containers/libpod/cmd/podman/varlink"
|
iopodman "github.com/containers/libpod/cmd/podman/varlink"
|
||||||
"github.com/containers/libpod/libpod"
|
"github.com/containers/libpod/libpod"
|
||||||
"github.com/containers/libpod/pkg/inspect"
|
"github.com/containers/libpod/pkg/inspect"
|
||||||
|
"github.com/containers/libpod/pkg/varlinkapi/virtwriter"
|
||||||
|
"github.com/docker/docker/pkg/term"
|
||||||
|
"github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/varlink/go/varlink"
|
"github.com/varlink/go/varlink"
|
||||||
|
"golang.org/x/crypto/ssh/terminal"
|
||||||
|
"k8s.io/client-go/tools/remotecommand"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Inspect returns an inspect struct from varlink
|
// Inspect returns an inspect struct from varlink
|
||||||
@ -71,6 +77,19 @@ func (r *LocalRuntime) ContainerState(name string) (*libpod.ContainerState, erro
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Spec obtains the container spec.
|
||||||
|
func (r *LocalRuntime) Spec(name string) (*specs.Spec, error) {
|
||||||
|
reply, err := iopodman.Spec().Call(r.Conn, name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data := specs.Spec{}
|
||||||
|
if err := json.Unmarshal([]byte(reply), &data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &data, nil
|
||||||
|
}
|
||||||
|
|
||||||
// LookupContainer gets basic information about container over a varlink
|
// LookupContainer gets basic information about container over a varlink
|
||||||
// connection and then translates it to a *Container
|
// connection and then translates it to a *Container
|
||||||
func (r *LocalRuntime) LookupContainer(idOrName string) (*Container, error) {
|
func (r *LocalRuntime) LookupContainer(idOrName string) (*Container, error) {
|
||||||
@ -79,10 +98,6 @@ func (r *LocalRuntime) LookupContainer(idOrName string) (*Container, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
config := r.Config(idOrName)
|
config := r.Config(idOrName)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Container{
|
return &Container{
|
||||||
remoteContainer{
|
remoteContainer{
|
||||||
r,
|
r,
|
||||||
@ -322,18 +337,32 @@ func (r *LocalRuntime) CreateContainer(ctx context.Context, c *cliconfig.CreateV
|
|||||||
|
|
||||||
// Run creates a container overvarlink and then starts it
|
// Run creates a container overvarlink and then starts it
|
||||||
func (r *LocalRuntime) Run(ctx context.Context, c *cliconfig.RunValues, exitCode int) (int, error) {
|
func (r *LocalRuntime) Run(ctx context.Context, c *cliconfig.RunValues, exitCode int) (int, error) {
|
||||||
|
// FIXME
|
||||||
|
// podman-remote run -it alpine ls DOES NOT WORK YET
|
||||||
|
// podman-remote run -it alpine /bin/sh does, i suspect there is some sort of
|
||||||
|
// timing issue between the socket availability and terminal setup and the command
|
||||||
|
// being run.
|
||||||
|
|
||||||
// TODO the exit codes for run need to be figured out for remote connections
|
// 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)
|
results := shared.NewIntermediateLayer(&c.PodmanCommand)
|
||||||
cid, err := iopodman.CreateContainer().Call(r.Conn, results.MakeVarlink())
|
cid, err := iopodman.CreateContainer().Call(r.Conn, results.MakeVarlink())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
fmt.Println(cid)
|
|
||||||
_, err = iopodman.StartContainer().Call(r.Conn, cid)
|
_, err = iopodman.StartContainer().Call(r.Conn, cid)
|
||||||
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
|
}
|
||||||
|
errChan, err := r.attach(ctx, os.Stdin, os.Stdout, cid)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if c.Bool("detach") {
|
||||||
|
fmt.Println(cid)
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
finalError := <-errChan
|
||||||
|
return 0, finalError
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReadExitFile(runtimeTmp, ctrID string) (int, error) {
|
func ReadExitFile(runtimeTmp, ctrID string) (int, error) {
|
||||||
@ -411,3 +440,102 @@ func (r *LocalRuntime) Ps(c *cliconfig.PsValues, opts shared.PsOptions) ([]share
|
|||||||
}
|
}
|
||||||
return psContainers, nil
|
return psContainers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *LocalRuntime) attach(ctx context.Context, stdin, stdout *os.File, cid 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)
|
||||||
|
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())
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = iopodman.Attach().Send(r.Conn, varlink.Upgrade, cid)
|
||||||
|
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])
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if ctr.state.State != libpod.ContainerStateRunning {
|
||||||
|
return errors.New("you can only attach to running containers")
|
||||||
|
}
|
||||||
|
inputStream := os.Stdin
|
||||||
|
if c.NoStdin {
|
||||||
|
inputStream = nil
|
||||||
|
}
|
||||||
|
errChan, err := r.attach(ctx, inputStream, os.Stdout, c.InputArgs[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return <-errChan
|
||||||
|
}
|
||||||
|
75
pkg/varlinkapi/attach.go
Normal file
75
pkg/varlinkapi/attach.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
// +build varlink
|
||||||
|
|
||||||
|
package varlinkapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/containers/libpod/cmd/podman/varlink"
|
||||||
|
"github.com/containers/libpod/libpod"
|
||||||
|
"github.com/containers/libpod/pkg/varlinkapi/virtwriter"
|
||||||
|
"k8s.io/client-go/tools/remotecommand"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Close is method to close the writer
|
||||||
|
|
||||||
|
// Attach ...
|
||||||
|
func (i *LibpodAPI) Attach(call iopodman.VarlinkCall, name string) error {
|
||||||
|
var finalErr error
|
||||||
|
resize := make(chan remotecommand.TerminalSize)
|
||||||
|
errChan := make(chan error)
|
||||||
|
|
||||||
|
if !call.WantsUpgrade() {
|
||||||
|
return call.ReplyErrorOccurred("client must use upgraded connection to attach")
|
||||||
|
}
|
||||||
|
ctr, err := i.Runtime.LookupContainer(name)
|
||||||
|
if err != nil {
|
||||||
|
return call.ReplyErrorOccurred(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// These are the varlink sockets
|
||||||
|
reader := call.Call.Reader
|
||||||
|
writer := call.Call.Writer
|
||||||
|
|
||||||
|
// This pipe is used to pass stdin from the client to the input stream
|
||||||
|
// once the msg has been "decoded"
|
||||||
|
pr, pw := io.Pipe()
|
||||||
|
|
||||||
|
stdoutWriter := virtwriter.NewVirtWriteCloser(writer, virtwriter.ToStdout)
|
||||||
|
// TODO if runc ever starts passing stderr, we can too
|
||||||
|
//stderrWriter := NewVirtWriteCloser(writer, ToStderr)
|
||||||
|
|
||||||
|
streams := libpod.AttachStreams{
|
||||||
|
OutputStream: stdoutWriter,
|
||||||
|
InputStream: pr,
|
||||||
|
// Runc eats the error stream
|
||||||
|
ErrorStream: stdoutWriter,
|
||||||
|
AttachInput: true,
|
||||||
|
AttachOutput: true,
|
||||||
|
// Runc eats the error stream
|
||||||
|
AttachError: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := virtwriter.Reader(reader, nil, nil, pw, resize); err != nil {
|
||||||
|
errChan <- err
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
// TODO allow for customizable detach keys
|
||||||
|
if err := ctr.Attach(&streams, "", resize); err != nil {
|
||||||
|
errChan <- err
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
// Blocking on an error
|
||||||
|
case finalErr = <-errChan:
|
||||||
|
// Need to close up shop
|
||||||
|
_ = finalErr
|
||||||
|
}
|
||||||
|
quitWriter := virtwriter.NewVirtWriteCloser(writer, virtwriter.Quit)
|
||||||
|
_, err = quitWriter.Write([]byte("HANG-UP"))
|
||||||
|
return call.Writer.Flush()
|
||||||
|
}
|
@ -634,6 +634,22 @@ func (i *LibpodAPI) GetContainerStatsWithHistory(call iopodman.VarlinkCall, prev
|
|||||||
return call.ReplyGetContainerStatsWithHistory(cStats)
|
return call.ReplyGetContainerStatsWithHistory(cStats)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Spec ...
|
||||||
|
func (i *LibpodAPI) Spec(call iopodman.VarlinkCall, name string) error {
|
||||||
|
ctr, err := i.Runtime.LookupContainer(name)
|
||||||
|
if err != nil {
|
||||||
|
return call.ReplyErrorOccurred(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
spec := ctr.Spec()
|
||||||
|
b, err := json.Marshal(spec)
|
||||||
|
if err != nil {
|
||||||
|
return call.ReplyErrorOccurred(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return call.ReplySpec(string(b))
|
||||||
|
}
|
||||||
|
|
||||||
// GetContainersLogs is the varlink endpoint to obtain one or more container logs
|
// GetContainersLogs is the varlink endpoint to obtain one or more container logs
|
||||||
func (i *LibpodAPI) GetContainersLogs(call iopodman.VarlinkCall, names []string, follow, latest bool, since string, tail int64, timestamps bool) error {
|
func (i *LibpodAPI) GetContainersLogs(call iopodman.VarlinkCall, names []string, follow, latest bool, since string, tail int64, timestamps bool) error {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
155
pkg/varlinkapi/virtwriter/virtwriter.go
Normal file
155
pkg/varlinkapi/virtwriter/virtwriter.go
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
package virtwriter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"k8s.io/client-go/tools/remotecommand"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SocketDest is the "key" to where IO should go on the varlink
|
||||||
|
// multiplexed socket
|
||||||
|
type SocketDest int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ToStdout indicates traffic should go stdout
|
||||||
|
ToStdout SocketDest = iota
|
||||||
|
// ToStdin indicates traffic came from stdin
|
||||||
|
ToStdin SocketDest = iota
|
||||||
|
// ToStderr indicates traffuc should go to stderr
|
||||||
|
ToStderr SocketDest = iota
|
||||||
|
// TerminalResize indicates a terminal resize event has occurred
|
||||||
|
// and data should be passed to resizer
|
||||||
|
TerminalResize SocketDest = iota
|
||||||
|
// Quit and detach
|
||||||
|
Quit SocketDest = iota
|
||||||
|
)
|
||||||
|
|
||||||
|
// IntToSocketDest returns a socketdest based on integer input
|
||||||
|
func IntToSocketDest(i int) SocketDest {
|
||||||
|
switch i {
|
||||||
|
case ToStdout.Int():
|
||||||
|
return ToStdout
|
||||||
|
case ToStderr.Int():
|
||||||
|
return ToStderr
|
||||||
|
case ToStdin.Int():
|
||||||
|
return ToStdin
|
||||||
|
case TerminalResize.Int():
|
||||||
|
return TerminalResize
|
||||||
|
case Quit.Int():
|
||||||
|
return Quit
|
||||||
|
default:
|
||||||
|
return ToStderr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int returns the integer representation of the socket dest
|
||||||
|
func (sd SocketDest) Int() int {
|
||||||
|
return int(sd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VirtWriteCloser are writers for attach which include the dest
|
||||||
|
// of the data
|
||||||
|
type VirtWriteCloser struct {
|
||||||
|
writer *bufio.Writer
|
||||||
|
dest SocketDest
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewVirtWriteCloser is a constructor
|
||||||
|
func NewVirtWriteCloser(w *bufio.Writer, dest SocketDest) VirtWriteCloser {
|
||||||
|
return VirtWriteCloser{w, dest}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close is a required method for a writecloser
|
||||||
|
func (v VirtWriteCloser) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write prepends a header to the input message. The header is
|
||||||
|
// 8bytes. Position one contains the destination. Positions
|
||||||
|
// 5,6,7,8 are a big-endian encoded uint32 for len of the message.
|
||||||
|
func (v VirtWriteCloser) Write(input []byte) (int, error) {
|
||||||
|
header := []byte{byte(v.dest), 0, 0, 0}
|
||||||
|
// Go makes us define the byte for big endian
|
||||||
|
mlen := make([]byte, 4)
|
||||||
|
binary.BigEndian.PutUint32(mlen, uint32(len(input)))
|
||||||
|
// append the message len to the header
|
||||||
|
msg := append(header, mlen...)
|
||||||
|
// append the message to the header
|
||||||
|
msg = append(msg, input...)
|
||||||
|
_, err := v.writer.Write(msg)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
err = v.writer.Flush()
|
||||||
|
return len(input), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reader decodes the content that comes over the wire and directs it to the proper destination.
|
||||||
|
func Reader(r *bufio.Reader, output, errput *os.File, input *io.PipeWriter, resize chan remotecommand.TerminalSize) error {
|
||||||
|
var saveb []byte
|
||||||
|
var eom int
|
||||||
|
for {
|
||||||
|
readb := make([]byte, 32*1024)
|
||||||
|
n, err := r.Read(readb)
|
||||||
|
// TODO, later may be worth checking in len of the read is 0
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
b := append(saveb, readb[0:n]...)
|
||||||
|
// no sense in reading less than the header len
|
||||||
|
for len(b) > 7 {
|
||||||
|
eom = int(binary.BigEndian.Uint32(b[4:8])) + 8
|
||||||
|
// The message and header are togther
|
||||||
|
if len(b) >= eom {
|
||||||
|
out := append([]byte{}, b[8:eom]...)
|
||||||
|
|
||||||
|
switch IntToSocketDest(int(b[0])) {
|
||||||
|
case ToStdout:
|
||||||
|
n, err := output.Write(out)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if n < len(out) {
|
||||||
|
return errors.New("short write error occurred on stdout")
|
||||||
|
}
|
||||||
|
case ToStderr:
|
||||||
|
n, err := errput.Write(out)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if n < len(out) {
|
||||||
|
return errors.New("short write error occurred on stderr")
|
||||||
|
}
|
||||||
|
case ToStdin:
|
||||||
|
n, err := input.Write(out)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if n < len(out) {
|
||||||
|
return errors.New("short write error occurred on stdin")
|
||||||
|
}
|
||||||
|
case TerminalResize:
|
||||||
|
// 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
|
||||||
|
case Quit:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
b = b[eom:]
|
||||||
|
} else {
|
||||||
|
// We do not have the header and full message, need to slurp again
|
||||||
|
saveb = b
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
Reference in New Issue
Block a user