summaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
Diffstat (limited to 'pkg')
-rw-r--r--pkg/api/handlers/compat/images_build.go19
-rw-r--r--pkg/bindings/connection.go3
-rw-r--r--pkg/bindings/containers/types.go1
-rw-r--r--pkg/bindings/containers/types_restore_options.go16
-rw-r--r--pkg/bindings/images/build.go38
-rw-r--r--pkg/checkpoint/checkpoint_restore.go96
-rw-r--r--pkg/checkpoint/crutils/checkpoint_restore_utils.go11
-rw-r--r--pkg/criu/criu.go10
-rw-r--r--pkg/domain/entities/containers.go1
-rw-r--r--pkg/domain/filters/containers.go30
-rw-r--r--pkg/domain/infra/abi/containers.go1
-rw-r--r--pkg/domain/infra/runtime_abi.go1
-rw-r--r--pkg/rootless/rootless_linux.c25
-rw-r--r--pkg/rootless/rootless_linux.go20
14 files changed, 233 insertions, 39 deletions
diff --git a/pkg/api/handlers/compat/images_build.go b/pkg/api/handlers/compat/images_build.go
index 64805b7fa..2c98a5361 100644
--- a/pkg/api/handlers/compat/images_build.go
+++ b/pkg/api/handlers/compat/images_build.go
@@ -393,16 +393,16 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
defer auth.RemoveAuthfile(authfile)
// Channels all mux'ed in select{} below to follow API build protocol
- stdout := channel.NewWriter(make(chan []byte, 1))
+ stdout := channel.NewWriter(make(chan []byte))
defer stdout.Close()
- auxout := channel.NewWriter(make(chan []byte, 1))
+ auxout := channel.NewWriter(make(chan []byte))
defer auxout.Close()
- stderr := channel.NewWriter(make(chan []byte, 1))
+ stderr := channel.NewWriter(make(chan []byte))
defer stderr.Close()
- reporter := channel.NewWriter(make(chan []byte, 1))
+ reporter := channel.NewWriter(make(chan []byte))
defer reporter.Close()
runtime := r.Context().Value("runtime").(*libpod.Runtime)
@@ -529,7 +529,7 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
enc := json.NewEncoder(body)
enc.SetEscapeHTML(true)
-loop:
+
for {
m := struct {
Stream string `json:"stream,omitempty"`
@@ -543,13 +543,13 @@ loop:
stderr.Write([]byte(err.Error()))
}
flush()
- case e := <-auxout.Chan():
+ case e := <-reporter.Chan():
m.Stream = string(e)
if err := enc.Encode(m); err != nil {
stderr.Write([]byte(err.Error()))
}
flush()
- case e := <-reporter.Chan():
+ case e := <-auxout.Chan():
m.Stream = string(e)
if err := enc.Encode(m); err != nil {
stderr.Write([]byte(err.Error()))
@@ -561,8 +561,8 @@ loop:
logrus.Warnf("Failed to json encode error %v", err)
}
flush()
+ return
case <-runCtx.Done():
- flush()
if success {
if !utils.IsLibpodRequest(r) {
m.Stream = fmt.Sprintf("Successfully built %12.12s\n", imageID)
@@ -579,7 +579,8 @@ loop:
}
}
}
- break loop
+ flush()
+ return
case <-r.Context().Done():
cancel()
logrus.Infof("Client disconnect reported for build %q / %q.", registry, query.Dockerfile)
diff --git a/pkg/bindings/connection.go b/pkg/bindings/connection.go
index fd93c5ac7..62b1655ac 100644
--- a/pkg/bindings/connection.go
+++ b/pkg/bindings/connection.go
@@ -327,7 +327,7 @@ func (c *Connection) DoRequest(httpBody io.Reader, httpMethod, endpoint string,
uri := fmt.Sprintf("http://d/v%d.%d.%d/libpod"+endpoint, params...)
logrus.Debugf("DoRequest Method: %s URI: %v", httpMethod, uri)
- req, err := http.NewRequest(httpMethod, uri, httpBody)
+ req, err := http.NewRequestWithContext(context.WithValue(context.Background(), clientKey, c), httpMethod, uri, httpBody)
if err != nil {
return nil, err
}
@@ -337,7 +337,6 @@ func (c *Connection) DoRequest(httpBody io.Reader, httpMethod, endpoint string,
for key, val := range header {
req.Header.Set(key, val)
}
- req = req.WithContext(context.WithValue(context.Background(), clientKey, c))
// Give the Do three chances in the case of a comm/service hiccup
for i := 0; i < 3; i++ {
response, err = c.Client.Do(req) // nolint
diff --git a/pkg/bindings/containers/types.go b/pkg/bindings/containers/types.go
index 39492077b..cf088441f 100644
--- a/pkg/bindings/containers/types.go
+++ b/pkg/bindings/containers/types.go
@@ -62,6 +62,7 @@ type RestoreOptions struct {
Keep *bool
Name *string
TCPEstablished *bool
+ Pod *string
}
//go:generate go run ../generator/generator.go CreateOptions
diff --git a/pkg/bindings/containers/types_restore_options.go b/pkg/bindings/containers/types_restore_options.go
index ea6c810a2..820a7696f 100644
--- a/pkg/bindings/containers/types_restore_options.go
+++ b/pkg/bindings/containers/types_restore_options.go
@@ -131,3 +131,19 @@ func (o *RestoreOptions) GetTCPEstablished() bool {
}
return *o.TCPEstablished
}
+
+// WithPod
+func (o *RestoreOptions) WithPod(value string) *RestoreOptions {
+ v := &value
+ o.Pod = v
+ return o
+}
+
+// GetPod
+func (o *RestoreOptions) GetPod() string {
+ var pod string
+ if o.Pod == nil {
+ return pod
+ }
+ return *o.Pod
+}
diff --git a/pkg/bindings/images/build.go b/pkg/bindings/images/build.go
index 142204f27..a35f461a7 100644
--- a/pkg/bindings/images/build.go
+++ b/pkg/bindings/images/build.go
@@ -391,42 +391,50 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO
dec := json.NewDecoder(body)
var id string
- var mErr error
for {
var s struct {
Stream string `json:"stream,omitempty"`
Error string `json:"error,omitempty"`
}
- if err := dec.Decode(&s); err != nil {
- if errors.Is(err, io.EOF) {
- if mErr == nil && id == "" {
- mErr = errors.New("stream dropped, unexpected failure")
- }
- break
- }
- s.Error = err.Error() + "\n"
- }
select {
+ // FIXME(vrothberg): it seems we always hit the EOF case below,
+ // even when the server quit but it seems desirable to
+ // distinguish a proper build from a transient EOF.
case <-response.Request.Context().Done():
- return &entities.BuildReport{ID: id}, mErr
+ return &entities.BuildReport{ID: id}, nil
default:
// non-blocking select
}
+ if err := dec.Decode(&s); err != nil {
+ if errors.Is(err, io.ErrUnexpectedEOF) {
+ return nil, errors.Wrap(err, "server probably quit")
+ }
+ // EOF means the stream is over in which case we need
+ // to have read the id.
+ if errors.Is(err, io.EOF) && id != "" {
+ break
+ }
+ return &entities.BuildReport{ID: id}, errors.Wrap(err, "decoding stream")
+ }
+
switch {
case s.Stream != "":
- stdout.Write([]byte(s.Stream))
- if iidRegex.Match([]byte(s.Stream)) {
+ raw := []byte(s.Stream)
+ stdout.Write(raw)
+ if iidRegex.Match(raw) {
id = strings.TrimSuffix(s.Stream, "\n")
}
case s.Error != "":
- mErr = errors.New(s.Error)
+ // If there's an error, return directly. The stream
+ // will be closed on return.
+ return &entities.BuildReport{ID: id}, errors.New(s.Error)
default:
return &entities.BuildReport{ID: id}, errors.New("failed to parse build results stream, unexpected input")
}
}
- return &entities.BuildReport{ID: id}, mErr
+ return &entities.BuildReport{ID: id}, nil
}
func nTar(excludes []string, sources ...string) (io.ReadCloser, error) {
diff --git a/pkg/checkpoint/checkpoint_restore.go b/pkg/checkpoint/checkpoint_restore.go
index 0d45cab5f..9fdf04933 100644
--- a/pkg/checkpoint/checkpoint_restore.go
+++ b/pkg/checkpoint/checkpoint_restore.go
@@ -9,6 +9,9 @@ import (
"github.com/containers/common/libimage"
"github.com/containers/common/pkg/config"
"github.com/containers/podman/v3/libpod"
+ ann "github.com/containers/podman/v3/pkg/annotations"
+ "github.com/containers/podman/v3/pkg/checkpoint/crutils"
+ "github.com/containers/podman/v3/pkg/criu"
"github.com/containers/podman/v3/pkg/domain/entities"
"github.com/containers/podman/v3/pkg/errorhandling"
"github.com/containers/podman/v3/pkg/specgen/generate"
@@ -68,6 +71,14 @@ func CRImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, restoreOpt
return nil, err
}
+ if ctrConfig.Pod != "" && restoreOptions.Pod == "" {
+ return nil, errors.New("cannot restore pod container without --pod")
+ }
+
+ if ctrConfig.Pod == "" && restoreOptions.Pod != "" {
+ return nil, errors.New("cannot restore non pod container into pod")
+ }
+
// This should not happen as checkpoints with these options are not exported.
if len(ctrConfig.Dependencies) > 0 {
return nil, errors.Errorf("Cannot import checkpoints of containers with dependencies")
@@ -96,6 +107,91 @@ func CRImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, restoreOpt
newName = true
}
+ if restoreOptions.Pod != "" {
+ // Restoring into a Pod requires much newer versions of CRIU
+ if !criu.CheckForCriu(criu.PodCriuVersion) {
+ return nil, errors.Errorf("restoring containers into pods requires at least CRIU %d", criu.PodCriuVersion)
+ }
+ // The runtime also has to support it
+ if !crutils.CRRuntimeSupportsPodCheckpointRestore(runtime.GetOCIRuntimePath()) {
+ return nil, errors.Errorf("runtime %s does not support pod restore", runtime.GetOCIRuntimePath())
+ }
+ // Restoring into an existing Pod
+ ctrConfig.Pod = restoreOptions.Pod
+
+ // According to podman pod create a pod can share the following namespaces:
+ // cgroup, ipc, net, pid, uts
+ // Let's make sure we a restoring into a pod with the same shared namespaces.
+ pod, err := runtime.LookupPod(ctrConfig.Pod)
+ if err != nil {
+ return nil, errors.Wrapf(err, "pod %q cannot be retrieved", ctrConfig.Pod)
+ }
+
+ infraContainer, err := pod.InfraContainer()
+ if err != nil {
+ return nil, errors.Wrapf(err, "cannot retrieve infra container from pod %q", ctrConfig.Pod)
+ }
+
+ // If a namespaces was shared (!= "") it needs to be set to the new infrastructure container
+ // If the infrastructure container does not share the same namespaces as the to be restored
+ // container we abort.
+ if ctrConfig.IPCNsCtr != "" {
+ if !pod.SharesIPC() {
+ return nil, errors.Errorf("pod %s does not share the IPC namespace", ctrConfig.Pod)
+ }
+ ctrConfig.IPCNsCtr = infraContainer.ID()
+ }
+
+ if ctrConfig.NetNsCtr != "" {
+ if !pod.SharesNet() {
+ return nil, errors.Errorf("pod %s does not share the network namespace", ctrConfig.Pod)
+ }
+ ctrConfig.NetNsCtr = infraContainer.ID()
+ }
+
+ if ctrConfig.PIDNsCtr != "" {
+ if !pod.SharesPID() {
+ return nil, errors.Errorf("pod %s does not share the PID namespace", ctrConfig.Pod)
+ }
+ ctrConfig.PIDNsCtr = infraContainer.ID()
+ }
+
+ if ctrConfig.UTSNsCtr != "" {
+ if !pod.SharesUTS() {
+ return nil, errors.Errorf("pod %s does not share the UTS namespace", ctrConfig.Pod)
+ }
+ ctrConfig.UTSNsCtr = infraContainer.ID()
+ }
+
+ if ctrConfig.CgroupNsCtr != "" {
+ if !pod.SharesCgroup() {
+ return nil, errors.Errorf("pod %s does not share the cgroup namespace", ctrConfig.Pod)
+ }
+ ctrConfig.CgroupNsCtr = infraContainer.ID()
+ }
+
+ // Change SELinux labels to infrastructure container labels
+ ctrConfig.MountLabel = infraContainer.MountLabel()
+ ctrConfig.ProcessLabel = infraContainer.ProcessLabel()
+
+ // Fix parent cgroup
+ cgroupPath, err := pod.CgroupPath()
+ if err != nil {
+ return nil, errors.Wrapf(err, "cannot retrieve cgroup path from pod %q", ctrConfig.Pod)
+ }
+ ctrConfig.CgroupParent = cgroupPath
+
+ oldPodID := dumpSpec.Annotations[ann.SandboxID]
+ // Fix up SandboxID in the annotations
+ dumpSpec.Annotations[ann.SandboxID] = ctrConfig.Pod
+ // Fix up CreateCommand
+ for i, c := range ctrConfig.CreateCommand {
+ if c == oldPodID {
+ ctrConfig.CreateCommand[i] = ctrConfig.Pod
+ }
+ }
+ }
+
if len(restoreOptions.PublishPorts) > 0 {
ports, _, _, err := generate.ParsePortMapping(restoreOptions.PublishPorts)
if err != nil {
diff --git a/pkg/checkpoint/crutils/checkpoint_restore_utils.go b/pkg/checkpoint/crutils/checkpoint_restore_utils.go
index 53ff55865..3b77368bb 100644
--- a/pkg/checkpoint/crutils/checkpoint_restore_utils.go
+++ b/pkg/checkpoint/crutils/checkpoint_restore_utils.go
@@ -1,6 +1,7 @@
package crutils
import (
+ "bytes"
"io"
"os"
"os/exec"
@@ -189,3 +190,13 @@ func CRRuntimeSupportsCheckpointRestore(runtimePath string) bool {
}
return false
}
+
+// CRRuntimeSupportsCheckpointRestore tests if the runtime at 'runtimePath'
+// supports restoring into existing Pods. The runtime needs to support
+// the CRIU option --lsm-mount-context and the existence of this is checked
+// by this function. In addition it is necessary to at least have CRIU 3.16.
+func CRRuntimeSupportsPodCheckpointRestore(runtimePath string) bool {
+ cmd := exec.Command(runtimePath, "restore", "--lsm-mount-context")
+ out, _ := cmd.CombinedOutput()
+ return bytes.Contains(out, []byte("flag needs an argument"))
+}
diff --git a/pkg/criu/criu.go b/pkg/criu/criu.go
index f4cce238a..2a6805979 100644
--- a/pkg/criu/criu.go
+++ b/pkg/criu/criu.go
@@ -1,17 +1,21 @@
package criu
import (
- "github.com/checkpoint-restore/go-criu"
+ "github.com/checkpoint-restore/go-criu/v5"
)
// MinCriuVersion for Podman at least CRIU 3.11 is required
const MinCriuVersion = 31100
+// PodCriuVersion is the version of CRIU needed for
+// checkpointing and restoring containers out of and into Pods.
+const PodCriuVersion = 31600
+
// CheckForCriu uses CRIU's go bindings to check if the CRIU
// binary exists and if it at least the version Podman needs.
-func CheckForCriu() bool {
+func CheckForCriu(version int) bool {
c := criu.MakeCriu()
- result, err := c.IsCriuAtLeast(MinCriuVersion)
+ result, err := c.IsCriuAtLeast(version)
if err != nil {
return false
}
diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go
index 7655e2e63..564921c52 100644
--- a/pkg/domain/entities/containers.go
+++ b/pkg/domain/entities/containers.go
@@ -209,6 +209,7 @@ type RestoreOptions struct {
TCPEstablished bool
ImportPrevious string
PublishPorts []specgen.PortMapping
+ Pod string
}
type RestoreReport struct {
diff --git a/pkg/domain/filters/containers.go b/pkg/domain/filters/containers.go
index 965a12468..dc9fed2a4 100644
--- a/pkg/domain/filters/containers.go
+++ b/pkg/domain/filters/containers.go
@@ -211,6 +211,36 @@ func GenerateContainerFilterFuncs(filter string, filterValues []string, r *libpo
}, nil
case "network":
return func(c *libpod.Container) bool {
+ networkMode := c.NetworkMode()
+ // support docker like `--filter network=container:<IDorName>`
+ // check if networkMode is configured as `container:<ctr>`
+ // peform a match against filter `container:<IDorName>`
+ // networks is already going to be empty if `container:<ctr>` is configured as Mode
+ if strings.HasPrefix(networkMode, "container:") {
+ networkModeContainerPart := strings.SplitN(networkMode, ":", 2)
+ if len(networkModeContainerPart) < 2 {
+ return false
+ }
+ networkModeContainerID := networkModeContainerPart[1]
+ for _, val := range filterValues {
+ if strings.HasPrefix(val, "container:") {
+ filterNetworkModePart := strings.SplitN(val, ":", 2)
+ if len(filterNetworkModePart) < 2 {
+ return false
+ }
+ filterNetworkModeIDorName := filterNetworkModePart[1]
+ filterID, err := r.LookupContainerID(filterNetworkModeIDorName)
+ if err != nil {
+ return false
+ }
+ if filterID == networkModeContainerID {
+ return true
+ }
+ }
+ }
+ return false
+ }
+
networks, _, err := c.Networks()
// if err or no networks, quick out
if err != nil || len(networks) == 0 {
diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go
index 2c89fc66b..2003879b8 100644
--- a/pkg/domain/infra/abi/containers.go
+++ b/pkg/domain/infra/abi/containers.go
@@ -529,6 +529,7 @@ func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []st
IgnoreStaticIP: options.IgnoreStaticIP,
IgnoreStaticMAC: options.IgnoreStaticMAC,
ImportPrevious: options.ImportPrevious,
+ Pod: options.Pod,
}
filterFuncs := []libpod.ContainerFilter{
diff --git a/pkg/domain/infra/runtime_abi.go b/pkg/domain/infra/runtime_abi.go
index ca201b5ae..177e9cff4 100644
--- a/pkg/domain/infra/runtime_abi.go
+++ b/pkg/domain/infra/runtime_abi.go
@@ -33,6 +33,7 @@ func NewImageEngine(facts *entities.PodmanConfig) (entities.ImageEngine, error)
r, err := NewLibpodImageRuntime(facts.FlagSet, facts)
return r, err
case entities.TunnelMode:
+ // TODO: look at me!
ctx, err := bindings.NewConnectionWithIdentity(context.Background(), facts.URI, facts.Identity)
return &tunnel.ImageEngine{ClientCtx: ctx}, err
}
diff --git a/pkg/rootless/rootless_linux.c b/pkg/rootless/rootless_linux.c
index e5f9e88d9..4d8443fcb 100644
--- a/pkg/rootless/rootless_linux.c
+++ b/pkg/rootless/rootless_linux.c
@@ -465,38 +465,43 @@ reexec_in_user_namespace_wait (int pid, int options)
static int
create_pause_process (const char *pause_pid_file_path, char **argv)
{
- int r, p[2];
+ pid_t pid;
+ int p[2];
if (pipe (p) < 0)
- _exit (EXIT_FAILURE);
+ return -1;
- r = fork ();
- if (r < 0)
- _exit (EXIT_FAILURE);
+ pid = fork ();
+ if (pid < 0)
+ {
+ close (p[0]);
+ close (p[1]);
+ return -1;
+ }
- if (r)
+ if (pid)
{
char b;
+ int r;
close (p[1]);
/* Block until we write the pid file. */
r = TEMP_FAILURE_RETRY (read (p[0], &b, 1));
close (p[0]);
- reexec_in_user_namespace_wait (r, 0);
+ reexec_in_user_namespace_wait (pid, 0);
return r == 1 && b == '0' ? 0 : -1;
}
else
{
- int fd;
- pid_t pid;
+ int r, fd;
close (p[0]);
setsid ();
pid = fork ();
- if (r < 0)
+ if (pid < 0)
_exit (EXIT_FAILURE);
if (pid)
diff --git a/pkg/rootless/rootless_linux.go b/pkg/rootless/rootless_linux.go
index f76eab0e3..9ef56acb4 100644
--- a/pkg/rootless/rootless_linux.go
+++ b/pkg/rootless/rootless_linux.go
@@ -14,11 +14,13 @@ import (
"os/user"
"runtime"
"strconv"
+ "strings"
"sync"
"unsafe"
"github.com/containers/podman/v3/pkg/errorhandling"
"github.com/containers/storage/pkg/idtools"
+ pmount "github.com/containers/storage/pkg/mount"
"github.com/containers/storage/pkg/unshare"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -235,6 +237,24 @@ func becomeRootInUserNS(pausePid, fileToRead string, fileOutput *os.File) (_ boo
return false, 0, nil
}
+ if mounts, err := pmount.GetMounts(); err == nil {
+ for _, m := range mounts {
+ if m.Mountpoint == "/" {
+ isShared := false
+ for _, o := range strings.Split(m.Optional, ",") {
+ if strings.HasPrefix(o, "shared:") {
+ isShared = true
+ break
+ }
+ }
+ if !isShared {
+ logrus.Warningf("%q is not a shared mount, this could cause issues or missing mounts with rootless containers", m.Mountpoint)
+ }
+ break
+ }
+ }
+ }
+
cPausePid := C.CString(pausePid)
defer C.free(unsafe.Pointer(cPausePid))