mirror of
https://github.com/containers/podman.git
synced 2025-06-23 10:38:20 +08:00
Merge pull request #2874 from baude/varlinkterm
Add the ability to attach remotely to a container
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)
|
||||||
return 0, err
|
if err != nil {
|
||||||
|
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