summaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
Diffstat (limited to 'pkg')
-rw-r--r--pkg/api/handlers/compat/exec.go48
-rw-r--r--pkg/api/server/register_exec.go6
-rw-r--r--pkg/bindings/bindings.go43
-rw-r--r--pkg/bindings/connection.go80
-rw-r--r--pkg/bindings/containers/exec.go29
-rw-r--r--pkg/domain/entities/engine.go6
-rw-r--r--pkg/domain/infra/abi/containers.go19
-rw-r--r--pkg/domain/infra/abi/manifest.go2
-rw-r--r--pkg/domain/infra/runtime_abi.go6
-rw-r--r--pkg/domain/infra/runtime_abi_unsupported.go2
-rw-r--r--pkg/domain/infra/runtime_libpod.go2
-rw-r--r--pkg/domain/infra/runtime_proxy.go2
-rw-r--r--pkg/domain/infra/runtime_tunnel.go6
-rw-r--r--pkg/domain/infra/tunnel/containers.go32
-rw-r--r--pkg/env/env.go13
-rw-r--r--pkg/env/env_supported.go15
-rw-r--r--pkg/env/env_unsupported.go8
-rw-r--r--pkg/specgen/generate/container_create.go17
-rw-r--r--pkg/util/utils_linux.go16
-rw-r--r--pkg/util/utils_unsupported.go5
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
+}