diff options
Diffstat (limited to 'pkg')
-rw-r--r-- | pkg/api/handlers/compat/exec.go | 48 | ||||
-rw-r--r-- | pkg/api/server/register_exec.go | 6 | ||||
-rw-r--r-- | pkg/bindings/bindings.go | 43 | ||||
-rw-r--r-- | pkg/bindings/connection.go | 80 | ||||
-rw-r--r-- | pkg/bindings/containers/exec.go | 29 | ||||
-rw-r--r-- | pkg/domain/entities/engine.go | 6 | ||||
-rw-r--r-- | pkg/domain/infra/abi/containers.go | 19 | ||||
-rw-r--r-- | pkg/domain/infra/abi/manifest.go | 2 | ||||
-rw-r--r-- | pkg/domain/infra/runtime_abi.go | 6 | ||||
-rw-r--r-- | pkg/domain/infra/runtime_abi_unsupported.go | 2 | ||||
-rw-r--r-- | pkg/domain/infra/runtime_libpod.go | 2 | ||||
-rw-r--r-- | pkg/domain/infra/runtime_proxy.go | 2 | ||||
-rw-r--r-- | pkg/domain/infra/runtime_tunnel.go | 6 | ||||
-rw-r--r-- | pkg/domain/infra/tunnel/containers.go | 32 | ||||
-rw-r--r-- | pkg/env/env.go | 13 | ||||
-rw-r--r-- | pkg/env/env_supported.go | 15 | ||||
-rw-r--r-- | pkg/env/env_unsupported.go | 8 | ||||
-rw-r--r-- | pkg/specgen/generate/container_create.go | 17 | ||||
-rw-r--r-- | pkg/util/utils_linux.go | 16 | ||||
-rw-r--r-- | pkg/util/utils_unsupported.go | 5 |
20 files changed, 286 insertions, 71 deletions
diff --git a/pkg/api/handlers/compat/exec.go b/pkg/api/handlers/compat/exec.go index 6865a3319..8f7016903 100644 --- a/pkg/api/handlers/compat/exec.go +++ b/pkg/api/handlers/compat/exec.go @@ -10,6 +10,7 @@ import ( "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/api/handlers" "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/containers/libpod/pkg/specgen/generate" "github.com/gorilla/mux" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -54,6 +55,24 @@ func ExecCreateHandler(w http.ResponseWriter, r *http.Request) { libpodConfig.Privileged = input.Privileged libpodConfig.User = input.User + // Make our exit command + storageConfig := runtime.StorageConfig() + runtimeConfig, err := runtime.GetConfig() + if err != nil { + utils.InternalServerError(w, err) + return + } + exitCommandArgs, err := generate.CreateExitCommandArgs(storageConfig, runtimeConfig, false, true, true) + if err != nil { + utils.InternalServerError(w, err) + return + } + libpodConfig.ExitCommand = exitCommandArgs + + // Run the exit command after 5 minutes, to mimic Docker's exec cleanup + // behavior. + libpodConfig.ExitCommandDelay = 5 * 60 + sessID, err := ctr.ExecCreate(libpodConfig) if err != nil { if errors.Cause(err) == define.ErrCtrStateInvalid { @@ -104,15 +123,6 @@ func ExecInspectHandler(w http.ResponseWriter, r *http.Request) { } utils.WriteResponse(w, http.StatusOK, inspectOut) - - // Only for the Compat API: we want to remove sessions that were - // stopped. This is very hacky, but should suffice for now. - if !utils.IsLibpodRequest(r) && inspectOut.CanRemove { - logrus.Infof("Pruning stale exec session %s from container %s", sessionID, sessionCtr.ID()) - if err := sessionCtr.ExecRemove(sessionID, false); err != nil && errors.Cause(err) != define.ErrNoSuchExecSession { - logrus.Errorf("Error removing stale exec session %s from container %s: %v", sessionID, sessionCtr.ID(), err) - } - } } // ExecStartHandler runs a given exec session. @@ -121,7 +131,7 @@ func ExecStartHandler(w http.ResponseWriter, r *http.Request) { sessionID := mux.Vars(r)["id"] - // TODO: We should read/support Tty and Detach from here. + // TODO: We should read/support Tty from here. bodyParams := new(handlers.ExecStartConfig) if err := json.NewDecoder(r.Body).Decode(&bodyParams); err != nil { @@ -129,11 +139,6 @@ func ExecStartHandler(w http.ResponseWriter, r *http.Request) { errors.Wrapf(err, "failed to decode parameters for %s", r.URL.String())) return } - if bodyParams.Detach { - utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, - errors.Errorf("Detached exec is not yet supported")) - return - } // TODO: Verify TTY setting against what inspect session was made with sessionCtr, err := runtime.GetExecSessionContainer(sessionID) @@ -154,6 +159,19 @@ func ExecStartHandler(w http.ResponseWriter, r *http.Request) { return } + if bodyParams.Detach { + // If we are detaching, we do NOT want to hijack. + // Instead, we perform a detached start, and return 200 if + // successful. + if err := sessionCtr.ExecStart(sessionID); err != nil { + utils.InternalServerError(w, err) + return + } + // This is a 200 despite having no content + utils.WriteResponse(w, http.StatusOK, "") + return + } + // Hijack the connection hijacker, ok := w.(http.Hijacker) if !ok { diff --git a/pkg/api/server/register_exec.go b/pkg/api/server/register_exec.go index 17181d286..af9a83496 100644 --- a/pkg/api/server/register_exec.go +++ b/pkg/api/server/register_exec.go @@ -13,7 +13,7 @@ func (s *APIServer) registerExecHandlers(r *mux.Router) error { // tags: // - exec (compat) // summary: Create an exec instance - // description: Run a command inside a running container. + // description: Create an exec session to run a command inside a running container. Exec sessions will be automatically removed 5 minutes after they exit. // parameters: // - in: path // name: name @@ -153,7 +153,7 @@ func (s *APIServer) registerExecHandlers(r *mux.Router) error { // tags: // - exec (compat) // summary: Inspect an exec instance - // description: Return low-level information about an exec instance. Stale (stopped) exec sessions will be auto-removed after inspect runs. + // description: Return low-level information about an exec instance. // parameters: // - in: path // name: id @@ -182,7 +182,7 @@ func (s *APIServer) registerExecHandlers(r *mux.Router) error { // tags: // - exec // summary: Create an exec instance - // description: Run a command inside a running container. + // description: Create an exec session to run a command inside a running container. Exec sessions will be automatically removed 5 minutes after they exit. // parameters: // - in: path // name: name diff --git a/pkg/bindings/bindings.go b/pkg/bindings/bindings.go index 7e2a444bd..da47ea713 100644 --- a/pkg/bindings/bindings.go +++ b/pkg/bindings/bindings.go @@ -9,7 +9,13 @@ package bindings import ( + "errors" + "fmt" + "io" + "os" + "github.com/blang/semver" + "golang.org/x/crypto/ssh/terminal" ) var ( @@ -25,3 +31,40 @@ var ( // _*YES*- podman will fail to run if this value is wrong APIVersion = semver.MustParse("1.0.0") ) + +// readPassword prompts for a secret and returns value input by user from stdin +// Unlike terminal.ReadPassword(), $(echo $SECRET | podman...) is supported. +// Additionally, all input after `<secret>/n` is queued to podman command. +func readPassword(prompt string) (pw []byte, err error) { + fd := int(os.Stdin.Fd()) + if terminal.IsTerminal(fd) { + fmt.Fprint(os.Stderr, prompt) + pw, err = terminal.ReadPassword(fd) + fmt.Fprintln(os.Stderr) + return + } + + var b [1]byte + for { + n, err := os.Stdin.Read(b[:]) + // terminal.ReadPassword discards any '\r', so we do the same + if n > 0 && b[0] != '\r' { + if b[0] == '\n' { + return pw, nil + } + pw = append(pw, b[0]) + // limit size, so that a wrong input won't fill up the memory + if len(pw) > 1024 { + err = errors.New("password too long, 1024 byte limit") + } + } + if err != nil { + // terminal.ReadPassword accepts EOF-terminated passwords + // if non-empty, so we do the same + if err == io.EOF && len(pw) > 0 { + err = nil + } + return pw, err + } + } +} diff --git a/pkg/bindings/connection.go b/pkg/bindings/connection.go index c26093a7f..b130b9598 100644 --- a/pkg/bindings/connection.go +++ b/pkg/bindings/connection.go @@ -13,6 +13,7 @@ import ( "path/filepath" "strconv" "strings" + "sync" "time" "github.com/blang/semver" @@ -20,6 +21,7 @@ import ( "github.com/pkg/errors" "github.com/sirupsen/logrus" "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh/agent" "k8s.io/client-go/util/homedir" ) @@ -29,6 +31,8 @@ var ( Host: "d", Path: "/v" + APIVersion.String() + "/libpod", } + passPhrase []byte + phraseSync sync.Once ) type APIResponse struct { @@ -61,6 +65,10 @@ func JoinURL(elements ...string) string { return "/" + strings.Join(elements, "/") } +func NewConnection(ctx context.Context, uri string) (context.Context, error) { + return NewConnectionWithIdentity(ctx, uri, "") +} + // NewConnection takes a URI as a string and returns a context with the // Connection embedded as a value. This context needs to be passed to each // endpoint to work correctly. @@ -69,23 +77,28 @@ func JoinURL(elements ...string) string { // For example tcp://localhost:<port> // or unix:///run/podman/podman.sock // or ssh://<user>@<host>[:port]/run/podman/podman.sock?secure=True -func NewConnection(ctx context.Context, uri string, identity ...string) (context.Context, error) { +func NewConnectionWithIdentity(ctx context.Context, uri string, passPhrase string, identities ...string) (context.Context, error) { var ( err error secure bool ) - if v, found := os.LookupEnv("PODMAN_HOST"); found { + if v, found := os.LookupEnv("CONTAINER_HOST"); found && uri == "" { uri = v } - if v, found := os.LookupEnv("PODMAN_SSHKEY"); found { - identity = []string{v} + if v, found := os.LookupEnv("CONTAINER_SSHKEY"); found && len(identities) == 0 { + identities = append(identities, v) + } + + if v, found := os.LookupEnv("CONTAINER_PASSPHRASE"); found && passPhrase == "" { + passPhrase = v } _url, err := url.Parse(uri) if err != nil { - return nil, errors.Wrapf(err, "Value of PODMAN_HOST is not a valid url: %s", uri) + return nil, errors.Wrapf(err, "Value of CONTAINER_HOST is not a valid url: %s", uri) } + // TODO Fill in missing defaults for _url... // Now we setup the http Client to use the connection above var connection Connection @@ -95,7 +108,7 @@ func NewConnection(ctx context.Context, uri string, identity ...string) (context if err != nil { secure = false } - connection, err = sshClient(_url, identity[0], secure) + connection, err = sshClient(_url, secure, passPhrase, identities...) case "unix": if !strings.HasPrefix(uri, "unix:///") { // autofix unix://path_element vs unix:///path_element @@ -172,10 +185,31 @@ func pingNewConnection(ctx context.Context) error { return errors.Errorf("ping response was %q", response.StatusCode) } -func sshClient(_url *url.URL, identity string, secure bool) (Connection, error) { - auth, err := publicKey(identity) - if err != nil { - return Connection{}, errors.Wrapf(err, "Failed to parse identity %s: %v\n", _url.String(), identity) +func sshClient(_url *url.URL, secure bool, passPhrase string, identities ...string) (Connection, error) { + var authMethods []ssh.AuthMethod + + for _, i := range identities { + auth, err := publicKey(i, []byte(passPhrase)) + if err != nil { + fmt.Fprint(os.Stderr, errors.Wrapf(err, "failed to parse identity %q", i).Error()+"\n") + continue + } + authMethods = append(authMethods, auth) + } + + if sock, found := os.LookupEnv("SSH_AUTH_SOCK"); found { + logrus.Debugf("Found SSH_AUTH_SOCK %q, ssh-agent signer enabled", sock) + + c, err := net.Dial("unix", sock) + if err != nil { + return Connection{}, err + } + a := agent.NewClient(c) + authMethods = append(authMethods, ssh.PublicKeysCallback(a.Signers)) + } + + if pw, found := _url.User.Password(); found { + authMethods = append(authMethods, ssh.Password(pw)) } callback := ssh.InsecureIgnoreHostKey() @@ -195,7 +229,7 @@ func sshClient(_url *url.URL, identity string, secure bool) (Connection, error) net.JoinHostPort(_url.Hostname(), port), &ssh.ClientConfig{ User: _url.User.Username(), - Auth: []ssh.AuthMethod{auth}, + Auth: authMethods, HostKeyCallback: callback, HostKeyAlgorithms: []string{ ssh.KeyAlgoRSA, @@ -307,7 +341,7 @@ func (h *APIResponse) IsServerError() bool { return h.Response.StatusCode/100 == 5 } -func publicKey(path string) (ssh.AuthMethod, error) { +func publicKey(path string, passphrase []byte) (ssh.AuthMethod, error) { key, err := ioutil.ReadFile(path) if err != nil { return nil, err @@ -315,12 +349,30 @@ func publicKey(path string) (ssh.AuthMethod, error) { signer, err := ssh.ParsePrivateKey(key) if err != nil { - return nil, err + if _, ok := err.(*ssh.PassphraseMissingError); !ok { + return nil, err + } + if len(passphrase) == 0 { + phraseSync.Do(promptPassphrase) + passphrase = passPhrase + } + signer, err = ssh.ParsePrivateKeyWithPassphrase(key, passphrase) + if err != nil { + return nil, err + } } - return ssh.PublicKeys(signer), nil } +func promptPassphrase() { + phrase, err := readPassword("Key Passphrase: ") + if err != nil { + passPhrase = []byte{} + return + } + passPhrase = phrase +} + func hostKey(host string) ssh.PublicKey { // parse OpenSSH known_hosts file // ssh or use ssh-keyscan to get initial key diff --git a/pkg/bindings/containers/exec.go b/pkg/bindings/containers/exec.go index 2aeeae1f8..73cfb5079 100644 --- a/pkg/bindings/containers/exec.go +++ b/pkg/bindings/containers/exec.go @@ -1,6 +1,7 @@ package containers import ( + "bytes" "context" "net/http" "strings" @@ -69,3 +70,31 @@ func ExecInspect(ctx context.Context, sessionID string) (*define.InspectExecSess return respStruct, nil } + +// ExecStart starts (but does not attach to) a given exec session. +func ExecStart(ctx context.Context, sessionID string) error { + conn, err := bindings.GetClient(ctx) + if err != nil { + return err + } + + logrus.Debugf("Starting exec session ID %q", sessionID) + + // We force Detach to true + body := struct { + Detach bool `json:"Detach"` + }{ + Detach: true, + } + bodyJSON, err := json.Marshal(body) + if err != nil { + return err + } + + resp, err := conn.DoRequest(bytes.NewReader(bodyJSON), http.MethodPost, "/exec/%s/start", nil, nil, sessionID) + if err != nil { + return err + } + + return resp.Process(nil) +} diff --git a/pkg/domain/entities/engine.go b/pkg/domain/entities/engine.go index db58befa5..b2bef0eea 100644 --- a/pkg/domain/entities/engine.go +++ b/pkg/domain/entities/engine.go @@ -43,14 +43,16 @@ type PodmanConfig struct { EngineMode EngineMode // ABI or Tunneling mode Identities []string // ssh identities for connecting to server MaxWorks int // maximum number of parallel threads + PassPhrase string // ssh passphrase for identity for connecting to server RegistriesConf string // allows for specifying a custom registries.conf + Remote bool // Connection to Podman API Service will use RESTful API RuntimePath string // --runtime flag will set Engine.RuntimePath + Span opentracing.Span // tracing object SpanCloser io.Closer // Close() for tracing object SpanCtx context.Context // context to use when tracing - Span opentracing.Span // tracing object Syslog bool // write to StdOut and Syslog, not supported when tunneling Trace bool // Hidden: Trace execution - Uri string // URI to API Service + Uri string // URI to RESTful API Service Runroot string StorageDriver string diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index e982c7c11..19232eff1 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -44,8 +44,10 @@ func getContainersAndInputByContext(all, latest bool, names []string, runtime *l ctrs, err = runtime.GetAllContainers() case latest: ctr, err = runtime.GetLatestContainer() - rawInput = append(rawInput, ctr.ID()) - ctrs = append(ctrs, ctr) + if err == nil { + rawInput = append(rawInput, ctr.ID()) + ctrs = append(ctrs, ctr) + } default: for _, n := range names { ctr, e := runtime.LookupContainer(n) @@ -177,6 +179,12 @@ func (ic *ContainerEngine) ContainerStop(ctx context.Context, namesOrIds []strin report.Err = err reports = append(reports, &report) continue + } else if err := con.Cleanup(ctx); err != nil { + // Only if no error, proceed to cleanup to ensure all + // mounts are removed before we exit. + report.Err = err + reports = append(reports, &report) + continue } reports = append(reports, &report) } @@ -613,12 +621,11 @@ func (ic *ContainerEngine) ContainerExecDetached(ctx context.Context, nameOrId s if err != nil { return "", errors.Wrapf(err, "error retrieving Libpod configuration to build exec exit command") } - podmanPath, err := os.Executable() + // TODO: Add some ability to toggle syslog + exitCommandArgs, err := generate.CreateExitCommandArgs(storageConfig, runtimeConfig, false, true, true) if err != nil { - return "", errors.Wrapf(err, "error retrieving executable to build exec exit command") + return "", errors.Wrapf(err, "error constructing exit command for exec session") } - // TODO: Add some ability to toggle syslog - exitCommandArgs := generate.CreateExitCommandArgs(storageConfig, runtimeConfig, podmanPath, false, true, true) execConfig.ExitCommand = exitCommandArgs // Create and start the exec session diff --git a/pkg/domain/infra/abi/manifest.go b/pkg/domain/infra/abi/manifest.go index 6e311dec7..a2b5fc0fc 100644 --- a/pkg/domain/infra/abi/manifest.go +++ b/pkg/domain/infra/abi/manifest.go @@ -1,4 +1,4 @@ -// +build ABISupport +// +build !remote package abi diff --git a/pkg/domain/infra/runtime_abi.go b/pkg/domain/infra/runtime_abi.go index 67c1cd534..0a82b9a6b 100644 --- a/pkg/domain/infra/runtime_abi.go +++ b/pkg/domain/infra/runtime_abi.go @@ -1,4 +1,4 @@ -// +build ABISupport +// +build !remote package infra @@ -20,7 +20,7 @@ func NewContainerEngine(facts *entities.PodmanConfig) (entities.ContainerEngine, r, err := NewLibpodRuntime(facts.FlagSet, facts) return r, err case entities.TunnelMode: - ctx, err := bindings.NewConnection(context.Background(), facts.Uri, facts.Identities...) + ctx, err := bindings.NewConnectionWithIdentity(context.Background(), facts.Uri, facts.PassPhrase, facts.Identities...) return &tunnel.ContainerEngine{ClientCxt: ctx}, err } return nil, fmt.Errorf("runtime mode '%v' is not supported", facts.EngineMode) @@ -33,7 +33,7 @@ func NewImageEngine(facts *entities.PodmanConfig) (entities.ImageEngine, error) r, err := NewLibpodImageRuntime(facts.FlagSet, facts) return r, err case entities.TunnelMode: - ctx, err := bindings.NewConnection(context.Background(), facts.Uri, facts.Identities...) + ctx, err := bindings.NewConnectionWithIdentity(context.Background(), facts.Uri, facts.PassPhrase, facts.Identities...) return &tunnel.ImageEngine{ClientCxt: ctx}, err } return nil, fmt.Errorf("runtime mode '%v' is not supported", facts.EngineMode) diff --git a/pkg/domain/infra/runtime_abi_unsupported.go b/pkg/domain/infra/runtime_abi_unsupported.go index c4e25e990..3d7d457fc 100644 --- a/pkg/domain/infra/runtime_abi_unsupported.go +++ b/pkg/domain/infra/runtime_abi_unsupported.go @@ -1,4 +1,4 @@ -// +build !ABISupport +// +build remote package infra diff --git a/pkg/domain/infra/runtime_libpod.go b/pkg/domain/infra/runtime_libpod.go index a57eadc63..2f2b0f90f 100644 --- a/pkg/domain/infra/runtime_libpod.go +++ b/pkg/domain/infra/runtime_libpod.go @@ -1,4 +1,4 @@ -// +build ABISupport +// +build !remote package infra diff --git a/pkg/domain/infra/runtime_proxy.go b/pkg/domain/infra/runtime_proxy.go index e7002e20f..fed9b1008 100644 --- a/pkg/domain/infra/runtime_proxy.go +++ b/pkg/domain/infra/runtime_proxy.go @@ -1,4 +1,4 @@ -// +build ABISupport +// +build !remote package infra diff --git a/pkg/domain/infra/runtime_tunnel.go b/pkg/domain/infra/runtime_tunnel.go index 752218aaf..bba7d2c0c 100644 --- a/pkg/domain/infra/runtime_tunnel.go +++ b/pkg/domain/infra/runtime_tunnel.go @@ -1,4 +1,4 @@ -// +build !ABISupport +// +build remote package infra @@ -16,7 +16,7 @@ func NewContainerEngine(facts *entities.PodmanConfig) (entities.ContainerEngine, case entities.ABIMode: return nil, fmt.Errorf("direct runtime not supported") case entities.TunnelMode: - ctx, err := bindings.NewConnection(context.Background(), facts.Uri, facts.Identities...) + ctx, err := bindings.NewConnectionWithIdentity(context.Background(), facts.Uri, facts.PassPhrase, facts.Identities...) return &tunnel.ContainerEngine{ClientCxt: ctx}, err } return nil, fmt.Errorf("runtime mode '%v' is not supported", facts.EngineMode) @@ -28,7 +28,7 @@ func NewImageEngine(facts *entities.PodmanConfig) (entities.ImageEngine, error) case entities.ABIMode: return nil, fmt.Errorf("direct image runtime not supported") case entities.TunnelMode: - ctx, err := bindings.NewConnection(context.Background(), facts.Uri, facts.Identities...) + ctx, err := bindings.NewConnectionWithIdentity(context.Background(), facts.Uri, facts.PassPhrase, facts.Identities...) return &tunnel.ImageEngine{ClientCxt: ctx}, err } return nil, fmt.Errorf("runtime mode '%v' is not supported", facts.EngineMode) diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index e1c859e7c..97b98eec2 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "io" + "io/ioutil" "os" "strconv" "strings" @@ -162,6 +163,14 @@ func (ic *ContainerEngine) ContainerRm(ctx context.Context, namesOrIds []string, var ( reports []*entities.RmReport ) + for _, cidFile := range options.CIDFiles { + content, err := ioutil.ReadFile(cidFile) + if err != nil { + return nil, errors.Wrapf(err, "error reading CIDFile %s", cidFile) + } + id := strings.Split(string(content), "\n")[0] + namesOrIds = append(namesOrIds, id) + } ctrs, err := getContainersByContext(ic.ClientCxt, options.All, namesOrIds) if err != nil { return nil, err @@ -376,7 +385,7 @@ func (ic *ContainerEngine) ContainerAttach(ctx context.Context, nameOrId string, return containers.Attach(ic.ClientCxt, nameOrId, &options.DetachKeys, nil, bindings.PTrue, options.Stdin, options.Stdout, options.Stderr, nil) } -func (ic *ContainerEngine) ContainerExec(ctx context.Context, nameOrId string, options entities.ExecOptions, streams define.AttachStreams) (int, error) { +func makeExecConfig(options entities.ExecOptions) *handlers.ExecCreateConfig { env := []string{} for k, v := range options.Envs { env = append(env, fmt.Sprintf("%s=%s", k, v)) @@ -395,6 +404,12 @@ func (ic *ContainerEngine) ContainerExec(ctx context.Context, nameOrId string, o createConfig.WorkingDir = options.WorkDir createConfig.Cmd = options.Cmd + return createConfig +} + +func (ic *ContainerEngine) ContainerExec(ctx context.Context, nameOrId string, options entities.ExecOptions, streams define.AttachStreams) (int, error) { + createConfig := makeExecConfig(options) + sessionID, err := containers.ExecCreate(ic.ClientCxt, nameOrId, createConfig) if err != nil { return 125, err @@ -412,8 +427,19 @@ func (ic *ContainerEngine) ContainerExec(ctx context.Context, nameOrId string, o return inspectOut.ExitCode, nil } -func (ic *ContainerEngine) ContainerExecDetached(ctx context.Context, nameOrID string, options entities.ExecOptions) (string, error) { - return "", errors.New("not implemented") +func (ic *ContainerEngine) ContainerExecDetached(ctx context.Context, nameOrId string, options entities.ExecOptions) (string, error) { + createConfig := makeExecConfig(options) + + sessionID, err := containers.ExecCreate(ic.ClientCxt, nameOrId, createConfig) + if err != nil { + return "", err + } + + if err := containers.ExecStart(ic.ClientCxt, sessionID); err != nil { + return "", err + } + + return sessionID, nil } func startAndAttach(ic *ContainerEngine, name string, detachKeys *string, input, output, errput *os.File) error { //nolint diff --git a/pkg/env/env.go b/pkg/env/env.go index c6a1a0d28..a16007a50 100644 --- a/pkg/env/env.go +++ b/pkg/env/env.go @@ -20,18 +20,6 @@ var DefaultEnvVariables = map[string]string{ const whiteSpaces = " \t" -// ParseSlice parses the specified slice and transforms it into an environment -// map. -func ParseSlice(s []string) (map[string]string, error) { - env := make(map[string]string, len(s)) - for _, e := range s { - if err := parseEnv(env, e); err != nil { - return nil, err - } - } - return env, nil -} - // Slice transforms the specified map of environment variables into a // slice. If a value is non-empty, the key and value are joined with '='. func Slice(m map[string]string) []string { @@ -96,7 +84,6 @@ func parseEnv(env map[string]string, line string) error { if data[0] == "" { return errors.Errorf("invalid environment variable: %q", line) } - // trim the front of a variable, but nothing else name := strings.TrimLeft(data[0], whiteSpaces) if strings.ContainsAny(name, whiteSpaces) { diff --git a/pkg/env/env_supported.go b/pkg/env/env_supported.go new file mode 100644 index 000000000..8be9f9592 --- /dev/null +++ b/pkg/env/env_supported.go @@ -0,0 +1,15 @@ +// +build linux darwin + +package env + +// ParseSlice parses the specified slice and transforms it into an environment +// map. +func ParseSlice(s []string) (map[string]string, error) { + env := make(map[string]string, len(s)) + for _, e := range s { + if err := parseEnv(env, e); err != nil { + return nil, err + } + } + return env, nil +} diff --git a/pkg/env/env_unsupported.go b/pkg/env/env_unsupported.go new file mode 100644 index 000000000..a71c2956d --- /dev/null +++ b/pkg/env/env_unsupported.go @@ -0,0 +1,8 @@ +// +build !linux,!darwin + +package env + +func ParseSlice(s []string) (map[string]string, error) { + m := make(map[string]string) + return m, nil +} diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index ffd7fd4dd..7ddfed339 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -107,12 +107,12 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener } options = append(options, opts...) - podmanPath, err := os.Executable() + // TODO: Enable syslog support - we'll need to put this in SpecGen. + exitCommandArgs, err := CreateExitCommandArgs(rt.StorageConfig(), rtc, false, s.Remove, false) if err != nil { return nil, err } - // TODO: Enable syslog support - we'll need to put this in SpecGen. - options = append(options, libpod.WithExitCommand(CreateExitCommandArgs(rt.StorageConfig(), rtc, podmanPath, false, s.Remove, false))) + options = append(options, libpod.WithExitCommand(exitCommandArgs)) runtimeSpec, err := SpecGenToOCI(ctx, s, rt, rtc, newImage, finalMounts) if err != nil { @@ -229,13 +229,18 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen. return options, nil } -func CreateExitCommandArgs(storageConfig storage.StoreOptions, config *config.Config, podmanPath string, syslog, rm bool, exec bool) []string { +func CreateExitCommandArgs(storageConfig storage.StoreOptions, config *config.Config, syslog, rm, exec bool) ([]string, error) { // We need a cleanup process for containers in the current model. // But we can't assume that the caller is Podman - it could be another // user of the API. // As such, provide a way to specify a path to Podman, so we can // still invoke a cleanup process. + podmanPath, err := os.Executable() + if err != nil { + return nil, err + } + command := []string{podmanPath, "--root", storageConfig.GraphRoot, "--runroot", storageConfig.RunRoot, @@ -265,9 +270,11 @@ func CreateExitCommandArgs(storageConfig storage.StoreOptions, config *config.Co command = append(command, "--rm") } + // This has to be absolutely last, to ensure that the exec session ID + // will be added after it by Libpod. if exec { command = append(command, "--exec") } - return command + return command, nil } diff --git a/pkg/util/utils_linux.go b/pkg/util/utils_linux.go index 288137ca5..5e4dc4a51 100644 --- a/pkg/util/utils_linux.go +++ b/pkg/util/utils_linux.go @@ -6,6 +6,7 @@ import ( "path/filepath" "syscall" + "github.com/containers/libpod/pkg/rootless" "github.com/containers/psgo" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -52,3 +53,18 @@ func FindDeviceNodes() (map[string]string, error) { return nodes, nil } + +// CheckRootlessUIDRange checks the uid within the rootless container is in the range from /etc/subuid +func CheckRootlessUIDRange(uid int) error { + uids, _, err := rootless.GetConfiguredMappings() + if err != nil { + return err + } + for _, u := range uids { + // add 1 since we also map in the user's own UID + if uid > u.Size+1 { + return errors.Errorf("requested user's UID %d is too large for the rootless user namespace", uid) + } + } + return nil +} diff --git a/pkg/util/utils_unsupported.go b/pkg/util/utils_unsupported.go index 62805d7c8..f8d5a37c1 100644 --- a/pkg/util/utils_unsupported.go +++ b/pkg/util/utils_unsupported.go @@ -10,3 +10,8 @@ import ( func FindDeviceNodes() (map[string]string, error) { return nil, errors.Errorf("not supported on non-Linux OSes") } + +// CheckRootlessUIDRange is not implemented anywhere except Linux. +func CheckRootlessUIDRange(uid int) error { + return nil +} |