diff options
Diffstat (limited to 'pkg')
38 files changed, 614 insertions, 330 deletions
diff --git a/pkg/api/handlers/compat/containers_attach.go b/pkg/api/handlers/compat/containers_attach.go index 325f96b40..71586fca4 100644 --- a/pkg/api/handlers/compat/containers_attach.go +++ b/pkg/api/handlers/compat/containers_attach.go @@ -1,23 +1,22 @@ package compat import ( + "bufio" "fmt" + "io" + "net" "net/http" + "strings" "github.com/containers/libpod/v2/libpod" "github.com/containers/libpod/v2/libpod/define" "github.com/containers/libpod/v2/pkg/api/handlers/utils" + "github.com/containers/libpod/v2/pkg/api/server/idletracker" "github.com/gorilla/schema" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) -// AttachHeader is the literal header sent for upgraded/hijacked connections for -// attach, sourced from Docker at: -// https://raw.githubusercontent.com/moby/moby/b95fad8e51bd064be4f4e58a996924f343846c85/api/server/router/container/container_routes.go -// Using literally to ensure compatibility with existing clients. -const AttachHeader = "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n" - func AttachContainer(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) decoder := r.Context().Value("decoder").(*schema.Decoder) @@ -98,21 +97,11 @@ func AttachContainer(w http.ResponseWriter, r *http.Request) { return } - // Hijack the connection - hijacker, ok := w.(http.Hijacker) - if !ok { - utils.InternalServerError(w, errors.Errorf("unable to hijack connection")) - return - } - - connection, buffer, err := hijacker.Hijack() + connection, buffer, err := AttachConnection(w, r) if err != nil { - utils.InternalServerError(w, errors.Wrapf(err, "error hijacking connection")) + utils.InternalServerError(w, err) return } - - fmt.Fprintf(connection, AttachHeader) - logrus.Debugf("Hijack for attach of container %s successful", ctr.ID()) // Perform HTTP attach. @@ -126,3 +115,57 @@ func AttachContainer(w http.ResponseWriter, r *http.Request) { logrus.Debugf("Attach for container %s completed successfully", ctr.ID()) } + +type HijackedConnection struct { + net.Conn // Connection + idleTracker *idletracker.IdleTracker // Connection tracker +} + +func (c HijackedConnection) Close() error { + logrus.Debugf("Hijacked connection closed") + + c.idleTracker.TrackHijackedClosed() + return c.Conn.Close() +} + +func AttachConnection(w http.ResponseWriter, r *http.Request) (net.Conn, *bufio.ReadWriter, error) { + idleTracker := r.Context().Value("idletracker").(*idletracker.IdleTracker) + + // Hijack the connection + hijacker, ok := w.(http.Hijacker) + if !ok { + return nil, nil, errors.Errorf("unable to hijack connection") + } + + connection, buffer, err := hijacker.Hijack() + if err != nil { + return nil, nil, errors.Wrapf(err, "error hijacking connection") + } + trackedConnection := HijackedConnection{ + Conn: connection, + idleTracker: idleTracker, + } + + WriteAttachHeaders(r, trackedConnection) + + return trackedConnection, buffer, nil +} + +func WriteAttachHeaders(r *http.Request, connection io.Writer) { + // AttachHeader is the literal header sent for upgraded/hijacked connections for + // attach, sourced from Docker at: + // https://raw.githubusercontent.com/moby/moby/b95fad8e51bd064be4f4e58a996924f343846c85/api/server/router/container/container_routes.go + // Using literally to ensure compatibility with existing clients. + c := r.Header.Get("Connection") + proto := r.Header.Get("Upgrade") + if len(proto) == 0 || !strings.EqualFold(c, "Upgrade") { + // OK - can't upgrade if not requested or protocol is not specified + fmt.Fprintf(connection, + "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") + } else { + // Upraded + fmt.Fprintf(connection, + "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: %s\r\n\r\n", + proto) + } +} diff --git a/pkg/api/handlers/compat/containers_create.go b/pkg/api/handlers/compat/containers_create.go index a4511b3b0..cbcda474a 100644 --- a/pkg/api/handlers/compat/containers_create.go +++ b/pkg/api/handlers/compat/containers_create.go @@ -1,6 +1,7 @@ package compat import ( + "context" "encoding/json" "fmt" "net/http" @@ -8,6 +9,7 @@ import ( "github.com/containers/common/pkg/config" "github.com/containers/libpod/v2/libpod" + "github.com/containers/libpod/v2/libpod/define" image2 "github.com/containers/libpod/v2/libpod/image" "github.com/containers/libpod/v2/pkg/api/handlers" "github.com/containers/libpod/v2/pkg/api/handlers/utils" @@ -40,9 +42,15 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { } if len(input.HostConfig.Links) > 0 { utils.Error(w, utils.ErrLinkNotSupport.Error(), http.StatusBadRequest, errors.Wrapf(utils.ErrLinkNotSupport, "bad parameter")) + return } newImage, err := runtime.ImageRuntime().NewFromLocal(input.Image) if err != nil { + if errors.Cause(err) == define.ErrNoSuchImage { + utils.Error(w, "No such image", http.StatusNotFound, err) + return + } + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "NewFromLocal()")) return } @@ -51,7 +59,7 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "GetConfig()")) return } - cc, err := makeCreateConfig(containerConfig, input, newImage) + cc, err := makeCreateConfig(r.Context(), containerConfig, input, newImage) if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "makeCreatConfig()")) return @@ -60,7 +68,7 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { utils.CreateContainer(r.Context(), w, runtime, &cc) } -func makeCreateConfig(containerConfig *config.Config, input handlers.CreateContainerConfig, newImage *image2.Image) (createconfig.CreateConfig, error) { +func makeCreateConfig(ctx context.Context, containerConfig *config.Config, input handlers.CreateContainerConfig, newImage *image2.Image) (createconfig.CreateConfig, error) { var ( err error init bool @@ -79,6 +87,22 @@ func makeCreateConfig(containerConfig *config.Config, input handlers.CreateConta workDir = input.WorkingDir } + if input.Entrypoint == nil { + entrypointSlice, err := newImage.Entrypoint(ctx) + if err != nil { + return createconfig.CreateConfig{}, err + } + input.Entrypoint = entrypointSlice + } + + if len(input.Cmd) == 0 { + cmdSlice, err := newImage.Cmd(ctx) + if err != nil { + return createconfig.CreateConfig{}, err + } + input.Cmd = cmdSlice + } + stopTimeout := containerConfig.Engine.StopTimeout if input.StopTimeout != nil { stopTimeout = uint(*input.StopTimeout) @@ -217,5 +241,16 @@ func makeCreateConfig(containerConfig *config.Config, input handlers.CreateConta Pid: pidConfig, } + + fullCmd := append(input.Entrypoint, input.Cmd...) + if len(fullCmd) > 0 { + m.PodmanPath = fullCmd[0] + if len(fullCmd) == 1 { + m.Args = fullCmd + } else { + m.Args = fullCmd[1:] + } + } + return m, nil } diff --git a/pkg/api/handlers/compat/containers_logs.go b/pkg/api/handlers/compat/containers_logs.go index 8147f4d38..30ee030e8 100644 --- a/pkg/api/handlers/compat/containers_logs.go +++ b/pkg/api/handlers/compat/containers_logs.go @@ -92,7 +92,7 @@ func LogsFromContainer(w http.ResponseWriter, r *http.Request) { options.WaitGroup = &wg logChannel := make(chan *logs.LogLine, tail+1) - if err := runtime.Log([]*libpod.Container{ctnr}, options, logChannel); err != nil { + if err := runtime.Log(r.Context(), []*libpod.Container{ctnr}, options, logChannel); err != nil { utils.InternalServerError(w, errors.Wrapf(err, "Failed to obtain logs for Container '%s'", name)) return } @@ -105,50 +105,48 @@ func LogsFromContainer(w http.ResponseWriter, r *http.Request) { var frame strings.Builder header := make([]byte, 8) - for ok := true; ok; ok = query.Follow { - for line := range logChannel { - if _, found := r.URL.Query()["until"]; found { - if line.Time.After(until) { - break - } + for line := range logChannel { + if _, found := r.URL.Query()["until"]; found { + if line.Time.After(until) { + break } + } - // Reset buffer we're ready to loop again - frame.Reset() - switch line.Device { - case "stdout": - if !query.Stdout { - continue - } - header[0] = 1 - case "stderr": - if !query.Stderr { - continue - } - header[0] = 2 - default: - // Logging and moving on is the best we can do here. We may have already sent - // a Status and Content-Type to client therefore we can no longer report an error. - log.Infof("unknown Device type '%s' in log file from Container %s", line.Device, ctnr.ID()) + // Reset buffer we're ready to loop again + frame.Reset() + switch line.Device { + case "stdout": + if !query.Stdout { continue } - - if query.Timestamps { - frame.WriteString(line.Time.Format(time.RFC3339)) - frame.WriteString(" ") + header[0] = 1 + case "stderr": + if !query.Stderr { + continue } - frame.WriteString(line.Msg) + header[0] = 2 + default: + // Logging and moving on is the best we can do here. We may have already sent + // a Status and Content-Type to client therefore we can no longer report an error. + log.Infof("unknown Device type '%s' in log file from Container %s", line.Device, ctnr.ID()) + continue + } - binary.BigEndian.PutUint32(header[4:], uint32(frame.Len())) - if _, err := w.Write(header[0:8]); err != nil { - log.Errorf("unable to write log output header: %q", err) - } - if _, err := io.WriteString(w, frame.String()); err != nil { - log.Errorf("unable to write frame string: %q", err) - } - if flusher, ok := w.(http.Flusher); ok { - flusher.Flush() - } + if query.Timestamps { + frame.WriteString(line.Time.Format(time.RFC3339)) + frame.WriteString(" ") + } + frame.WriteString(line.Msg) + + binary.BigEndian.PutUint32(header[4:], uint32(frame.Len())) + if _, err := w.Write(header[0:8]); err != nil { + log.Errorf("unable to write log output header: %q", err) + } + if _, err := io.WriteString(w, frame.String()); err != nil { + log.Errorf("unable to write frame string: %q", err) + } + if flusher, ok := w.(http.Flusher); ok { + flusher.Flush() } } } diff --git a/pkg/api/handlers/compat/events.go b/pkg/api/handlers/compat/events.go index 5acc94153..9d5cb5045 100644 --- a/pkg/api/handlers/compat/events.go +++ b/pkg/api/handlers/compat/events.go @@ -1,9 +1,10 @@ package compat import ( - "context" + "encoding/json" "fmt" "net/http" + "sync" "github.com/containers/libpod/v2/libpod" "github.com/containers/libpod/v2/libpod/events" @@ -15,77 +16,132 @@ import ( "github.com/sirupsen/logrus" ) +// filtersFromRequests extracts the "filters" parameter from the specified +// http.Request. The paramater can either be a `map[string][]string` as done +// in new versions of Docker and libpod, or a `map[string]map[string]bool` as +// done in older versions of Docker. We have to do a bit of Yoga to support +// both - just as Docker does as well. +// +// Please refer to https://github.com/containers/podman/issues/6899 for some +// background. +func filtersFromRequest(r *http.Request) ([]string, error) { + var ( + compatFilters map[string]map[string]bool + filters map[string][]string + libpodFilters []string + ) + raw := []byte(r.Form.Get("filters")) + + // Backwards compat with older versions of Docker. + if err := json.Unmarshal(raw, &compatFilters); err == nil { + for filterKey, filterMap := range compatFilters { + for filterValue, toAdd := range filterMap { + if toAdd { + libpodFilters = append(libpodFilters, fmt.Sprintf("%s=%s", filterKey, filterValue)) + } + } + } + return libpodFilters, nil + } + + if err := json.Unmarshal(raw, &filters); err != nil { + return nil, err + } + + for filterKey, filterSlice := range filters { + for _, filterValue := range filterSlice { + libpodFilters = append(libpodFilters, fmt.Sprintf("%s=%s", filterKey, filterValue)) + } + } + + return libpodFilters, nil +} + +// NOTE: this endpoint serves both the docker-compatible one and the new libpod +// one. func GetEvents(w http.ResponseWriter, r *http.Request) { var ( - fromStart bool - eventsError error - decoder = r.Context().Value("decoder").(*schema.Decoder) - runtime = r.Context().Value("runtime").(*libpod.Runtime) + fromStart bool + decoder = r.Context().Value("decoder").(*schema.Decoder) + runtime = r.Context().Value("runtime").(*libpod.Runtime) + json = jsoniter.ConfigCompatibleWithStandardLibrary // FIXME: this should happen on the package level ) + // NOTE: the "filters" parameter is extracted separately for backwards + // compat via `fitlerFromRequest()`. query := struct { - Since string `schema:"since"` - Until string `schema:"until"` - Filters map[string][]string `schema:"filters"` - Stream bool `schema:"stream"` + Since string `schema:"since"` + Until string `schema:"until"` + Stream bool `schema:"stream"` }{ Stream: true, } if err := decoder.Decode(&query, r.URL.Query()); err != nil { utils.Error(w, "Failed to parse parameters", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) - } - - var libpodFilters = []string{} - if _, found := r.URL.Query()["filters"]; found { - for k, v := range query.Filters { - libpodFilters = append(libpodFilters, fmt.Sprintf("%s=%s", k, v[0])) - } + return } if len(query.Since) > 0 || len(query.Until) > 0 { fromStart = true } - eventCtx, eventCancel := context.WithCancel(r.Context()) - eventChannel := make(chan *events.Event) - go func() { - readOpts := events.ReadOptions{FromStart: fromStart, Stream: query.Stream, Filters: libpodFilters, EventChannel: eventChannel, Since: query.Since, Until: query.Until} - eventsError = runtime.Events(eventCtx, readOpts) - }() - if eventsError != nil { - utils.InternalServerError(w, eventsError) - eventCancel() - close(eventChannel) + libpodFilters, err := filtersFromRequest(r) + if err != nil { + utils.Error(w, "Failed to parse parameters", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) return } - // If client disappears we need to stop listening for events - go func(done <-chan struct{}) { - <-done - eventCancel() - if _, ok := <-eventChannel; ok { - close(eventChannel) + eventChannel := make(chan *events.Event) + errorChannel := make(chan error) + + // Start reading events. + go func() { + readOpts := events.ReadOptions{ + FromStart: fromStart, + Stream: query.Stream, + Filters: libpodFilters, + EventChannel: eventChannel, + Since: query.Since, + Until: query.Until, } - }(r.Context().Done()) + errorChannel <- runtime.Events(r.Context(), readOpts) + }() - // Headers need to be written out before turning Writer() over to json encoder - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - if flusher, ok := w.(http.Flusher); ok { - flusher.Flush() - } + var coder *jsoniter.Encoder + var writeHeader sync.Once - json := jsoniter.ConfigCompatibleWithStandardLibrary - coder := json.NewEncoder(w) - coder.SetEscapeHTML(true) + for stream := true; stream; stream = query.Stream { + select { + case err := <-errorChannel: + if err != nil { + utils.InternalServerError(w, err) + return + } + case evt := <-eventChannel: + writeHeader.Do(func() { + // Use a sync.Once so that we write the header + // only once. + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + if flusher, ok := w.(http.Flusher); ok { + flusher.Flush() + } + coder = json.NewEncoder(w) + coder.SetEscapeHTML(true) + }) - for event := range eventChannel { - e := entities.ConvertToEntitiesEvent(*event) - if err := coder.Encode(e); err != nil { - logrus.Errorf("unable to write json: %q", err) - } - if flusher, ok := w.(http.Flusher); ok { - flusher.Flush() + if evt == nil { + continue + } + + e := entities.ConvertToEntitiesEvent(*evt) + if err := coder.Encode(e); err != nil { + logrus.Errorf("unable to write json: %q", err) + } + if flusher, ok := w.(http.Flusher); ok { + flusher.Flush() + } } + } } diff --git a/pkg/api/handlers/compat/exec.go b/pkg/api/handlers/compat/exec.go index aee4196dd..a3b8cb573 100644 --- a/pkg/api/handlers/compat/exec.go +++ b/pkg/api/handlers/compat/exec.go @@ -173,21 +173,11 @@ func ExecStartHandler(w http.ResponseWriter, r *http.Request) { return } - // Hijack the connection - hijacker, ok := w.(http.Hijacker) - if !ok { - utils.InternalServerError(w, errors.Errorf("unable to hijack connection")) - return - } - - connection, buffer, err := hijacker.Hijack() + connection, buffer, err := AttachConnection(w, r) if err != nil { - utils.InternalServerError(w, errors.Wrapf(err, "error hijacking connection")) + utils.InternalServerError(w, err) return } - - fmt.Fprintf(connection, AttachHeader) - logrus.Debugf("Hijack for attach of container %s exec session %s successful", sessionCtr.ID(), sessionID) if err := sessionCtr.ExecHTTPStartAndAttach(sessionID, connection, buffer, nil, nil, nil); err != nil { diff --git a/pkg/api/server/handler_api.go b/pkg/api/server/handler_api.go index b0fd932ba..53fe8952b 100644 --- a/pkg/api/server/handler_api.go +++ b/pkg/api/server/handler_api.go @@ -37,6 +37,7 @@ func (s *APIServer) APIHandler(h http.HandlerFunc) http.HandlerFunc { c := context.WithValue(r.Context(), "decoder", s.Decoder) //nolint c = context.WithValue(c, "runtime", s.Runtime) //nolint c = context.WithValue(c, "shutdownFunc", s.Shutdown) //nolint + c = context.WithValue(c, "idletracker", s.idleTracker) //nolint r = r.WithContext(c) h(w, r) diff --git a/pkg/api/server/idletracker/idletracker.go b/pkg/api/server/idletracker/idletracker.go new file mode 100644 index 000000000..1ee905a99 --- /dev/null +++ b/pkg/api/server/idletracker/idletracker.go @@ -0,0 +1,74 @@ +package idletracker + +import ( + "net" + "net/http" + "sync" + "time" + + "github.com/sirupsen/logrus" +) + +type IdleTracker struct { + http map[net.Conn]struct{} + hijacked int + total int + mux sync.Mutex + timer *time.Timer + Duration time.Duration +} + +func NewIdleTracker(idle time.Duration) *IdleTracker { + return &IdleTracker{ + http: make(map[net.Conn]struct{}), + Duration: idle, + timer: time.NewTimer(idle), + } +} + +func (t *IdleTracker) ConnState(conn net.Conn, state http.ConnState) { + t.mux.Lock() + defer t.mux.Unlock() + + oldActive := t.ActiveConnections() + logrus.Debugf("IdleTracker %p:%v %d/%d connection(s)", conn, state, oldActive, t.TotalConnections()) + switch state { + case http.StateNew, http.StateActive: + t.http[conn] = struct{}{} + // stop the timer if we transitioned from idle + if oldActive == 0 { + t.timer.Stop() + } + t.total++ + case http.StateHijacked: + // hijacked connections are handled elsewhere + delete(t.http, conn) + t.hijacked++ + case http.StateIdle, http.StateClosed: + delete(t.http, conn) + // Restart the timer if we've become idle + if oldActive > 0 && len(t.http) == 0 { + t.timer.Stop() + t.timer.Reset(t.Duration) + } + } +} + +func (t *IdleTracker) TrackHijackedClosed() { + t.mux.Lock() + defer t.mux.Unlock() + + t.hijacked-- +} + +func (t *IdleTracker) ActiveConnections() int { + return len(t.http) + t.hijacked +} + +func (t *IdleTracker) TotalConnections() int { + return t.total +} + +func (t *IdleTracker) Done() <-chan time.Time { + return t.timer.C +} diff --git a/pkg/api/server/register_generate.go b/pkg/api/server/register_generate.go index 82f1dc680..a1ab3f727 100644 --- a/pkg/api/server/register_generate.go +++ b/pkg/api/server/register_generate.go @@ -13,8 +13,8 @@ func (s *APIServer) registerGenerateHandlers(r *mux.Router) error { // tags: // - containers // - pods - // summary: Play a Kubernetes YAML file. - // description: Create and run pods based on a Kubernetes YAML file (pod or service kind). + // summary: Generate a Kubernetes YAML file. + // description: Generate Kubernetes YAML based on a pod or container. // parameters: // - in: path // name: name:.* diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go index 8af6d3186..1c6007745 100644 --- a/pkg/api/server/server.go +++ b/pkg/api/server/server.go @@ -10,12 +10,12 @@ import ( "runtime" goRuntime "runtime" "strings" - "sync" "syscall" "time" "github.com/containers/libpod/v2/libpod" "github.com/containers/libpod/v2/pkg/api/handlers" + "github.com/containers/libpod/v2/pkg/api/server/idletracker" "github.com/coreos/go-systemd/v22/activation" "github.com/gorilla/mux" "github.com/gorilla/schema" @@ -24,14 +24,14 @@ import ( ) type APIServer struct { - http.Server // The HTTP work happens here - *schema.Decoder // Decoder for Query parameters to structs - context.Context // Context to carry objects to handlers - *libpod.Runtime // Where the real work happens - net.Listener // mux for routing HTTP API calls to libpod routines - context.CancelFunc // Stop APIServer - idleTracker *IdleTracker // Track connections to support idle shutdown - pprof *http.Server // Sidecar http server for providing performance data + http.Server // The HTTP work happens here + *schema.Decoder // Decoder for Query parameters to structs + context.Context // Context to carry objects to handlers + *libpod.Runtime // Where the real work happens + net.Listener // mux for routing HTTP API calls to libpod routines + context.CancelFunc // Stop APIServer + idleTracker *idletracker.IdleTracker // Track connections to support idle shutdown + pprof *http.Server // Sidecar http server for providing performance data } // Number of seconds to wait for next request, if exceeded shutdown server @@ -68,7 +68,7 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li } router := mux.NewRouter().UseEncodedPath() - idle := NewIdleTracker(duration) + idle := idletracker.NewIdleTracker(duration) server := APIServer{ Server: http.Server{ @@ -231,55 +231,3 @@ func (s *APIServer) Shutdown() error { func (s *APIServer) Close() error { return s.Server.Close() } - -type IdleTracker struct { - active map[net.Conn]struct{} - total int - mux sync.Mutex - timer *time.Timer - Duration time.Duration -} - -func NewIdleTracker(idle time.Duration) *IdleTracker { - return &IdleTracker{ - active: make(map[net.Conn]struct{}), - Duration: idle, - timer: time.NewTimer(idle), - } -} - -func (t *IdleTracker) ConnState(conn net.Conn, state http.ConnState) { - t.mux.Lock() - defer t.mux.Unlock() - - oldActive := len(t.active) - logrus.Debugf("IdleTracker %p:%v %d/%d connection(s)", conn, state, t.ActiveConnections(), t.TotalConnections()) - switch state { - case http.StateNew, http.StateActive, http.StateHijacked: - t.active[conn] = struct{}{} - // stop the timer if we transitioned from idle - if oldActive == 0 { - t.timer.Stop() - } - t.total++ - case http.StateIdle, http.StateClosed: - delete(t.active, conn) - // Restart the timer if we've become idle - if oldActive > 0 && len(t.active) == 0 { - t.timer.Stop() - t.timer.Reset(t.Duration) - } - } -} - -func (t *IdleTracker) ActiveConnections() int { - return len(t.active) -} - -func (t *IdleTracker) TotalConnections() int { - return t.total -} - -func (t *IdleTracker) Done() <-chan time.Time { - return t.timer.C -} diff --git a/pkg/bindings/containers/attach.go b/pkg/bindings/containers/attach.go index 077bb244f..297563688 100644 --- a/pkg/bindings/containers/attach.go +++ b/pkg/bindings/containers/attach.go @@ -457,15 +457,15 @@ func ExecStartAndAttach(ctx context.Context, sessionID string, streams *define.A switch { case fd == 0: - if streams.AttachOutput { + if streams.AttachInput { + // Write STDIN to STDOUT (echoing characters + // typed by another attach session) if _, err := streams.OutputStream.Write(frame[0:l]); err != nil { return err } } case fd == 1: - if streams.AttachInput { - // Write STDIN to STDOUT (echoing characters - // typed by another attach session) + if streams.AttachOutput { if _, err := streams.OutputStream.Write(frame[0:l]); err != nil { return err } diff --git a/pkg/bindings/test/system_test.go b/pkg/bindings/test/system_test.go index 93141400b..430184f4a 100644 --- a/pkg/bindings/test/system_test.go +++ b/pkg/bindings/test/system_test.go @@ -1,6 +1,7 @@ package test_bindings import ( + "sync" "time" "github.com/containers/libpod/v2/pkg/bindings" @@ -38,22 +39,28 @@ var _ = Describe("Podman system", func() { }) It("podman events", func() { - eChan := make(chan entities.Event, 1) - var messages []entities.Event - cancelChan := make(chan bool, 1) + var name = "top" + _, err := bt.RunTopContainer(&name, bindings.PFalse, nil) + Expect(err).To(BeNil()) + + filters := make(map[string][]string) + filters["container"] = []string{name} + + binChan := make(chan entities.Event) + done := sync.Mutex{} + done.Lock() + eventCounter := 0 go func() { - for e := range eChan { - messages = append(messages, e) + defer done.Unlock() + for range binChan { + eventCounter++ } }() - go func() { - system.Events(bt.conn, eChan, cancelChan, nil, nil, nil, bindings.PFalse) - }() - _, err := bt.RunTopContainer(nil, nil, nil) + err = system.Events(bt.conn, binChan, nil, nil, nil, filters, bindings.PFalse) Expect(err).To(BeNil()) - cancelChan <- true - Expect(len(messages)).To(BeNumerically("==", 5)) + done.Lock() + Expect(eventCounter).To(BeNumerically(">", 0)) }) It("podman system prune - pod,container stopped", func() { diff --git a/pkg/domain/entities/pods.go b/pkg/domain/entities/pods.go index 8023034ef..9e9b834ef 100644 --- a/pkg/domain/entities/pods.go +++ b/pkg/domain/entities/pods.go @@ -104,6 +104,7 @@ type PodRmReport struct { type PodCreateOptions struct { CGroupParent string + CreateCommand []string Hostname string Infra bool InfraImage string @@ -133,6 +134,7 @@ func (p PodCreateOptions) ToPodSpecGen(s *specgen.PodSpecGenerator) { } s.InfraImage = p.InfraImage s.SharedNamespaces = p.Share + s.PodCreateCommand = p.CreateCommand // Networking config s.NetNS = p.Net.Network diff --git a/pkg/domain/entities/types.go b/pkg/domain/entities/types.go index 7e910ff61..b313e5f8b 100644 --- a/pkg/domain/entities/types.go +++ b/pkg/domain/entities/types.go @@ -42,6 +42,8 @@ type NetOptions struct { PublishPorts []specgen.PortMapping StaticIP *net.IP StaticMAC *net.HardwareAddr + // NetworkOptions are additional options for each network + NetworkOptions map[string][]string } // All CLI inspect commands and inspect sub-commands use the same options diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index 596fc2cc1..8909f831d 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -924,7 +924,7 @@ func (ic *ContainerEngine) ContainerLogs(ctx context.Context, containers []strin } logChannel := make(chan *logs.LogLine, chSize) - if err := ic.Libpod.Log(ctrs, logOpts, logChannel); err != nil { + if err := ic.Libpod.Log(ctx, ctrs, logOpts, logChannel); err != nil { return err } diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go index f82da2c95..52a62a25d 100644 --- a/pkg/domain/infra/abi/play.go +++ b/pkg/domain/infra/abi/play.go @@ -340,9 +340,7 @@ func getPodPorts(containers []v1.Container) []ocicni.PortMapping { HostPort: p.HostPort, ContainerPort: p.ContainerPort, Protocol: strings.ToLower(string(p.Protocol)), - } - if p.HostIP != "" { - logrus.Debug("HostIP on port bindings is not supported") + HostIP: p.HostIP, } // only hostPort is utilized in podman context, all container ports // are accessible inside the shared network namespace @@ -453,11 +451,16 @@ func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container containerConfig.Command = []string{} if imageData != nil && imageData.Config != nil { - containerConfig.Command = append(containerConfig.Command, imageData.Config.Entrypoint...) + containerConfig.Command = imageData.Config.Entrypoint } if len(containerYAML.Command) != 0 { - containerConfig.Command = append(containerConfig.Command, containerYAML.Command...) - } else if imageData != nil && imageData.Config != nil { + containerConfig.Command = containerYAML.Command + } + // doc https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#notes + if len(containerYAML.Args) != 0 { + containerConfig.Command = append(containerConfig.Command, containerYAML.Args...) + } else if len(containerYAML.Command) == 0 { + // Add the Cmd from the image config only if containerYAML.Command and containerYAML.Args are empty containerConfig.Command = append(containerConfig.Command, imageData.Config.Cmd...) } if imageData != nil && len(containerConfig.Command) == 0 { diff --git a/pkg/domain/infra/abi/system.go b/pkg/domain/infra/abi/system.go index e4ae6156c..9727f1d4e 100644 --- a/pkg/domain/infra/abi/system.go +++ b/pkg/domain/infra/abi/system.go @@ -8,7 +8,6 @@ import ( "os/exec" "path/filepath" "strconv" - "syscall" "github.com/containers/common/pkg/config" "github.com/containers/libpod/v2/libpod/define" @@ -146,27 +145,6 @@ func movePauseProcessToScope() error { return utils.RunUnderSystemdScope(int(pid), "user.slice", "podman-pause.scope") } -func setRLimits() error { // nolint:deadcode,unused - rlimits := new(syscall.Rlimit) - rlimits.Cur = 1048576 - rlimits.Max = 1048576 - if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil { - if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil { - return errors.Wrapf(err, "error getting rlimits") - } - rlimits.Cur = rlimits.Max - if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil { - return errors.Wrapf(err, "error setting new rlimits") - } - } - return nil -} - -func setUMask() { // nolint:deadcode,unused - // Be sure we can create directories with 0755 mode. - syscall.Umask(0022) -} - // checkInput can be used to verify any of the globalopt values func checkInput() error { // nolint:deadcode,unused return nil @@ -252,13 +230,18 @@ func (ic *ContainerEngine) SystemDf(ctx context.Context, options entities.System } } - named, err := reference.ParseNormalizedNamed(name) - if err != nil { - return nil, err - } - repository = named.Name() - if tagged, isTagged := named.(reference.NamedTagged); isTagged { - tag = tagged.Tag() + if len(name) > 0 { + named, err := reference.ParseNormalizedNamed(name) + if err != nil { + return nil, err + } + repository = named.Name() + if tagged, isTagged := named.(reference.NamedTagged); isTagged { + tag = tagged.Tag() + } + } else { + repository = "<none>" + tag = "<none>" } report := entities.SystemDfImageReport{ diff --git a/pkg/namespaces/namespaces.go b/pkg/namespaces/namespaces.go index 2ffbde977..7831af8f9 100644 --- a/pkg/namespaces/namespaces.go +++ b/pkg/namespaces/namespaces.go @@ -385,7 +385,7 @@ func (n NetworkMode) IsBridge() bool { // IsSlirp4netns indicates if we are running a rootless network stack func (n NetworkMode) IsSlirp4netns() bool { - return n == slirpType + return n == slirpType || strings.HasPrefix(string(n), slirpType+":") } // IsNS indicates a network namespace passed in by path (ns:<path>) diff --git a/pkg/network/config.go b/pkg/network/config.go index e5c981419..a504e0ad0 100644 --- a/pkg/network/config.go +++ b/pkg/network/config.go @@ -6,8 +6,8 @@ import ( "net" ) -// TODO once the libpod.conf file stuff is worked out, this should be modified -// to honor defines in the libpod.conf as well as overrides? +// TODO once the containers.conf file stuff is worked out, this should be modified +// to honor defines in the containers.conf as well as overrides? const ( // CNIConfigDir is the path where CNI config files exist diff --git a/pkg/spec/config_linux.go b/pkg/spec/config_linux.go index 93aa8a1b6..34cf9695c 100644 --- a/pkg/spec/config_linux.go +++ b/pkg/spec/config_linux.go @@ -167,7 +167,7 @@ func addDevice(g *generate.Generator, device string) error { GID: &dev.Gid, } g.AddDevice(linuxdev) - g.AddLinuxResourcesDevice(true, string(dev.Type), &dev.Major, &dev.Minor, dev.Permissions) + g.AddLinuxResourcesDevice(true, string(dev.Type), &dev.Major, &dev.Minor, string(dev.Permissions)) return nil } diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index 879c66895..55c3238d2 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -406,10 +406,6 @@ func CreateContainerFromCreateConfig(ctx context.Context, r *libpod.Runtime, cre return nil, err } - // Set the CreateCommand explicitly. Some (future) consumers of libpod - // might not want to set it. - options = append(options, libpod.WithCreateCommand()) - ctr, err := r.NewContainer(ctx, runtimeSpec, options...) if err != nil { return nil, err diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go index 6f52b88b1..b974772d5 100644 --- a/pkg/spec/spec.go +++ b/pkg/spec/spec.go @@ -505,10 +505,9 @@ func BlockAccessToKernelFilesystems(privileged, pidModeIsHost bool, g *generate. func addRlimits(config *CreateConfig, g *generate.Generator) error { var ( - kernelMax uint64 = 1048576 - isRootless = rootless.IsRootless() - nofileSet = false - nprocSet = false + isRootless = rootless.IsRootless() + nofileSet = false + nprocSet = false ) for _, u := range config.Resources.Ulimit { @@ -538,8 +537,8 @@ func addRlimits(config *CreateConfig, g *generate.Generator) error { // files and number of processes to the maximum they can be set to // (without overriding a sysctl) if !nofileSet { - max := kernelMax - current := kernelMax + max := define.RLimitDefaultValue + current := define.RLimitDefaultValue if isRootless { var rlimit unix.Rlimit if err := unix.Getrlimit(unix.RLIMIT_NOFILE, &rlimit); err != nil { @@ -555,8 +554,8 @@ func addRlimits(config *CreateConfig, g *generate.Generator) error { g.AddProcessRlimits("RLIMIT_NOFILE", max, current) } if !nprocSet { - max := kernelMax - current := kernelMax + max := define.RLimitDefaultValue + current := define.RLimitDefaultValue if isRootless { var rlimit unix.Rlimit if err := unix.Getrlimit(unix.RLIMIT_NPROC, &rlimit); err != nil { diff --git a/pkg/specgen/container_validate.go b/pkg/specgen/container_validate.go index bf03ff0e7..eddc4ad5d 100644 --- a/pkg/specgen/container_validate.go +++ b/pkg/specgen/container_validate.go @@ -28,6 +28,15 @@ func exclusiveOptions(opt1, opt2 string) error { // input for creating a container. func (s *SpecGenerator) Validate() error { + if rootless.IsRootless() { + if s.StaticIP != nil || s.StaticIPv6 != nil { + return ErrNoStaticIPRootless + } + if s.StaticMAC != nil { + return ErrNoStaticMACRootless + } + } + // // ContainerBasicConfig // @@ -72,10 +81,6 @@ func (s *SpecGenerator) Validate() error { if len(s.CapAdd) > 0 && s.Privileged { return exclusiveOptions("CapAdd", "privileged") } - // apparmor and privileged are exclusive - if len(s.ApparmorProfile) > 0 && s.Privileged { - return exclusiveOptions("AppArmorProfile", "privileged") - } // userns and idmappings conflict if s.UserNS.IsPrivate() && s.IDMappings == nil { return errors.Wrap(ErrInvalidSpecConfig, "IDMappings are required when not creating a User namespace") diff --git a/pkg/specgen/generate/config_linux.go b/pkg/specgen/generate/config_linux.go index 5d928cc5d..9b6bd2827 100644 --- a/pkg/specgen/generate/config_linux.go +++ b/pkg/specgen/generate/config_linux.go @@ -8,30 +8,19 @@ import ( "strings" "github.com/containers/libpod/v2/pkg/rootless" - "github.com/opencontainers/runc/libcontainer/configs" - "github.com/opencontainers/runc/libcontainer/devices" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-tools/generate" "github.com/pkg/errors" "golang.org/x/sys/unix" ) +var ( + errNotADevice = errors.New("not a device node") +) + func u32Ptr(i int64) *uint32 { u := uint32(i); return &u } func fmPtr(i int64) *os.FileMode { fm := os.FileMode(i); return &fm } -// Device transforms a libcontainer configs.Device to a specs.LinuxDevice object. -func Device(d *configs.Device) spec.LinuxDevice { - return spec.LinuxDevice{ - Type: string(d.Type), - Path: d.Path, - Major: d.Major, - Minor: d.Minor, - FileMode: fmPtr(int64(d.FileMode)), - UID: u32Ptr(int64(d.Uid)), - GID: u32Ptr(int64(d.Gid)), - } -} - func addPrivilegedDevices(g *generate.Generator) error { hostDevices, err := getDevices("/dev") if err != nil { @@ -77,7 +66,7 @@ func addPrivilegedDevices(g *generate.Generator) error { } } else { for _, d := range hostDevices { - g.AddDevice(Device(d)) + g.AddDevice(d) } // Add resources device - need to clear the existing one first. if g.Config.Linux.Resources != nil { @@ -161,6 +150,7 @@ func BlockAccessToKernelFilesystems(privileged, pidModeIsHost bool, g *generate. "/proc/scsi", "/sys/firmware", "/sys/fs/selinux", + "/sys/dev", } { g.AddLinuxMaskedPaths(mp) } @@ -183,7 +173,7 @@ func BlockAccessToKernelFilesystems(privileged, pidModeIsHost bool, g *generate. } // based on getDevices from runc (libcontainer/devices/devices.go) -func getDevices(path string) ([]*configs.Device, error) { +func getDevices(path string) ([]spec.LinuxDevice, error) { files, err := ioutil.ReadDir(path) if err != nil { if rootless.IsRootless() && os.IsPermission(err) { @@ -191,7 +181,7 @@ func getDevices(path string) ([]*configs.Device, error) { } return nil, err } - out := []*configs.Device{} + out := []spec.LinuxDevice{} for _, f := range files { switch { case f.IsDir(): @@ -211,10 +201,13 @@ func getDevices(path string) ([]*configs.Device, error) { } case f.Name() == "console": continue + case f.Mode()&os.ModeSymlink != 0: + continue } - device, err := devices.DeviceFromPath(filepath.Join(path, f.Name()), "rwm") + + device, err := deviceFromPath(filepath.Join(path, f.Name())) if err != nil { - if err == devices.ErrNotADevice { + if err == errNotADevice { continue } if os.IsNotExist(err) { @@ -222,7 +215,7 @@ func getDevices(path string) ([]*configs.Device, error) { } return nil, err } - out = append(out, device) + out = append(out, *device) } return out, nil } @@ -232,7 +225,7 @@ func addDevice(g *generate.Generator, device string) error { if err != nil { return err } - dev, err := devices.DeviceFromPath(src, permissions) + dev, err := deviceFromPath(src) if err != nil { return errors.Wrapf(err, "%s is not a valid device", src) } @@ -257,17 +250,8 @@ func addDevice(g *generate.Generator, device string) error { return nil } dev.Path = dst - linuxdev := spec.LinuxDevice{ - Path: dev.Path, - Type: string(dev.Type), - Major: dev.Major, - Minor: dev.Minor, - FileMode: &dev.FileMode, - UID: &dev.Uid, - GID: &dev.Gid, - } - g.AddDevice(linuxdev) - g.AddLinuxResourcesDevice(true, string(dev.Type), &dev.Major, &dev.Minor, dev.Permissions) + g.AddDevice(*dev) + g.AddLinuxResourcesDevice(true, dev.Type, &dev.Major, &dev.Minor, permissions) return nil } @@ -325,3 +309,40 @@ func IsValidDeviceMode(mode string) bool { } return true } + +// Copied from github.com/opencontainers/runc/libcontainer/devices +// Given the path to a device look up the information about a linux device +func deviceFromPath(path string) (*spec.LinuxDevice, error) { + var stat unix.Stat_t + err := unix.Lstat(path, &stat) + if err != nil { + return nil, err + } + var ( + devType string + mode = stat.Mode + devNumber = uint64(stat.Rdev) + m = os.FileMode(mode) + ) + + switch { + case mode&unix.S_IFBLK == unix.S_IFBLK: + devType = "b" + case mode&unix.S_IFCHR == unix.S_IFCHR: + devType = "c" + case mode&unix.S_IFIFO == unix.S_IFIFO: + devType = "p" + default: + return nil, errNotADevice + } + + return &spec.LinuxDevice{ + Type: devType, + Path: path, + FileMode: &m, + UID: &stat.Uid, + GID: &stat.Gid, + Major: int64(unix.Major(devNumber)), + Minor: int64(unix.Minor(devNumber)), + }, nil +} diff --git a/pkg/specgen/generate/container.go b/pkg/specgen/generate/container.go index dee79cf67..f0d52d0c3 100644 --- a/pkg/specgen/generate/container.go +++ b/pkg/specgen/generate/container.go @@ -10,6 +10,7 @@ import ( envLib "github.com/containers/libpod/v2/pkg/env" "github.com/containers/libpod/v2/pkg/signal" "github.com/containers/libpod/v2/pkg/specgen" + spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" "golang.org/x/sys/unix" ) @@ -169,6 +170,21 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat } } + // If caller did not specify Pids Limits load default + if s.ResourceLimits == nil || s.ResourceLimits.Pids == nil { + if s.CgroupsMode != "disabled" { + limit := rtc.PidsLimit() + if limit != 0 { + if s.ResourceLimits == nil { + s.ResourceLimits = &spec.LinuxResources{} + } + s.ResourceLimits.Pids = &spec.LinuxPids{ + Limit: limit, + } + } + } + } + return verifyContainerResources(s) } diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index 8df5b996e..934d5fbac 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -78,7 +78,9 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener } options := []libpod.CtrCreateOption{} - options = append(options, libpod.WithCreateCommand()) + if s.ContainerCreateCommand != nil { + options = append(options, libpod.WithCreateCommand(s.ContainerCreateCommand)) + } var newImage *image.Image if s.Rootfs != "" { @@ -104,11 +106,12 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener return nil, err } - if s.PreserveFDs > 0 { - options = append(options, libpod.WithPreserveFDs(s.PreserveFDs)) + command, err := makeCommand(ctx, s, newImage, rtc) + if err != nil { + return nil, err } - opts, err := createContainerOptions(ctx, rt, s, pod, finalVolumes, newImage) + opts, err := createContainerOptions(ctx, rt, s, pod, finalVolumes, newImage, command) if err != nil { return nil, err } @@ -120,17 +123,21 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener } options = append(options, libpod.WithExitCommand(exitCommandArgs)) - runtimeSpec, err := SpecGenToOCI(ctx, s, rt, rtc, newImage, finalMounts, pod) + runtimeSpec, err := SpecGenToOCI(ctx, s, rt, rtc, newImage, finalMounts, pod, command) if err != nil { return nil, err } return rt.NewContainer(ctx, runtimeSpec, options...) } -func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGenerator, pod *libpod.Pod, volumes []*specgen.NamedVolume, img *image.Image) ([]libpod.CtrCreateOption, error) { +func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGenerator, pod *libpod.Pod, volumes []*specgen.NamedVolume, img *image.Image, command []string) ([]libpod.CtrCreateOption, error) { var options []libpod.CtrCreateOption var err error + if s.PreserveFDs > 0 { + options = append(options, libpod.WithPreserveFDs(s.PreserveFDs)) + } + if s.Stdin { options = append(options, libpod.WithStdin()) } @@ -138,6 +145,9 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen. if s.Timezone != "" { options = append(options, libpod.WithTimezone(s.Timezone)) } + if s.Umask != "" { + options = append(options, libpod.WithUmask(s.Umask)) + } useSystemd := false switch s.Systemd { @@ -146,7 +156,6 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen. case "false": break case "", "true": - command := s.Command if len(command) == 0 { command, err = img.Cmd(ctx) if err != nil { @@ -195,6 +204,9 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen. for _, volume := range volumes { destinations = append(destinations, volume.Dest) } + for _, overlayVolume := range s.OverlayVolumes { + destinations = append(destinations, overlayVolume.Destination) + } options = append(options, libpod.WithUserVolumes(destinations)) if len(volumes) != 0 { @@ -209,6 +221,17 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen. options = append(options, libpod.WithNamedVolumes(vols)) } + if len(s.OverlayVolumes) != 0 { + var vols []*libpod.ContainerOverlayVolume + for _, v := range s.OverlayVolumes { + vols = append(vols, &libpod.ContainerOverlayVolume{ + Dest: v.Destination, + Source: v.Source, + }) + } + options = append(options, libpod.WithOverlayVolumes(vols)) + } + if s.Command != nil { options = append(options, libpod.WithCommand(s.Command)) } diff --git a/pkg/specgen/generate/namespaces.go b/pkg/specgen/generate/namespaces.go index 09d6ba445..a19009bc2 100644 --- a/pkg/specgen/generate/namespaces.go +++ b/pkg/specgen/generate/namespaces.go @@ -2,6 +2,7 @@ package generate import ( "context" + "fmt" "os" "strings" @@ -226,7 +227,11 @@ func namespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod. if err != nil { return nil, err } - toReturn = append(toReturn, libpod.WithNetNS(portMappings, postConfigureNetNS, "slirp4netns", nil)) + val := "slirp4netns" + if s.NetNS.Value != "" { + val = fmt.Sprintf("slirp4netns:%s", s.NetNS.Value) + } + toReturn = append(toReturn, libpod.WithNetNS(portMappings, postConfigureNetNS, val, nil)) case specgen.Bridge: portMappings, err := createPortMappings(ctx, s, img) if err != nil { @@ -261,6 +266,9 @@ func namespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod. if s.StaticMAC != nil { toReturn = append(toReturn, libpod.WithStaticMAC(*s.StaticMAC)) } + if s.NetworkOptions != nil { + toReturn = append(toReturn, libpod.WithNetworkOptions(s.NetworkOptions)) + } return toReturn, nil } @@ -465,7 +473,7 @@ func GetNamespaceOptions(ns []string) ([]libpod.PodCreateOption, error) { case "pid": options = append(options, libpod.WithPodPID()) case "user": - return erroredOptions, errors.Errorf("User sharing functionality not supported on pod level") + continue case "ipc": options = append(options, libpod.WithPodIPC()) case "uts": diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go index 0a485e7cd..f279aac1c 100644 --- a/pkg/specgen/generate/oci.go +++ b/pkg/specgen/generate/oci.go @@ -20,10 +20,9 @@ import ( func addRlimits(s *specgen.SpecGenerator, g *generate.Generator) error { var ( - kernelMax uint64 = 1048576 - isRootless = rootless.IsRootless() - nofileSet = false - nprocSet = false + isRootless = rootless.IsRootless() + nofileSet = false + nprocSet = false ) if s.Rlimits == nil { @@ -45,8 +44,8 @@ func addRlimits(s *specgen.SpecGenerator, g *generate.Generator) error { // files and number of processes to the maximum they can be set to // (without overriding a sysctl) if !nofileSet { - max := kernelMax - current := kernelMax + max := define.RLimitDefaultValue + current := define.RLimitDefaultValue if isRootless { var rlimit unix.Rlimit if err := unix.Getrlimit(unix.RLIMIT_NOFILE, &rlimit); err != nil { @@ -62,8 +61,8 @@ func addRlimits(s *specgen.SpecGenerator, g *generate.Generator) error { g.AddProcessRlimits("RLIMIT_NOFILE", max, current) } if !nprocSet { - max := kernelMax - current := kernelMax + max := define.RLimitDefaultValue + current := define.RLimitDefaultValue if isRootless { var rlimit unix.Rlimit if err := unix.Getrlimit(unix.RLIMIT_NPROC, &rlimit); err != nil { @@ -87,7 +86,7 @@ func makeCommand(ctx context.Context, s *specgen.SpecGenerator, img *image.Image finalCommand := []string{} entrypoint := s.Entrypoint - if len(entrypoint) == 0 && img != nil { + if entrypoint == nil && img != nil { newEntry, err := img.Entrypoint(ctx) if err != nil { return nil, err @@ -126,7 +125,7 @@ func makeCommand(ctx context.Context, s *specgen.SpecGenerator, img *image.Image return finalCommand, nil } -func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runtime, rtc *config.Config, newImage *image.Image, mounts []spec.Mount, pod *libpod.Pod) (*spec.Spec, error) { +func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runtime, rtc *config.Config, newImage *image.Image, mounts []spec.Mount, pod *libpod.Pod, finalCmd []string) (*spec.Spec, error) { var ( inUserNS bool ) @@ -252,10 +251,6 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt } g.SetProcessCwd(s.WorkDir) - finalCmd, err := makeCommand(ctx, s, newImage, rtc) - if err != nil { - return nil, err - } g.SetProcessArgs(finalCmd) g.SetProcessTerminal(s.Terminal) @@ -290,13 +285,6 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt } } - // SECURITY OPTS - g.SetProcessNoNewPrivileges(s.NoNewPrivileges) - - if !s.Privileged { - g.SetProcessApparmorProfile(s.ApparmorProfile) - } - BlockAccessToKernelFilesystems(s.Privileged, s.PidNS.IsHost(), &g) for name, val := range s.Env { diff --git a/pkg/specgen/generate/pod_create.go b/pkg/specgen/generate/pod_create.go index 690651a23..4fe1b6435 100644 --- a/pkg/specgen/generate/pod_create.go +++ b/pkg/specgen/generate/pod_create.go @@ -93,7 +93,9 @@ func createPodOptions(p *specgen.PodSpecGenerator) ([]libpod.PodCreateOption, er options = append(options, libpod.WithInfraContainerPorts(ports)) } options = append(options, libpod.WithPodCgroups()) - options = append(options, libpod.WithPodCreateCommand()) + if p.PodCreateCommand != nil { + options = append(options, libpod.WithPodCreateCommand(p.PodCreateCommand)) + } if len(p.InfraConmonPidFile) > 0 { options = append(options, libpod.WithInfraConmonPidFile(p.InfraConmonPidFile)) } diff --git a/pkg/specgen/generate/ports.go b/pkg/specgen/generate/ports.go index 9412ecfbf..c8d1c27c5 100644 --- a/pkg/specgen/generate/ports.go +++ b/pkg/specgen/generate/ports.go @@ -356,6 +356,7 @@ func checkProtocol(protocol string, allowSCTP bool) ([]string, error) { splitProto := strings.Split(protocol, ",") // Don't error on duplicates - just deduplicate for _, p := range splitProto { + p = strings.ToLower(p) switch p { case protoTCP, "": protocols[protoTCP] = struct{}{} diff --git a/pkg/specgen/generate/security.go b/pkg/specgen/generate/security.go index 70493cd5f..fcd1622f9 100644 --- a/pkg/specgen/generate/security.go +++ b/pkg/specgen/generate/security.go @@ -3,6 +3,7 @@ package generate import ( "strings" + "github.com/containers/common/pkg/apparmor" "github.com/containers/common/pkg/capabilities" "github.com/containers/common/pkg/config" "github.com/containers/libpod/v2/libpod" @@ -56,6 +57,28 @@ func setLabelOpts(s *specgen.SpecGenerator, runtime *libpod.Runtime, pidConfig s return nil } +func setupApparmor(s *specgen.SpecGenerator, rtc *config.Config, g *generate.Generator) error { + hasProfile := len(s.ApparmorProfile) > 0 + if !apparmor.IsEnabled() { + if hasProfile { + return errors.Errorf("Apparmor profile %q specified, but Apparmor is not enabled on this system", s.ApparmorProfile) + } + return nil + } + // If privileged and caller did not specify apparmor profiles return + if s.Privileged && !hasProfile { + return nil + } + if !hasProfile { + s.ApparmorProfile = rtc.Containers.ApparmorProfile + } + if len(s.ApparmorProfile) > 0 { + g.SetProcessApparmorProfile(s.ApparmorProfile) + } + + return nil +} + func securityConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator, newImage *image.Image, rtc *config.Config) error { var ( caplist []string @@ -105,6 +128,13 @@ func securityConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator, } } } + + g.SetProcessNoNewPrivileges(s.NoNewPrivileges) + + if err := setupApparmor(s, rtc, g); err != nil { + return err + } + configSpec := g.Config configSpec.Process.Capabilities.Bounding = caplist diff --git a/pkg/specgen/namespaces.go b/pkg/specgen/namespaces.go index 5f56b242b..9bf2c5d05 100644 --- a/pkg/specgen/namespaces.go +++ b/pkg/specgen/namespaces.go @@ -108,7 +108,9 @@ func validateNetNS(n *Namespace) error { return nil } switch n.NSMode { - case "", Default, Host, Path, FromContainer, FromPod, Private, NoNetwork, Bridge, Slirp: + case Slirp: + break + case "", Default, Host, Path, FromContainer, FromPod, Private, NoNetwork, Bridge: break default: return errors.Errorf("invalid network %q", n.NSMode) @@ -119,8 +121,8 @@ func validateNetNS(n *Namespace) error { if len(n.Value) < 1 { return errors.Errorf("namespace mode %s requires a value", n.NSMode) } - } else { - // All others must NOT set a string value + } else if n.NSMode != Slirp { + // All others except must NOT set a string value if len(n.Value) > 0 { return errors.Errorf("namespace value %s cannot be provided with namespace mode %s", n.Value, n.NSMode) } @@ -250,7 +252,7 @@ func ParseNetworkNamespace(ns string) (Namespace, []string, error) { var cniNetworks []string // Net defaults to Slirp on rootless switch { - case ns == "slirp4netns": + case ns == "slirp4netns", strings.HasPrefix(ns, "slirp4netns:"): toReturn.NSMode = Slirp case ns == "pod": toReturn.NSMode = FromPod diff --git a/pkg/specgen/pod_validate.go b/pkg/specgen/pod_validate.go index 070bb1e41..69c3b58ed 100644 --- a/pkg/specgen/pod_validate.go +++ b/pkg/specgen/pod_validate.go @@ -1,6 +1,7 @@ package specgen import ( + "github.com/containers/libpod/v2/pkg/rootless" "github.com/containers/libpod/v2/pkg/util" "github.com/pkg/errors" ) @@ -18,6 +19,16 @@ func exclusivePodOptions(opt1, opt2 string) error { // Validate verifies the input is valid func (p *PodSpecGenerator) Validate() error { + + if rootless.IsRootless() { + if p.StaticIP != nil { + return ErrNoStaticIPRootless + } + if p.StaticMAC != nil { + return ErrNoStaticMACRootless + } + } + // PodBasicConfig if p.NoInfra { if len(p.InfraCommand) > 0 { diff --git a/pkg/specgen/podspecgen.go b/pkg/specgen/podspecgen.go index 600d27004..3c32ec365 100644 --- a/pkg/specgen/podspecgen.go +++ b/pkg/specgen/podspecgen.go @@ -49,6 +49,12 @@ type PodBasicConfig struct { // Conflicts with NoInfra=true. // Optional. SharedNamespaces []string `json:"shared_namespaces,omitempty"` + // PodCreateCommand is the command used to create this pod. + // This will be shown in the output of Inspect() on the pod, and may + // also be used by some tools that wish to recreate the pod + // (e.g. `podman generate systemd --new`). + // Optional. + PodCreateCommand []string `json:"pod_create_command,omitempty"` } // PodNetworkConfig contains networking configuration for a pod. diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go index b4e10fa87..84a6c36a0 100644 --- a/pkg/specgen/specgen.go +++ b/pkg/specgen/specgen.go @@ -1,6 +1,7 @@ package specgen import ( + "errors" "net" "syscall" @@ -135,6 +136,13 @@ type ContainerBasicConfig struct { // Remove indicates if the container should be removed once it has been started // and exits Remove bool `json:"remove,omitempty"` + // ContainerCreateCommand is the command that was used to create this + // container. + // This will be shown in the output of Inspect() on the container, and + // may also be used by some tools that wish to recreate the container + // (e.g. `podman generate systemd --new`). + // Optional. + ContainerCreateCommand []string `json:"containerCreateCommand,omitempty"` // PreserveFDs is a number of additional file descriptors (in addition // to 0, 1, 2) that will be passed to the executed process. The total FDs // passed will be 3 + PreserveFDs. @@ -190,6 +198,9 @@ type ContainerStorageConfig struct { // there are conflicts. // Optional. Volumes []*NamedVolume `json:"volumes,omitempty"` + // Overlay volumes are named volumes that will be added to the container. + // Optional. + OverlayVolumes []*OverlayVolume `json:"overlay_volumes,omitempty"` // Devices are devices that will be added to the container. // Optional. Devices []spec.LinuxDevice `json:"devices,omitempty"` @@ -276,6 +287,8 @@ type ContainerSecurityConfig struct { // ReadOnlyFilesystem indicates that everything will be mounted // as read-only ReadOnlyFilesystem bool `json:"read_only_filesystem,omittempty"` + // Umask is the umask the init process of the container will be run with. + Umask string `json:"umask,omitempty"` } // ContainerCgroupConfig contains configuration information about a container's @@ -371,6 +384,9 @@ type ContainerNetworkConfig struct { // Conflicts with UseImageHosts. // Optional. HostAdd []string `json:"hostadd,omitempty"` + // NetworkOptions are additional options for each network + // Optional. + NetworkOptions map[string][]string `json:"network_options,omitempty"` } // ContainerResourceConfig contains information on container resource limits. @@ -432,6 +448,15 @@ type NamedVolume struct { Options []string } +// OverlayVolume holds information about a overlay volume that will be mounted into +// the container. +type OverlayVolume struct { + // Destination is the absolute path where the mount will be placed in the container. + Destination string `json:"destination"` + // Source specifies the source path of the mount. + Source string `json:"source,omitempty"` +} + // PortMapping is one or more ports that will be mapped into the container. type PortMapping struct { // HostIP is the IP that we will bind to on the host. @@ -462,6 +487,15 @@ type PortMapping struct { Protocol string `json:"protocol,omitempty"` } +var ( + // ErrNoStaticIPRootless is used when a rootless user requests to assign a static IP address + // to a pod or container + ErrNoStaticIPRootless error = errors.New("rootless containers and pods cannot be assigned static IP addresses") + // ErrNoStaticMACRootless is used when a rootless user requests to assign a static MAC address + // to a pod or container + ErrNoStaticMACRootless error = errors.New("rootless containers and pods cannot be assigned static MAC addresses") +) + // NewSpecGenerator returns a SpecGenerator struct given one of two mandatory inputs func NewSpecGenerator(arg string, rootfs bool) *SpecGenerator { csc := ContainerStorageConfig{} diff --git a/pkg/util/mountOpts.go b/pkg/util/mountOpts.go index 416e60728..eab2657e3 100644 --- a/pkg/util/mountOpts.go +++ b/pkg/util/mountOpts.go @@ -33,6 +33,10 @@ func ProcessOptions(options []string, isTmpfs bool, sourcePath string) ([]string // Some options have parameters - size, mode splitOpt := strings.SplitN(opt, "=", 2) switch splitOpt[0] { + case "O": + if len(options) > 1 { + return nil, errors.Wrapf(ErrDupeMntOption, "'O' option can not be used with other options") + } case "exec", "noexec": if foundExec { return nil, errors.Wrapf(ErrDupeMntOption, "only one of 'noexec' and 'exec' can be used") diff --git a/pkg/util/utils.go b/pkg/util/utils.go index 47d3e231d..9eeb116c0 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -555,7 +555,7 @@ func ValidatePullType(pullType string) (PullType, error) { switch pullType { case "always": return PullImageAlways, nil - case "missing": + case "missing", "IfNotPresent": return PullImageMissing, nil case "never": return PullImageNever, nil @@ -641,7 +641,7 @@ func ValidateSysctls(strSlice []string) (map[string]string, error) { } } if !foundMatch { - return nil, errors.Errorf("sysctl '%s' is not whitelisted", arr[0]) + return nil, errors.Errorf("sysctl '%s' is not allowed", arr[0]) } } return sysctl, nil diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go index 8650ba000..07b492331 100644 --- a/pkg/varlinkapi/containers.go +++ b/pkg/varlinkapi/containers.go @@ -754,7 +754,7 @@ func (i *VarlinkAPI) GetContainersLogs(call iopodman.VarlinkCall, names []string if err != nil { return call.ReplyErrorOccurred(err.Error()) } - if err := i.Runtime.Log(containers, &options, logChannel); err != nil { + if err := i.Runtime.Log(getContext(), containers, &options, logChannel); err != nil { return err } go func() { diff --git a/pkg/varlinkapi/create.go b/pkg/varlinkapi/create.go index 5c5f075f7..ac93939d9 100644 --- a/pkg/varlinkapi/create.go +++ b/pkg/varlinkapi/create.go @@ -915,10 +915,6 @@ func CreateContainerFromCreateConfig(ctx context.Context, r *libpod.Runtime, cre return nil, err } - // Set the CreateCommand explicitly. Some (future) consumers of libpod - // might not want to set it. - options = append(options, libpod.WithCreateCommand()) - ctr, err := r.NewContainer(ctx, runtimeSpec, options...) if err != nil { return nil, err |