diff options
37 files changed, 830 insertions, 64 deletions
diff --git a/cmd/podmanV2/common/create.go b/cmd/podmanV2/common/create.go index 724ed2f42..f81d021c8 100644 --- a/cmd/podmanV2/common/create.go +++ b/cmd/podmanV2/common/create.go @@ -138,7 +138,7 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { ) createFlags.StringVar( &cf.DetachKeys, - "detach-keys", getDefaultDetachKeys(), + "detach-keys", GetDefaultDetachKeys(), "Override the key sequence for detaching a container. Format is a single character `[a-Z]` or a comma separated sequence of `ctrl-<value>`, where `<value>` is one of: `a-cf`, `@`, `^`, `[`, `\\`, `]`, `^` or `_`", ) createFlags.StringSliceVar( diff --git a/cmd/podmanV2/common/default.go b/cmd/podmanV2/common/default.go index fea161edf..b71fcb6f0 100644 --- a/cmd/podmanV2/common/default.go +++ b/cmd/podmanV2/common/default.go @@ -116,6 +116,6 @@ func getDefaultPidsDescription() string { return "Tune container pids limit (set 0 for unlimited)" } -func getDefaultDetachKeys() string { +func GetDefaultDetachKeys() string { return defaultContainerConfig.Engine.DetachKeys } diff --git a/cmd/podmanV2/containers/attach.go b/cmd/podmanV2/containers/attach.go new file mode 100644 index 000000000..d62dcff86 --- /dev/null +++ b/cmd/podmanV2/containers/attach.go @@ -0,0 +1,60 @@ +package containers + +import ( + "os" + + "github.com/containers/libpod/cmd/podmanV2/common" + "github.com/containers/libpod/cmd/podmanV2/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + attachDescription = "The podman attach command allows you to attach to a running container using the container's ID or name, either to view its ongoing output or to control it interactively." + attachCommand = &cobra.Command{ + Use: "attach [flags] CONTAINER", + Short: "Attach to a running container", + Long: attachDescription, + RunE: attach, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) > 1 || (len(args) == 0 && !cmd.Flag("latest").Changed) { + return errors.Errorf("attach requires the name or id of one running container or the latest flag") + } + return nil + }, + PreRunE: preRunE, + Example: `podman attach ctrID + podman attach 1234 + podman attach --no-stdin foobar`, + } +) + +var ( + attachOpts entities.AttachOptions +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: attachCommand, + }) + flags := attachCommand.Flags() + flags.StringVar(&attachOpts.DetachKeys, "detach-keys", common.GetDefaultDetachKeys(), "Select the key sequence for detaching a container. Format is a single character `[a-Z]` or a comma separated sequence of `ctrl-<value>`, where `<value>` is one of: `a-z`, `@`, `^`, `[`, `\\`, `]`, `^` or `_`") + flags.BoolVar(&attachOpts.NoStdin, "no-stdin", false, "Do not attach STDIN. The default is false") + flags.BoolVar(&attachOpts.SigProxy, "sig-proxy", true, "Proxy received signals to the process") + flags.BoolVarP(&attachOpts.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + if registry.IsRemote() { + _ = flags.MarkHidden("latest") + } +} + +func attach(cmd *cobra.Command, args []string) error { + attachOpts.Stdin = os.Stdin + if attachOpts.NoStdin { + attachOpts.Stdin = nil + } + attachOpts.Stdout = os.Stdout + attachOpts.Stderr = os.Stderr + return registry.ContainerEngine().ContainerAttach(registry.GetContext(), args[0], attachOpts) +} diff --git a/cmd/podmanV2/containers/exec.go b/cmd/podmanV2/containers/exec.go new file mode 100644 index 000000000..4bff57dbb --- /dev/null +++ b/cmd/podmanV2/containers/exec.go @@ -0,0 +1,93 @@ +package containers + +import ( + "bufio" + "os" + + "github.com/containers/libpod/cmd/podmanV2/common" + "github.com/containers/libpod/cmd/podmanV2/registry" + "github.com/containers/libpod/pkg/domain/entities" + envLib "github.com/containers/libpod/pkg/env" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + execDescription = `Execute the specified command inside a running container. +` + execCommand = &cobra.Command{ + Use: "exec [flags] CONTAINER [COMMAND [ARG...]]", + Short: "Run a process in a running container", + Long: execDescription, + PreRunE: preRunE, + RunE: exec, + Example: `podman exec -it ctrID ls + podman exec -it -w /tmp myCtr pwd + podman exec --user root ctrID ls`, + } +) + +var ( + envInput, envFile []string + execOpts entities.ExecOptions +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: execCommand, + }) + flags := execCommand.Flags() + flags.SetInterspersed(false) + flags.StringVar(&execOpts.DetachKeys, "detach-keys", common.GetDefaultDetachKeys(), "Select the key sequence for detaching a container. Format is a single character [a-Z] or ctrl-<value> where <value> is one of: a-z, @, ^, [, , or _") + flags.StringArrayVarP(&envInput, "env", "e", []string{}, "Set environment variables") + flags.StringSliceVar(&envFile, "env-file", []string{}, "Read in a file of environment variables") + flags.BoolVarP(&execOpts.Interactive, "interactive", "i", false, "Keep STDIN open even if not attached") + flags.BoolVarP(&execOpts.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + flags.BoolVar(&execOpts.Privileged, "privileged", false, "Give the process extended Linux capabilities inside the container. The default is false") + flags.BoolVarP(&execOpts.Tty, "tty", "t", false, "Allocate a pseudo-TTY. The default is false") + flags.StringVarP(&execOpts.User, "user", "u", "", "Sets the username or UID used and optionally the groupname or GID for the specified command") + flags.UintVar(&execOpts.PreserveFDs, "preserve-fds", 0, "Pass N additional file descriptors to the container") + flags.StringVarP(&execOpts.WorkDir, "workdir", "w", "", "Working directory inside the container") + if registry.IsRemote() { + _ = flags.MarkHidden("latest") + _ = flags.MarkHidden("preserve-fds") + } + +} +func exec(cmd *cobra.Command, args []string) error { + var nameOrId string + execOpts.Cmd = args + if !execOpts.Latest { + execOpts.Cmd = args[1:] + nameOrId = args[0] + } + // Validate given environment variables + execOpts.Envs = make(map[string]string) + for _, f := range envFile { + fileEnv, err := envLib.ParseFile(f) + if err != nil { + return err + } + execOpts.Envs = envLib.Join(execOpts.Envs, fileEnv) + } + + cliEnv, err := envLib.ParseSlice(envInput) + if err != nil { + return errors.Wrap(err, "error parsing environment variables") + } + + execOpts.Envs = envLib.Join(execOpts.Envs, cliEnv) + execOpts.Streams.OutputStream = os.Stdout + execOpts.Streams.ErrorStream = os.Stderr + if execOpts.Interactive { + execOpts.Streams.InputStream = bufio.NewReader(os.Stdin) + execOpts.Streams.AttachInput = true + } + execOpts.Streams.AttachOutput = true + execOpts.Streams.AttachError = true + + exitCode, err := registry.ContainerEngine().ContainerExec(registry.GetContext(), nameOrId, execOpts) + registry.SetExitCode(exitCode) + return err +} diff --git a/cmd/podmanV2/main.go b/cmd/podmanV2/main.go index 6781a7f06..fe3cd9f16 100644 --- a/cmd/podmanV2/main.go +++ b/cmd/podmanV2/main.go @@ -12,6 +12,7 @@ import ( _ "github.com/containers/libpod/cmd/podmanV2/networks" _ "github.com/containers/libpod/cmd/podmanV2/pods" "github.com/containers/libpod/cmd/podmanV2/registry" + _ "github.com/containers/libpod/cmd/podmanV2/system" _ "github.com/containers/libpod/cmd/podmanV2/volumes" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/domain/entities" diff --git a/cmd/podmanV2/pods/inspect.go b/cmd/podmanV2/pods/inspect.go new file mode 100644 index 000000000..9aab610f2 --- /dev/null +++ b/cmd/podmanV2/pods/inspect.go @@ -0,0 +1,64 @@ +package pods + +import ( + "context" + "fmt" + + "github.com/containers/libpod/cmd/podmanV2/registry" + "github.com/containers/libpod/pkg/domain/entities" + jsoniter "github.com/json-iterator/go" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + inspectOptions = entities.PodInspectOptions{} +) + +var ( + inspectDescription = fmt.Sprintf(`Display the configuration for a pod by name or id + + By default, this will render all results in a JSON array.`) + + inspectCmd = &cobra.Command{ + Use: "inspect [flags] POD [POD...]", + Short: "Displays a pod configuration", + Long: inspectDescription, + RunE: inspect, + Example: `podman pod inspect podID`, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: inspectCmd, + Parent: podCmd, + }) + flags := inspectCmd.Flags() + flags.BoolVarP(&inspectOptions.Latest, "latest", "l", false, "Act on the latest pod podman is aware of") + if registry.IsRemote() { + _ = flags.MarkHidden("latest") + } +} + +func inspect(cmd *cobra.Command, args []string) error { + + if len(args) < 1 && !inspectOptions.Latest { + return errors.Errorf("you must provide the name or id of a running pod") + } + + if !inspectOptions.Latest { + inspectOptions.NameOrID = args[0] + } + responses, err := registry.ContainerEngine().PodInspect(context.Background(), inspectOptions) + if err != nil { + return err + } + b, err := jsoniter.MarshalIndent(responses, "", " ") + if err != nil { + return err + } + fmt.Println(string(b)) + return nil +} diff --git a/cmd/podmanV2/system/version.go b/cmd/podmanV2/system/version.go new file mode 100644 index 000000000..e8002056b --- /dev/null +++ b/cmd/podmanV2/system/version.go @@ -0,0 +1,119 @@ +package system + +import ( + "fmt" + "io" + "os" + "strings" + "text/tabwriter" + "time" + + "github.com/containers/buildah/pkg/formats" + "github.com/containers/libpod/cmd/podmanV2/registry" + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + versionCommand = &cobra.Command{ + Use: "version", + Args: cobra.NoArgs, + Short: "Display the Podman Version Information", + RunE: version, + PersistentPreRunE: preRunE, + } + format string +) + +type versionStruct struct { + Client define.Version + Server define.Version +} + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: versionCommand, + }) + flags := versionCommand.Flags() + flags.StringVarP(&format, "format", "f", "", "Change the output format to JSON or a Go template") +} + +func version(cmd *cobra.Command, args []string) error { + var ( + v versionStruct + err error + ) + v.Client, err = define.GetVersion() + if err != nil { + return errors.Wrapf(err, "unable to determine version") + } + // TODO we need to discuss how to implement + // this more. current endpoints dont have a + // version endpoint. maybe we use info? + //if remote { + // v.Server, err = getRemoteVersion(c) + // if err != nil { + // return err + // } + //} else { + v.Server = v.Client + //} + + versionOutputFormat := format + if versionOutputFormat != "" { + if strings.Join(strings.Fields(versionOutputFormat), "") == "{{json.}}" { + versionOutputFormat = formats.JSONString + } + var out formats.Writer + switch versionOutputFormat { + case formats.JSONString: + out = formats.JSONStruct{Output: v} + return out.Out() + default: + out = formats.StdoutTemplate{Output: v, Template: versionOutputFormat} + err := out.Out() + if err != nil { + // On Failure, assume user is using older version of podman version --format and check client + out = formats.StdoutTemplate{Output: v.Client, Template: versionOutputFormat} + if err1 := out.Out(); err1 != nil { + return err + } + } + } + return nil + } + w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + defer w.Flush() + + if registry.IsRemote() { + if _, err := fmt.Fprintf(w, "Client:\n"); err != nil { + return err + } + formatVersion(w, v.Client) + if _, err := fmt.Fprintf(w, "\nServer:\n"); err != nil { + return err + } + formatVersion(w, v.Server) + } else { + formatVersion(w, v.Client) + } + return nil +} + +func formatVersion(writer io.Writer, version define.Version) { + fmt.Fprintf(writer, "Version:\t%s\n", version.Version) + fmt.Fprintf(writer, "RemoteAPI Version:\t%d\n", version.RemoteAPIVersion) + fmt.Fprintf(writer, "Go Version:\t%s\n", version.GoVersion) + if version.GitCommit != "" { + fmt.Fprintf(writer, "Git Commit:\t%s\n", version.GitCommit) + } + // Prints out the build time in readable format + if version.Built != 0 { + fmt.Fprintf(writer, "Built:\t%s\n", time.Unix(version.Built, 0).Format(time.ANSIC)) + } + + fmt.Fprintf(writer, "OS/Arch:\t%s\n", version.OsArch) +} @@ -10,11 +10,11 @@ require ( github.com/containernetworking/cni v0.7.2-0.20200304161608-4fae32b84921 github.com/containernetworking/plugins v0.8.5 github.com/containers/buildah v1.14.6-0.20200402210551-e9a6703edee2 - github.com/containers/common v0.8.0 + github.com/containers/common v0.8.1 github.com/containers/conmon v2.0.14+incompatible github.com/containers/image/v5 v5.3.1 github.com/containers/psgo v1.4.0 - github.com/containers/storage v1.18.1 + github.com/containers/storage v1.18.2 github.com/coreos/go-systemd/v22 v22.0.0 github.com/cri-o/ocicni v0.1.1-0.20190920040751-deac903fd99b github.com/cyphar/filepath-securejoin v0.2.2 @@ -71,6 +71,8 @@ github.com/containers/common v0.6.1/go.mod h1:m62kenckrWi5rZx32kaLje2Og0hpf6NsaT github.com/containers/common v0.7.0/go.mod h1:UmhIdvSkhTR0hWR01AnuZGNufm80+A0s8isb05eTmz0= github.com/containers/common v0.8.0 h1:C+wjkcmR4gooeKCXZpyjsHSFARm5AZRegflGz0x0MMw= github.com/containers/common v0.8.0/go.mod h1:QJTx9+SvhHKP6e+p7Nxqc8oNnS5rSf0KVhxudIbDslU= +github.com/containers/common v0.8.1 h1:1IUwAtZ4mC7GYRr4AC23cHf2oXCuoLzTUoSzIkSgnYw= +github.com/containers/common v0.8.1/go.mod h1:VxDJbaA1k6N1TNv9Rt6bQEF4hyKVHNfOfGA5L91ADEs= github.com/containers/common v1.0.0 h1:sZB48LzGP4bP1CmrkQIFUzdUVBysqRv3kWVk4+qbaVA= github.com/containers/common v1.0.0/go.mod h1:m62kenckrWi5rZx32kaLje2Og0hpf6NsaTBn6+b+Oys= github.com/containers/conmon v2.0.14+incompatible h1:knU1O1QxXy5YxtjMQVKEyCajROaehizK9FHaICl+P5Y= @@ -92,6 +94,8 @@ github.com/containers/storage v1.16.6/go.mod h1:Fws4I+U+C4DmJxDbBs1z9SKk50DzN4Lt github.com/containers/storage v1.18.0/go.mod h1:gbFeFybWhlVCk3buJ0sovNKs8MzWEBTrk8/sbJw8irQ= github.com/containers/storage v1.18.1 h1:W134oYa8ALd78yo6DKiDp6n7EWXrc+fCnYmJi6o49vo= github.com/containers/storage v1.18.1/go.mod h1:6NtCgnUeYsRlyZyrZ5qKkXYC560GRgvA7YrKRSAYSlo= +github.com/containers/storage v1.18.2 h1:4cgFbrrgr9nR9xCeOmfpyxk1MtXYZGr7XGPJfAVkGmc= +github.com/containers/storage v1.18.2/go.mod h1:WTBMf+a9ZZ/LbmEVeLHH2TX4CikWbO1Bt+/m58ZHVPg= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-iptables v0.4.5 h1:DpHb9vJrZQEFMcVLFKAAGMUVX0XoRC0ptCthinRYm38= @@ -587,6 +591,7 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72 h1:bw9doJza/SFBEweII/rHQh338oozWyiFsBRHtrflcws= golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= diff --git a/libpod/container_api.go b/libpod/container_api.go index 967180437..55c79fa74 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -3,7 +3,6 @@ package libpod import ( "bufio" "context" - "io" "io/ioutil" "net" "os" @@ -96,7 +95,7 @@ func (c *Container) Start(ctx context.Context, recursive bool) (err error) { // The channel will be closed automatically after the result of attach has been // sent. // If recursive is set, StartAndAttach will also start all containers this container depends on. -func (c *Container) StartAndAttach(ctx context.Context, streams *AttachStreams, keys string, resize <-chan remotecommand.TerminalSize, recursive bool) (attachResChan <-chan error, err error) { +func (c *Container) StartAndAttach(ctx context.Context, streams *define.AttachStreams, keys string, resize <-chan remotecommand.TerminalSize, recursive bool) (attachResChan <-chan error, err error) { if !c.batched { c.lock.Lock() defer c.lock.Unlock() @@ -213,29 +212,10 @@ func (c *Container) Kill(signal uint) error { return c.save() } -// AttachStreams contains streams that will be attached to the container -type AttachStreams struct { - // OutputStream will be attached to container's STDOUT - OutputStream io.WriteCloser - // ErrorStream will be attached to container's STDERR - ErrorStream io.WriteCloser - // InputStream will be attached to container's STDIN - InputStream *bufio.Reader - // AttachOutput is whether to attach to STDOUT - // If false, stdout will not be attached - AttachOutput bool - // AttachError is whether to attach to STDERR - // If false, stdout will not be attached - AttachError bool - // AttachInput is whether to attach to STDIN - // If false, stdout will not be attached - AttachInput bool -} - // Attach attaches to a container. // This function returns when the attach finishes. It does not hold the lock for // the duration of its runtime, only using it at the beginning to verify state. -func (c *Container) Attach(streams *AttachStreams, keys string, resize <-chan remotecommand.TerminalSize) error { +func (c *Container) Attach(streams *define.AttachStreams, keys string, resize <-chan remotecommand.TerminalSize) error { if !c.batched { c.lock.Lock() if err := c.syncContainer(); err != nil { diff --git a/libpod/container_exec.go b/libpod/container_exec.go index 5469462f8..c1ce8b724 100644 --- a/libpod/container_exec.go +++ b/libpod/container_exec.go @@ -221,7 +221,7 @@ func (c *Container) ExecStart(sessionID string) error { // ExecStartAndAttach starts and attaches to an exec session in a container. // TODO: Should we include detach keys in the signature to allow override? // TODO: How do we handle AttachStdin/AttachStdout/AttachStderr? -func (c *Container) ExecStartAndAttach(sessionID string, streams *AttachStreams) error { +func (c *Container) ExecStartAndAttach(sessionID string, streams *define.AttachStreams) error { if !c.batched { c.lock.Lock() defer c.lock.Unlock() @@ -544,7 +544,7 @@ func (c *Container) ExecResize(sessionID string, newSize remotecommand.TerminalS // Exec emulates the old Libpod exec API, providing a single call to create, // run, and remove an exec session. Returns exit code and error. Exit code is // not guaranteed to be set sanely if error is not nil. -func (c *Container) Exec(config *ExecConfig, streams *AttachStreams, resize <-chan remotecommand.TerminalSize) (int, error) { +func (c *Container) Exec(config *ExecConfig, streams *define.AttachStreams, resize <-chan remotecommand.TerminalSize) (int, error) { sessionID, err := c.ExecCreate(config) if err != nil { return -1, err diff --git a/libpod/container_top_linux.go b/libpod/container_top_linux.go index 2a35a2ae9..98a69966a 100644 --- a/libpod/container_top_linux.go +++ b/libpod/container_top_linux.go @@ -112,7 +112,7 @@ func (c *Container) execPS(args []string) ([]string, error) { defer wErrPipe.Close() defer rErrPipe.Close() - streams := new(AttachStreams) + streams := new(define.AttachStreams) streams.OutputStream = wPipe streams.ErrorStream = wErrPipe streams.AttachOutput = true diff --git a/libpod/define/config.go b/libpod/define/config.go index 7b967f17d..10e00062a 100644 --- a/libpod/define/config.go +++ b/libpod/define/config.go @@ -1,5 +1,10 @@ package define +import ( + "bufio" + "io" +) + var ( // DefaultInfraImage to use for infra container DefaultInfraImage = "k8s.gcr.io/pause:3.2" @@ -33,3 +38,22 @@ const ( V2s2ManifestDir = "docker-dir" V2s2Archive = "docker-archive" ) + +// AttachStreams contains streams that will be attached to the container +type AttachStreams struct { + // OutputStream will be attached to container's STDOUT + OutputStream io.WriteCloser + // ErrorStream will be attached to container's STDERR + ErrorStream io.WriteCloser + // InputStream will be attached to container's STDIN + InputStream *bufio.Reader + // AttachOutput is whether to attach to STDOUT + // If false, stdout will not be attached + AttachOutput bool + // AttachError is whether to attach to STDERR + // If false, stdout will not be attached + AttachError bool + // AttachInput is whether to attach to STDIN + // If false, stdout will not be attached + AttachInput bool +} diff --git a/libpod/healthcheck.go b/libpod/healthcheck.go index 08a613dfe..daddb6561 100644 --- a/libpod/healthcheck.go +++ b/libpod/healthcheck.go @@ -108,7 +108,7 @@ func (c *Container) runHealthCheck() (HealthCheckStatus, error) { hcw := hcWriteCloser{ captureBuffer, } - streams := new(AttachStreams) + streams := new(define.AttachStreams) streams.OutputStream = hcw streams.ErrorStream = hcw diff --git a/libpod/oci.go b/libpod/oci.go index ef46cf5c3..e4fbcb62e 100644 --- a/libpod/oci.go +++ b/libpod/oci.go @@ -4,6 +4,8 @@ import ( "bufio" "net" + "github.com/containers/libpod/libpod/define" + "k8s.io/client-go/tools/remotecommand" ) @@ -141,7 +143,7 @@ type ExecOptions struct { // the container was run as will be used. User string // Streams are the streams that will be attached to the container. - Streams *AttachStreams + Streams *define.AttachStreams // PreserveFDs is a number of additional file descriptors (in addition // to 0, 1, 2) that will be passed to the executed process. The total FDs // passed will be 3 + PreserveFDs. diff --git a/libpod/oci_attach_linux.go b/libpod/oci_attach_linux.go index fb0a54bff..ff158c2d1 100644 --- a/libpod/oci_attach_linux.go +++ b/libpod/oci_attach_linux.go @@ -31,7 +31,7 @@ const ( // Attach to the given container // Does not check if state is appropriate // started is only required if startContainer is true -func (c *Container) attach(streams *AttachStreams, keys string, resize <-chan remotecommand.TerminalSize, startContainer bool, started chan bool) error { +func (c *Container) attach(streams *define.AttachStreams, keys string, resize <-chan remotecommand.TerminalSize, startContainer bool, started chan bool) error { if !streams.AttachOutput && !streams.AttachError && !streams.AttachInput { return errors.Wrapf(define.ErrInvalidArg, "must provide at least one stream to attach to") } @@ -94,7 +94,7 @@ func (c *Container) attach(streams *AttachStreams, keys string, resize <-chan re // 4. attachToExec sends on startFd, signalling it has attached to the socket and child is ready to go // 5. child receives on startFd, runs the runtime exec command // attachToExec is responsible for closing startFd and attachFd -func (c *Container) attachToExec(streams *AttachStreams, keys *string, sessionID string, startFd, attachFd *os.File) error { +func (c *Container) attachToExec(streams *define.AttachStreams, keys *string, sessionID string, startFd, attachFd *os.File) error { if !streams.AttachOutput && !streams.AttachError && !streams.AttachInput { return errors.Wrapf(define.ErrInvalidArg, "must provide at least one stream to attach to") } @@ -189,7 +189,7 @@ func buildSocketPath(socketPath string) string { return socketPath } -func setupStdioChannels(streams *AttachStreams, conn *net.UnixConn, detachKeys []byte) (chan error, chan error) { +func setupStdioChannels(streams *define.AttachStreams, conn *net.UnixConn, detachKeys []byte) (chan error, chan error) { receiveStdoutError := make(chan error) go func() { receiveStdoutError <- redirectResponseToOutputStreams(streams.OutputStream, streams.ErrorStream, streams.AttachOutput, streams.AttachError, conn) @@ -257,7 +257,7 @@ func redirectResponseToOutputStreams(outputStream, errorStream io.Writer, writeO return err } -func readStdio(streams *AttachStreams, receiveStdoutError, stdinDone chan error) error { +func readStdio(streams *define.AttachStreams, receiveStdoutError, stdinDone chan error) error { var err error select { case err = <-receiveStdoutError: diff --git a/libpod/oci_attach_unsupported.go b/libpod/oci_attach_unsupported.go index 987d2c973..3b0216e5d 100644 --- a/libpod/oci_attach_unsupported.go +++ b/libpod/oci_attach_unsupported.go @@ -9,10 +9,10 @@ import ( "k8s.io/client-go/tools/remotecommand" ) -func (c *Container) attach(streams *AttachStreams, keys string, resize <-chan remotecommand.TerminalSize, startContainer bool, started chan bool) error { +func (c *Container) attach(streams *define.AttachStreams, keys string, resize <-chan remotecommand.TerminalSize, startContainer bool, started chan bool) error { return define.ErrNotImplemented } -func (c *Container) attachToExec(streams *AttachStreams, keys string, resize <-chan remotecommand.TerminalSize, sessionID string, startFd *os.File, attachFd *os.File) error { +func (c *Container) attachToExec(streams *define.AttachStreams, keys string, resize <-chan remotecommand.TerminalSize, sessionID string, startFd *os.File, attachFd *os.File) error { return define.ErrNotImplemented } diff --git a/pkg/adapter/containers.go b/pkg/adapter/containers.go index b4ebeb944..ecadbd2f9 100644 --- a/pkg/adapter/containers.go +++ b/pkg/adapter/containers.go @@ -1004,7 +1004,7 @@ func (r *LocalRuntime) ExecContainer(ctx context.Context, cli *cliconfig.ExecVal } env = envLib.Join(env, cliEnv) - streams := new(libpod.AttachStreams) + streams := new(define.AttachStreams) streams.OutputStream = os.Stdout streams.ErrorStream = os.Stderr if cli.Interactive { diff --git a/pkg/adapter/terminal_linux.go b/pkg/adapter/terminal_linux.go index ef5a6f926..a56704be6 100644 --- a/pkg/adapter/terminal_linux.go +++ b/pkg/adapter/terminal_linux.go @@ -7,6 +7,7 @@ import ( "os" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" "github.com/pkg/errors" "github.com/sirupsen/logrus" "golang.org/x/crypto/ssh/terminal" @@ -14,7 +15,7 @@ import ( ) // ExecAttachCtr execs and attaches to a container -func ExecAttachCtr(ctx context.Context, ctr *libpod.Container, tty, privileged bool, env map[string]string, cmd []string, user, workDir string, streams *libpod.AttachStreams, preserveFDs uint, detachKeys string) (int, error) { +func ExecAttachCtr(ctx context.Context, ctr *libpod.Container, tty, privileged bool, env map[string]string, cmd []string, user, workDir string, streams *define.AttachStreams, preserveFDs uint, detachKeys string) (int, error) { resize := make(chan remotecommand.TerminalSize) haveTerminal := terminal.IsTerminal(int(os.Stdin.Fd())) @@ -69,7 +70,7 @@ func StartAttachCtr(ctx context.Context, ctr *libpod.Container, stdout, stderr, defer cancel() } - streams := new(libpod.AttachStreams) + streams := new(define.AttachStreams) streams.OutputStream = stdout streams.ErrorStream = stderr streams.InputStream = bufio.NewReader(stdin) diff --git a/pkg/adapter/terminal_unsupported.go b/pkg/adapter/terminal_unsupported.go index 3009f0a38..9067757a1 100644 --- a/pkg/adapter/terminal_unsupported.go +++ b/pkg/adapter/terminal_unsupported.go @@ -11,7 +11,7 @@ import ( ) // ExecAttachCtr execs and attaches to a container -func ExecAttachCtr(ctx context.Context, ctr *libpod.Container, tty, privileged bool, env map[string]string, cmd []string, user, workDir string, streams *libpod.AttachStreams, preserveFDs uint, detachKeys string) (int, error) { +func ExecAttachCtr(ctx context.Context, ctr *libpod.Container, tty, privileged bool, env map[string]string, cmd []string, user, workDir string, streams *define.AttachStreams, preserveFDs uint, detachKeys string) (int, error) { return -1, define.ErrNotImplemented } diff --git a/pkg/api/handlers/libpod/pods.go b/pkg/api/handlers/libpod/pods.go index e834029b2..a890169a1 100644 --- a/pkg/api/handlers/libpod/pods.go +++ b/pkg/api/handlers/libpod/pods.go @@ -73,7 +73,10 @@ func PodInspect(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) return } - utils.WriteResponse(w, http.StatusOK, podData) + report := entities.PodInspectReport{ + PodInspect: podData, + } + utils.WriteResponse(w, http.StatusOK, report) } func PodStop(w http.ResponseWriter, r *http.Request) { diff --git a/pkg/bindings/pods/pods.go b/pkg/bindings/pods/pods.go index ae87c00e9..83847614a 100644 --- a/pkg/bindings/pods/pods.go +++ b/pkg/bindings/pods/pods.go @@ -7,7 +7,6 @@ import ( "strconv" "strings" - "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/api/handlers" "github.com/containers/libpod/pkg/bindings" "github.com/containers/libpod/pkg/domain/entities" @@ -49,17 +48,19 @@ func Exists(ctx context.Context, nameOrID string) (bool, error) { } // Inspect returns low-level information about the given pod. -func Inspect(ctx context.Context, nameOrID string) (*libpod.PodInspect, error) { +func Inspect(ctx context.Context, nameOrID string) (*entities.PodInspectReport, error) { + var ( + report entities.PodInspectReport + ) conn, err := bindings.GetClient(ctx) if err != nil { return nil, err } - inspect := libpod.PodInspect{} response, err := conn.DoRequest(nil, http.MethodGet, "/pods/%s/json", nil, nameOrID) if err != nil { - return &inspect, err + return nil, err } - return &inspect, response.Process(&inspect) + return &report, response.Process(&report) } // Kill sends a SIGTERM to all the containers in a pod. The optional signal parameter diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go index 74b23cd71..cf907eb1b 100644 --- a/pkg/domain/entities/containers.go +++ b/pkg/domain/entities/containers.go @@ -2,6 +2,7 @@ package entities import ( "io" + "os" "time" "github.com/containers/libpod/libpod/define" @@ -157,3 +158,31 @@ type RestoreReport struct { type ContainerCreateReport struct { Id string } + +// AttachOptions describes the cli and other values +// needed to perform an attach +type AttachOptions struct { + DetachKeys string + Latest bool + NoStdin bool + SigProxy bool + Stdin *os.File + Stdout *os.File + Stderr *os.File +} + +// ExecOptions describes the cli values to exec into +// a container +type ExecOptions struct { + Cmd []string + DetachKeys string + Envs map[string]string + Interactive bool + Latest bool + PreserveFDs uint + Privileged bool + Streams define.AttachStreams + Tty bool + User string + WorkDir string +} diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go index 025da50f3..9bf3d51de 100644 --- a/pkg/domain/entities/engine_container.go +++ b/pkg/domain/entities/engine_container.go @@ -8,10 +8,12 @@ import ( ) type ContainerEngine interface { + ContainerAttach(ctx context.Context, nameOrId string, options AttachOptions) error ContainerCommit(ctx context.Context, nameOrId string, options CommitOptions) (*CommitReport, error) ContainerCheckpoint(ctx context.Context, namesOrIds []string, options CheckpointOptions) ([]*CheckpointReport, error) ContainerRestore(ctx context.Context, namesOrIds []string, options RestoreOptions) ([]*RestoreReport, error) ContainerCreate(ctx context.Context, s *specgen.SpecGenerator) (*ContainerCreateReport, error) + ContainerExec(ctx context.Context, nameOrId string, options ExecOptions) (int, error) ContainerExists(ctx context.Context, nameOrId string) (*BoolReport, error) ContainerInspect(ctx context.Context, namesOrIds []string, options InspectOptions) ([]*ContainerInspectReport, error) ContainerExport(ctx context.Context, nameOrId string, options ContainerExportOptions) error @@ -24,6 +26,7 @@ type ContainerEngine interface { ContainerUnpause(ctx context.Context, namesOrIds []string, options PauseUnPauseOptions) ([]*PauseUnpauseReport, error) ContainerWait(ctx context.Context, namesOrIds []string, options WaitOptions) ([]WaitReport, error) HealthCheckRun(ctx context.Context, nameOrId string, options HealthCheckOptions) (*define.HealthCheckResults, error) + PodCreate(ctx context.Context, opts PodCreateOptions) (*PodCreateReport, error) PodExists(ctx context.Context, nameOrId string) (*BoolReport, error) PodKill(ctx context.Context, namesOrIds []string, options PodKillOptions) ([]*PodKillReport, error) @@ -35,6 +38,8 @@ type ContainerEngine interface { PodStop(ctx context.Context, namesOrIds []string, options PodStopOptions) ([]*PodStopReport, error) PodTop(ctx context.Context, options PodTopOptions) (*StringSliceReport, error) PodUnpause(ctx context.Context, namesOrIds []string, options PodunpauseOptions) ([]*PodUnpauseReport, error) + PodInspect(ctx context.Context, options PodInspectOptions) (*PodInspectReport, error) + VolumeCreate(ctx context.Context, opts VolumeCreateOptions) (*IdOrNameResponse, error) VolumeInspect(ctx context.Context, namesOrIds []string, opts VolumeInspectOptions) ([]*VolumeInspectReport, error) VolumeList(ctx context.Context, opts VolumeListOptions) ([]*VolumeListReport, error) diff --git a/pkg/domain/entities/pods.go b/pkg/domain/entities/pods.go index a0b2c6cec..cd2e79961 100644 --- a/pkg/domain/entities/pods.go +++ b/pkg/domain/entities/pods.go @@ -3,6 +3,7 @@ package entities import ( "time" + "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/specgen" ) @@ -164,3 +165,14 @@ type PodPSOptions struct { Quiet bool Sort string } + +type PodInspectOptions struct { + Latest bool + + // Options for the API. + NameOrID string +} + +type PodInspectReport struct { + *libpod.PodInspect +} diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index d3d51db82..b93e7665a 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -5,6 +5,7 @@ package abi import ( "context" "io/ioutil" + "strconv" "strings" "github.com/containers/buildah" @@ -12,9 +13,9 @@ import ( "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/image" - "github.com/containers/libpod/pkg/adapter/shortcuts" "github.com/containers/libpod/pkg/checkpoint" "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/domain/infra/abi/terminal" "github.com/containers/libpod/pkg/signal" "github.com/containers/libpod/pkg/specgen" "github.com/containers/libpod/pkg/specgen/generate" @@ -64,7 +65,7 @@ func (ic *ContainerEngine) ContainerWait(ctx context.Context, namesOrIds []strin var ( responses []entities.WaitReport ) - ctrs, err := shortcuts.GetContainersByContext(false, options.Latest, namesOrIds, ic.Libpod) + ctrs, err := getContainersByContext(false, options.Latest, namesOrIds, ic.Libpod) if err != nil { return nil, err } @@ -90,7 +91,7 @@ func (ic *ContainerEngine) ContainerPause(ctx context.Context, namesOrIds []stri if options.All { ctrs, err = ic.Libpod.GetAllContainers() } else { - ctrs, err = shortcuts.GetContainersByContext(false, false, namesOrIds, ic.Libpod) + ctrs, err = getContainersByContext(false, false, namesOrIds, ic.Libpod) } if err != nil { return nil, err @@ -111,7 +112,7 @@ func (ic *ContainerEngine) ContainerUnpause(ctx context.Context, namesOrIds []st if options.All { ctrs, err = ic.Libpod.GetAllContainers() } else { - ctrs, err = shortcuts.GetContainersByContext(false, false, namesOrIds, ic.Libpod) + ctrs, err = getContainersByContext(false, false, namesOrIds, ic.Libpod) } if err != nil { return nil, err @@ -135,7 +136,7 @@ func (ic *ContainerEngine) ContainerStop(ctx context.Context, namesOrIds []strin id := strings.Split(string(content), "\n")[0] names = append(names, id) } - ctrs, err := shortcuts.GetContainersByContext(options.All, options.Latest, names, ic.Libpod) + ctrs, err := getContainersByContext(options.All, options.Latest, names, ic.Libpod) if err != nil && !(options.Ignore && errors.Cause(err) == define.ErrNoSuchCtr) { return nil, err } @@ -171,7 +172,7 @@ func (ic *ContainerEngine) ContainerKill(ctx context.Context, namesOrIds []strin if err != nil { return nil, err } - ctrs, err := shortcuts.GetContainersByContext(options.All, options.Latest, namesOrIds, ic.Libpod) + ctrs, err := getContainersByContext(options.All, options.Latest, namesOrIds, ic.Libpod) if err != nil { return nil, err } @@ -187,7 +188,7 @@ func (ic *ContainerEngine) ContainerRestart(ctx context.Context, namesOrIds []st var ( reports []*entities.RestartReport ) - ctrs, err := shortcuts.GetContainersByContext(options.All, options.Latest, namesOrIds, ic.Libpod) + ctrs, err := getContainersByContext(options.All, options.Latest, namesOrIds, ic.Libpod) if err != nil { return nil, err } @@ -229,7 +230,7 @@ func (ic *ContainerEngine) ContainerRm(ctx context.Context, namesOrIds []string, names = append(names, id) } - ctrs, err := shortcuts.GetContainersByContext(options.All, options.Latest, names, ic.Libpod) + ctrs, err := getContainersByContext(options.All, options.Latest, names, ic.Libpod) if err != nil && !(options.Ignore && errors.Cause(err) == define.ErrNoSuchCtr) { // Failed to get containers. If force is specified, get the containers ID // and evict them @@ -277,7 +278,7 @@ func (ic *ContainerEngine) ContainerRm(ctx context.Context, namesOrIds []string, func (ic *ContainerEngine) ContainerInspect(ctx context.Context, namesOrIds []string, options entities.InspectOptions) ([]*entities.ContainerInspectReport, error) { var reports []*entities.ContainerInspectReport - ctrs, err := shortcuts.GetContainersByContext(false, options.Latest, namesOrIds, ic.Libpod) + ctrs, err := getContainersByContext(false, options.Latest, namesOrIds, ic.Libpod) if err != nil { return nil, err } @@ -455,3 +456,56 @@ func (ic *ContainerEngine) ContainerCreate(ctx context.Context, s *specgen.SpecG } return &entities.ContainerCreateReport{Id: ctr.ID()}, nil } + +func (ic *ContainerEngine) ContainerAttach(ctx context.Context, nameOrId string, options entities.AttachOptions) error { + ctrs, err := getContainersByContext(false, options.Latest, []string{nameOrId}, ic.Libpod) + if err != nil { + return err + } + ctr := ctrs[0] + conState, err := ctr.State() + if err != nil { + return errors.Wrapf(err, "unable to determine state of %s", ctr.ID()) + } + if conState != define.ContainerStateRunning { + return errors.Errorf("you can only attach to running containers") + } + + // If the container is in a pod, also set to recursively start dependencies + if err := terminal.StartAttachCtr(ctx, ctr, options.Stdin, options.Stderr, options.Stdin, options.DetachKeys, options.SigProxy, false, ctr.PodID() != ""); err != nil && errors.Cause(err) != define.ErrDetach { + return errors.Wrapf(err, "error attaching to container %s", ctr.ID()) + } + return nil +} + +func (ic *ContainerEngine) ContainerExec(ctx context.Context, nameOrId string, options entities.ExecOptions) (int, error) { + ec := define.ExecErrorCodeGeneric + if options.PreserveFDs > 0 { + entries, err := ioutil.ReadDir("/proc/self/fd") + if err != nil { + return ec, errors.Wrapf(err, "unable to read /proc/self/fd") + } + + m := make(map[int]bool) + for _, e := range entries { + i, err := strconv.Atoi(e.Name()) + if err != nil { + return ec, errors.Wrapf(err, "cannot parse %s in /proc/self/fd", e.Name()) + } + m[i] = true + } + + for i := 3; i < 3+int(options.PreserveFDs); i++ { + if _, found := m[i]; !found { + return ec, errors.New("invalid --preserve-fds=N specified. Not enough FDs available") + } + } + } + ctrs, err := getContainersByContext(false, options.Latest, []string{nameOrId}, ic.Libpod) + if err != nil { + return ec, err + } + ctr := ctrs[0] + ec, err = terminal.ExecAttachCtr(ctx, ctr, options.Tty, options.Privileged, options.Envs, options.Cmd, options.User, options.WorkDir, &options.Streams, options.PreserveFDs, options.DetachKeys) + return define.TranslateExecErrorToExitCode(ec, err), err +} diff --git a/pkg/domain/infra/abi/pods.go b/pkg/domain/infra/abi/pods.go index 494a048ec..073cd8d5c 100644 --- a/pkg/domain/infra/abi/pods.go +++ b/pkg/domain/infra/abi/pods.go @@ -331,3 +331,24 @@ func (ic *ContainerEngine) PodPs(ctx context.Context, options entities.PodPSOpti } return reports, nil } + +func (ic *ContainerEngine) PodInspect(ctx context.Context, options entities.PodInspectOptions) (*entities.PodInspectReport, error) { + var ( + pod *libpod.Pod + err error + ) + // Look up the pod. + if options.Latest { + pod, err = ic.Libpod.GetLatestPod() + } else { + pod, err = ic.Libpod.LookupPod(options.NameOrID) + } + if err != nil { + return nil, errors.Wrap(err, "unable to lookup requested container") + } + inspect, err := pod.Inspect() + if err != nil { + return nil, err + } + return &entities.PodInspectReport{PodInspect: inspect}, nil +} diff --git a/pkg/domain/infra/abi/terminal/sigproxy_linux.go b/pkg/domain/infra/abi/terminal/sigproxy_linux.go new file mode 100644 index 000000000..d7f5853d8 --- /dev/null +++ b/pkg/domain/infra/abi/terminal/sigproxy_linux.go @@ -0,0 +1,47 @@ +// +build ABISupport + +package terminal + +import ( + "os" + "syscall" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/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. + // SIGURG was added because of golang 1.14 and its preemptive changes + // causing more signals to "show up". + // https://github.com/containers/libpod/issues/5483 + if s == syscall.SIGCHLD || s == syscall.SIGPIPE || s == syscall.SIGURG { + continue + } + + if err := ctr.Kill(uint(s.(syscall.Signal))); err != nil { + // If the container dies, and we find out here, + // we need to forward that one signal to + // ourselves so that it is not lost, and then + // we terminate the proxy and let the defaults + // play out. + logrus.Errorf("Error forwarding signal %d to container %s: %v", s, ctr.ID(), err) + signal.StopCatch(sigBuffer) + if err := syscall.Kill(syscall.Getpid(), s.(syscall.Signal)); err != nil { + logrus.Errorf("failed to kill pid %d", syscall.Getpid()) + } + return + } + } + }() +} diff --git a/pkg/domain/infra/abi/terminal/terminal.go b/pkg/domain/infra/abi/terminal/terminal.go new file mode 100644 index 000000000..f187bdd6b --- /dev/null +++ b/pkg/domain/infra/abi/terminal/terminal.go @@ -0,0 +1,103 @@ +// +build ABISupport + +package terminal + +import ( + "context" + "os" + "os/signal" + + lsignal "github.com/containers/libpod/pkg/signal" + "github.com/docker/docker/pkg/term" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "k8s.io/client-go/tools/remotecommand" +) + +// RawTtyFormatter ... +type RawTtyFormatter struct { +} + +// 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) + signal.Notify(sigchan, lsignal.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 +} + +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/domain/infra/abi/terminal/terminal_linux.go b/pkg/domain/infra/abi/terminal/terminal_linux.go new file mode 100644 index 000000000..664205df1 --- /dev/null +++ b/pkg/domain/infra/abi/terminal/terminal_linux.go @@ -0,0 +1,123 @@ +// +build ABISupport + +package terminal + +import ( + "bufio" + "context" + "fmt" + "os" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "golang.org/x/crypto/ssh/terminal" + "k8s.io/client-go/tools/remotecommand" +) + +// ExecAttachCtr execs and attaches to a container +func ExecAttachCtr(ctx context.Context, ctr *libpod.Container, tty, privileged bool, env map[string]string, cmd []string, user, workDir string, streams *define.AttachStreams, preserveFDs uint, detachKeys string) (int, 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 && tty { + cancel, oldTermState, err := handleTerminalAttach(ctx, resize) + if err != nil { + return -1, err + } + defer cancel() + defer func() { + if err := restoreTerminal(oldTermState); err != nil { + logrus.Errorf("unable to restore terminal: %q", err) + } + }() + } + + execConfig := new(libpod.ExecConfig) + execConfig.Command = cmd + execConfig.Terminal = tty + execConfig.Privileged = privileged + execConfig.Environment = env + execConfig.User = user + execConfig.WorkDir = workDir + execConfig.DetachKeys = &detachKeys + execConfig.PreserveFDs = preserveFDs + + return ctr.Exec(execConfig, streams, resize) +} + +// StartAttachCtr starts and (if required) attaches to a container +// if you change the signature of this function from os.File to io.Writer, it will trigger a downstream +// error. we may need to just lint disable this one. +func StartAttachCtr(ctx context.Context, ctr *libpod.Container, stdout, stderr, stdin *os.File, detachKeys string, sigProxy bool, startContainer bool, recursive bool) error { //nolint-interfacer + 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 { + cancel, oldTermState, err := handleTerminalAttach(ctx, resize) + if err != nil { + return err + } + defer func() { + if err := restoreTerminal(oldTermState); err != nil { + logrus.Errorf("unable to restore terminal: %q", err) + } + }() + defer cancel() + } + + streams := new(define.AttachStreams) + streams.OutputStream = stdout + streams.ErrorStream = stderr + streams.InputStream = bufio.NewReader(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 +} diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index ae8994cba..1e71cba2c 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -305,3 +305,11 @@ func (ic *ContainerEngine) ContainerCreate(ctx context.Context, s *specgen.SpecG } return &entities.ContainerCreateReport{Id: response.ID}, nil } + +func (ic *ContainerEngine) ContainerAttach(ctx context.Context, nameOrId string, options entities.AttachOptions) error { + return errors.New("not implemented") +} + +func (ic *ContainerEngine) ContainerExec(ctx context.Context, nameOrId string, options entities.ExecOptions) (int, error) { + return 125, errors.New("not implemented") +} diff --git a/pkg/domain/infra/tunnel/pods.go b/pkg/domain/infra/tunnel/pods.go index ad87a0a29..dad77284f 100644 --- a/pkg/domain/infra/tunnel/pods.go +++ b/pkg/domain/infra/tunnel/pods.go @@ -197,3 +197,13 @@ func (ic *ContainerEngine) PodTop(ctx context.Context, options entities.PodTopOp func (ic *ContainerEngine) PodPs(ctx context.Context, options entities.PodPSOptions) ([]*entities.ListPodsReport, error) { return pods.List(ic.ClientCxt, options.Filters) } + +func (ic *ContainerEngine) PodInspect(ctx context.Context, options entities.PodInspectOptions) (*entities.PodInspectReport, error) { + switch { + case options.Latest: + return nil, errors.New("latest is not supported") + case options.NameOrID == "": + return nil, errors.New("NameOrID must be specified") + } + return pods.Inspect(ic.ClientCxt, options.NameOrID) +} diff --git a/pkg/domain/infra/tunnel/system.go b/pkg/domain/infra/tunnel/system.go new file mode 100644 index 000000000..5bafef1fe --- /dev/null +++ b/pkg/domain/infra/tunnel/system.go @@ -0,0 +1 @@ +package tunnel diff --git a/pkg/varlinkapi/attach.go b/pkg/varlinkapi/attach.go index 94f4d653e..34f351669 100644 --- a/pkg/varlinkapi/attach.go +++ b/pkg/varlinkapi/attach.go @@ -16,7 +16,7 @@ import ( "k8s.io/client-go/tools/remotecommand" ) -func setupStreams(call iopodman.VarlinkCall) (*bufio.Reader, *bufio.Writer, *io.PipeReader, *io.PipeWriter, *libpod.AttachStreams) { +func setupStreams(call iopodman.VarlinkCall) (*bufio.Reader, *bufio.Writer, *io.PipeReader, *io.PipeWriter, *define.AttachStreams) { // These are the varlink sockets reader := call.Call.Reader @@ -30,7 +30,7 @@ func setupStreams(call iopodman.VarlinkCall) (*bufio.Reader, *bufio.Writer, *io. // TODO if runc ever starts passing stderr, we can too // stderrWriter := NewVirtWriteCloser(writer, ToStderr) - streams := libpod.AttachStreams{ + streams := define.AttachStreams{ OutputStream: stdoutWriter, InputStream: bufio.NewReader(pr), // Runc eats the error stream @@ -117,7 +117,7 @@ func (i *LibpodAPI) Attach(call iopodman.VarlinkCall, name string, detachKeys st return call.Writer.Flush() } -func attach(ctr *libpod.Container, streams *libpod.AttachStreams, detachKeys string, resize chan remotecommand.TerminalSize, errChan chan error) error { +func attach(ctr *libpod.Container, streams *define.AttachStreams, detachKeys string, resize chan remotecommand.TerminalSize, errChan chan error) error { go func() { if err := ctr.Attach(streams, detachKeys, resize); err != nil { errChan <- err @@ -127,7 +127,7 @@ func attach(ctr *libpod.Container, streams *libpod.AttachStreams, detachKeys str return attachError } -func startAndAttach(ctr *libpod.Container, streams *libpod.AttachStreams, detachKeys string, resize chan remotecommand.TerminalSize, errChan chan error) error { +func startAndAttach(ctr *libpod.Container, streams *define.AttachStreams, detachKeys string, resize chan remotecommand.TerminalSize, errChan chan error) error { var finalErr error attachChan, err := ctr.StartAndAttach(getContext(), streams, detachKeys, resize, false) if err != nil { diff --git a/vendor/github.com/containers/storage/VERSION b/vendor/github.com/containers/storage/VERSION index ec6d649be..b57fc7228 100644 --- a/vendor/github.com/containers/storage/VERSION +++ b/vendor/github.com/containers/storage/VERSION @@ -1 +1 @@ -1.18.1 +1.18.2 diff --git a/vendor/github.com/containers/storage/go.mod b/vendor/github.com/containers/storage/go.mod index b2426c9f9..f18f84f2f 100644 --- a/vendor/github.com/containers/storage/go.mod +++ b/vendor/github.com/containers/storage/go.mod @@ -16,7 +16,7 @@ require ( github.com/opencontainers/selinux v1.4.0 github.com/pkg/errors v0.9.1 github.com/pquerna/ffjson v0.0.0-20181028064349-e517b90714f7 - github.com/sirupsen/logrus v1.5.0 + github.com/sirupsen/logrus v1.4.2 github.com/stretchr/testify v1.5.1 github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2 github.com/tchap/go-patricia v2.3.0+incompatible diff --git a/vendor/modules.txt b/vendor/modules.txt index 1ab14101d..f4663b04d 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -82,7 +82,7 @@ github.com/containers/buildah/pkg/secrets github.com/containers/buildah/pkg/supplemented github.com/containers/buildah/pkg/umask github.com/containers/buildah/util -# github.com/containers/common v0.8.0 +# github.com/containers/common v0.8.1 github.com/containers/common/pkg/apparmor github.com/containers/common/pkg/capabilities github.com/containers/common/pkg/cgroupv2 @@ -150,7 +150,7 @@ github.com/containers/psgo/internal/dev github.com/containers/psgo/internal/host github.com/containers/psgo/internal/proc github.com/containers/psgo/internal/process -# github.com/containers/storage v1.18.1 +# github.com/containers/storage v1.18.2 github.com/containers/storage github.com/containers/storage/drivers github.com/containers/storage/drivers/aufs |