mirror of
https://github.com/containers/podman.git
synced 2026-07-10 07:15:05 -04:00
This was implemented by containers/netavark #1369; this commit completes the process by wiring it into Podman. We now respect the CLI order for configured networks - if a user passes `--net net1,net2` we guarantee that net1 will be configured before net2. For containers created before this patch, we don't retain enough information to configure networks in CLI order, so we use alphabetical order instead to still guarantee consistency. No breaking API changes have been made, but we do add a new field to supplement the existing map to (optionally) provide ordering information. The Podman CLI will always pass this. Existing applications that do not will, again, receive] deterministic ordering based on an alphabetical sort of network names. This requires the latest version of Netavark to work properly. Signed-off-by: Matthew Heon <matthew.heon@pm.me>
225 lines
8.5 KiB
Go
225 lines
8.5 KiB
Go
// Copyright 2014 go-dockerclient authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package docker
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
)
|
|
|
|
// Exec is the type representing a `docker exec` instance and containing the
|
|
// instance ID
|
|
type Exec struct {
|
|
ID string `json:"Id,omitempty" yaml:"Id,omitempty"`
|
|
}
|
|
|
|
// CreateExecOptions specify parameters to the CreateExecContainer function.
|
|
//
|
|
// See https://goo.gl/60TeBP for more details
|
|
type CreateExecOptions struct {
|
|
Env []string `json:"Env,omitempty" yaml:"Env,omitempty" toml:"Env,omitempty"`
|
|
Cmd []string `json:"Cmd,omitempty" yaml:"Cmd,omitempty" toml:"Cmd,omitempty"`
|
|
Container string `json:"Container,omitempty" yaml:"Container,omitempty" toml:"Container,omitempty"`
|
|
User string `json:"User,omitempty" yaml:"User,omitempty" toml:"User,omitempty"`
|
|
WorkingDir string `json:"WorkingDir,omitempty" yaml:"WorkingDir,omitempty" toml:"WorkingDir,omitempty"`
|
|
DetachKeys string `json:"DetachKeys,omitempty" yaml:"DetachKeys,omitempty" toml:"DetachKeys,omitempty"`
|
|
Context context.Context `json:"-"`
|
|
AttachStdin bool `json:"AttachStdin,omitempty" yaml:"AttachStdin,omitempty" toml:"AttachStdin,omitempty"`
|
|
AttachStdout bool `json:"AttachStdout,omitempty" yaml:"AttachStdout,omitempty" toml:"AttachStdout,omitempty"`
|
|
AttachStderr bool `json:"AttachStderr,omitempty" yaml:"AttachStderr,omitempty" toml:"AttachStderr,omitempty"`
|
|
Tty bool `json:"Tty,omitempty" yaml:"Tty,omitempty" toml:"Tty,omitempty"`
|
|
Privileged bool `json:"Privileged,omitempty" yaml:"Privileged,omitempty" toml:"Privileged,omitempty"`
|
|
}
|
|
|
|
// CreateExec sets up an exec instance in a running container `id`, returning the exec
|
|
// instance, or an error in case of failure.
|
|
//
|
|
// See https://goo.gl/60TeBP for more details
|
|
func (c *Client) CreateExec(opts CreateExecOptions) (*Exec, error) {
|
|
if c.serverAPIVersion == nil {
|
|
c.checkAPIVersion()
|
|
}
|
|
if len(opts.Env) > 0 && c.serverAPIVersion.LessThan(apiVersion125) {
|
|
return nil, errors.New("exec configuration Env is only supported in API#1.25 and above")
|
|
}
|
|
if len(opts.WorkingDir) > 0 && c.serverAPIVersion.LessThan(apiVersion135) {
|
|
return nil, errors.New("exec configuration WorkingDir is only supported in API#1.35 and above")
|
|
}
|
|
path := fmt.Sprintf("/containers/%s/exec", opts.Container)
|
|
resp, err := c.do(http.MethodPost, path, doOptions{data: opts, context: opts.Context})
|
|
if err != nil {
|
|
var e *Error
|
|
if errors.As(err, &e) && e.Status == http.StatusNotFound {
|
|
return nil, &NoSuchContainer{ID: opts.Container}
|
|
}
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
var exec Exec
|
|
if err := json.NewDecoder(resp.Body).Decode(&exec); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &exec, nil
|
|
}
|
|
|
|
// StartExecOptions specify parameters to the StartExecContainer function.
|
|
//
|
|
// See https://goo.gl/1EeDWi for more details
|
|
type StartExecOptions struct {
|
|
InputStream io.Reader `qs:"-"`
|
|
OutputStream io.Writer `qs:"-"`
|
|
ErrorStream io.Writer `qs:"-"`
|
|
|
|
Detach bool `json:"Detach,omitempty" yaml:"Detach,omitempty" toml:"Detach,omitempty"`
|
|
Tty bool `json:"Tty,omitempty" yaml:"Tty,omitempty" toml:"Tty,omitempty"`
|
|
|
|
// Use raw terminal? Usually true when the container contains a TTY.
|
|
RawTerminal bool `qs:"-"`
|
|
|
|
// If set, after a successful connect, a sentinel will be sent and then the
|
|
// client will block on receive before continuing.
|
|
//
|
|
// It must be an unbuffered channel. Using a buffered channel can lead
|
|
// to unexpected behavior.
|
|
Success chan struct{} `json:"-"`
|
|
|
|
Context context.Context `json:"-"`
|
|
}
|
|
|
|
// StartExec starts a previously set up exec instance id. If opts.Detach is
|
|
// true, it returns after starting the exec command. Otherwise, it sets up an
|
|
// interactive session with the exec command.
|
|
//
|
|
// See https://goo.gl/1EeDWi for more details
|
|
func (c *Client) StartExec(id string, opts StartExecOptions) error {
|
|
cw, err := c.StartExecNonBlocking(id, opts)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if cw != nil {
|
|
return cw.Wait()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// StartExecNonBlocking starts a previously set up exec instance id. If opts.Detach is
|
|
// true, it returns after starting the exec command. Otherwise, it sets up an
|
|
// interactive session with the exec command.
|
|
//
|
|
// See https://goo.gl/1EeDWi for more details
|
|
func (c *Client) StartExecNonBlocking(id string, opts StartExecOptions) (CloseWaiter, error) {
|
|
if id == "" {
|
|
return nil, &NoSuchExec{ID: id}
|
|
}
|
|
|
|
path := fmt.Sprintf("/exec/%s/start", id)
|
|
|
|
if opts.Detach {
|
|
resp, err := c.do(http.MethodPost, path, doOptions{data: opts, context: opts.Context})
|
|
if err != nil {
|
|
var e *Error
|
|
if errors.As(err, &e) && e.Status == http.StatusNotFound {
|
|
return nil, &NoSuchExec{ID: id}
|
|
}
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
return nil, nil
|
|
}
|
|
|
|
return c.hijack(http.MethodPost, path, hijackOptions{
|
|
success: opts.Success,
|
|
setRawTerminal: opts.RawTerminal,
|
|
in: opts.InputStream,
|
|
stdout: opts.OutputStream,
|
|
stderr: opts.ErrorStream,
|
|
data: opts,
|
|
})
|
|
}
|
|
|
|
// ResizeExecTTY resizes the tty session used by the exec command id. This API
|
|
// is valid only if Tty was specified as part of creating and starting the exec
|
|
// command.
|
|
//
|
|
// See https://goo.gl/Mo5bxx for more details
|
|
func (c *Client) ResizeExecTTY(id string, height, width int) error {
|
|
params := make(url.Values)
|
|
params.Set("h", strconv.Itoa(height))
|
|
params.Set("w", strconv.Itoa(width))
|
|
|
|
path := fmt.Sprintf("/exec/%s/resize?%s", id, params.Encode())
|
|
resp, err := c.do(http.MethodPost, path, doOptions{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resp.Body.Close()
|
|
return nil
|
|
}
|
|
|
|
// ExecProcessConfig is a type describing the command associated to a Exec
|
|
// instance. It's used in the ExecInspect type.
|
|
type ExecProcessConfig struct {
|
|
User string `json:"user,omitempty" yaml:"user,omitempty" toml:"user,omitempty"`
|
|
Privileged bool `json:"privileged,omitempty" yaml:"privileged,omitempty" toml:"privileged,omitempty"`
|
|
Tty bool `json:"tty,omitempty" yaml:"tty,omitempty" toml:"tty,omitempty"`
|
|
EntryPoint string `json:"entrypoint,omitempty" yaml:"entrypoint,omitempty" toml:"entrypoint,omitempty"`
|
|
Arguments []string `json:"arguments,omitempty" yaml:"arguments,omitempty" toml:"arguments,omitempty"`
|
|
}
|
|
|
|
// ExecInspect is a type with details about a exec instance, including the
|
|
// exit code if the command has finished running. It's returned by a api
|
|
// call to /exec/(id)/json
|
|
//
|
|
// See https://goo.gl/ctMUiW for more details
|
|
type ExecInspect struct {
|
|
ID string `json:"ID,omitempty" yaml:"ID,omitempty" toml:"ID,omitempty"`
|
|
ExitCode int `json:"ExitCode,omitempty" yaml:"ExitCode,omitempty" toml:"ExitCode,omitempty"`
|
|
ProcessConfig ExecProcessConfig `json:"ProcessConfig" yaml:"ProcessConfig,omitempty" toml:"ProcessConfig,omitempty"`
|
|
ContainerID string `json:"ContainerID,omitempty" yaml:"ContainerID,omitempty" toml:"ContainerID,omitempty"`
|
|
DetachKeys string `json:"DetachKeys,omitempty" yaml:"DetachKeys,omitempty" toml:"DetachKeys,omitempty"`
|
|
Running bool `json:"Running,omitempty" yaml:"Running,omitempty" toml:"Running,omitempty"`
|
|
OpenStdin bool `json:"OpenStdin,omitempty" yaml:"OpenStdin,omitempty" toml:"OpenStdin,omitempty"`
|
|
OpenStderr bool `json:"OpenStderr,omitempty" yaml:"OpenStderr,omitempty" toml:"OpenStderr,omitempty"`
|
|
OpenStdout bool `json:"OpenStdout,omitempty" yaml:"OpenStdout,omitempty" toml:"OpenStdout,omitempty"`
|
|
CanRemove bool `json:"CanRemove,omitempty" yaml:"CanRemove,omitempty" toml:"CanRemove,omitempty"`
|
|
}
|
|
|
|
// InspectExec returns low-level information about the exec command id.
|
|
//
|
|
// See https://goo.gl/ctMUiW for more details
|
|
func (c *Client) InspectExec(id string) (*ExecInspect, error) {
|
|
path := fmt.Sprintf("/exec/%s/json", id)
|
|
resp, err := c.do(http.MethodGet, path, doOptions{})
|
|
if err != nil {
|
|
var e *Error
|
|
if errors.As(err, &e) && e.Status == http.StatusNotFound {
|
|
return nil, &NoSuchExec{ID: id}
|
|
}
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
var exec ExecInspect
|
|
if err := json.NewDecoder(resp.Body).Decode(&exec); err != nil {
|
|
return nil, err
|
|
}
|
|
return &exec, nil
|
|
}
|
|
|
|
// NoSuchExec is the error returned when a given exec instance does not exist.
|
|
type NoSuchExec struct {
|
|
ID string
|
|
}
|
|
|
|
func (err *NoSuchExec) Error() string {
|
|
return "No such exec instance: " + err.ID
|
|
}
|