diff options
Diffstat (limited to 'pkg')
37 files changed, 464 insertions, 113 deletions
diff --git a/pkg/api/handlers/compat/containers_archive.go b/pkg/api/handlers/compat/containers_archive.go index a9d74e5f4..541f702e7 100644 --- a/pkg/api/handlers/compat/containers_archive.go +++ b/pkg/api/handlers/compat/containers_archive.go @@ -1,6 +1,7 @@ package compat import ( + "encoding/json" "fmt" "net/http" "os" @@ -93,8 +94,9 @@ func handleHeadAndGet(w http.ResponseWriter, r *http.Request, decoder *schema.De func handlePut(w http.ResponseWriter, r *http.Request, decoder *schema.Decoder, runtime *libpod.Runtime) { query := struct { - Path string `schema:"path"` - Chown bool `schema:"copyUIDGID"` + Path string `schema:"path"` + Chown bool `schema:"copyUIDGID"` + Rename string `schema:"rename"` // TODO handle params below NoOverwriteDirNonDir bool `schema:"noOverwriteDirNonDir"` }{ @@ -107,10 +109,19 @@ func handlePut(w http.ResponseWriter, r *http.Request, decoder *schema.Decoder, return } + var rename map[string]string + if query.Rename != "" { + if err := json.Unmarshal([]byte(query.Rename), &rename); err != nil { + utils.Error(w, "Bad Request.", http.StatusBadRequest, errors.Wrap(err, "couldn't decode the query")) + return + } + } + containerName := utils.GetName(r) containerEngine := abi.ContainerEngine{Libpod: runtime} - copyFunc, err := containerEngine.ContainerCopyFromArchive(r.Context(), containerName, query.Path, r.Body, entities.CopyOptions{Chown: query.Chown}) + copyOptions := entities.CopyOptions{Chown: query.Chown, Rename: rename} + copyFunc, err := containerEngine.ContainerCopyFromArchive(r.Context(), containerName, query.Path, r.Body, copyOptions) if errors.Cause(err) == define.ErrNoSuchCtr || os.IsNotExist(err) { // 404 is returned for an absent container and path. The // clients must deal with it accordingly. diff --git a/pkg/api/handlers/compat/images.go b/pkg/api/handlers/compat/images.go index 7baa1145a..6f8fb21f0 100644 --- a/pkg/api/handlers/compat/images.go +++ b/pkg/api/handlers/compat/images.go @@ -266,41 +266,26 @@ func CreateImageFromImage(w http.ResponseWriter, r *http.Request) { } defer auth.RemoveAuthfile(authfile) - platformSpecs := strings.Split(query.Platform, "/") // split query into its parts - - addOS := true // default assume true due to structure of if/else below - addArch := false - addVariant := false - - if len(platformSpecs) > 1 { // if we have two arguments then we have os and arch - addArch = true - if len(platformSpecs) > 2 { // if we have 3 arguments then we have os arch and variant - addVariant = true - } - } else if len(platformSpecs) == 0 { - addOS = false - } - pullOptions := &libimage.PullOptions{} pullOptions.AuthFilePath = authfile if authConf != nil { pullOptions.Username = authConf.Username pullOptions.Password = authConf.Password pullOptions.IdentityToken = authConf.IdentityToken - if addOS { // if the len is not 0 - pullOptions.OS = platformSpecs[0] - if addArch { - pullOptions.Architecture = platformSpecs[1] - } - if addVariant { - pullOptions.Variant = platformSpecs[2] - } - } } pullOptions.Writer = os.Stderr // allows for debugging on the server - progress := make(chan types.ProgressProperties) + // Handle the platform. + platformSpecs := strings.Split(query.Platform, "/") + pullOptions.OS = platformSpecs[0] // may be empty + if len(platformSpecs) > 1 { + pullOptions.Architecture = platformSpecs[1] + if len(platformSpecs) > 2 { + pullOptions.Variant = platformSpecs[2] + } + } + progress := make(chan types.ProgressProperties) pullOptions.Progress = progress pullResChan := make(chan pullResult) 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/api/handlers/libpod/containers_create.go b/pkg/api/handlers/libpod/containers_create.go index b92588346..65951861b 100644 --- a/pkg/api/handlers/libpod/containers_create.go +++ b/pkg/api/handlers/libpod/containers_create.go @@ -22,6 +22,7 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()")) return } + warn, err := generate.CompleteSpec(r.Context(), runtime, &sg) if err != nil { utils.InternalServerError(w, err) diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go index 3cc10d70f..af5878798 100644 --- a/pkg/api/handlers/types.go +++ b/pkg/api/handlers/types.go @@ -176,6 +176,11 @@ func ImageToImageSummary(l *libimage.Image) (*entities.ImageSummary, error) { } containerCount := len(containers) + isDangling, err := l.IsDangling(context.TODO()) + if err != nil { + return nil, errors.Wrapf(err, "failed to check if image %s is dangling", l.ID()) + } + is := entities.ImageSummary{ ID: l.ID(), ParentId: imageData.Parent, @@ -188,7 +193,7 @@ func ImageToImageSummary(l *libimage.Image) (*entities.ImageSummary, error) { Labels: imageData.Labels, Containers: containerCount, ReadOnly: l.IsReadOnly(), - Dangling: l.IsDangling(), + Dangling: isDangling, Names: l.Names(), Digest: string(imageData.Digest), ConfigDigest: "", // TODO: libpod/image didn't set it but libimage should diff --git a/pkg/api/server/docs.go b/pkg/api/server/docs.go index e72b78221..bf15afbf9 100644 --- a/pkg/api/server/docs.go +++ b/pkg/api/server/docs.go @@ -42,7 +42,7 @@ // // InfoExtensions: // x-logo: -// - url: https://raw.githubusercontent.com/containers/libpod/master/logo/podman-logo.png +// - url: https://raw.githubusercontent.com/containers/libpod/main/logo/podman-logo.png // - altText: "Podman logo" // // Produces: diff --git a/pkg/api/server/register_archive.go b/pkg/api/server/register_archive.go index ee7449fbb..82d72ee6a 100644 --- a/pkg/api/server/register_archive.go +++ b/pkg/api/server/register_archive.go @@ -151,6 +151,10 @@ func (s *APIServer) registerArchiveHandlers(r *mux.Router) error { // type: string // description: Path to a directory in the container to extract // required: true + // - in: query + // name: rename + // type: string + // description: JSON encoded map[string]string to translate paths // responses: // 200: // description: no error diff --git a/pkg/api/server/register_volumes.go b/pkg/api/server/register_volumes.go index d58bf0662..fb02cffcf 100644 --- a/pkg/api/server/register_volumes.go +++ b/pkg/api/server/register_volumes.go @@ -68,6 +68,7 @@ func (s *APIServer) registerVolumeHandlers(r *mux.Router) error { // - label=<key> or label=<key>:<value> Matches volumes based on the presence of a label alone or a label and a value. // - name=<volume-name> Matches all of volume name. // - opt=<driver-option> Matches a storage driver options + // - `until=<timestamp>` List volumes created before this timestamp. The `<timestamp>` can be Unix timestamps, date formatted timestamps, or Go duration strings (e.g. `10m`, `1h30m`) computed relative to the daemon machine’s time. // responses: // '200': // "$ref": "#/responses/VolumeList" @@ -166,6 +167,7 @@ func (s *APIServer) registerVolumeHandlers(r *mux.Router) error { // - driver=<volume-driver-name> Matches volumes based on their driver. // - label=<key> or label=<key>:<value> Matches volumes based on the presence of a label alone or a label and a value. // - name=<volume-name> Matches all of volume name. + // - `until=<timestamp>` List volumes created before this timestamp. The `<timestamp>` can be Unix timestamps, date formatted timestamps, or Go duration strings (e.g. `10m`, `1h30m`) computed relative to the daemon machine’s time. // // Note: // The boolean `dangling` filter is not yet implemented for this endpoint. diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go index 1e8faf8f5..72ae27276 100644 --- a/pkg/api/server/server.go +++ b/pkg/api/server/server.go @@ -90,11 +90,10 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li server := APIServer{ Server: http.Server{ - Handler: router, - ReadHeaderTimeout: 20 * time.Second, - IdleTimeout: duration * 2, - ConnState: idle.ConnState, - ErrorLog: log.New(logrus.StandardLogger().Out, "", 0), + Handler: router, + IdleTimeout: duration * 2, + ConnState: idle.ConnState, + ErrorLog: log.New(logrus.StandardLogger().Out, "", 0), }, Decoder: handlers.NewAPIDecoder(), idleTracker: idle, 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 1058c7a48..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 @@ -263,4 +264,6 @@ type CopyOptions struct { // If used with CopyFromArchive and set to true it will change ownership of files from the source tar archive // to the primary uid/gid of the target container. Chown *bool `schema:"copyUIDGID"` + // Map to translate path names. + Rename map[string]string } diff --git a/pkg/bindings/containers/types_copy_options.go b/pkg/bindings/containers/types_copy_options.go index 12ad085fd..0624b450e 100644 --- a/pkg/bindings/containers/types_copy_options.go +++ b/pkg/bindings/containers/types_copy_options.go @@ -35,3 +35,19 @@ func (o *CopyOptions) GetChown() bool { } return *o.Chown } + +// WithRename +func (o *CopyOptions) WithRename(value map[string]string) *CopyOptions { + v := value + o.Rename = v + return o +} + +// GetRename +func (o *CopyOptions) GetRename() map[string]string { + var rename map[string]string + if o.Rename == nil { + return rename + } + return o.Rename +} 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/copy/parse.go b/pkg/copy/parse.go index 39e0e1547..93edec5fa 100644 --- a/pkg/copy/parse.go +++ b/pkg/copy/parse.go @@ -18,18 +18,6 @@ func ParseSourceAndDestination(source, destination string) (string, string, stri sourceContainer, sourcePath := parseUserInput(source) destContainer, destPath := parseUserInput(destination) - numContainers := 0 - if len(sourceContainer) > 0 { - numContainers++ - } - if len(destContainer) > 0 { - numContainers++ - } - - if numContainers != 1 { - return "", "", "", "", errors.Errorf("invalid arguments %q, %q: exactly 1 container expected but %d specified", source, destination, numContainers) - } - if len(sourcePath) == 0 || len(destPath) == 0 { return "", "", "", "", errors.Errorf("invalid arguments %q, %q: you must specify paths", source, destination) } 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 302b35a47..564921c52 100644 --- a/pkg/domain/entities/containers.go +++ b/pkg/domain/entities/containers.go @@ -165,6 +165,8 @@ type CopyOptions struct { // it will change ownership of files from the source tar archive // to the primary uid/gid of the destination container. Chown bool + // Map to translate path names. + Rename map[string]string } type CommitReport struct { @@ -207,6 +209,7 @@ type RestoreOptions struct { TCPEstablished bool ImportPrevious string PublishPorts []specgen.PortMapping + Pod string } type RestoreReport struct { @@ -242,6 +245,8 @@ type ContainerLogsOptions struct { Names bool // Show logs since this timestamp. Since time.Time + // Show logs until this timestamp. + Until time.Time // Number of lines to display at the end of the output. Tail int64 // Show timestamps in the logs. diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go index 62e83fab3..d573e4704 100644 --- a/pkg/domain/entities/engine_container.go +++ b/pkg/domain/entities/engine_container.go @@ -8,7 +8,6 @@ import ( "github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/pkg/domain/entities/reports" "github.com/containers/podman/v3/pkg/specgen" - "github.com/spf13/cobra" ) type ContainerCopyFunc func() error @@ -82,7 +81,7 @@ 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) - SetupRootless(ctx context.Context, cmd *cobra.Command) error + SetupRootless(ctx context.Context, noMoveProcess bool) error SecretCreate(ctx context.Context, name string, reader io.Reader, options SecretCreateOptions) (*SecretCreateReport, error) SecretInspect(ctx context.Context, nameOrIDs []string) ([]*SecretInfoReport, []error, error) SecretList(ctx context.Context) ([]*SecretInfoReport, error) 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/filters/volumes.go b/pkg/domain/filters/volumes.go index df23c31c0..d55c44ef5 100644 --- a/pkg/domain/filters/volumes.go +++ b/pkg/domain/filters/volumes.go @@ -51,6 +51,12 @@ func GenerateVolumeFilters(filters url.Values) ([]libpod.VolumeFilter, error) { } return false }) + case "until": + f, err := createUntilFilterVolumeFunction(val) + if err != nil { + return nil, err + } + vf = append(vf, f) case "dangling": danglingVal := val invert := false @@ -93,16 +99,11 @@ func GeneratePruneVolumeFilters(filters url.Values) ([]libpod.VolumeFilter, erro return util.MatchLabelFilters([]string{filterVal}, v.Labels()) }) case "until": - until, err := util.ComputeUntilTimestamp([]string{filterVal}) + f, err := createUntilFilterVolumeFunction(filterVal) if err != nil { return nil, err } - vf = append(vf, func(v *libpod.Volume) bool { - if !until.IsZero() && v.CreatedTime().Before(until) { - return true - } - return false - }) + vf = append(vf, f) default: return nil, errors.Errorf("%q is an invalid volume filter", filter) } @@ -110,3 +111,16 @@ func GeneratePruneVolumeFilters(filters url.Values) ([]libpod.VolumeFilter, erro } return vf, nil } + +func createUntilFilterVolumeFunction(filter string) (libpod.VolumeFilter, error) { + until, err := util.ComputeUntilTimestamp([]string{filter}) + if err != nil { + return nil, err + } + return func(v *libpod.Volume) bool { + if !until.IsZero() && v.CreatedTime().Before(until) { + return true + } + return false + }, nil +} diff --git a/pkg/domain/infra/abi/archive.go b/pkg/domain/infra/abi/archive.go index 1a5bb6dc4..b60baa935 100644 --- a/pkg/domain/infra/abi/archive.go +++ b/pkg/domain/infra/abi/archive.go @@ -12,7 +12,7 @@ func (ic *ContainerEngine) ContainerCopyFromArchive(ctx context.Context, nameOrI if err != nil { return nil, err } - return container.CopyFromArchive(ctx, containerPath, options.Chown, reader) + return container.CopyFromArchive(ctx, containerPath, options.Chown, options.Rename, reader) } func (ic *ContainerEngine) ContainerCopyToArchive(ctx context.Context, nameOrID string, containerPath string, writer io.Writer) (entities.ContainerCopyFunc, error) { diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index 50751aa12..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{ @@ -618,7 +619,7 @@ func makeExecConfig(options entities.ExecOptions, rt *libpod.Runtime) (*libpod.E return nil, errors.Wrapf(err, "error retrieving Libpod configuration to build exec exit command") } // TODO: Add some ability to toggle syslog - exitCommandArgs, err := generate.CreateExitCommandArgs(storageConfig, runtimeConfig, false, true, true) + exitCommandArgs, err := generate.CreateExitCommandArgs(storageConfig, runtimeConfig, false, false, true) if err != nil { return nil, errors.Wrapf(err, "error constructing exit command for exec session") } @@ -998,6 +999,7 @@ func (ic *ContainerEngine) ContainerLogs(ctx context.Context, containers []strin Details: options.Details, Follow: options.Follow, Since: options.Since, + Until: options.Until, Tail: options.Tail, Timestamps: options.Timestamps, UseName: options.Names, diff --git a/pkg/domain/infra/abi/images_list.go b/pkg/domain/infra/abi/images_list.go index b0e947991..2ec4ad244 100644 --- a/pkg/domain/infra/abi/images_list.go +++ b/pkg/domain/infra/abi/images_list.go @@ -30,12 +30,16 @@ func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions) for j, d := range img.Digests() { digests[j] = string(d) } + isDangling, err := img.IsDangling(ctx) + if err != nil { + return nil, errors.Wrapf(err, "error checking if image %q is dangling", img.ID()) + } e := entities.ImageSummary{ ID: img.ID(), // ConfigDigest: string(img.ConfigDigest), Created: img.Created().Unix(), - Dangling: img.IsDangling(), + Dangling: isDangling, Digest: string(img.Digest()), RepoDigests: digests, History: img.NamesHistory(), diff --git a/pkg/domain/infra/abi/manifest.go b/pkg/domain/infra/abi/manifest.go index 68e29f006..666bc997d 100644 --- a/pkg/domain/infra/abi/manifest.go +++ b/pkg/domain/infra/abi/manifest.go @@ -337,6 +337,7 @@ func (ir *ImageEngine) ManifestPush(ctx context.Context, name, destination strin pushOptions.ManifestMIMEType = manifestType pushOptions.RemoveSignatures = opts.RemoveSignatures pushOptions.SignBy = opts.SignBy + pushOptions.InsecureSkipTLSVerify = opts.SkipTLSVerify if opts.All { pushOptions.ImageListSelection = cp.CopyAllImages diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go index 4782f0d01..d257bad18 100644 --- a/pkg/domain/infra/abi/play.go +++ b/pkg/domain/infra/abi/play.go @@ -277,7 +277,10 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY // registry on localhost. pullPolicy := config.PullPolicyNewer if len(container.ImagePullPolicy) > 0 { - pullPolicy, err = config.ParsePullPolicy(string(container.ImagePullPolicy)) + // Make sure to lower the strings since K8s pull policy + // may be capitalized (see bugzilla.redhat.com/show_bug.cgi?id=1985905). + rawPolicy := string(container.ImagePullPolicy) + pullPolicy, err = config.ParsePullPolicy(strings.ToLower(rawPolicy)) if err != nil { return nil, err } diff --git a/pkg/domain/infra/abi/system.go b/pkg/domain/infra/abi/system.go index 155cda21d..bc98edd06 100644 --- a/pkg/domain/infra/abi/system.go +++ b/pkg/domain/infra/abi/system.go @@ -24,7 +24,6 @@ import ( "github.com/containers/storage/pkg/unshare" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/spf13/cobra" "github.com/spf13/pflag" ) @@ -57,7 +56,7 @@ func (ic *ContainerEngine) Info(ctx context.Context) (*define.Info, error) { return info, err } -func (ic *ContainerEngine) SetupRootless(_ context.Context, cmd *cobra.Command) error { +func (ic *ContainerEngine) SetupRootless(_ context.Context, noMoveProcess bool) error { // do it only after podman has already re-execed and running with uid==0. hasCapSysAdmin, err := unshare.HasCapSysAdmin() if err != nil { @@ -104,6 +103,9 @@ func (ic *ContainerEngine) SetupRootless(_ context.Context, cmd *cobra.Command) if became { os.Exit(ret) } + if noMoveProcess { + return nil + } // if there is no pid file, try to join existing containers, and create a pause process. ctrs, err := ic.Libpod.GetRunningContainers() @@ -118,9 +120,10 @@ func (ic *ContainerEngine) SetupRootless(_ context.Context, cmd *cobra.Command) } became, ret, err = rootless.TryJoinFromFilePaths(pausePidPath, true, paths) + if err := movePauseProcessToScope(ic.Libpod); err != nil { - conf, err := ic.Config(context.Background()) - if err != nil { + conf, err2 := ic.Config(context.Background()) + if err2 != nil { return err } if conf.Engine.CgroupManager == config.SystemdCgroupsManager { @@ -148,7 +151,6 @@ func movePauseProcessToScope(r *libpod.Runtime) error { if err != nil { return errors.Wrapf(err, "could not get pause process pid file path") } - data, err := ioutil.ReadFile(pausePidPath) if err != nil { return errors.Wrapf(err, "cannot read pause pid file") 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/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index c17d7b54f..58f9c5fb0 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -369,10 +369,11 @@ func (ic *ContainerEngine) ContainerCreate(ctx context.Context, s *specgen.SpecG func (ic *ContainerEngine) ContainerLogs(_ context.Context, nameOrIDs []string, opts entities.ContainerLogsOptions) error { since := opts.Since.Format(time.RFC3339) + until := opts.Until.Format(time.RFC3339) tail := strconv.FormatInt(opts.Tail, 10) stdout := opts.StdoutWriter != nil stderr := opts.StderrWriter != nil - options := new(containers.LogOptions).WithFollow(opts.Follow).WithSince(since).WithStderr(stderr) + options := new(containers.LogOptions).WithFollow(opts.Follow).WithSince(since).WithUntil(until).WithStderr(stderr) options.WithStdout(stdout).WithTail(tail) var err error @@ -852,7 +853,8 @@ func (ic *ContainerEngine) ContainerPort(ctx context.Context, nameOrID string, o } func (ic *ContainerEngine) ContainerCopyFromArchive(ctx context.Context, nameOrID, path string, reader io.Reader, options entities.CopyOptions) (entities.ContainerCopyFunc, error) { - return containers.CopyFromArchiveWithOptions(ic.ClientCtx, nameOrID, path, reader, new(containers.CopyOptions).WithChown(options.Chown)) + copyOptions := new(containers.CopyOptions).WithChown(options.Chown).WithRename(options.Rename) + return containers.CopyFromArchiveWithOptions(ic.ClientCtx, nameOrID, path, reader, copyOptions) } func (ic *ContainerEngine) ContainerCopyToArchive(ctx context.Context, nameOrID string, path string, writer io.Writer) (entities.ContainerCopyFunc, error) { diff --git a/pkg/domain/infra/tunnel/system.go b/pkg/domain/infra/tunnel/system.go index 7400d3771..6b43cf038 100644 --- a/pkg/domain/infra/tunnel/system.go +++ b/pkg/domain/infra/tunnel/system.go @@ -7,14 +7,13 @@ import ( "github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/pkg/bindings/system" "github.com/containers/podman/v3/pkg/domain/entities" - "github.com/spf13/cobra" ) func (ic *ContainerEngine) Info(ctx context.Context) (*define.Info, error) { return system.Info(ic.ClientCtx, nil) } -func (ic *ContainerEngine) SetupRootless(_ context.Context, cmd *cobra.Command) error { +func (ic *ContainerEngine) SetupRootless(_ context.Context, noMoveProcess bool) error { panic(errors.New("rootless engine mode is not supported when tunneling")) } 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)) diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index b569f8390..4e3a86ae4 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -153,7 +153,15 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener if err != nil { return nil, err } - return rt.NewContainer(ctx, runtimeSpec, options...) + + ctr, err := rt.NewContainer(ctx, runtimeSpec, options...) + if err != nil { + return ctr, err + } + + // Copy the content from the underlying image into the newly created + // volume if configured to do so. + return ctr, rt.PrepareVolumeOnCreateContainer(ctx, ctr) } func extractCDIDevices(s *specgen.SpecGenerator) []libpod.CtrCreateOption { diff --git a/pkg/specgen/generate/kube/kube.go b/pkg/specgen/generate/kube/kube.go index fb563f935..fb7eb99a2 100644 --- a/pkg/specgen/generate/kube/kube.go +++ b/pkg/specgen/generate/kube/kube.go @@ -6,10 +6,12 @@ import ( "fmt" "net" "strings" + "time" "github.com/containers/common/libimage" "github.com/containers/common/pkg/parse" "github.com/containers/common/pkg/secrets" + "github.com/containers/image/v5/manifest" ann "github.com/containers/podman/v3/pkg/annotations" "github.com/containers/podman/v3/pkg/specgen" "github.com/containers/podman/v3/pkg/specgen/generate" @@ -129,6 +131,10 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener } setupSecurityContext(s, opts.Container) + err := setupLivenessProbe(s, opts.Container, opts.RestartPolicy) + if err != nil { + return nil, errors.Wrap(err, "Failed to configure livenessProbe") + } // Since we prefix the container name with pod name to work-around the uniqueness requirement, // the seccomp profile should reference the actual container name from the YAML @@ -270,10 +276,11 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener return nil, err } + volume.MountPath = dest switch volumeSource.Type { case KubeVolumeTypeBindMount: mount := spec.Mount{ - Destination: dest, + Destination: volume.MountPath, Source: volumeSource.Source, Type: "bind", Options: options, @@ -281,7 +288,7 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener s.Mounts = append(s.Mounts, mount) case KubeVolumeTypeNamed: namedVolume := specgen.NamedVolume{ - Dest: dest, + Dest: volume.MountPath, Name: volumeSource.Source, Options: options, } @@ -324,12 +331,105 @@ func parseMountPath(mountPath string, readOnly bool) (string, []string, error) { options = strings.Split(splitVol[1], ",") } if err := parse.ValidateVolumeCtrDir(dest); err != nil { - return "", options, errors.Wrapf(err, "error in parsing MountPath") + return "", options, errors.Wrapf(err, "parsing MountPath") } if readOnly { options = append(options, "ro") } - return dest, options, nil + opts, err := parse.ValidateVolumeOpts(options) + if err != nil { + return "", opts, errors.Wrapf(err, "parsing MountOptions") + } + return dest, opts, nil +} + +func setupLivenessProbe(s *specgen.SpecGenerator, containerYAML v1.Container, restartPolicy string) error { + var err error + if containerYAML.LivenessProbe == nil { + return nil + } + emptyHandler := v1.Handler{} + if containerYAML.LivenessProbe.Handler != emptyHandler { + var commandString string + failureCmd := "exit 1" + probe := containerYAML.LivenessProbe + probeHandler := probe.Handler + + // append `exit 1` to `cmd` so healthcheck can be marked as `unhealthy`. + // append `kill 1` to `cmd` if appropriate restart policy is configured. + if restartPolicy == "always" || restartPolicy == "onfailure" { + // container will be restarted so we can kill init. + failureCmd = "kill 1" + } + + // configure healthcheck on the basis of Handler Actions. + if probeHandler.Exec != nil { + execString := strings.Join(probeHandler.Exec.Command, " ") + commandString = fmt.Sprintf("%s || %s", execString, failureCmd) + } else if probeHandler.HTTPGet != nil { + commandString = fmt.Sprintf("curl %s://%s:%d/%s || %s", probeHandler.HTTPGet.Scheme, probeHandler.HTTPGet.Host, probeHandler.HTTPGet.Port.IntValue(), probeHandler.HTTPGet.Path, failureCmd) + } else if probeHandler.TCPSocket != nil { + commandString = fmt.Sprintf("nc -z -v %s %d || %s", probeHandler.TCPSocket.Host, probeHandler.TCPSocket.Port.IntValue(), failureCmd) + } + s.HealthConfig, err = makeHealthCheck(commandString, probe.PeriodSeconds, probe.FailureThreshold, probe.TimeoutSeconds, probe.InitialDelaySeconds) + if err != nil { + return err + } + return nil + } + return nil +} + +func makeHealthCheck(inCmd string, interval int32, retries int32, timeout int32, startPeriod int32) (*manifest.Schema2HealthConfig, error) { + // Every healthcheck requires a command + if len(inCmd) == 0 { + return nil, errors.New("Must define a healthcheck command for all healthchecks") + } + + // first try to parse option value as JSON array of strings... + cmd := []string{} + + if inCmd == "none" { + cmd = []string{"NONE"} + } else { + err := json.Unmarshal([]byte(inCmd), &cmd) + if err != nil { + // ...otherwise pass it to "/bin/sh -c" inside the container + cmd = []string{"CMD-SHELL"} + cmd = append(cmd, strings.Split(inCmd, " ")...) + } + } + hc := manifest.Schema2HealthConfig{ + Test: cmd, + } + + if interval < 1 { + //kubernetes interval defaults to 10 sec and cannot be less than 1 + interval = 10 + } + hc.Interval = (time.Duration(interval) * time.Second) + if retries < 1 { + //kubernetes retries defaults to 3 + retries = 3 + } + hc.Retries = int(retries) + if timeout < 1 { + //kubernetes timeout defaults to 1 + timeout = 1 + } + timeoutDuration := (time.Duration(timeout) * time.Second) + if timeoutDuration < time.Duration(1) { + return nil, errors.New("healthcheck-timeout must be at least 1 second") + } + hc.Timeout = timeoutDuration + + startPeriodDuration := (time.Duration(startPeriod) * time.Second) + if startPeriodDuration < time.Duration(0) { + return nil, errors.New("healthcheck-start-period must be 0 seconds or greater") + } + hc.StartPeriod = startPeriodDuration + + return &hc, nil } func setupSecurityContext(s *specgen.SpecGenerator, containerYAML v1.Container) { diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go index bf8d44ed6..6e310d8a6 100644 --- a/pkg/specgen/generate/oci.go +++ b/pkg/specgen/generate/oci.go @@ -321,6 +321,10 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt } } + for _, dev := range s.DeviceCGroupRule { + g.AddLinuxResourcesDevice(true, dev.Type, dev.Major, dev.Minor, dev.Access) + } + BlockAccessToKernelFilesystems(s.Privileged, s.PidNS.IsHost(), s.Mask, s.Unmask, &g) for name, val := range s.Env { diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go index c5cc726d7..7eec48a55 100644 --- a/pkg/specgen/specgen.go +++ b/pkg/specgen/specgen.go @@ -239,6 +239,9 @@ type ContainerStorageConfig struct { // Devices are devices that will be added to the container. // Optional. Devices []spec.LinuxDevice `json:"devices,omitempty"` + // DeviceCGroupRule are device cgroup rules that allow containers + // to use additional types of devices. + DeviceCGroupRule []spec.LinuxDeviceCgroup `json:"device_cgroup_rule,omitempty"` // IpcNS is the container's IPC namespace. // Default is private. // Conflicts with ShmSize if not set to private. |