diff options
Diffstat (limited to 'pkg')
91 files changed, 2772 insertions, 3063 deletions
diff --git a/pkg/api/handlers/compat/containers_create.go b/pkg/api/handlers/compat/containers_create.go index 12af40876..3d4bd4fb5 100644 --- a/pkg/api/handlers/compat/containers_create.go +++ b/pkg/api/handlers/compat/containers_create.go @@ -46,12 +46,12 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "NewFromLocal()")) return } - defaultContainerConfig, err := runtime.GetConfig() + containerConfig, err := runtime.GetConfig() if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "GetConfig()")) return } - cc, err := makeCreateConfig(defaultContainerConfig, input, newImage) + cc, err := makeCreateConfig(containerConfig, input, newImage) if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "makeCreatConfig()")) return @@ -60,7 +60,7 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { utils.CreateContainer(r.Context(), w, runtime, &cc) } -func makeCreateConfig(defaultContainerConfig *config.Config, input handlers.CreateContainerConfig, newImage *image2.Image) (createconfig.CreateConfig, error) { +func makeCreateConfig(containerConfig *config.Config, input handlers.CreateContainerConfig, newImage *image2.Image) (createconfig.CreateConfig, error) { var ( err error init bool @@ -81,7 +81,7 @@ func makeCreateConfig(defaultContainerConfig *config.Config, input handlers.Crea workDir = input.WorkingDir } - stopTimeout := defaultContainerConfig.Engine.StopTimeout + stopTimeout := containerConfig.Engine.StopTimeout if input.StopTimeout != nil { stopTimeout = uint(*input.StopTimeout) } diff --git a/pkg/api/handlers/compat/events.go b/pkg/api/handlers/compat/events.go index 8ef32716d..7ebfb0d1e 100644 --- a/pkg/api/handlers/compat/events.go +++ b/pkg/api/handlers/compat/events.go @@ -6,8 +6,8 @@ import ( "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/events" - "github.com/containers/libpod/pkg/api/handlers" "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/containers/libpod/pkg/domain/entities" "github.com/gorilla/schema" jsoniter "github.com/json-iterator/go" "github.com/pkg/errors" @@ -70,7 +70,7 @@ func GetEvents(w http.ResponseWriter, r *http.Request) { coder.SetEscapeHTML(true) for event := range eventChannel { - e := handlers.EventToApiEvent(event) + e := entities.ConvertToEntitiesEvent(*event) if err := coder.Encode(e); err != nil { logrus.Errorf("unable to write json: %q", err) } diff --git a/pkg/api/handlers/compat/info.go b/pkg/api/handlers/compat/info.go index 179b4a3e0..e9756a03f 100644 --- a/pkg/api/handlers/compat/info.go +++ b/pkg/api/handlers/compat/info.go @@ -10,12 +10,12 @@ import ( "time" "github.com/containers/common/pkg/config" + "github.com/containers/common/pkg/sysinfo" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/api/handlers" "github.com/containers/libpod/pkg/api/handlers/utils" "github.com/containers/libpod/pkg/rootless" - "github.com/containers/libpod/pkg/sysinfo" docker "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" "github.com/google/uuid" diff --git a/pkg/api/handlers/libpod/containers_create.go b/pkg/api/handlers/libpod/containers_create.go index f64132d55..40b6cacdb 100644 --- a/pkg/api/handlers/libpod/containers_create.go +++ b/pkg/api/handlers/libpod/containers_create.go @@ -1,6 +1,7 @@ package libpod import ( + "context" "encoding/json" "net/http" @@ -26,7 +27,7 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { utils.InternalServerError(w, err) return } - ctr, err := generate.MakeContainer(runtime, &sg) + ctr, err := generate.MakeContainer(context.Background(), runtime, &sg) if err != nil { utils.InternalServerError(w, err) return diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go index 284b33637..f7be5ce9a 100644 --- a/pkg/api/handlers/libpod/images.go +++ b/pkg/api/handlers/libpod/images.go @@ -22,6 +22,7 @@ import ( "github.com/containers/libpod/pkg/api/handlers" "github.com/containers/libpod/pkg/api/handlers/utils" "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/domain/infra/abi" "github.com/containers/libpod/pkg/util" utils2 "github.com/containers/libpod/utils" "github.com/gorilla/schema" @@ -282,7 +283,7 @@ func ImagesLoad(w http.ResponseWriter, r *http.Request) { return } } - utils.WriteResponse(w, http.StatusOK, entities.ImageLoadReport{Name: loadedImage}) + utils.WriteResponse(w, http.StatusOK, entities.ImageLoadReport{Names: split}) } func ImagesImport(w http.ResponseWriter, r *http.Request) { @@ -442,7 +443,7 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) { nil, util.PullImageAlways) if err != nil { - utils.InternalServerError(w, errors.Wrapf(err, "error pulling image %q", query.Reference)) + utils.InternalServerError(w, err) return } res = append(res, handlers.LibpodImagesPullReport{ID: newImage.ID()}) @@ -698,3 +699,30 @@ func SearchImages(w http.ResponseWriter, r *http.Request) { utils.WriteResponse(w, http.StatusOK, reports) } + +// ImagesRemove is the endpoint for image removal. +func ImagesRemove(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + decoder := r.Context().Value("decoder").(*schema.Decoder) + query := struct { + All bool `schema:"all"` + Force bool `schema:"force"` + Images []string `schema:"images"` + }{ + All: false, + Force: false, + } + + if err := decoder.Decode(&query, r.URL.Query()); err != nil { + utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, + errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + return + } + + opts := entities.ImageRemoveOptions{All: query.All, Force: query.Force} + + imageEngine := abi.ImageEngine{Libpod: runtime} + rmReport, rmError := imageEngine.Remove(r.Context(), query.Images, opts) + report := handlers.LibpodImagesRemoveReport{ImageRemoveReport: *rmReport, Error: rmError.Error()} + utils.WriteResponse(w, http.StatusOK, report) +} diff --git a/pkg/api/handlers/libpod/pods.go b/pkg/api/handlers/libpod/pods.go index 4eba4af05..c3f8d5d66 100644 --- a/pkg/api/handlers/libpod/pods.go +++ b/pkg/api/handlers/libpod/pods.go @@ -11,6 +11,7 @@ import ( "github.com/containers/libpod/pkg/api/handlers" "github.com/containers/libpod/pkg/api/handlers/utils" "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/domain/infra/abi" "github.com/containers/libpod/pkg/specgen" "github.com/containers/libpod/pkg/specgen/generate" "github.com/containers/libpod/pkg/util" @@ -427,3 +428,44 @@ func PodExists(w http.ResponseWriter, r *http.Request) { } utils.WriteResponse(w, http.StatusNoContent, "") } + +func PodStats(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + decoder := r.Context().Value("decoder").(*schema.Decoder) + + query := struct { + NamesOrIDs []string `schema:"namesOrIDs"` + All bool `schema:"all"` + }{ + // default would go here + } + if err := decoder.Decode(&query, r.URL.Query()); err != nil { + utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, + errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) + return + } + + // Validate input. + options := entities.PodStatsOptions{All: query.All} + if err := entities.ValidatePodStatsOptions(query.NamesOrIDs, &options); err != nil { + utils.InternalServerError(w, err) + } + + // Collect the stats and send them over the wire. + containerEngine := abi.ContainerEngine{Libpod: runtime} + reports, err := containerEngine.PodStats(r.Context(), query.NamesOrIDs, options) + + // Error checks as documented in swagger. + switch errors.Cause(err) { + case define.ErrNoSuchPod: + utils.Error(w, "one or more pods not found", http.StatusNotFound, err) + return + case nil: + // Nothing to do. + default: + utils.InternalServerError(w, err) + return + } + + utils.WriteResponse(w, http.StatusOK, reports) +} diff --git a/pkg/api/handlers/swagger/swagger.go b/pkg/api/handlers/swagger/swagger.go index ba97a4755..0aceaf5f6 100644 --- a/pkg/api/handlers/swagger/swagger.go +++ b/pkg/api/handlers/swagger/swagger.go @@ -49,6 +49,13 @@ type swagLibpodImagesPullResponse struct { Body handlers.LibpodImagesPullReport } +// Remove response +// swagger:response DocsLibpodImagesRemoveResponse +type swagLibpodImagesRemoveResponse struct { + // in:body + Body handlers.LibpodImagesRemoveReport +} + // Delete response // swagger:response DocsImageDeleteResponse type swagImageDeleteResponse struct { @@ -115,6 +122,13 @@ type swagPodTopResponse struct { } } +// List processes in pod +// swagger:response DocsPodStatsResponse +type swagPodStatsResponse struct { + // in:body + Body []*entities.PodStatsReport +} + // Inspect container // swagger:response LibpodInspectContainerResponse type swagLibpodInspectContainerResponse struct { @@ -136,7 +150,7 @@ type swagListPodsResponse struct { type swagInspectPodResponse struct { // in:body Body struct { - libpod.PodInspect + define.InspectPodData } } diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go index 4c081cf85..58a12ea6a 100644 --- a/pkg/api/handlers/types.go +++ b/pkg/api/handlers/types.go @@ -4,16 +4,13 @@ import ( "context" "encoding/json" "fmt" - "strconv" "time" "github.com/containers/image/v5/manifest" - "github.com/containers/libpod/libpod/events" libpodImage "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/domain/entities" docker "github.com/docker/docker/api/types" dockerContainer "github.com/docker/docker/api/types/container" - dockerEvents "github.com/docker/docker/api/types/events" dockerNetwork "github.com/docker/docker/api/types/network" "github.com/docker/go-connections/nat" "github.com/pkg/errors" @@ -39,6 +36,14 @@ type LibpodImagesPullReport struct { ID string `json:"id"` } +// LibpodImagesRemoveReport is the return type for image removal via the rest +// api. +type LibpodImagesRemoveReport struct { + entities.ImageRemoveReport + // Image removal requires is to return data and an error. + Error string +} + type ContainersPruneReport struct { docker.ContainersPruneReport } @@ -143,10 +148,6 @@ type PodCreateConfig struct { Share string `json:"share"` } -type Event struct { - dockerEvents.Message -} - type HistoryResponse struct { ID string `json:"Id"` Created int64 `json:"Created"` @@ -173,49 +174,6 @@ type ExecCreateResponse struct { docker.IDResponse } -func (e *Event) ToLibpodEvent() *events.Event { - exitCode, err := strconv.Atoi(e.Actor.Attributes["containerExitCode"]) - if err != nil { - return nil - } - status, err := events.StringToStatus(e.Action) - if err != nil { - return nil - } - t, err := events.StringToType(e.Type) - if err != nil { - return nil - } - lp := events.Event{ - ContainerExitCode: exitCode, - ID: e.Actor.ID, - Image: e.Actor.Attributes["image"], - Name: e.Actor.Attributes["name"], - Status: status, - Time: time.Unix(e.Time, e.TimeNano), - Type: t, - } - return &lp -} - -func EventToApiEvent(e *events.Event) *Event { - return &Event{dockerEvents.Message{ - Type: e.Type.String(), - Action: e.Status.String(), - Actor: dockerEvents.Actor{ - ID: e.ID, - Attributes: map[string]string{ - "image": e.Image, - "name": e.Name, - "containerExitCode": strconv.Itoa(e.ContainerExitCode), - }, - }, - Scope: "local", - Time: e.Time.Unix(), - TimeNano: e.Time.UnixNano(), - }} -} - func ImageToImageSummary(l *libpodImage.Image) (*entities.ImageSummary, error) { containers, err := l.Containers() if err != nil { @@ -311,7 +269,7 @@ func ImageDataToImageInspect(ctx context.Context, l *libpodImage.Image) (*ImageI // NetworkDisabled: false, // MacAddress: "", // OnBuild: nil, - // Labels: nil, + Labels: info.Labels, // StopSignal: "", // StopTimeout: nil, // Shell: nil, diff --git a/pkg/api/handlers/utils/errors.go b/pkg/api/handlers/utils/errors.go index aafc64353..3253a9be3 100644 --- a/pkg/api/handlers/utils/errors.go +++ b/pkg/api/handlers/utils/errors.go @@ -14,6 +14,9 @@ var ( ErrLinkNotSupport = errors.New("Link is not supported") ) +// TODO: document the exported functions in this file and make them more +// generic (e.g., not tied to one ctr/pod). + // Error formats an API response to an error // // apiMessage and code must match the container API, and are sent to client diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go index 6cc6f0cfa..f59dca6f5 100644 --- a/pkg/api/server/register_images.go +++ b/pkg/api/server/register_images.go @@ -822,6 +822,38 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // 500: // $ref: '#/responses/InternalError' r.Handle(VersionedPath("/libpod/images/import"), s.APIHandler(libpod.ImagesImport)).Methods(http.MethodPost) + // swagger:operation GET /libpod/images/remove libpod libpodImagesRemove + // --- + // tags: + // - images + // summary: Remove one or more images from the storage. + // description: Remove one or more images from the storage. + // parameters: + // - in: query + // name: images + // description: Images IDs or names to remove. + // type: array + // items: + // type: string + // - in: query + // name: all + // description: Remove all images. + // type: boolean + // default: true + // - in: query + // name: force + // description: Force image removal (including containers using the images). + // type: boolean + // produces: + // - application/json + // responses: + // 200: + // $ref: "#/responses/DocsLibpodImagesRemoveResponse" + // 400: + // $ref: "#/responses/BadParamError" + // 500: + // $ref: '#/responses/InternalError' + r.Handle(VersionedPath("/libpod/images/remove"), s.APIHandler(libpod.ImagesRemove)).Methods(http.MethodGet) // swagger:operation POST /libpod/images/pull libpod libpodImagesPull // --- // tags: diff --git a/pkg/api/server/register_pods.go b/pkg/api/server/register_pods.go index 63060af41..4156dd86b 100644 --- a/pkg/api/server/register_pods.go +++ b/pkg/api/server/register_pods.go @@ -286,9 +286,36 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // 200: // $ref: "#/responses/DocsPodTopResponse" // 404: - // $ref: "#/responses/NoSuchContainer" + // $ref: "#/responses/NoSuchPod" // 500: // $ref: "#/responses/InternalError" r.Handle(VersionedPath("/libpod/pods/{name}/top"), s.APIHandler(libpod.PodTop)).Methods(http.MethodGet) + // swagger:operation GET /libpod/pods/stats pods statsPod + // --- + // tags: + // - pods + // summary: Get stats for one or more pods + // description: Display a live stream of resource usage statistics for the containers in one or more pods + // parameters: + // - in: query + // name: all + // description: Provide statistics for all running pods. + // type: boolean + // - in: query + // name: namesOrIDs + // description: Names or IDs of pods. + // type: array + // items: + // type: string + // produces: + // - application/json + // responses: + // 200: + // $ref: "#/responses/DocsPodTopResponse" + // 404: + // $ref: "#/responses/NoSuchPod" + // 500: + // $ref: "#/responses/InternalError" + r.Handle(VersionedPath("/libpod/pods/stats"), s.APIHandler(libpod.PodStats)).Methods(http.MethodGet) return nil } diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go index 5f1a86183..ce2d152e0 100644 --- a/pkg/api/server/server.go +++ b/pkg/api/server/server.go @@ -8,6 +8,7 @@ import ( "os" "os/signal" "runtime" + goRuntime "runtime" "strings" "sync" "syscall" @@ -30,6 +31,7 @@ type APIServer struct { 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 } // Number of seconds to wait for next request, if exceeded shutdown server @@ -51,7 +53,7 @@ func NewServerWithSettings(runtime *libpod.Runtime, duration time.Duration, list func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Listener) (*APIServer, error) { // If listener not provided try socket activation protocol if listener == nil { - if _, found := os.LookupEnv("LISTEN_FDS"); !found { + if _, found := os.LookupEnv("LISTEN_PID"); !found { return nil, errors.Errorf("Cannot create API Server, no listener provided and socket activation protocol is not active.") } @@ -125,7 +127,7 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li if err != nil { methods = []string{"<N/A>"} } - logrus.Debugf("Methods: %s Path: %s", strings.Join(methods, ", "), path) + logrus.Debugf("Methods: %6s Path: %s", strings.Join(methods, ", "), path) return nil }) } @@ -145,6 +147,20 @@ func (s *APIServer) Serve() error { _ = s.Shutdown() }() + if logrus.IsLevelEnabled(logrus.DebugLevel) { + go func() { + pprofMux := mux.NewRouter() + pprofMux.PathPrefix("/debug/pprof").Handler(http.DefaultServeMux) + goRuntime.SetMutexProfileFraction(1) + goRuntime.SetBlockProfileRate(1) + s.pprof = &http.Server{Addr: "localhost:8888", Handler: pprofMux} + err := s.pprof.ListenAndServe() + if err != nil && err != http.ErrServerClosed { + logrus.Warn("Profiler Service failed: " + err.Error()) + } + }() + } + go func() { err := s.Server.Serve(s.Listener) if err != nil && err != http.ErrServerClosed { @@ -166,25 +182,29 @@ func (s *APIServer) Serve() error { // Shutdown is a clean shutdown waiting on existing clients func (s *APIServer) Shutdown() error { + if s.idleTracker.Duration == UnlimitedServiceDuration { + logrus.Debug("APIServer.Shutdown ignored as Duration is UnlimitedService.") + return nil + } + + // Gracefully shutdown server(s), duration of wait same as idle window + // TODO: Should we really wait the idle window for shutdown? + ctx, cancel := context.WithTimeout(context.Background(), s.idleTracker.Duration) + defer cancel() + if logrus.IsLevelEnabled(logrus.DebugLevel) { _, file, line, _ := runtime.Caller(1) logrus.Debugf("APIServer.Shutdown by %s:%d, %d/%d connection(s)", file, line, s.idleTracker.ActiveConnections(), s.idleTracker.TotalConnections()) + if err := s.pprof.Shutdown(ctx); err != nil { + logrus.Warn("Failed to cleanly shutdown pprof Server: " + err.Error()) + } } - // Duration == 0 flags no auto-shutdown of the server - if s.idleTracker.Duration == 0 { - logrus.Debug("APIServer.Shutdown ignored as Duration == 0") - return nil - } - - // Gracefully shutdown server, duration of wait same as idle window - ctx, cancel := context.WithTimeout(context.Background(), s.idleTracker.Duration) - defer cancel() go func() { err := s.Server.Shutdown(ctx) if err != nil && err != context.Canceled && err != http.ErrServerClosed { - logrus.Errorf("Failed to cleanly shutdown APIServer: %s", err.Error()) + logrus.Error("Failed to cleanly shutdown APIServer: " + err.Error()) } }() <-ctx.Done() diff --git a/pkg/api/types/types.go b/pkg/api/types/types.go new file mode 100644 index 000000000..1b91364e3 --- /dev/null +++ b/pkg/api/types/types.go @@ -0,0 +1,9 @@ +package types + +const ( + // DefaultAPIVersion is the version of the API the server defaults to. + DefaultAPIVersion = "1.40" // See https://docs.docker.com/engine/api/v1.40/ + + // DefaultAPIVersion is the minimal required version of the API. + MinimalAPIVersion = "1.24" +) diff --git a/pkg/apparmor/apparmor.go b/pkg/apparmor/apparmor.go deleted file mode 100644 index 8e17361cb..000000000 --- a/pkg/apparmor/apparmor.go +++ /dev/null @@ -1,19 +0,0 @@ -package apparmor - -import ( - "errors" - - "github.com/containers/common/pkg/config" - libpodVersion "github.com/containers/libpod/version" -) - -var ( - // DefaultLipodProfilePrefix is used for version-independent presence checks. - DefaultLipodProfilePrefix = config.DefaultApparmorProfile - // DefaultLibpodProfile is the name of default libpod AppArmor profile. - DefaultLibpodProfile = DefaultLipodProfilePrefix + "-" + libpodVersion.Version - // ErrApparmorUnsupported indicates that AppArmor support is not supported. - ErrApparmorUnsupported = errors.New("AppArmor is not supported") - // ErrApparmorRootless indicates that AppArmor support is not supported in rootless mode. - ErrApparmorRootless = errors.New("AppArmor is not supported in rootless mode") -) diff --git a/pkg/apparmor/apparmor_linux.go b/pkg/apparmor/apparmor_linux.go deleted file mode 100644 index 33710ff56..000000000 --- a/pkg/apparmor/apparmor_linux.go +++ /dev/null @@ -1,289 +0,0 @@ -// +build linux,apparmor - -package apparmor - -import ( - "bufio" - "bytes" - "fmt" - "io" - "os" - "os/exec" - "path" - "strconv" - "strings" - "text/template" - - "github.com/containers/libpod/pkg/rootless" - runcaa "github.com/opencontainers/runc/libcontainer/apparmor" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" -) - -// profileDirectory is the file store for apparmor profiles and macros. -var profileDirectory = "/etc/apparmor.d" - -// IsEnabled returns true if AppArmor is enabled on the host. -func IsEnabled() bool { - if rootless.IsRootless() { - return false - } - return runcaa.IsEnabled() -} - -// profileData holds information about the given profile for generation. -type profileData struct { - // Name is profile name. - Name string - // Imports defines the apparmor functions to import, before defining the profile. - Imports []string - // InnerImports defines the apparmor functions to import in the profile. - InnerImports []string - // Version is the {major, minor, patch} version of apparmor_parser as a single number. - Version int -} - -// generateDefault creates an apparmor profile from ProfileData. -func (p *profileData) generateDefault(out io.Writer) error { - compiled, err := template.New("apparmor_profile").Parse(libpodProfileTemplate) - if err != nil { - return err - } - - if macroExists("tunables/global") { - p.Imports = append(p.Imports, "#include <tunables/global>") - } else { - p.Imports = append(p.Imports, "@{PROC}=/proc/") - } - - if macroExists("abstractions/base") { - p.InnerImports = append(p.InnerImports, "#include <abstractions/base>") - } - - ver, err := getAAParserVersion() - if err != nil { - return err - } - p.Version = ver - - return compiled.Execute(out, p) -} - -// macrosExists checks if the passed macro exists. -func macroExists(m string) bool { - _, err := os.Stat(path.Join(profileDirectory, m)) - return err == nil -} - -// InstallDefault generates a default profile and loads it into the kernel -// using 'apparmor_parser'. -func InstallDefault(name string) error { - if rootless.IsRootless() { - return ErrApparmorRootless - } - - p := profileData{ - Name: name, - } - - cmd := exec.Command("apparmor_parser", "-Kr") - pipe, err := cmd.StdinPipe() - if err != nil { - return err - } - if err := cmd.Start(); err != nil { - if pipeErr := pipe.Close(); pipeErr != nil { - logrus.Errorf("unable to close apparmor pipe: %q", pipeErr) - } - return err - } - if err := p.generateDefault(pipe); err != nil { - if pipeErr := pipe.Close(); pipeErr != nil { - logrus.Errorf("unable to close apparmor pipe: %q", pipeErr) - } - if cmdErr := cmd.Wait(); cmdErr != nil { - logrus.Errorf("unable to wait for apparmor command: %q", cmdErr) - } - return err - } - - if pipeErr := pipe.Close(); pipeErr != nil { - logrus.Errorf("unable to close apparmor pipe: %q", pipeErr) - } - return cmd.Wait() -} - -// DefaultContent returns the default profile content as byte slice. The -// profile is named as the provided `name`. The function errors if the profile -// generation fails. -func DefaultContent(name string) ([]byte, error) { - p := profileData{Name: name} - var bytes bytes.Buffer - if err := p.generateDefault(&bytes); err != nil { - return nil, err - } - return bytes.Bytes(), nil -} - -// IsLoaded checks if a profile with the given name has been loaded into the -// kernel. -func IsLoaded(name string) (bool, error) { - if name != "" && rootless.IsRootless() { - return false, errors.Wrapf(ErrApparmorRootless, "cannot load AppArmor profile %q", name) - } - - file, err := os.Open("/sys/kernel/security/apparmor/profiles") - if err != nil { - if os.IsNotExist(err) { - return false, nil - } - return false, err - } - defer file.Close() - - r := bufio.NewReader(file) - for { - p, err := r.ReadString('\n') - if err == io.EOF { - break - } - if err != nil { - return false, err - } - if strings.HasPrefix(p, name+" ") { - return true, nil - } - } - - return false, nil -} - -// execAAParser runs `apparmor_parser` with the passed arguments. -func execAAParser(dir string, args ...string) (string, error) { - c := exec.Command("apparmor_parser", args...) - c.Dir = dir - - output, err := c.CombinedOutput() - if err != nil { - return "", fmt.Errorf("running `%s %s` failed with output: %s\nerror: %v", c.Path, strings.Join(c.Args, " "), output, err) - } - - return string(output), nil -} - -// getAAParserVersion returns the major and minor version of apparmor_parser. -func getAAParserVersion() (int, error) { - output, err := execAAParser("", "--version") - if err != nil { - return -1, err - } - return parseAAParserVersion(output) -} - -// parseAAParserVersion parses the given `apparmor_parser --version` output and -// returns the major and minor version number as an integer. -func parseAAParserVersion(output string) (int, error) { - // output is in the form of the following: - // AppArmor parser version 2.9.1 - // Copyright (C) 1999-2008 Novell Inc. - // Copyright 2009-2012 Canonical Ltd. - lines := strings.SplitN(output, "\n", 2) - words := strings.Split(lines[0], " ") - version := words[len(words)-1] - - // split by major minor version - v := strings.Split(version, ".") - if len(v) == 0 || len(v) > 3 { - return -1, fmt.Errorf("parsing version failed for output: `%s`", output) - } - - // Default the versions to 0. - var majorVersion, minorVersion, patchLevel int - - majorVersion, err := strconv.Atoi(v[0]) - if err != nil { - return -1, err - } - - if len(v) > 1 { - minorVersion, err = strconv.Atoi(v[1]) - if err != nil { - return -1, err - } - } - if len(v) > 2 { - patchLevel, err = strconv.Atoi(v[2]) - if err != nil { - return -1, err - } - } - - // major*10^5 + minor*10^3 + patch*10^0 - numericVersion := majorVersion*1e5 + minorVersion*1e3 + patchLevel - return numericVersion, nil - -} - -// CheckProfileAndLoadDefault checks if the specified profile is loaded and -// loads the DefaultLibpodProfile if the specified on is prefixed by -// DefaultLipodProfilePrefix. This allows to always load and apply the latest -// default AppArmor profile. Note that AppArmor requires root. If it's a -// default profile, return DefaultLipodProfilePrefix, otherwise the specified -// one. -func CheckProfileAndLoadDefault(name string) (string, error) { - if name == "unconfined" { - return name, nil - } - - // AppArmor is not supported in rootless mode as it requires root - // privileges. Return an error in case a specific profile is specified. - if rootless.IsRootless() { - if name != "" { - return "", errors.Wrapf(ErrApparmorRootless, "cannot load AppArmor profile %q", name) - } else { - logrus.Debug("skipping loading default AppArmor profile (rootless mode)") - return "", nil - } - } - - // Check if AppArmor is disabled and error out if a profile is to be set. - if !runcaa.IsEnabled() { - if name == "" { - return "", nil - } else { - return "", fmt.Errorf("profile %q specified but AppArmor is disabled on the host", name) - } - } - - // If the specified name is not empty or is not a default libpod one, - // ignore it and return the name. - if name != "" && !strings.HasPrefix(name, DefaultLipodProfilePrefix) { - isLoaded, err := IsLoaded(name) - if err != nil { - return "", err - } - if !isLoaded { - return "", fmt.Errorf("AppArmor profile %q specified but not loaded", name) - } - return name, nil - } - - name = DefaultLibpodProfile - // To avoid expensive redundant loads on each invocation, check - // if it's loaded before installing it. - isLoaded, err := IsLoaded(name) - if err != nil { - return "", err - } - if !isLoaded { - err = InstallDefault(name) - if err != nil { - return "", err - } - logrus.Infof("successfully loaded AppAmor profile %q", name) - } else { - logrus.Infof("AppAmor profile %q is already loaded", name) - } - - return name, nil -} diff --git a/pkg/apparmor/apparmor_linux_template.go b/pkg/apparmor/apparmor_linux_template.go deleted file mode 100644 index 8d9a92ef7..000000000 --- a/pkg/apparmor/apparmor_linux_template.go +++ /dev/null @@ -1,49 +0,0 @@ -// +build linux,apparmor - -package apparmor - -const libpodProfileTemplate = ` -{{range $value := .Imports}} -{{$value}} -{{end}} - -profile {{.Name}} flags=(attach_disconnected,mediate_deleted) { -{{range $value := .InnerImports}} - {{$value}} -{{end}} - - network, - capability, - file, - umount, - -{{if ge .Version 208096}} - # Allow signals from privileged profiles and from within the same profile - signal (receive) peer=unconfined, - signal (send,receive) peer={{.Name}}, -{{end}} - - deny @{PROC}/* w, # deny write for all files directly in /proc (not in a subdir) - # deny write to files not in /proc/<number>/** or /proc/sys/** - deny @{PROC}/{[^1-9],[^1-9][^0-9],[^1-9s][^0-9y][^0-9s],[^1-9][^0-9][^0-9][^0-9]*}/** w, - deny @{PROC}/sys/[^k]** w, # deny /proc/sys except /proc/sys/k* (effectively /proc/sys/kernel) - deny @{PROC}/sys/kernel/{?,??,[^s][^h][^m]**} w, # deny everything except shm* in /proc/sys/kernel/ - deny @{PROC}/sysrq-trigger rwklx, - deny @{PROC}/kcore rwklx, - - deny mount, - - deny /sys/[^f]*/** wklx, - deny /sys/f[^s]*/** wklx, - deny /sys/fs/[^c]*/** wklx, - deny /sys/fs/c[^g]*/** wklx, - deny /sys/fs/cg[^r]*/** wklx, - deny /sys/firmware/** rwklx, - deny /sys/kernel/security/** rwklx, - -{{if ge .Version 208095}} - # suppress ptrace denials when using using 'ps' inside a container - ptrace (trace,read) peer={{.Name}}, -{{end}} -} -` diff --git a/pkg/apparmor/apparmor_linux_test.go b/pkg/apparmor/apparmor_linux_test.go deleted file mode 100644 index 3ff6e18bc..000000000 --- a/pkg/apparmor/apparmor_linux_test.go +++ /dev/null @@ -1,140 +0,0 @@ -// +build linux,apparmor - -package apparmor - -import ( - "os" - "testing" -) - -type versionExpected struct { - output string - version int -} - -func TestParseAAParserVersion(t *testing.T) { - if !IsEnabled() { - t.Skip("AppArmor disabled: skipping tests") - } - versions := []versionExpected{ - { - output: `AppArmor parser version 2.10 -Copyright (C) 1999-2008 Novell Inc. -Copyright 2009-2012 Canonical Ltd. - -`, - version: 210000, - }, - { - output: `AppArmor parser version 2.8 -Copyright (C) 1999-2008 Novell Inc. -Copyright 2009-2012 Canonical Ltd. - -`, - version: 208000, - }, - { - output: `AppArmor parser version 2.20 -Copyright (C) 1999-2008 Novell Inc. -Copyright 2009-2012 Canonical Ltd. - -`, - version: 220000, - }, - { - output: `AppArmor parser version 2.05 -Copyright (C) 1999-2008 Novell Inc. -Copyright 2009-2012 Canonical Ltd. - -`, - version: 205000, - }, - { - output: `AppArmor parser version 2.9.95 -Copyright (C) 1999-2008 Novell Inc. -Copyright 2009-2012 Canonical Ltd. - -`, - version: 209095, - }, - { - output: `AppArmor parser version 3.14.159 -Copyright (C) 1999-2008 Novell Inc. -Copyright 2009-2012 Canonical Ltd. - -`, - version: 314159, - }, - } - - for _, v := range versions { - version, err := parseAAParserVersion(v.output) - if err != nil { - t.Fatalf("expected error to be nil for %#v, got: %v", v, err) - } - if version != v.version { - t.Fatalf("expected version to be %d, was %d, for: %#v\n", v.version, version, v) - } - } -} - -const ( - aapath = "/sys/kernel/security/apparmor/" - profile = "libpod-default-testing" -) - -func TestInstallDefault(t *testing.T) { - if _, err := os.Stat(aapath); err != nil { - t.Skip("AppArmor isn't available in this environment") - } - - // removes `profile` - removeProfile := func() error { - path := aapath + ".remove" - - f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, os.ModeAppend) - if err != nil { - return err - } - defer f.Close() - - _, err = f.WriteString(profile) - return err - } - - // makes sure `profile` is loaded according to `state` - checkLoaded := func(state bool) { - loaded, err := IsLoaded(profile) - if err != nil { - t.Fatalf("Error searching AppArmor profile '%s': %v", profile, err) - } - if state != loaded { - if state { - t.Fatalf("AppArmor profile '%s' isn't loaded but should", profile) - } else { - t.Fatalf("AppArmor profile '%s' is loaded but shouldn't", profile) - } - } - } - - // test installing the profile - if err := InstallDefault(profile); err != nil { - t.Fatalf("Couldn't install AppArmor profile '%s': %v", profile, err) - } - checkLoaded(true) - - // remove the profile and check again - if err := removeProfile(); err != nil { - t.Fatalf("Couldn't remove AppArmor profile '%s': %v", profile, err) - } - checkLoaded(false) -} - -func TestDefaultContent(t *testing.T) { - if _, err := os.Stat(aapath); err != nil { - t.Skip("AppArmor isn't available in this environment") - } - if _, err := DefaultContent(profile); err != nil { - t.Fatalf("Couldn't retrieve default AppArmor profile content '%s': %v", profile, err) - } -} diff --git a/pkg/apparmor/apparmor_unsupported.go b/pkg/apparmor/apparmor_unsupported.go deleted file mode 100644 index 13469f1b6..000000000 --- a/pkg/apparmor/apparmor_unsupported.go +++ /dev/null @@ -1,31 +0,0 @@ -// +build !linux !apparmor - -package apparmor - -// IsEnabled dummy. -func IsEnabled() bool { - return false -} - -// InstallDefault dummy. -func InstallDefault(name string) error { - return ErrApparmorUnsupported -} - -// IsLoaded dummy. -func IsLoaded(name string) (bool, error) { - return false, ErrApparmorUnsupported -} - -// CheckProfileAndLoadDefault dummy. -func CheckProfileAndLoadDefault(name string) (string, error) { - if name == "" { - return "", nil - } - return "", ErrApparmorUnsupported -} - -// DefaultContent dummy. -func DefaultContent(name string) ([]byte, error) { - return nil, nil -} diff --git a/pkg/bindings/connection.go b/pkg/bindings/connection.go index 4fe4dd72d..da3755fc8 100644 --- a/pkg/bindings/connection.go +++ b/pkg/bindings/connection.go @@ -15,7 +15,7 @@ import ( "strings" "time" - "github.com/containers/libpod/pkg/api/handlers" + "github.com/containers/libpod/pkg/api/types" jsoniter "github.com/json-iterator/go" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -27,7 +27,7 @@ var ( basePath = &url.URL{ Scheme: "http", Host: "d", - Path: "/v" + handlers.MinimalApiVersion + "/libpod", + Path: "/v" + types.MinimalAPIVersion + "/libpod", } ) @@ -126,7 +126,7 @@ func tcpClient(_url *url.URL) (*http.Client, error) { return &http.Client{ Transport: &http.Transport{ DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { - return net.Dial("tcp", _url.Path) + return net.Dial("tcp", _url.Host) }, DisableCompression: true, }, diff --git a/pkg/bindings/images/images.go b/pkg/bindings/images/images.go index 3550c3968..06f01c7a0 100644 --- a/pkg/bindings/images/images.go +++ b/pkg/bindings/images/images.go @@ -74,7 +74,7 @@ func GetImage(ctx context.Context, nameOrID string, size *bool) (*entities.Image return &inspectedData, response.Process(&inspectedData) } -func ImageTree(ctx context.Context, nameOrId string) error { +func Tree(ctx context.Context, nameOrId string) error { return bindings.ErrNotImplemented } @@ -109,23 +109,34 @@ func Load(ctx context.Context, r io.Reader, name *string) (*entities.ImageLoadRe return &report, response.Process(&report) } -// Remove deletes an image from local storage. The optional force parameter will forcibly remove -// the image by removing all all containers, including those that are Running, first. -func Remove(ctx context.Context, nameOrID string, force *bool) ([]map[string]string, error) { - var deletes []map[string]string +// Remove deletes an image from local storage. The optional force parameter +// will forcibly remove the image by removing all all containers, including +// those that are Running, first. +func Remove(ctx context.Context, images []string, opts entities.ImageRemoveOptions) (*entities.ImageRemoveReport, error) { + var report handlers.LibpodImagesRemoveReport conn, err := bindings.GetClient(ctx) if err != nil { return nil, err } params := url.Values{} - if force != nil { - params.Set("force", strconv.FormatBool(*force)) + params.Set("all", strconv.FormatBool(opts.All)) + params.Set("force", strconv.FormatBool(opts.Force)) + for _, i := range images { + params.Add("images", i) } - response, err := conn.DoRequest(nil, http.MethodDelete, "/images/%s", params, nameOrID) + + response, err := conn.DoRequest(nil, http.MethodGet, "/images/remove", params) if err != nil { return nil, err } - return deletes, response.Process(&deletes) + if err := response.Process(&report); err != nil { + return nil, err + } + var rmError error + if report.Error != "" { + rmError = errors.New(report.Error) + } + return &report.ImageRemoveReport, rmError } // Export saves an image from local storage as a tarball or image archive. The optional format diff --git a/pkg/bindings/pods/pods.go b/pkg/bindings/pods/pods.go index 3c60fa2a0..b213c8c73 100644 --- a/pkg/bindings/pods/pods.go +++ b/pkg/bindings/pods/pods.go @@ -2,6 +2,7 @@ package pods import ( "context" + "errors" "net/http" "net/url" "strconv" @@ -189,11 +190,6 @@ func Start(ctx context.Context, nameOrID string) (*entities.PodStartReport, erro return &report, response.Process(&report) } -func Stats() error { - // TODO - return bindings.ErrNotImplemented -} - // Stop stops all containers in a Pod. The optional timeout parameter can be // used to override the timeout before the container is killed. func Stop(ctx context.Context, nameOrID string, timeout *int) (*entities.PodStopReport, error) { @@ -264,3 +260,26 @@ func Unpause(ctx context.Context, nameOrID string) (*entities.PodUnpauseReport, } return &report, response.Process(&report) } + +// Stats display resource-usage statistics of one or more pods. +func Stats(ctx context.Context, namesOrIDs []string, options entities.PodStatsOptions) ([]*entities.PodStatsReport, error) { + if options.Latest { + return nil, errors.New("latest is not supported") + } + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + params := url.Values{} + for _, i := range namesOrIDs { + params.Add("namesOrIDs", i) + } + params.Set("all", strconv.FormatBool(options.All)) + + var reports []*entities.PodStatsReport + response, err := conn.DoRequest(nil, http.MethodGet, "/pods/stats", params) + if err != nil { + return nil, err + } + return reports, response.Process(&reports) +} diff --git a/pkg/bindings/system/system.go b/pkg/bindings/system/system.go index aee9a7ffd..df6b529de 100644 --- a/pkg/bindings/system/system.go +++ b/pkg/bindings/system/system.go @@ -8,7 +8,6 @@ import ( "net/url" "strconv" - "github.com/containers/libpod/pkg/api/handlers" "github.com/containers/libpod/pkg/bindings" "github.com/containers/libpod/pkg/domain/entities" "github.com/pkg/errors" @@ -18,7 +17,7 @@ import ( // Events allows you to monitor libdpod related events like container creation and // removal. The events are then passed to the eventChan provided. The optional cancelChan // can be used to cancel the read of events and close down the HTTP connection. -func Events(ctx context.Context, eventChan chan (handlers.Event), cancelChan chan bool, since, until *string, filters map[string][]string) error { +func Events(ctx context.Context, eventChan chan (entities.Event), cancelChan chan bool, since, until *string, filters map[string][]string) error { conn, err := bindings.GetClient(ctx) if err != nil { return err @@ -50,7 +49,7 @@ func Events(ctx context.Context, eventChan chan (handlers.Event), cancelChan cha } dec := json.NewDecoder(response.Body) for { - e := handlers.Event{} + e := entities.Event{} if err := dec.Decode(&e); err != nil { if err == io.EOF { break diff --git a/pkg/bindings/test/common_test.go b/pkg/bindings/test/common_test.go index 6b8d6788c..f33e42440 100644 --- a/pkg/bindings/test/common_test.go +++ b/pkg/bindings/test/common_test.go @@ -3,13 +3,13 @@ package test_bindings import ( "context" "fmt" - "github.com/containers/libpod/libpod/define" "io/ioutil" "os" "os/exec" "path/filepath" "strings" + "github.com/containers/libpod/libpod/define" . "github.com/containers/libpod/pkg/bindings" "github.com/containers/libpod/pkg/bindings/containers" "github.com/containers/libpod/pkg/specgen" @@ -189,7 +189,7 @@ func (b *bindingTest) restoreImageFromCache(i testImage) { // Run a container within or without a pod // and add or append the alpine image to it func (b *bindingTest) RunTopContainer(containerName *string, insidePod *bool, podName *string) (string, error) { - s := specgen.NewSpecGenerator(alpine.name) + s := specgen.NewSpecGenerator(alpine.name, false) s.Terminal = false s.Command = []string{"top"} if containerName != nil { diff --git a/pkg/bindings/test/containers_test.go b/pkg/bindings/test/containers_test.go index e288dc368..c79d89b73 100644 --- a/pkg/bindings/test/containers_test.go +++ b/pkg/bindings/test/containers_test.go @@ -360,7 +360,7 @@ var _ = Describe("Podman containers ", func() { It("logging", func() { stdoutChan := make(chan string, 10) - s := specgen.NewSpecGenerator(alpine.name) + s := specgen.NewSpecGenerator(alpine.name, false) s.Terminal = true s.Command = []string{"date", "-R"} r, err := containers.CreateWithSpec(bt.conn, s) @@ -521,7 +521,7 @@ var _ = Describe("Podman containers ", func() { }) It("container init", func() { - s := specgen.NewSpecGenerator(alpine.name) + s := specgen.NewSpecGenerator(alpine.name, false) ctr, err := containers.CreateWithSpec(bt.conn, s) Expect(err).To(BeNil()) err = containers.ContainerInit(bt.conn, ctr.ID) diff --git a/pkg/bindings/test/create_test.go b/pkg/bindings/test/create_test.go index f83a9b14d..a63aa79cf 100644 --- a/pkg/bindings/test/create_test.go +++ b/pkg/bindings/test/create_test.go @@ -31,7 +31,7 @@ var _ = Describe("Create containers ", func() { }) It("create a container running top", func() { - s := specgen.NewSpecGenerator(alpine.name) + s := specgen.NewSpecGenerator(alpine.name, false) s.Command = []string{"top"} s.Terminal = true s.Name = "top" diff --git a/pkg/bindings/test/info_test.go b/pkg/bindings/test/info_test.go index d0e651134..64f2b458f 100644 --- a/pkg/bindings/test/info_test.go +++ b/pkg/bindings/test/info_test.go @@ -45,7 +45,7 @@ var _ = Describe("Podman info", func() { }) It("podman info container counts", func() { - s := specgen.NewSpecGenerator(alpine.name) + s := specgen.NewSpecGenerator(alpine.name, false) _, err := containers.CreateWithSpec(bt.conn, s) Expect(err).To(BeNil()) diff --git a/pkg/bindings/test/pods_test.go b/pkg/bindings/test/pods_test.go index 4d682a522..8a0b9c7a6 100644 --- a/pkg/bindings/test/pods_test.go +++ b/pkg/bindings/test/pods_test.go @@ -174,8 +174,7 @@ var _ = Describe("Podman pods", func() { Expect(err).To(BeNil()) response, err := pods.Inspect(bt.conn, newpod) Expect(err).To(BeNil()) - // FIXME sujil please fix this - //Expect(response.Status).To(Equal(define.PodStatePaused)) + Expect(response.State).To(Equal(define.PodStatePaused)) for _, i := range response.Containers { Expect(define.StringToContainerStatus(i.State)). To(Equal(define.ContainerStatePaused)) @@ -186,8 +185,7 @@ var _ = Describe("Podman pods", func() { Expect(err).To(BeNil()) response, err = pods.Inspect(bt.conn, newpod) Expect(err).To(BeNil()) - // FIXME sujil please fix this - //Expect(response.State.Status).To(Equal(define.PodStateRunning)) + Expect(response.State).To(Equal(define.PodStateRunning)) for _, i := range response.Containers { Expect(define.StringToContainerStatus(i.State)). To(Equal(define.ContainerStateRunning)) @@ -219,8 +217,7 @@ var _ = Describe("Podman pods", func() { response, err := pods.Inspect(bt.conn, newpod) Expect(err).To(BeNil()) - // FIXME sujil please fix this - //Expect(response.State.Status).To(Equal(define.PodStateRunning)) + Expect(response.State).To(Equal(define.PodStateRunning)) for _, i := range response.Containers { Expect(define.StringToContainerStatus(i.State)). To(Equal(define.ContainerStateRunning)) @@ -234,8 +231,7 @@ var _ = Describe("Podman pods", func() { _, err = pods.Stop(bt.conn, newpod, nil) Expect(err).To(BeNil()) response, _ = pods.Inspect(bt.conn, newpod) - // FIXME sujil please fix this - //Expect(response.State.Status).To(Equal(define.PodStateExited)) + Expect(response.State).To(Equal(define.PodStateExited)) for _, i := range response.Containers { Expect(define.StringToContainerStatus(i.State)). To(Equal(define.ContainerStateStopped)) @@ -248,8 +244,7 @@ var _ = Describe("Podman pods", func() { _, err = pods.Restart(bt.conn, newpod) Expect(err).To(BeNil()) response, _ = pods.Inspect(bt.conn, newpod) - // FIXME sujil please fix this - //Expect(response.State.Status).To(Equal(define.PodStateRunning)) + Expect(response.State).To(Equal(define.PodStateRunning)) for _, i := range response.Containers { Expect(define.StringToContainerStatus(i.State)). To(Equal(define.ContainerStateRunning)) @@ -277,15 +272,15 @@ var _ = Describe("Podman pods", func() { Expect(err).To(BeNil()) response, err := pods.Inspect(bt.conn, newpod) Expect(err).To(BeNil()) - // FIXME sujil please fix this - //Expect(response.State.Status).To(Equal(define.PodStateExited)) + Expect(response.State).To(Equal(define.PodStateExited)) pruneResponse, err = pods.Prune(bt.conn) Expect(err).To(BeNil()) // Validate status and record pod id of pod to be pruned - //Expect(response.State.Status).To(Equal(define.PodStateExited)) - //podID := response.Config.ID + Expect(response.State).To(Equal(define.PodStateExited)) + podID := response.ID // Check if right pod was pruned Expect(len(pruneResponse)).To(Equal(1)) + Expect(pruneResponse[0].Id).To(Equal(podID)) // One pod is pruned hence only one pod should be active. podSummary, err = pods.List(bt.conn, nil) Expect(err).To(BeNil()) @@ -301,8 +296,7 @@ var _ = Describe("Podman pods", func() { Expect(err).To(BeNil()) response, err = pods.Inspect(bt.conn, newpod) Expect(err).To(BeNil()) - // FIXME sujil please fix this - //Expect(response.State.Status).To(Equal(define.PodStateExited)) + Expect(response.State).To(Equal(define.PodStateExited)) for _, i := range response.Containers { Expect(define.StringToContainerStatus(i.State)). To(Equal(define.ContainerStateStopped)) @@ -311,8 +305,7 @@ var _ = Describe("Podman pods", func() { Expect(err).To(BeNil()) response, err = pods.Inspect(bt.conn, newpod2) Expect(err).To(BeNil()) - // FIXME sujil please fix this - //Expect(response.State.Status).To(Equal(define.PodStateExited)) + Expect(response.State).To(Equal(define.PodStateExited)) for _, i := range response.Containers { Expect(define.StringToContainerStatus(i.State)). To(Equal(define.ContainerStateStopped)) diff --git a/pkg/domain/entities/container_ps.go b/pkg/domain/entities/container_ps.go index 709bb58d6..fd94d93be 100644 --- a/pkg/domain/entities/container_ps.go +++ b/pkg/domain/entities/container_ps.go @@ -25,6 +25,8 @@ type ListContainer struct { ID string `json:"Id"` // Container image Image string + // Container image ID + ImageID string // If this container is a Pod infra container IsInfra bool // Labels for container @@ -159,3 +161,31 @@ func SortPsOutput(sortBy string, psOutput SortListContainers) (SortListContainer } return psOutput, nil } + +func (l ListContainer) CGROUPNS() string { + return l.Namespaces.Cgroup +} + +func (l ListContainer) IPC() string { + return l.Namespaces.IPC +} + +func (l ListContainer) MNT() string { + return l.Namespaces.MNT +} + +func (l ListContainer) NET() string { + return l.Namespaces.NET +} + +func (l ListContainer) PIDNS() string { + return l.Namespaces.PIDNS +} + +func (l ListContainer) USERNS() string { + return l.Namespaces.User +} + +func (l ListContainer) UTS() string { + return l.Namespaces.UTS +} diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go index 52327a905..e58258b75 100644 --- a/pkg/domain/entities/containers.go +++ b/pkg/domain/entities/containers.go @@ -8,6 +8,7 @@ import ( "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/specgen" + "github.com/cri-o/ocicni/pkg/ocicni" ) type WaitOptions struct { @@ -341,3 +342,27 @@ type ContainerPruneReport struct { ID map[string]int64 Err map[string]error } + +// ContainerPortOptions describes the options to obtain +// port information on containers +type ContainerPortOptions struct { + All bool + Latest bool +} + +// ContainerPortReport describes the output needed for +// the CLI to output ports +type ContainerPortReport struct { + Id string + Ports []ocicni.PortMapping +} + +// ContainerCpOptions describes input options for cp +type ContainerCpOptions struct { + Pause bool + Extract bool +} + +// ContainerCpReport describes the output from a cp operation +type ContainerCpReport struct { +} diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go index b730f8743..502279bcf 100644 --- a/pkg/domain/entities/engine_container.go +++ b/pkg/domain/entities/engine_container.go @@ -14,8 +14,8 @@ type ContainerEngine interface { ContainerAttach(ctx context.Context, nameOrId string, options AttachOptions) error ContainerCheckpoint(ctx context.Context, namesOrIds []string, options CheckpointOptions) ([]*CheckpointReport, error) ContainerCleanup(ctx context.Context, namesOrIds []string, options ContainerCleanupOptions) ([]*ContainerCleanupReport, error) - ContainerPrune(ctx context.Context, options ContainerPruneOptions) (*ContainerPruneReport, error) ContainerCommit(ctx context.Context, nameOrId string, options CommitOptions) (*CommitReport, error) + ContainerCp(ctx context.Context, source, dest string, options ContainerCpOptions) (*ContainerCpReport, error) ContainerCreate(ctx context.Context, s *specgen.SpecGenerator) (*ContainerCreateReport, error) ContainerDiff(ctx context.Context, nameOrId string, options DiffOptions) (*DiffReport, error) ContainerExec(ctx context.Context, nameOrId string, options ExecOptions) (int, error) @@ -28,6 +28,8 @@ type ContainerEngine interface { ContainerLogs(ctx context.Context, containers []string, options ContainerLogsOptions) error ContainerMount(ctx context.Context, nameOrIds []string, options ContainerMountOptions) ([]*ContainerMountReport, error) ContainerPause(ctx context.Context, namesOrIds []string, options PauseUnPauseOptions) ([]*PauseUnpauseReport, error) + ContainerPort(ctx context.Context, nameOrId string, options ContainerPortOptions) ([]*ContainerPortReport, error) + ContainerPrune(ctx context.Context, options ContainerPruneOptions) (*ContainerPruneReport, error) ContainerRestart(ctx context.Context, namesOrIds []string, options RestartOptions) ([]*RestartReport, error) ContainerRestore(ctx context.Context, namesOrIds []string, options RestoreOptions) ([]*RestoreReport, error) ContainerRm(ctx context.Context, namesOrIds []string, options RmOptions) ([]*RmReport, error) @@ -46,16 +48,17 @@ type ContainerEngine interface { PodInspect(ctx context.Context, options PodInspectOptions) (*PodInspectReport, error) PodKill(ctx context.Context, namesOrIds []string, options PodKillOptions) ([]*PodKillReport, error) PodPause(ctx context.Context, namesOrIds []string, options PodPauseOptions) ([]*PodPauseReport, error) + PodPrune(ctx context.Context, options PodPruneOptions) ([]*PodPruneReport, error) PodPs(ctx context.Context, options PodPSOptions) ([]*ListPodsReport, error) PodRestart(ctx context.Context, namesOrIds []string, options PodRestartOptions) ([]*PodRestartReport, error) PodRm(ctx context.Context, namesOrIds []string, options PodRmOptions) ([]*PodRmReport, error) - PodPrune(ctx context.Context, options PodPruneOptions) ([]*PodPruneReport, error) PodStart(ctx context.Context, namesOrIds []string, options PodStartOptions) ([]*PodStartReport, error) + PodStats(ctx context.Context, namesOrIds []string, options PodStatsOptions) ([]*PodStatsReport, error) 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) - RestService(ctx context.Context, opts ServiceOptions) error SetupRootless(ctx context.Context, cmd *cobra.Command) error + Shutdown(ctx context.Context) VarlinkService(ctx context.Context, opts ServiceOptions) error VolumeCreate(ctx context.Context, opts VolumeCreateOptions) (*IdOrNameResponse, error) VolumeInspect(ctx context.Context, namesOrIds []string, opts VolumeInspectOptions) ([]*VolumeInspectReport, error) diff --git a/pkg/domain/entities/engine_image.go b/pkg/domain/entities/engine_image.go index 052e7bee5..b118a4104 100644 --- a/pkg/domain/entities/engine_image.go +++ b/pkg/domain/entities/engine_image.go @@ -9,7 +9,6 @@ import ( type ImageEngine interface { Build(ctx context.Context, containerFiles []string, opts BuildOptions) (*BuildReport, error) Config(ctx context.Context) (*config.Config, error) - Delete(ctx context.Context, nameOrId []string, opts ImageDeleteOptions) (*ImageDeleteReport, error) Diff(ctx context.Context, nameOrId string, options DiffOptions) (*DiffReport, error) Exists(ctx context.Context, nameOrId string) (*BoolReport, error) History(ctx context.Context, nameOrId string, opts ImageHistoryOptions) (*ImageHistoryReport, error) @@ -20,8 +19,14 @@ type ImageEngine interface { Prune(ctx context.Context, opts ImagePruneOptions) (*ImagePruneReport, error) Pull(ctx context.Context, rawImage string, opts ImagePullOptions) (*ImagePullReport, error) Push(ctx context.Context, source string, destination string, opts ImagePushOptions) error + Remove(ctx context.Context, images []string, opts ImageRemoveOptions) (*ImageRemoveReport, error) Save(ctx context.Context, nameOrId string, tags []string, options ImageSaveOptions) error Search(ctx context.Context, term string, opts ImageSearchOptions) ([]ImageSearchReport, error) + Shutdown(ctx context.Context) Tag(ctx context.Context, nameOrId string, tags []string, options ImageTagOptions) error + Tree(ctx context.Context, nameOrId string, options ImageTreeOptions) (*ImageTreeReport, error) Untag(ctx context.Context, nameOrId string, tags []string, options ImageUntagOptions) error + ManifestCreate(ctx context.Context, names, images []string, opts ManifestCreateOptions) (string, error) + ManifestInspect(ctx context.Context, name string) ([]byte, error) + ManifestAdd(ctx context.Context, opts ManifestAddOptions) (string, error) } diff --git a/pkg/domain/entities/events.go b/pkg/domain/entities/events.go new file mode 100644 index 000000000..8861be158 --- /dev/null +++ b/pkg/domain/entities/events.go @@ -0,0 +1,61 @@ +package entities + +import ( + "strconv" + "time" + + libpodEvents "github.com/containers/libpod/libpod/events" + dockerEvents "github.com/docker/docker/api/types/events" +) + +// Event combines various event-related data such as time, event type, status +// and more. +type Event struct { + // TODO: it would be nice to have full control over the types at some + // point and fork such Docker types. + dockerEvents.Message +} + +// ConvertToLibpodEvent converts an entities event to a libpod one. +func ConvertToLibpodEvent(e Event) *libpodEvents.Event { + exitCode, err := strconv.Atoi(e.Actor.Attributes["containerExitCode"]) + if err != nil { + return nil + } + status, err := libpodEvents.StringToStatus(e.Action) + if err != nil { + return nil + } + t, err := libpodEvents.StringToType(e.Type) + if err != nil { + return nil + } + return &libpodEvents.Event{ + ContainerExitCode: exitCode, + ID: e.Actor.ID, + Image: e.Actor.Attributes["image"], + Name: e.Actor.Attributes["name"], + Status: status, + Time: time.Unix(e.Time, e.TimeNano), + Type: t, + } +} + +// ConvertToEntitiesEvent converts a libpod event to an entities one. +func ConvertToEntitiesEvent(e libpodEvents.Event) *Event { + return &Event{dockerEvents.Message{ + Type: e.Type.String(), + Action: e.Status.String(), + Actor: dockerEvents.Actor{ + ID: e.ID, + Attributes: map[string]string{ + "image": e.Image, + "name": e.Name, + "containerExitCode": strconv.Itoa(e.ContainerExitCode), + }, + }, + Scope: "local", + Time: e.Time.Unix(), + TimeNano: e.Time.UnixNano(), + }} +} diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go index 3a6d159e4..460965b34 100644 --- a/pkg/domain/entities/images.go +++ b/pkg/domain/entities/images.go @@ -82,19 +82,24 @@ func (i *ImageSummary) IsDangling() bool { return i.Dangling } -type ImageDeleteOptions struct { - All bool +// ImageRemoveOptions can be used to alter image removal. +type ImageRemoveOptions struct { + // All will remove all images. + All bool + // Foce will force image removal including containers using the images. Force bool } -// ImageDeleteResponse is the response for removing one or more image(s) from storage -// and containers what was untagged vs actually removed -type ImageDeleteReport struct { - Untagged []string `json:",omitempty"` - Deleted []string `json:",omitempty"` - Errors []error - ImageNotFound error - ImageInUse error +// ImageRemoveResponse is the response for removing one or more image(s) from storage +// and containers what was untagged vs actually removed. +type ImageRemoveReport struct { + // Deleted images. + Deleted []string `json:",omitempty"` + // Untagged images. Can be longer than Deleted. + Untagged []string `json:",omitempty"` + // ExitCode describes the exit codes as described in the `podman rmi` + // man page. + ExitCode int } type ImageHistoryOptions struct{} @@ -251,7 +256,7 @@ type ImageLoadOptions struct { } type ImageLoadReport struct { - Name string + Names []string } type ImageImportOptions struct { @@ -273,3 +278,13 @@ type ImageSaveOptions struct { Output string Quiet bool } + +// ImageTreeOptions provides options for ImageEngine.Tree() +type ImageTreeOptions struct { + WhatRequires bool // Show all child images and layers of the specified image +} + +// ImageTreeReport provides results from ImageEngine.Tree() +type ImageTreeReport struct { + Tree string // TODO: Refactor move presentation work out of server +} diff --git a/pkg/domain/entities/manifest.go b/pkg/domain/entities/manifest.go new file mode 100644 index 000000000..7316735b0 --- /dev/null +++ b/pkg/domain/entities/manifest.go @@ -0,0 +1,16 @@ +package entities + +type ManifestCreateOptions struct { + All bool `schema:"all"` +} + +type ManifestAddOptions struct { + All bool `json:"all" schema:"all"` + Annotation []string `json:"annotation" schema:"annotation"` + Arch string `json:"arch" schema:"arch"` + Features []string `json:"features" schema:"features"` + Images []string `json:"images" schema:"images"` + OS string `json:"os" schema:"os"` + OSVersion string `json:"os_version" schema:"os_version"` + Variant string `json:"variant" schema:"variant"` +} diff --git a/pkg/domain/entities/pods.go b/pkg/domain/entities/pods.go index 04673ef18..a4896ce4d 100644 --- a/pkg/domain/entities/pods.go +++ b/pkg/domain/entities/pods.go @@ -1,6 +1,7 @@ package entities import ( + "errors" "strings" "time" @@ -134,7 +135,7 @@ func (p PodCreateOptions) ToPodSpecGen(s *specgen.PodSpecGenerator) { s.StaticMAC = p.Net.StaticMAC s.PortMappings = p.Net.PublishPorts s.CNINetworks = p.Net.CNINetworks - if p.Net.DNSHost { + if p.Net.UseImageResolvConf { s.NoManageResolvConf = true } s.DNSServer = p.Net.DNSServers @@ -188,3 +189,50 @@ type PodInspectOptions struct { type PodInspectReport struct { *define.InspectPodData } + +// PodStatsOptions are options for the pod stats command. +type PodStatsOptions struct { + // All - provide stats for all running pods. + All bool + // Latest - provide stats for the latest pod. + Latest bool +} + +// PodStatsReport includes pod-resource statistics data. +type PodStatsReport struct { + CPU string + MemUsage string + Mem string + NetIO string + BlockIO string + PIDS string + Pod string + CID string + Name string +} + +// ValidatePodStatsOptions validates the specified slice and options. Allows +// for sharing code in the front- and the back-end. +func ValidatePodStatsOptions(args []string, options *PodStatsOptions) error { + num := 0 + if len(args) > 0 { + num++ + } + if options.All { + num++ + } + if options.Latest { + num++ + } + switch num { + case 0: + // Podman v1 compat: if nothing's specified get all running + // pods. + options.All = true + return nil + case 1: + return nil + default: + return errors.New("--all, --latest and arguments cannot be used together") + } +} diff --git a/pkg/domain/entities/types.go b/pkg/domain/entities/types.go index b89aa869a..d742cc53d 100644 --- a/pkg/domain/entities/types.go +++ b/pkg/domain/entities/types.go @@ -32,17 +32,17 @@ type VolumeDeleteReport struct{ Report } // NetOptions reflect the shared network options between // pods and containers type NetOptions struct { - AddHosts []string - CNINetworks []string - DNSHost bool - DNSOptions []string - DNSSearch []string - DNSServers []net.IP - Network specgen.Namespace - NoHosts bool - PublishPorts []ocicni.PortMapping - StaticIP *net.IP - StaticMAC *net.HardwareAddr + AddHosts []string + CNINetworks []string + UseImageResolvConf bool + DNSOptions []string + DNSSearch []string + DNSServers []net.IP + Network specgen.Namespace + NoHosts bool + PublishPorts []ocicni.PortMapping + StaticIP *net.IP + StaticMAC *net.HardwareAddr } // All CLI inspect commands and inspect sub-commands use the same options @@ -50,6 +50,7 @@ type InspectOptions struct { Format string `json:",omitempty"` Latest bool `json:",omitempty"` Size bool `json:",omitempty"` + Type string `json:",omitempty"` } // All API and CLI diff commands and diff sub-commands use the same options diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index 50003dbe2..286d37c34 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -1,5 +1,3 @@ -// +build ABISupport - package abi import ( @@ -219,12 +217,23 @@ func (ic *ContainerEngine) ContainerKill(ctx context.Context, namesOrIds []strin } func (ic *ContainerEngine) ContainerRestart(ctx context.Context, namesOrIds []string, options entities.RestartOptions) ([]*entities.RestartReport, error) { var ( + ctrs []*libpod.Container + err error reports []*entities.RestartReport ) - ctrs, err := getContainersByContext(options.All, options.Latest, namesOrIds, ic.Libpod) - if err != nil { - return nil, err + + if options.Running { + ctrs, err = ic.Libpod.GetRunningContainers() + if err != nil { + return nil, err + } + } else { + ctrs, err = getContainersByContext(options.All, options.Latest, namesOrIds, ic.Libpod) + if err != nil { + return nil, err + } } + for _, con := range ctrs { timeout := con.StopTimeout() if options.Timeout != nil { @@ -483,7 +492,7 @@ func (ic *ContainerEngine) ContainerCreate(ctx context.Context, s *specgen.SpecG if err := generate.CompleteSpec(ctx, ic.Libpod, s); err != nil { return nil, err } - ctr, err := generate.MakeContainer(ic.Libpod, s) + ctr, err := generate.MakeContainer(ctx, ic.Libpod, s) if err != nil { return nil, err } @@ -671,7 +680,7 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta if err := generate.CompleteSpec(ctx, ic.Libpod, opts.Spec); err != nil { return nil, err } - ctr, err := generate.MakeContainer(ic.Libpod, opts.Spec) + ctr, err := generate.MakeContainer(ctx, ic.Libpod, opts.Spec) if err != nil { return nil, err } @@ -839,7 +848,13 @@ func (ic *ContainerEngine) ContainerInit(ctx context.Context, namesOrIds []strin } for _, ctr := range ctrs { report := entities.ContainerInitReport{Id: ctr.ID()} - report.Err = ctr.Init(ctx) + err := ctr.Init(ctx) + + // If we're initializing all containers, ignore invalid state errors + if options.All && errors.Cause(err) == define.ErrCtrStateInvalid { + err = nil + } + report.Err = err reports = append(reports, &report) } return reports, nil @@ -931,3 +946,38 @@ func (ic *ContainerEngine) ContainerUnmount(ctx context.Context, nameOrIds []str func (ic *ContainerEngine) Config(_ context.Context) (*config.Config, error) { return ic.Libpod.GetConfig() } + +func (ic *ContainerEngine) ContainerPort(ctx context.Context, nameOrId string, options entities.ContainerPortOptions) ([]*entities.ContainerPortReport, error) { + var reports []*entities.ContainerPortReport + ctrs, err := getContainersByContext(options.All, false, []string{nameOrId}, ic.Libpod) + if err != nil { + return nil, err + } + for _, con := range ctrs { + state, err := con.State() + if err != nil { + return nil, err + } + if state != define.ContainerStateRunning { + continue + } + portmappings, err := con.PortMappings() + if err != nil { + return nil, err + } + if len(portmappings) > 0 { + reports = append(reports, &entities.ContainerPortReport{ + Id: con.ID(), + Ports: portmappings, + }) + } + } + return reports, nil +} + +// Shutdown Libpod engine +func (ic *ContainerEngine) Shutdown(_ context.Context) { + shutdownSync.Do(func() { + _ = ic.Libpod.Shutdown(false) + }) +} diff --git a/pkg/domain/infra/abi/cp.go b/pkg/domain/infra/abi/cp.go new file mode 100644 index 000000000..9fc1e3bee --- /dev/null +++ b/pkg/domain/infra/abi/cp.go @@ -0,0 +1,433 @@ +package abi + +import ( + "archive/tar" + "context" + "fmt" + "io" + "os" + "path/filepath" + "strings" + + "github.com/containers/buildah/pkg/chrootuser" + "github.com/containers/buildah/util" + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/storage" + "github.com/containers/storage/pkg/chrootarchive" + "github.com/containers/storage/pkg/idtools" + securejoin "github.com/cyphar/filepath-securejoin" + "github.com/docker/docker/pkg/archive" + "github.com/opencontainers/go-digest" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +func (ic *ContainerEngine) ContainerCp(ctx context.Context, source, dest string, options entities.ContainerCpOptions) (*entities.ContainerCpReport, error) { + var extract bool + + srcCtr, srcPath := parsePath(ic.Libpod, source) + destCtr, destPath := parsePath(ic.Libpod, dest) + + if (srcCtr == nil && destCtr == nil) || (srcCtr != nil && destCtr != nil) { + return nil, errors.Errorf("invalid arguments %s, %s you must use just one container", source, dest) + } + + if len(srcPath) == 0 || len(destPath) == 0 { + return nil, errors.Errorf("invalid arguments %s, %s you must specify paths", source, dest) + } + ctr := srcCtr + isFromHostToCtr := ctr == nil + if isFromHostToCtr { + ctr = destCtr + } + + mountPoint, err := ctr.Mount() + if err != nil { + return nil, err + } + defer func() { + if err := ctr.Unmount(false); err != nil { + logrus.Errorf("unable to umount container '%s': %q", ctr.ID(), err) + } + }() + + if options.Pause { + if err := ctr.Pause(); err != nil { + // An invalid state error is fine. + // The container isn't running or is already paused. + // TODO: We can potentially start the container while + // the copy is running, which still allows a race where + // malicious code could mess with the symlink. + if errors.Cause(err) != define.ErrCtrStateInvalid { + return nil, err + } + } else { + // Only add the defer if we actually paused + defer func() { + if err := ctr.Unpause(); err != nil { + logrus.Errorf("Error unpausing container after copying: %v", err) + } + }() + } + } + + user, err := getUser(mountPoint, ctr.User()) + if err != nil { + return nil, err + } + idMappingOpts, err := ctr.IDMappings() + if err != nil { + return nil, errors.Wrapf(err, "error getting IDMappingOptions") + } + destOwner := idtools.IDPair{UID: int(user.UID), GID: int(user.GID)} + hostUID, hostGID, err := util.GetHostIDs(convertIDMap(idMappingOpts.UIDMap), convertIDMap(idMappingOpts.GIDMap), user.UID, user.GID) + if err != nil { + return nil, err + } + + hostOwner := idtools.IDPair{UID: int(hostUID), GID: int(hostGID)} + + if isFromHostToCtr { + if isVol, volDestName, volName := isVolumeDestName(destPath, ctr); isVol { //nolint(gocritic) + path, err := pathWithVolumeMount(ctr, ic.Libpod, volDestName, volName, destPath) + if err != nil { + return nil, errors.Wrapf(err, "error getting destination path from volume %s", volDestName) + } + destPath = path + } else if isBindMount, mount := isBindMountDestName(destPath, ctr); isBindMount { //nolint(gocritic) + path, err := pathWithBindMountSource(mount, destPath) + if err != nil { + return nil, errors.Wrapf(err, "error getting destination path from bind mount %s", mount.Destination) + } + destPath = path + } else if filepath.IsAbs(destPath) { //nolint(gocritic) + cleanedPath, err := securejoin.SecureJoin(mountPoint, destPath) + if err != nil { + return nil, err + } + destPath = cleanedPath + } else { //nolint(gocritic) + ctrWorkDir, err := securejoin.SecureJoin(mountPoint, ctr.WorkingDir()) + if err != nil { + return nil, err + } + if err = idtools.MkdirAllAndChownNew(ctrWorkDir, 0755, hostOwner); err != nil { + return nil, errors.Wrapf(err, "error creating directory %q", destPath) + } + cleanedPath, err := securejoin.SecureJoin(mountPoint, filepath.Join(ctr.WorkingDir(), destPath)) + if err != nil { + return nil, err + } + destPath = cleanedPath + } + } else { + destOwner = idtools.IDPair{UID: os.Getuid(), GID: os.Getgid()} + if isVol, volDestName, volName := isVolumeDestName(srcPath, ctr); isVol { //nolint(gocritic) + path, err := pathWithVolumeMount(ctr, ic.Libpod, volDestName, volName, srcPath) + if err != nil { + return nil, errors.Wrapf(err, "error getting source path from volume %s", volDestName) + } + srcPath = path + } else if isBindMount, mount := isBindMountDestName(srcPath, ctr); isBindMount { //nolint(gocritic) + path, err := pathWithBindMountSource(mount, srcPath) + if err != nil { + return nil, errors.Wrapf(err, "error getting source path from bind mount %s", mount.Destination) + } + srcPath = path + } else if filepath.IsAbs(srcPath) { //nolint(gocritic) + cleanedPath, err := securejoin.SecureJoin(mountPoint, srcPath) + if err != nil { + return nil, err + } + srcPath = cleanedPath + } else { //nolint(gocritic) + cleanedPath, err := securejoin.SecureJoin(mountPoint, filepath.Join(ctr.WorkingDir(), srcPath)) + if err != nil { + return nil, err + } + srcPath = cleanedPath + } + } + + if !filepath.IsAbs(destPath) { + dir, err := os.Getwd() + if err != nil { + return nil, errors.Wrapf(err, "err getting current working directory") + } + destPath = filepath.Join(dir, destPath) + } + + if source == "-" { + srcPath = os.Stdin.Name() + extract = true + } + err = containerCopy(srcPath, destPath, source, dest, idMappingOpts, &destOwner, extract, isFromHostToCtr) + return &entities.ContainerCpReport{}, err +} + +func getUser(mountPoint string, userspec string) (specs.User, error) { + uid, gid, _, err := chrootuser.GetUser(mountPoint, userspec) + u := specs.User{ + UID: uid, + GID: gid, + Username: userspec, + } + if !strings.Contains(userspec, ":") { + groups, err2 := chrootuser.GetAdditionalGroupsForUser(mountPoint, uint64(u.UID)) + if err2 != nil { + if errors.Cause(err2) != chrootuser.ErrNoSuchUser && err == nil { + err = err2 + } + } else { + u.AdditionalGids = groups + } + + } + return u, err +} + +func parsePath(runtime *libpod.Runtime, path string) (*libpod.Container, string) { + pathArr := strings.SplitN(path, ":", 2) + if len(pathArr) == 2 { + ctr, err := runtime.LookupContainer(pathArr[0]) + if err == nil { + return ctr, pathArr[1] + } + } + return nil, path +} + +func evalSymlinks(path string) (string, error) { + if path == os.Stdin.Name() { + return path, nil + } + return filepath.EvalSymlinks(path) +} + +func getPathInfo(path string) (string, os.FileInfo, error) { + path, err := evalSymlinks(path) + if err != nil { + return "", nil, errors.Wrapf(err, "error evaluating symlinks %q", path) + } + srcfi, err := os.Stat(path) + if err != nil { + return "", nil, errors.Wrapf(err, "error reading path %q", path) + } + return path, srcfi, nil +} + +func containerCopy(srcPath, destPath, src, dest string, idMappingOpts storage.IDMappingOptions, chownOpts *idtools.IDPair, extract, isFromHostToCtr bool) error { + srcPath, err := evalSymlinks(srcPath) + if err != nil { + return errors.Wrapf(err, "error evaluating symlinks %q", srcPath) + } + + srcPath, srcfi, err := getPathInfo(srcPath) + if err != nil { + return err + } + + filename := filepath.Base(destPath) + if filename == "-" && !isFromHostToCtr { + err := streamFileToStdout(srcPath, srcfi) + if err != nil { + return errors.Wrapf(err, "error streaming source file %s to Stdout", srcPath) + } + return nil + } + + destdir := destPath + if !srcfi.IsDir() { + destdir = filepath.Dir(destPath) + } + _, err = os.Stat(destdir) + if err != nil && !os.IsNotExist(err) { + return errors.Wrapf(err, "error checking directory %q", destdir) + } + destDirIsExist := err == nil + if err = os.MkdirAll(destdir, 0755); err != nil { + return errors.Wrapf(err, "error creating directory %q", destdir) + } + + // return functions for copying items + copyFileWithTar := chrootarchive.CopyFileWithTarAndChown(chownOpts, digest.Canonical.Digester().Hash(), idMappingOpts.UIDMap, idMappingOpts.GIDMap) + copyWithTar := chrootarchive.CopyWithTarAndChown(chownOpts, digest.Canonical.Digester().Hash(), idMappingOpts.UIDMap, idMappingOpts.GIDMap) + untarPath := chrootarchive.UntarPathAndChown(chownOpts, digest.Canonical.Digester().Hash(), idMappingOpts.UIDMap, idMappingOpts.GIDMap) + + if srcfi.IsDir() { + logrus.Debugf("copying %q to %q", srcPath+string(os.PathSeparator)+"*", dest+string(os.PathSeparator)+"*") + if destDirIsExist && !strings.HasSuffix(src, fmt.Sprintf("%s.", string(os.PathSeparator))) { + destPath = filepath.Join(destPath, filepath.Base(srcPath)) + } + if err = copyWithTar(srcPath, destPath); err != nil { + return errors.Wrapf(err, "error copying %q to %q", srcPath, dest) + } + return nil + } + + if extract { + // We're extracting an archive into the destination directory. + logrus.Debugf("extracting contents of %q into %q", srcPath, destPath) + if err = untarPath(srcPath, destPath); err != nil { + return errors.Wrapf(err, "error extracting %q into %q", srcPath, destPath) + } + return nil + } + + destfi, err := os.Stat(destPath) + if err != nil { + if !os.IsNotExist(err) || strings.HasSuffix(dest, string(os.PathSeparator)) { + return errors.Wrapf(err, "failed to get stat of dest path %s", destPath) + } + } + if destfi != nil && destfi.IsDir() { + destPath = filepath.Join(destPath, filepath.Base(srcPath)) + } + + // Copy the file, preserving attributes. + logrus.Debugf("copying %q to %q", srcPath, destPath) + if err = copyFileWithTar(srcPath, destPath); err != nil { + return errors.Wrapf(err, "error copying %q to %q", srcPath, destPath) + } + return nil +} + +func convertIDMap(idMaps []idtools.IDMap) (convertedIDMap []specs.LinuxIDMapping) { + for _, idmap := range idMaps { + tempIDMap := specs.LinuxIDMapping{ + ContainerID: uint32(idmap.ContainerID), + HostID: uint32(idmap.HostID), + Size: uint32(idmap.Size), + } + convertedIDMap = append(convertedIDMap, tempIDMap) + } + return convertedIDMap +} + +func streamFileToStdout(srcPath string, srcfi os.FileInfo) error { + if srcfi.IsDir() { + tw := tar.NewWriter(os.Stdout) + err := filepath.Walk(srcPath, func(path string, info os.FileInfo, err error) error { + if err != nil || !info.Mode().IsRegular() || path == srcPath { + return err + } + hdr, err := tar.FileInfoHeader(info, "") + if err != nil { + return err + } + + if err = tw.WriteHeader(hdr); err != nil { + return err + } + fh, err := os.Open(path) + if err != nil { + return err + } + defer fh.Close() + + _, err = io.Copy(tw, fh) + return err + }) + if err != nil { + return errors.Wrapf(err, "error streaming directory %s to Stdout", srcPath) + } + return nil + } + + file, err := os.Open(srcPath) + if err != nil { + return errors.Wrapf(err, "error opening file %s", srcPath) + } + defer file.Close() + if !archive.IsArchivePath(srcPath) { + tw := tar.NewWriter(os.Stdout) + hdr, err := tar.FileInfoHeader(srcfi, "") + if err != nil { + return err + } + err = tw.WriteHeader(hdr) + if err != nil { + return err + } + _, err = io.Copy(tw, file) + if err != nil { + return errors.Wrapf(err, "error streaming archive %s to Stdout", srcPath) + } + return nil + } + + _, err = io.Copy(os.Stdout, file) + if err != nil { + return errors.Wrapf(err, "error streaming file to Stdout") + } + return nil +} + +func isVolumeDestName(path string, ctr *libpod.Container) (bool, string, string) { + separator := string(os.PathSeparator) + if filepath.IsAbs(path) { + path = strings.TrimPrefix(path, separator) + } + if path == "" { + return false, "", "" + } + for _, vol := range ctr.Config().NamedVolumes { + volNamePath := strings.TrimPrefix(vol.Dest, separator) + if matchVolumePath(path, volNamePath) { + return true, vol.Dest, vol.Name + } + } + return false, "", "" +} + +// if SRCPATH or DESTPATH is from volume mount's destination -v or --mount type=volume, generates the path with volume mount point +func pathWithVolumeMount(ctr *libpod.Container, runtime *libpod.Runtime, volDestName, volName, path string) (string, error) { + destVolume, err := runtime.GetVolume(volName) + if err != nil { + return "", errors.Wrapf(err, "error getting volume destination %s", volName) + } + if !filepath.IsAbs(path) { + path = filepath.Join(string(os.PathSeparator), path) + } + path, err = securejoin.SecureJoin(destVolume.MountPoint(), strings.TrimPrefix(path, volDestName)) + return path, err +} + +func isBindMountDestName(path string, ctr *libpod.Container) (bool, specs.Mount) { + separator := string(os.PathSeparator) + if filepath.IsAbs(path) { + path = strings.TrimPrefix(path, string(os.PathSeparator)) + } + if path == "" { + return false, specs.Mount{} + } + for _, m := range ctr.Config().Spec.Mounts { + if m.Type != "bind" { + continue + } + mDest := strings.TrimPrefix(m.Destination, separator) + if matchVolumePath(path, mDest) { + return true, m + } + } + return false, specs.Mount{} +} + +func matchVolumePath(path, target string) bool { + pathStr := filepath.Clean(path) + target = filepath.Clean(target) + for len(pathStr) > len(target) && strings.Contains(pathStr, string(os.PathSeparator)) { + pathStr = pathStr[:strings.LastIndex(pathStr, string(os.PathSeparator))] + } + return pathStr == target +} + +func pathWithBindMountSource(m specs.Mount, path string) (string, error) { + if !filepath.IsAbs(path) { + path = filepath.Join(string(os.PathSeparator), path) + } + return securejoin.SecureJoin(m.Source, strings.TrimPrefix(path, m.Destination)) +} diff --git a/pkg/domain/infra/abi/events.go b/pkg/domain/infra/abi/events.go index 9540a5b96..20773cdce 100644 --- a/pkg/domain/infra/abi/events.go +++ b/pkg/domain/infra/abi/events.go @@ -1,5 +1,3 @@ -//+build ABISupport - package abi import ( diff --git a/pkg/domain/infra/abi/healthcheck.go b/pkg/domain/infra/abi/healthcheck.go index 699483243..351bf4f7e 100644 --- a/pkg/domain/infra/abi/healthcheck.go +++ b/pkg/domain/infra/abi/healthcheck.go @@ -1,5 +1,3 @@ -// +build ABISupport - package abi import ( diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go index 68758a71d..7ac111745 100644 --- a/pkg/domain/infra/abi/images.go +++ b/pkg/domain/infra/abi/images.go @@ -1,5 +1,3 @@ -// +build ABISupport - package abi import ( @@ -23,6 +21,7 @@ import ( domainUtils "github.com/containers/libpod/pkg/domain/utils" "github.com/containers/libpod/pkg/util" "github.com/containers/storage" + "github.com/hashicorp/go-multierror" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -36,76 +35,6 @@ func (ir *ImageEngine) Exists(_ context.Context, nameOrId string) (*entities.Boo return &entities.BoolReport{Value: err == nil}, nil } -func (ir *ImageEngine) Delete(ctx context.Context, nameOrId []string, opts entities.ImageDeleteOptions) (*entities.ImageDeleteReport, error) { - report := entities.ImageDeleteReport{} - - if opts.All { - var previousTargets []*libpodImage.Image - repeatRun: - targets, err := ir.Libpod.ImageRuntime().GetRWImages() - if err != nil { - return &report, errors.Wrapf(err, "unable to query local images") - } - if len(targets) == 0 { - return &report, nil - } - if len(targets) > 0 && len(targets) == len(previousTargets) { - return &report, errors.New("unable to delete all images; re-run the rmi command again.") - } - previousTargets = targets - - for _, img := range targets { - isParent, err := img.IsParent(ctx) - if err != nil { - return &report, err - } - if isParent { - continue - } - err = ir.deleteImage(ctx, img, opts, report) - report.Errors = append(report.Errors, err) - } - if len(previousTargets) != 1 { - goto repeatRun - } - return &report, nil - } - - for _, id := range nameOrId { - image, err := ir.Libpod.ImageRuntime().NewFromLocal(id) - if err != nil { - return nil, err - } - - err = ir.deleteImage(ctx, image, opts, report) - if err != nil { - return &report, err - } - } - return &report, nil -} - -func (ir *ImageEngine) deleteImage(ctx context.Context, img *libpodImage.Image, opts entities.ImageDeleteOptions, report entities.ImageDeleteReport) error { - results, err := ir.Libpod.RemoveImage(ctx, img, opts.Force) - switch errors.Cause(err) { - case nil: - break - case storage.ErrImageUsedByContainer: - report.ImageInUse = errors.New( - fmt.Sprintf("A container associated with containers/storage, i.e. via Buildah, CRI-O, etc., may be associated with this image: %-12.12s\n", img.ID())) - return nil - case libpodImage.ErrNoSuchImage: - report.ImageNotFound = err - return nil - default: - return err - } - - report.Deleted = append(report.Deleted, results.Deleted) - report.Untagged = append(report.Untagged, results.Untagged...) - return nil -} - func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOptions) (*entities.ImagePruneReport, error) { results, err := ir.Libpod.ImageRuntime().PruneImages(ctx, opts.All, opts.Filter) if err != nil { @@ -171,7 +100,7 @@ func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entiti if imageRef.Transport().Name() == dockerarchive.Transport.Name() { newImage, err := ir.Libpod.ImageRuntime().LoadFromArchiveReference(ctx, imageRef, options.SignaturePolicy, writer) if err != nil { - return nil, errors.Wrapf(err, "error pulling image %q", rawImage) + return nil, err } return &entities.ImagePullReport{Images: []string{newImage[0].ID()}}, nil } @@ -195,7 +124,7 @@ func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entiti if !options.AllTags { newImage, err := ir.Libpod.ImageRuntime().New(ctx, rawImage, options.SignaturePolicy, options.Authfile, writer, &dockerRegistryOptions, image.SigningOptions{}, nil, util.PullImageAlways) if err != nil { - return nil, errors.Wrapf(err, "error pulling image %q", rawImage) + return nil, err } return &entities.ImagePullReport{Images: []string{newImage.ID()}}, nil } @@ -236,7 +165,7 @@ func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entiti } if len(tags) != len(foundIDs) { - return nil, errors.Errorf("error pulling image %q", rawImage) + return nil, err } return &entities.ImagePullReport{Images: foundIDs}, nil } @@ -373,6 +302,10 @@ func (ir *ImageEngine) Untag(ctx context.Context, nameOrId string, tags []string if err != nil { return err } + // If only one arg is provided, all names are to be untagged + if len(tags) == 0 { + tags = newImage.Names() + } for _, tag := range tags { if err := newImage.UntagImage(tag); err != nil { return err @@ -392,16 +325,19 @@ func (ir *ImageEngine) Load(ctx context.Context, opts entities.ImageLoadOptions) if err != nil { return nil, err } - newImage, err := ir.Libpod.ImageRuntime().NewFromLocal(name) - if err != nil { - return nil, errors.Wrap(err, "image loaded but no additional tags were created") - } - if len(opts.Name) > 0 { - if err := newImage.TagImage(fmt.Sprintf("%s:%s", opts.Name, opts.Tag)); err != nil { - return nil, errors.Wrapf(err, "error adding %q to image %q", opts.Name, newImage.InputName) + names := strings.Split(name, ",") + if len(names) <= 1 { + newImage, err := ir.Libpod.ImageRuntime().NewFromLocal(name) + if err != nil { + return nil, errors.Wrap(err, "image loaded but no additional tags were created") + } + if len(opts.Name) > 0 { + if err := newImage.TagImage(fmt.Sprintf("%s:%s", opts.Name, opts.Tag)); err != nil { + return nil, errors.Wrapf(err, "error adding %q to image %q", opts.Name, newImage.InputName) + } } } - return &entities.ImageLoadReport{Name: name}, nil + return &entities.ImageLoadReport{Names: names}, nil } func (ir *ImageEngine) Import(ctx context.Context, opts entities.ImageImportOptions) (*entities.ImageImportReport, error) { @@ -475,3 +411,157 @@ func (ir *ImageEngine) Build(ctx context.Context, containerFiles []string, opts } return &entities.BuildReport{ID: id}, nil } + +func (ir *ImageEngine) Tree(ctx context.Context, nameOrId string, opts entities.ImageTreeOptions) (*entities.ImageTreeReport, error) { + img, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrId) + if err != nil { + return nil, err + } + results, err := img.GenerateTree(opts.WhatRequires) + if err != nil { + return nil, err + } + return &entities.ImageTreeReport{Tree: results}, nil +} + +// Remove removes one or more images from local storage. +func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entities.ImageRemoveOptions) (report *entities.ImageRemoveReport, finalError error) { + var ( + // noSuchImageErrors indicates that at least one image was not found. + noSuchImageErrors bool + // inUseErrors indicates that at least one image is being used by a + // container. + inUseErrors bool + // otherErrors indicates that at least one error other than the two + // above occured. + otherErrors bool + // deleteError is a multierror to conveniently collect errors during + // removal. We really want to delete as many images as possible and not + // error out immediately. + deleteError *multierror.Error + ) + + report = &entities.ImageRemoveReport{} + + // Set the removalCode and the error after all work is done. + defer func() { + switch { + // 2 + case inUseErrors: + // One of the specified images has child images or is + // being used by a container. + report.ExitCode = 2 + // 1 + case noSuchImageErrors && !(otherErrors || inUseErrors): + // One of the specified images did not exist, and no other + // failures. + report.ExitCode = 1 + // 0 + default: + // Nothing to do. + } + if deleteError != nil { + // go-multierror has a trailing new line which we need to remove to normalize the string. + finalError = deleteError.ErrorOrNil() + finalError = errors.New(strings.TrimSpace(finalError.Error())) + } + }() + + // deleteImage is an anonymous function to conveniently delete an image + // without having to pass all local data around. + deleteImage := func(img *image.Image) error { + results, err := ir.Libpod.RemoveImage(ctx, img, opts.Force) + switch errors.Cause(err) { + case nil: + break + case storage.ErrImageUsedByContainer: + inUseErrors = true // Important for exit codes in Podman. + return errors.New( + fmt.Sprintf("A container associated with containers/storage, i.e. via Buildah, CRI-O, etc., may be associated with this image: %-12.12s\n", img.ID())) + case define.ErrImageInUse: + inUseErrors = true + return err + default: + otherErrors = true // Important for exit codes in Podman. + return err + } + + report.Deleted = append(report.Deleted, results.Deleted) + report.Untagged = append(report.Untagged, results.Untagged...) + return nil + } + + // Delete all images from the local storage. + if opts.All { + previousImages := 0 + // Remove all images one-by-one. + for { + storageImages, err := ir.Libpod.ImageRuntime().GetRWImages() + if err != nil { + deleteError = multierror.Append(deleteError, + errors.Wrapf(err, "unable to query local images")) + otherErrors = true // Important for exit codes in Podman. + return + } + // No images (left) to remove, so we're done. + if len(storageImages) == 0 { + return + } + // Prevent infinity loops by making a delete-progress check. + if previousImages == len(storageImages) { + otherErrors = true // Important for exit codes in Podman. + deleteError = multierror.Append(deleteError, + errors.New("unable to delete all images, check errors and re-run image removal if needed")) + break + } + previousImages = len(storageImages) + // Delete all "leaves" (i.e., images without child images). + for _, img := range storageImages { + isParent, err := img.IsParent(ctx) + if err != nil { + otherErrors = true // Important for exit codes in Podman. + deleteError = multierror.Append(deleteError, err) + } + // Skip parent images. + if isParent { + continue + } + if err := deleteImage(img); err != nil { + deleteError = multierror.Append(deleteError, err) + } + } + } + + return + } + + // Delete only the specified images. + for _, id := range images { + img, err := ir.Libpod.ImageRuntime().NewFromLocal(id) + switch errors.Cause(err) { + case nil: + break + case image.ErrNoSuchImage: + noSuchImageErrors = true // Important for exit codes in Podman. + fallthrough + default: + deleteError = multierror.Append(deleteError, err) + continue + } + + err = deleteImage(img) + if err != nil { + otherErrors = true // Important for exit codes in Podman. + deleteError = multierror.Append(deleteError, err) + } + } + + return +} + +// Shutdown Libpod engine +func (ir *ImageEngine) Shutdown(_ context.Context) { + shutdownSync.Do(func() { + _ = ir.Libpod.Shutdown(false) + }) +} diff --git a/pkg/domain/infra/abi/images_list.go b/pkg/domain/infra/abi/images_list.go index 68b961cb6..9add915ea 100644 --- a/pkg/domain/infra/abi/images_list.go +++ b/pkg/domain/infra/abi/images_list.go @@ -1,5 +1,3 @@ -// +build ABISupport - package abi import ( diff --git a/pkg/domain/infra/abi/manifest.go b/pkg/domain/infra/abi/manifest.go new file mode 100644 index 000000000..88331f96c --- /dev/null +++ b/pkg/domain/infra/abi/manifest.go @@ -0,0 +1,102 @@ +// +build ABISupport + +package abi + +import ( + "context" + "encoding/json" + "fmt" + "strings" + + buildahUtil "github.com/containers/buildah/util" + "github.com/containers/image/v5/docker" + "github.com/containers/image/v5/transports/alltransports" + libpodImage "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/util" + + "github.com/pkg/errors" +) + +// ManifestCreate implements logic for creating manifest lists via ImageEngine +func (ir *ImageEngine) ManifestCreate(ctx context.Context, names, images []string, opts entities.ManifestCreateOptions) (string, error) { + fullNames, err := buildahUtil.ExpandNames(names, "", ir.Libpod.SystemContext(), ir.Libpod.GetStore()) + if err != nil { + return "", errors.Wrapf(err, "error encountered while expanding image name %q", names) + } + imageID, err := libpodImage.CreateManifestList(ir.Libpod.ImageRuntime(), *ir.Libpod.SystemContext(), fullNames, images, opts.All) + if err != nil { + return imageID, err + } + return imageID, err +} + +// ManifestInspect returns the content of a manifest list or image +func (ir *ImageEngine) ManifestInspect(ctx context.Context, name string) ([]byte, error) { + dockerPrefix := fmt.Sprintf("%s://", docker.Transport.Name()) + _, err := alltransports.ParseImageName(name) + if err != nil { + _, err = alltransports.ParseImageName(dockerPrefix + name) + if err != nil { + return nil, errors.Errorf("invalid image reference %q", name) + } + } + image, err := ir.Libpod.ImageRuntime().New(ctx, name, "", "", nil, nil, libpodImage.SigningOptions{}, nil, util.PullImageMissing) + if err != nil { + return nil, errors.Wrapf(err, "reading image %q", name) + } + + list, err := image.InspectManifest() + if err != nil { + return nil, errors.Wrapf(err, "loading manifest %q", name) + } + buf, err := json.MarshalIndent(list, "", " ") + if err != nil { + return buf, errors.Wrapf(err, "error rendering manifest for display") + } + return buf, nil +} + +// ManifestAdd adds images to the manifest list +func (ir *ImageEngine) ManifestAdd(ctx context.Context, opts entities.ManifestAddOptions) (string, error) { + imageSpec := opts.Images[0] + listImageSpec := opts.Images[1] + dockerPrefix := fmt.Sprintf("%s://", docker.Transport.Name()) + _, err := alltransports.ParseImageName(imageSpec) + if err != nil { + _, err = alltransports.ParseImageName(fmt.Sprintf("%s%s", dockerPrefix, imageSpec)) + if err != nil { + return "", errors.Errorf("invalid image reference %q", imageSpec) + } + } + listImage, err := ir.Libpod.ImageRuntime().NewFromLocal(listImageSpec) + if err != nil { + return "", errors.Wrapf(err, "error retriving local image from image name %s", listImageSpec) + } + + manifestAddOpts := libpodImage.ManifestAddOpts{ + All: opts.All, + Arch: opts.Arch, + Features: opts.Features, + Images: opts.Images, + OS: opts.OS, + OSVersion: opts.OSVersion, + Variant: opts.Variant, + } + if len(opts.Annotation) != 0 { + annotations := make(map[string]string) + for _, annotationSpec := range opts.Annotation { + spec := strings.SplitN(annotationSpec, "=", 2) + if len(spec) != 2 { + return "", errors.Errorf("no value given for annotation %q", spec[0]) + } + annotations[spec[0]] = spec[1] + } + manifestAddOpts.Annotation = annotations + } + listID, err := listImage.AddManifest(*ir.Libpod.SystemContext(), manifestAddOpts) + if err != nil { + return listID, err + } + return listID, nil +} diff --git a/pkg/domain/infra/abi/pods.go b/pkg/domain/infra/abi/pods.go index 6b6e13e24..7c06f9a4e 100644 --- a/pkg/domain/infra/abi/pods.go +++ b/pkg/domain/infra/abi/pods.go @@ -1,5 +1,3 @@ -// +build ABISupport - package abi import ( @@ -147,7 +145,7 @@ func (ic *ContainerEngine) PodStop(ctx context.Context, namesOrIds []string, opt reports []*entities.PodStopReport ) pods, err := getPodsByContext(options.All, options.Latest, namesOrIds, ic.Libpod) - if err != nil { + if err != nil && !(options.Ignore && errors.Cause(err) == define.ErrNoSuchPod) { return nil, err } for _, p := range pods { @@ -182,6 +180,7 @@ func (ic *ContainerEngine) PodRestart(ctx context.Context, namesOrIds []string, errs, err := p.Restart(ctx) if err != nil { report.Errs = []error{err} + reports = append(reports, &report) continue } if len(errs) > 0 { @@ -209,6 +208,7 @@ func (ic *ContainerEngine) PodStart(ctx context.Context, namesOrIds []string, op errs, err := p.Start(ctx) if err != nil { report.Errs = []error{err} + reports = append(reports, &report) continue } if len(errs) > 0 { @@ -228,7 +228,7 @@ func (ic *ContainerEngine) PodRm(ctx context.Context, namesOrIds []string, optio reports []*entities.PodRmReport ) pods, err := getPodsByContext(options.All, options.Latest, namesOrIds, ic.Libpod) - if err != nil { + if err != nil && !(options.Ignore && errors.Cause(err) == define.ErrNoSuchPod) { return nil, err } for _, p := range pods { @@ -236,6 +236,7 @@ func (ic *ContainerEngine) PodRm(ctx context.Context, namesOrIds []string, optio err := ic.Libpod.RemovePod(ctx, p, true, options.Force) if err != nil { report.Err = err + reports = append(reports, &report) continue } reports = append(reports, &report) @@ -294,9 +295,12 @@ func (ic *ContainerEngine) PodTop(ctx context.Context, options entities.PodTopOp func (ic *ContainerEngine) PodPs(ctx context.Context, options entities.PodPSOptions) ([]*entities.ListPodsReport, error) { var ( + err error filters []libpod.PodFilter + pds []*libpod.Pod reports []*entities.ListPodsReport ) + for k, v := range options.Filters { for _, filter := range v { f, err := lpfilters.GeneratePodFilterFunc(k, filter) @@ -307,10 +311,19 @@ func (ic *ContainerEngine) PodPs(ctx context.Context, options entities.PodPSOpti } } - pds, err := ic.Libpod.Pods(filters...) - if err != nil { - return nil, err + if options.Latest { + pod, err := ic.Libpod.GetLatestPod() + if err != nil { + return nil, err + } + pds = append(pds, pod) + } else { + pds, err = ic.Libpod.Pods(filters...) + if err != nil { + return nil, err + } } + for _, p := range pds { var lpcs []*entities.ListPodContainer status, err := p.GetPodStatus() diff --git a/pkg/domain/infra/abi/pods_stats.go b/pkg/domain/infra/abi/pods_stats.go new file mode 100644 index 000000000..a41c01da0 --- /dev/null +++ b/pkg/domain/infra/abi/pods_stats.go @@ -0,0 +1,85 @@ +package abi + +import ( + "context" + "fmt" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/cgroups" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/rootless" + "github.com/docker/go-units" + "github.com/pkg/errors" +) + +// PodStats implements printing stats about pods. +func (ic *ContainerEngine) PodStats(ctx context.Context, namesOrIds []string, options entities.PodStatsOptions) ([]*entities.PodStatsReport, error) { + // Cgroups v2 check for rootless. + if rootless.IsRootless() { + unified, err := cgroups.IsCgroup2UnifiedMode() + if err != nil { + return nil, err + } + if !unified { + return nil, errors.New("pod stats is not supported in rootless mode without cgroups v2") + } + } + // Get the (running) pods and convert them to the entities format. + pods, err := getPodsByContext(options.All, options.Latest, namesOrIds, ic.Libpod) + if err != nil { + return nil, errors.Wrap(err, "unable to get list of pods") + } + return ic.podsToStatsReport(pods) +} + +// podsToStatsReport converts a slice of pods into a corresponding slice of stats reports. +func (ic *ContainerEngine) podsToStatsReport(pods []*libpod.Pod) ([]*entities.PodStatsReport, error) { + reports := []*entities.PodStatsReport{} + for i := range pods { // Access by index to prevent potential loop-variable leaks. + podStats, err := pods[i].GetPodStats(nil) + if err != nil { + return nil, err + } + podID := pods[i].ID()[:12] + for j := range podStats { + r := entities.PodStatsReport{ + CPU: floatToPercentString(podStats[j].CPU), + MemUsage: combineHumanValues(podStats[j].MemUsage, podStats[j].MemLimit), + Mem: floatToPercentString(podStats[j].MemPerc), + NetIO: combineHumanValues(podStats[j].NetInput, podStats[j].NetOutput), + BlockIO: combineHumanValues(podStats[j].BlockInput, podStats[j].BlockOutput), + PIDS: pidsToString(podStats[j].PIDs), + CID: podStats[j].ContainerID[:12], + Name: podStats[j].Name, + Pod: podID, + } + reports = append(reports, &r) + } + } + + return reports, nil +} + +func combineHumanValues(a, b uint64) string { + if a == 0 && b == 0 { + return "-- / --" + } + return fmt.Sprintf("%s / %s", units.HumanSize(float64(a)), units.HumanSize(float64(b))) +} + +func floatToPercentString(f float64) string { + strippedFloat, err := libpod.RemoveScientificNotationFromFloat(f) + if err != nil || strippedFloat == 0 { + // If things go bazinga, return a safe value + return "--" + } + return fmt.Sprintf("%.2f", strippedFloat) + "%" +} + +func pidsToString(pid uint64) string { + if pid == 0 { + // If things go bazinga, return a safe value + return "--" + } + return fmt.Sprintf("%d", pid) +} diff --git a/pkg/domain/infra/abi/runtime.go b/pkg/domain/infra/abi/runtime.go index b53fb6d3a..fba422d8e 100644 --- a/pkg/domain/infra/abi/runtime.go +++ b/pkg/domain/infra/abi/runtime.go @@ -1,8 +1,8 @@ -// +build ABISupport - package abi import ( + "sync" + "github.com/containers/libpod/libpod" ) @@ -15,3 +15,5 @@ type ImageEngine struct { type ContainerEngine struct { Libpod *libpod.Runtime } + +var shutdownSync sync.Once diff --git a/pkg/domain/infra/abi/system.go b/pkg/domain/infra/abi/system.go index 67593b2dd..e5c109ee6 100644 --- a/pkg/domain/infra/abi/system.go +++ b/pkg/domain/infra/abi/system.go @@ -1,20 +1,15 @@ -// +build ABISupport - package abi import ( "context" "fmt" "io/ioutil" - "net" "os" "strconv" - "strings" "syscall" "github.com/containers/common/pkg/config" "github.com/containers/libpod/libpod/define" - api "github.com/containers/libpod/pkg/api/server" "github.com/containers/libpod/pkg/cgroups" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/rootless" @@ -33,39 +28,6 @@ func (ic *ContainerEngine) Info(ctx context.Context) (*define.Info, error) { return ic.Libpod.Info() } -func (ic *ContainerEngine) RestService(_ context.Context, opts entities.ServiceOptions) error { - var ( - listener net.Listener - err error - ) - - if opts.URI != "" { - fields := strings.Split(opts.URI, ":") - if len(fields) == 1 { - return errors.Errorf("%s is an invalid socket destination", opts.URI) - } - address := strings.Join(fields[1:], ":") - listener, err = net.Listen(fields[0], address) - if err != nil { - return errors.Wrapf(err, "unable to create socket %s", opts.URI) - } - } - - server, err := api.NewServerWithSettings(ic.Libpod, opts.Timeout, &listener) - if err != nil { - return err - } - defer func() { - if err := server.Shutdown(); err != nil { - logrus.Warnf("Error when stopping API service: %s", err) - } - }() - - err = server.Serve() - _ = listener.Close() - return err -} - func (ic *ContainerEngine) VarlinkService(_ context.Context, opts entities.ServiceOptions) error { var varlinkInterfaces = []*iopodman.VarlinkInterface{ iopodmanAPI.New(opts.Command, ic.Libpod), diff --git a/pkg/domain/infra/abi/terminal/sigproxy_linux.go b/pkg/domain/infra/abi/terminal/sigproxy_linux.go index d7f5853d8..b422e549e 100644 --- a/pkg/domain/infra/abi/terminal/sigproxy_linux.go +++ b/pkg/domain/infra/abi/terminal/sigproxy_linux.go @@ -1,5 +1,3 @@ -// +build ABISupport - package terminal import ( diff --git a/pkg/domain/infra/abi/terminal/terminal.go b/pkg/domain/infra/abi/terminal/terminal.go index f187bdd6b..0fc3af511 100644 --- a/pkg/domain/infra/abi/terminal/terminal.go +++ b/pkg/domain/infra/abi/terminal/terminal.go @@ -1,5 +1,3 @@ -// +build ABISupport - package terminal import ( diff --git a/pkg/domain/infra/abi/terminal/terminal_linux.go b/pkg/domain/infra/abi/terminal/terminal_linux.go index 664205df1..15701342f 100644 --- a/pkg/domain/infra/abi/terminal/terminal_linux.go +++ b/pkg/domain/infra/abi/terminal/terminal_linux.go @@ -1,5 +1,3 @@ -// +build ABISupport - package terminal import ( diff --git a/pkg/domain/infra/runtime_libpod.go b/pkg/domain/infra/runtime_libpod.go index dc59fec3d..7c9180d43 100644 --- a/pkg/domain/infra/runtime_libpod.go +++ b/pkg/domain/infra/runtime_libpod.go @@ -6,6 +6,7 @@ import ( "context" "fmt" "os" + "sync" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/cgroups" @@ -18,6 +19,14 @@ import ( flag "github.com/spf13/pflag" ) +var ( + // runtimeSync only guards the non-specialized runtime + runtimeSync sync.Once + // The default GetRuntime() always returns the same object and error + runtimeLib *libpod.Runtime + runtimeErr error +) + type engineOpts struct { name string renumber bool @@ -63,13 +72,16 @@ func GetRuntimeRenumber(ctx context.Context, fs *flag.FlagSet, cfg *entities.Pod // GetRuntime generates a new libpod runtime configured by command line options func GetRuntime(ctx context.Context, flags *flag.FlagSet, cfg *entities.PodmanConfig) (*libpod.Runtime, error) { - return getRuntime(ctx, flags, &engineOpts{ - renumber: false, - migrate: false, - noStore: false, - withFDS: true, - config: cfg, + runtimeSync.Do(func() { + runtimeLib, runtimeErr = getRuntime(ctx, flags, &engineOpts{ + renumber: false, + migrate: false, + noStore: false, + withFDS: true, + config: cfg, + }) }) + return runtimeLib, runtimeErr } // GetRuntimeNoStore generates a new libpod runtime configured by command line options @@ -234,6 +246,18 @@ func ParseIDMapping(mode namespaces.UsernsMode, uidMapSlice, gidMapSlice []strin HostGIDMapping: true, } + if mode.IsAuto() { + var err error + options.HostUIDMapping = false + options.HostGIDMapping = false + options.AutoUserNs = true + opts, err := mode.GetAutoOptions() + if err != nil { + return nil, err + } + options.AutoUserNsOpts = *opts + return &options, nil + } if mode.IsKeepID() { if len(uidMapSlice) > 0 || len(gidMapSlice) > 0 { return nil, errors.New("cannot specify custom mappings with --userns=keep-id") diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index 679bb371b..32f9c4e36 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -115,11 +115,15 @@ func (ic *ContainerEngine) ContainerRestart(ctx context.Context, namesOrIds []st t := int(*options.Timeout) timeout = &t } + ctrs, err := getContainersByContext(ic.ClientCxt, options.All, namesOrIds) if err != nil { return nil, err } for _, c := range ctrs { + if options.Running && c.State != define.ContainerStateRunning.String() { + continue + } reports = append(reports, &entities.RestartReport{ Id: c.ID, Err: containers.Restart(ic.ClientCxt, c.ID, timeout), @@ -371,3 +375,15 @@ func (ic *ContainerEngine) ContainerUnmount(ctx context.Context, nameOrIds []str func (ic *ContainerEngine) Config(_ context.Context) (*config.Config, error) { return config.Default() } + +func (ic *ContainerEngine) ContainerPort(ctx context.Context, nameOrId string, options entities.ContainerPortOptions) ([]*entities.ContainerPortReport, error) { + return nil, errors.New("not implemented") +} + +func (ic *ContainerEngine) ContainerCp(ctx context.Context, source, dest string, options entities.ContainerCpOptions) (*entities.ContainerCpReport, error) { + return nil, errors.New("not implemented") +} + +// Shutdown Libpod engine +func (ic *ContainerEngine) Shutdown(_ context.Context) { +} diff --git a/pkg/domain/infra/tunnel/events.go b/pkg/domain/infra/tunnel/events.go index 46d88341a..93da3aeb4 100644 --- a/pkg/domain/infra/tunnel/events.go +++ b/pkg/domain/infra/tunnel/events.go @@ -4,7 +4,6 @@ import ( "context" "strings" - "github.com/containers/libpod/pkg/api/handlers" "github.com/containers/libpod/pkg/bindings/system" "github.com/containers/libpod/pkg/domain/entities" "github.com/pkg/errors" @@ -21,10 +20,10 @@ func (ic *ContainerEngine) Events(ctx context.Context, opts entities.EventsOptio filters[split[0]] = append(filters[split[0]], strings.Join(split[1:], "=")) } } - binChan := make(chan handlers.Event) + binChan := make(chan entities.Event) go func() { for e := range binChan { - opts.EventChan <- e.ToLibpodEvent() + opts.EventChan <- entities.ConvertToLibpodEvent(e) } }() return system.Events(ic.ClientCxt, binChan, nil, &opts.Since, &opts.Until, filters) diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go index 6ea2bd9f2..66e4e6e3f 100644 --- a/pkg/domain/infra/tunnel/images.go +++ b/pkg/domain/infra/tunnel/images.go @@ -7,6 +7,7 @@ import ( "github.com/containers/common/pkg/config" "github.com/containers/image/v5/docker/reference" + "github.com/containers/libpod/pkg/bindings" images "github.com/containers/libpod/pkg/bindings/images" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/domain/utils" @@ -19,25 +20,8 @@ func (ir *ImageEngine) Exists(_ context.Context, nameOrId string) (*entities.Boo return &entities.BoolReport{Value: found}, err } -func (ir *ImageEngine) Delete(ctx context.Context, nameOrId []string, opts entities.ImageDeleteOptions) (*entities.ImageDeleteReport, error) { - report := entities.ImageDeleteReport{} - - for _, id := range nameOrId { - results, err := images.Remove(ir.ClientCxt, id, &opts.Force) - if err != nil { - return nil, err - } - for _, e := range results { - if a, ok := e["Deleted"]; ok { - report.Deleted = append(report.Deleted, a) - } - - if a, ok := e["Untagged"]; ok { - report.Untagged = append(report.Untagged, a) - } - } - } - return &report, nil +func (ir *ImageEngine) Remove(ctx context.Context, imagesArg []string, opts entities.ImageRemoveOptions) (*entities.ImageRemoveReport, error) { + return images.Remove(ir.ClientCxt, imagesArg, opts) } func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions) ([]*entities.ImageSummary, error) { @@ -126,6 +110,15 @@ func (ir *ImageEngine) Tag(ctx context.Context, nameOrId string, tags []string, } func (ir *ImageEngine) Untag(ctx context.Context, nameOrId string, tags []string, options entities.ImageUntagOptions) error { + // Remove all tags if none are provided + if len(tags) == 0 { + newImage, err := images.GetImage(ir.ClientCxt, nameOrId, &bindings.PFalse) + if err != nil { + return err + } + tags = newImage.NamesHistory + } + for _, newTag := range tags { var ( tag, repo string @@ -263,3 +256,11 @@ func (ir *ImageEngine) Config(_ context.Context) (*config.Config, error) { func (ir *ImageEngine) Build(ctx context.Context, containerFiles []string, opts entities.BuildOptions) (*entities.BuildReport, error) { return nil, errors.New("not implemented yet") } + +func (ir *ImageEngine) Tree(ctx context.Context, nameOrId string, opts entities.ImageTreeOptions) (*entities.ImageTreeReport, error) { + return nil, errors.New("not implemented yet") +} + +// Shutdown Libpod engine +func (ir *ImageEngine) Shutdown(_ context.Context) { +} diff --git a/pkg/domain/infra/tunnel/manifest.go b/pkg/domain/infra/tunnel/manifest.go new file mode 100644 index 000000000..18b400533 --- /dev/null +++ b/pkg/domain/infra/tunnel/manifest.go @@ -0,0 +1,64 @@ +package tunnel + +import ( + "context" + "encoding/json" + "strings" + + "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/bindings/manifests" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" +) + +// ManifestCreate implements manifest create via ImageEngine +func (ir *ImageEngine) ManifestCreate(ctx context.Context, names, images []string, opts entities.ManifestCreateOptions) (string, error) { + imageID, err := manifests.Create(ir.ClientCxt, names, images, &opts.All) + if err != nil { + return imageID, errors.Wrapf(err, "error creating manifest") + } + return imageID, err +} + +// ManifestInspect returns contents of manifest list with given name +func (ir *ImageEngine) ManifestInspect(ctx context.Context, name string) ([]byte, error) { + list, err := manifests.Inspect(ir.ClientCxt, name) + if err != nil { + return nil, errors.Wrapf(err, "error getting content of manifest list or image %s", name) + } + + buf, err := json.MarshalIndent(list, "", " ") + if err != nil { + return buf, errors.Wrapf(err, "error rendering manifest for display") + } + return buf, err +} + +// ManifestAdd adds images to the manifest list +func (ir *ImageEngine) ManifestAdd(ctx context.Context, opts entities.ManifestAddOptions) (string, error) { + manifestAddOpts := image.ManifestAddOpts{ + All: opts.All, + Arch: opts.Arch, + Features: opts.Features, + Images: opts.Images, + OS: opts.OS, + OSVersion: opts.OSVersion, + Variant: opts.Variant, + } + if len(opts.Annotation) != 0 { + annotations := make(map[string]string) + for _, annotationSpec := range opts.Annotation { + spec := strings.SplitN(annotationSpec, "=", 2) + if len(spec) != 2 { + return "", errors.Errorf("no value given for annotation %q", spec[0]) + } + annotations[spec[0]] = spec[1] + } + manifestAddOpts.Annotation = annotations + } + listID, err := manifests.Add(ctx, opts.Images[1], manifestAddOpts) + if err != nil { + return listID, errors.Wrapf(err, "error adding to manifest list %s", opts.Images[1]) + } + return listID, nil +} diff --git a/pkg/domain/infra/tunnel/pods.go b/pkg/domain/infra/tunnel/pods.go index e7641c077..c193c6752 100644 --- a/pkg/domain/infra/tunnel/pods.go +++ b/pkg/domain/infra/tunnel/pods.go @@ -211,3 +211,7 @@ func (ic *ContainerEngine) PodInspect(ctx context.Context, options entities.PodI } return pods.Inspect(ic.ClientCxt, options.NameOrID) } + +func (ic *ContainerEngine) PodStats(ctx context.Context, namesOrIds []string, options entities.PodStatsOptions) ([]*entities.PodStatsReport, error) { + return pods.Stats(ic.ClientCxt, namesOrIds, options) +} diff --git a/pkg/domain/infra/tunnel/system.go b/pkg/domain/infra/tunnel/system.go index f373525c5..97bf885e7 100644 --- a/pkg/domain/infra/tunnel/system.go +++ b/pkg/domain/infra/tunnel/system.go @@ -14,10 +14,6 @@ func (ic *ContainerEngine) Info(ctx context.Context) (*define.Info, error) { return system.Info(ic.ClientCxt) } -func (ic *ContainerEngine) RestService(_ context.Context, _ entities.ServiceOptions) error { - panic(errors.New("rest service is not supported when tunneling")) -} - func (ic *ContainerEngine) VarlinkService(_ context.Context, _ entities.ServiceOptions) error { panic(errors.New("varlink service is not supported when tunneling")) } diff --git a/pkg/namespaces/namespaces.go b/pkg/namespaces/namespaces.go index 2cb3c3f20..2ffbde977 100644 --- a/pkg/namespaces/namespaces.go +++ b/pkg/namespaces/namespaces.go @@ -31,7 +31,7 @@ func (n CgroupMode) IsHost() bool { // IsDefaultValue indicates whether the cgroup namespace has the default value. func (n CgroupMode) IsDefaultValue() bool { - return n == "" + return n == "" || n == defaultType } // IsNS indicates a cgroup namespace passed in by path (ns:<path>) @@ -102,6 +102,11 @@ func (n UsernsMode) IsAuto() bool { return parts[0] == "auto" } +// IsDefaultValue indicates whether the user namespace has the default value. +func (n UsernsMode) IsDefaultValue() bool { + return n == "" || n == defaultType +} + // GetAutoOptions returns a AutoUserNsOptions with the settings to setup automatically // a user namespace. func (n UsernsMode) GetAutoOptions() (*storage.AutoUserNsOptions, error) { diff --git a/pkg/ps/ps.go b/pkg/ps/ps.go index d0fef65c8..907063df9 100644 --- a/pkg/ps/ps.go +++ b/pkg/ps/ps.go @@ -158,6 +158,7 @@ func ListContainerBatch(rt *libpod.Runtime, ctr *libpod.Container, opts entities ExitedAt: exitedTime.Unix(), ID: conConfig.ID, Image: conConfig.RootfsImageName, + ImageID: conConfig.RootfsImageID, IsInfra: conConfig.IsInfra, Labels: conConfig.Labels, Mounts: ctr.UserVolumes(), diff --git a/pkg/rootless/rootless_linux.c b/pkg/rootless/rootless_linux.c index da52a7217..72d461cdc 100644 --- a/pkg/rootless/rootless_linux.c +++ b/pkg/rootless/rootless_linux.c @@ -535,8 +535,36 @@ create_pause_process (const char *pause_pid_file_path, char **argv) } } +static void +join_namespace_or_die (int pid_to_join, const char *ns_file) +{ + char ns_path[PATH_MAX]; + int ret; + int fd; + + ret = snprintf (ns_path, PATH_MAX, "/proc/%d/ns/%s", pid_to_join, ns_file); + if (ret == PATH_MAX) + { + fprintf (stderr, "internal error: namespace path too long\n"); + _exit (EXIT_FAILURE); + } + + fd = open (ns_path, O_CLOEXEC | O_RDONLY); + if (fd < 0) + { + fprintf (stderr, "cannot open: %s\n", ns_path); + _exit (EXIT_FAILURE); + } + if (setns (fd, 0) < 0) + { + fprintf (stderr, "cannot set namespace to %s: %s\n", ns_path, strerror (errno)); + _exit (EXIT_FAILURE); + } + close (fd); +} + int -reexec_userns_join (int userns, int mountns, char *pause_pid_file_path) +reexec_userns_join (int pid_to_join, char *pause_pid_file_path) { char uid[16]; char gid[16]; @@ -606,19 +634,8 @@ reexec_userns_join (int userns, int mountns, char *pause_pid_file_path) _exit (EXIT_FAILURE); } - if (setns (userns, 0) < 0) - { - fprintf (stderr, "cannot setns: %s\n", strerror (errno)); - _exit (EXIT_FAILURE); - } - close (userns); - - if (mountns >= 0 && setns (mountns, 0) < 0) - { - fprintf (stderr, "cannot setns: %s\n", strerror (errno)); - _exit (EXIT_FAILURE); - } - close (mountns); + join_namespace_or_die (pid_to_join, "user"); + join_namespace_or_die (pid_to_join, "mnt"); if (syscall_setresgid (0, 0, 0) < 0) { diff --git a/pkg/rootless/rootless_linux.go b/pkg/rootless/rootless_linux.go index 5ddfab7ad..3de136f12 100644 --- a/pkg/rootless/rootless_linux.go +++ b/pkg/rootless/rootless_linux.go @@ -31,7 +31,7 @@ extern uid_t rootless_uid(); extern uid_t rootless_gid(); extern int reexec_in_user_namespace(int ready, char *pause_pid_file_path, char *file_to_read, int fd); extern int reexec_in_user_namespace_wait(int pid, int options); -extern int reexec_userns_join(int userns, int mountns, char *pause_pid_file_path); +extern int reexec_userns_join(int pid, char *pause_pid_file_path); */ import "C" @@ -124,91 +124,6 @@ func tryMappingTool(tool string, pid int, hostID int, mappings []idtools.IDMap) return nil } -func readUserNs(path string) (string, error) { - b := make([]byte, 256) - _, err := unix.Readlink(path, b) - if err != nil { - return "", err - } - return string(b), nil -} - -func readUserNsFd(fd uintptr) (string, error) { - return readUserNs(fmt.Sprintf("/proc/self/fd/%d", fd)) -} - -func getParentUserNs(fd uintptr) (uintptr, error) { - const nsGetParent = 0xb702 - ret, _, errno := unix.Syscall(unix.SYS_IOCTL, fd, uintptr(nsGetParent), 0) - if errno != 0 { - return 0, errno - } - return (uintptr)(unsafe.Pointer(ret)), nil -} - -// getUserNSFirstChild returns an open FD for the first direct child user namespace that created the process -// Each container creates a new user namespace where the runtime runs. The current process in the container -// might have created new user namespaces that are child of the initial namespace we created. -// This function finds the initial namespace created for the container that is a child of the current namespace. -// -// current ns -// / \ -// TARGET -> a [other containers] -// / -// b -// / -// NS READ USING THE PID -> c -func getUserNSFirstChild(fd uintptr) (*os.File, error) { - currentNS, err := readUserNs("/proc/self/ns/user") - if err != nil { - return nil, err - } - - ns, err := readUserNsFd(fd) - if err != nil { - return nil, errors.Wrapf(err, "cannot read user namespace") - } - if ns == currentNS { - return nil, errors.New("process running in the same user namespace") - } - - for { - nextFd, err := getParentUserNs(fd) - if err != nil { - if err == unix.ENOTTY { - return os.NewFile(fd, "userns child"), nil - } - return nil, errors.Wrapf(err, "cannot get parent user namespace") - } - - ns, err = readUserNsFd(nextFd) - if err != nil { - return nil, errors.Wrapf(err, "cannot read user namespace") - } - - if ns == currentNS { - if err := unix.Close(int(nextFd)); err != nil { - return nil, err - } - - // Drop O_CLOEXEC for the fd. - _, _, errno := unix.Syscall(unix.SYS_FCNTL, fd, unix.F_SETFD, 0) - if errno != 0 { - if err := unix.Close(int(fd)); err != nil { - logrus.Errorf("failed to close file descriptor %d", fd) - } - return nil, errno - } - - return os.NewFile(fd, "userns child"), nil - } - if err := unix.Close(int(fd)); err != nil { - return nil, err - } - fd = nextFd - } -} - // joinUserAndMountNS re-exec podman in a new userNS and join the user and mount // namespace of the specified PID without looking up its parent. Useful to join directly // the conmon process. @@ -220,31 +135,7 @@ func joinUserAndMountNS(pid uint, pausePid string) (bool, int, error) { cPausePid := C.CString(pausePid) defer C.free(unsafe.Pointer(cPausePid)) - userNS, err := os.Open(fmt.Sprintf("/proc/%d/ns/user", pid)) - if err != nil { - return false, -1, err - } - defer func() { - if err := userNS.Close(); err != nil { - logrus.Errorf("unable to close namespace: %q", err) - } - }() - - mountNS, err := os.Open(fmt.Sprintf("/proc/%d/ns/mnt", pid)) - if err != nil { - return false, -1, err - } - defer func() { - if err := mountNS.Close(); err != nil { - logrus.Errorf("unable to close namespace: %q", err) - } - }() - - fd, err := getUserNSFirstChild(userNS.Fd()) - if err != nil { - return false, -1, err - } - pidC := C.reexec_userns_join(C.int(fd.Fd()), C.int(mountNS.Fd()), cPausePid) + pidC := C.reexec_userns_join(C.int(pid), cPausePid) if int(pidC) < 0 { return false, -1, errors.Errorf("cannot re-exec process") } diff --git a/pkg/selinux/selinux.go b/pkg/selinux/selinux.go new file mode 100644 index 000000000..6b6d065f7 --- /dev/null +++ b/pkg/selinux/selinux.go @@ -0,0 +1,40 @@ +package selinux + +import ( + "github.com/opencontainers/selinux/go-selinux" +) + +// KVMLabel returns labels for running kvm isolated containers +func KVMLabel(cLabel string) (string, error) { + if cLabel == "" { + // selinux is disabled + return "", nil + } + processLabel, _ := selinux.KVMContainerLabels() + selinux.ReleaseLabel(processLabel) + return swapSELinuxLabel(cLabel, processLabel) +} + +// InitLabel returns labels for running systemd based containers +func InitLabel(cLabel string) (string, error) { + if cLabel == "" { + // selinux is disabled + return "", nil + } + processLabel, _ := selinux.InitContainerLabels() + selinux.ReleaseLabel(processLabel) + return swapSELinuxLabel(cLabel, processLabel) +} + +func swapSELinuxLabel(cLabel, processLabel string) (string, error) { + dcon, err := selinux.NewContext(cLabel) + if err != nil { + return "", err + } + scon, err := selinux.NewContext(processLabel) + if err != nil { + return "", err + } + dcon["type"] = scon["type"] + return dcon.Get(), nil +} diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go index 5de07fc28..7ee2df890 100644 --- a/pkg/spec/spec.go +++ b/pkg/spec/spec.go @@ -5,11 +5,11 @@ import ( "github.com/containers/common/pkg/capabilities" cconfig "github.com/containers/common/pkg/config" + "github.com/containers/common/pkg/sysinfo" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/cgroups" "github.com/containers/libpod/pkg/env" "github.com/containers/libpod/pkg/rootless" - "github.com/containers/libpod/pkg/sysinfo" "github.com/containers/libpod/pkg/util" "github.com/docker/go-units" "github.com/opencontainers/runc/libcontainer/user" diff --git a/pkg/spec/spec_test.go b/pkg/spec/spec_test.go index 0f63b2bbc..71434fe73 100644 --- a/pkg/spec/spec_test.go +++ b/pkg/spec/spec_test.go @@ -4,9 +4,9 @@ import ( "runtime" "testing" + "github.com/containers/common/pkg/sysinfo" "github.com/containers/libpod/pkg/cgroups" "github.com/containers/libpod/pkg/rootless" - "github.com/containers/libpod/pkg/sysinfo" "github.com/containers/storage" "github.com/containers/storage/pkg/idtools" "github.com/docker/go-units" diff --git a/pkg/specgen/container_validate.go b/pkg/specgen/container_validate.go index 9152e7ee7..94e456c52 100644 --- a/pkg/specgen/container_validate.go +++ b/pkg/specgen/container_validate.go @@ -14,7 +14,7 @@ var ( // SystemDValues describes the only values that SystemD can be SystemDValues = []string{"true", "false", "always"} // ImageVolumeModeValues describes the only values that ImageVolumeMode can be - ImageVolumeModeValues = []string{"ignore", "tmpfs", "bind"} + ImageVolumeModeValues = []string{"ignore", "tmpfs", "anonymous"} ) func exclusiveOptions(opt1, opt2 string) error { @@ -34,7 +34,7 @@ func (s *SpecGenerator) Validate() error { } // Cannot set hostname and utsns if len(s.ContainerBasicConfig.Hostname) > 0 && !s.ContainerBasicConfig.UtsNS.IsPrivate() { - return errors.Wrap(ErrInvalidSpecConfig, "cannot set hostname when creating an UTS namespace") + return errors.Wrap(ErrInvalidSpecConfig, "cannot set hostname when running in the host UTS namespace") } // systemd values must be true, false, or always if len(s.ContainerBasicConfig.Systemd) > 0 && !util.StringInSlice(strings.ToLower(s.ContainerBasicConfig.Systemd), SystemDValues) { @@ -54,7 +54,7 @@ func (s *SpecGenerator) Validate() error { } // shmsize conflicts with IPC namespace if s.ContainerStorageConfig.ShmSize != nil && !s.ContainerStorageConfig.IpcNS.IsPrivate() { - return errors.New("cannot set shmsize when creating an IPC namespace") + return errors.New("cannot set shmsize when running in the host IPC Namespace") } // @@ -86,18 +86,15 @@ func (s *SpecGenerator) Validate() error { // // ContainerNetworkConfig // - if !s.NetNS.IsPrivate() && s.ConfigureNetNS { - return errors.New("can only configure network namespace when creating a network a network namespace") - } // useimageresolveconf conflicts with dnsserver, dnssearch, dnsoption if s.UseImageResolvConf { - if len(s.DNSServer) > 0 { + if len(s.DNSServers) > 0 { return exclusiveOptions("UseImageResolvConf", "DNSServer") } if len(s.DNSSearch) > 0 { return exclusiveOptions("UseImageResolvConf", "DNSSearch") } - if len(s.DNSOption) > 0 { + if len(s.DNSOptions) > 0 { return exclusiveOptions("UseImageResolvConf", "DNSOption") } } @@ -132,7 +129,7 @@ func (s *SpecGenerator) Validate() error { if err := s.CgroupNS.validate(); err != nil { return err } - if err := s.UserNS.validate(); err != nil { + if err := validateUserNS(&s.UserNS); err != nil { return err } diff --git a/pkg/specgen/generate/config_linux_cgo.go b/pkg/specgen/generate/config_linux_cgo.go index b06ef5c9a..5d629a6e6 100644 --- a/pkg/specgen/generate/config_linux_cgo.go +++ b/pkg/specgen/generate/config_linux_cgo.go @@ -24,6 +24,9 @@ func getSeccompConfig(s *specgen.SpecGenerator, configSpec *spec.Spec, img *imag } if scp == seccomp.PolicyImage { + if img == nil { + return nil, errors.New("cannot read seccomp profile without a valid image") + } labels, err := img.Labels(context.Background()) if err != nil { return nil, err diff --git a/pkg/specgen/generate/container.go b/pkg/specgen/generate/container.go index 7233acb8a..b27dd1cc2 100644 --- a/pkg/specgen/generate/container.go +++ b/pkg/specgen/generate/container.go @@ -8,29 +8,49 @@ import ( envLib "github.com/containers/libpod/pkg/env" "github.com/containers/libpod/pkg/signal" "github.com/containers/libpod/pkg/specgen" - "github.com/pkg/errors" "golang.org/x/sys/unix" ) func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerator) error { + // If a rootfs is used, then there is no image data + if s.ContainerStorageConfig.Rootfs != "" { + return nil + } newImage, err := r.ImageRuntime().NewFromLocal(s.Image) if err != nil { return err } + if s.HealthConfig == nil { + s.HealthConfig, err = newImage.GetHealthCheck(ctx) + if err != nil { + return err + } + } + // Image stop signal - if s.StopSignal == nil && newImage.Config != nil { - sig, err := signal.ParseSignalNameOrNumber(newImage.Config.StopSignal) + if s.StopSignal == nil { + stopSignal, err := newImage.StopSignal(ctx) + if err != nil { + return err + } + sig, err := signal.ParseSignalNameOrNumber(stopSignal) if err != nil { return err } s.StopSignal = &sig } + // Image envs from the image if they don't exist // already - if newImage.Config != nil && len(newImage.Config.Env) > 0 { - envs, err := envLib.ParseSlice(newImage.Config.Env) + env, err := newImage.Env(ctx) + if err != nil { + return err + } + + if len(env) > 0 { + envs, err := envLib.ParseSlice(env) if err != nil { return err } @@ -41,16 +61,29 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat } } + labels, err := newImage.Labels(ctx) + if err != nil { + return err + } + // labels from the image that dont exist already - if config := newImage.Config; config != nil { - for k, v := range config.Labels { - if _, exists := s.Labels[k]; !exists { - s.Labels[k] = v - } + for k, v := range labels { + if _, exists := s.Labels[k]; !exists { + s.Labels[k] = v } } // annotations + + // Add annotations from the image + annotations, err := newImage.Annotations(ctx) + if err != nil { + return err + } + for k, v := range annotations { + annotations[k] = v + } + // in the event this container is in a pod, and the pod has an infra container // we will want to configure it as a type "container" instead defaulting to // the behavior of a "sandbox" container @@ -59,36 +92,25 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat // VM, which is the default behavior // - "container" denotes the container should join the VM of the SandboxID // (the infra container) - s.Annotations = make(map[string]string) + if len(s.Pod) > 0 { - s.Annotations[ann.SandboxID] = s.Pod - s.Annotations[ann.ContainerType] = ann.ContainerTypeContainer + annotations[ann.SandboxID] = s.Pod + annotations[ann.ContainerType] = ann.ContainerTypeContainer } - // - // Next, add annotations from the image - annotations, err := newImage.Annotations(ctx) - if err != nil { - return err - } - for k, v := range annotations { + + // now pass in the values from client + for k, v := range s.Annotations { annotations[k] = v } + s.Annotations = annotations - // entrypoint - if config := newImage.Config; config != nil { - if len(s.Entrypoint) < 1 && len(config.Entrypoint) > 0 { - s.Entrypoint = config.Entrypoint - } - if len(s.Command) < 1 && len(config.Cmd) > 0 { - s.Command = config.Cmd - } - if len(s.Command) < 1 && len(s.Entrypoint) < 1 { - return errors.Errorf("No command provided or as CMD or ENTRYPOINT in this image") - } - // workdir - if len(s.WorkDir) < 1 && len(config.WorkingDir) > 1 { - s.WorkDir = config.WorkingDir - } + // workdir + workingDir, err := newImage.WorkingDir(ctx) + if err != nil { + return err + } + if len(s.WorkDir) < 1 && len(workingDir) > 1 { + s.WorkDir = workingDir } if len(s.SeccompProfilePath) < 1 { @@ -99,15 +121,17 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat s.SeccompProfilePath = p } - if user := s.User; len(user) == 0 { - switch { + if len(s.User) == 0 { + s.User, err = newImage.User(ctx) + if err != nil { + return err + } + // TODO This should be enabled when namespaces actually work //case usernsMode.IsKeepID(): // user = fmt.Sprintf("%d:%d", rootless.GetRootlessUID(), rootless.GetRootlessGID()) - case newImage.Config == nil || (newImage.Config != nil && len(newImage.Config.User) == 0): + if len(s.User) == 0 { s.User = "0" - default: - s.User = newImage.Config.User } } if err := finishThrottleDevices(s); err != nil { @@ -116,7 +140,7 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat // Unless already set via the CLI, check if we need to disable process // labels or set the defaults. if len(s.SelinuxOpts) == 0 { - if err := SetLabelOpts(s, r, s.PidNS, s.IpcNS); err != nil { + if err := setLabelOpts(s, r, s.PidNS, s.IpcNS); err != nil { return err } } diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index 264e0ff8e..bb84f0618 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -7,6 +7,7 @@ import ( "github.com/containers/common/pkg/config" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/specgen" "github.com/containers/storage" "github.com/pkg/errors" @@ -14,40 +15,107 @@ import ( ) // MakeContainer creates a container based on the SpecGenerator -func MakeContainer(rt *libpod.Runtime, s *specgen.SpecGenerator) (*libpod.Container, error) { +func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGenerator) (*libpod.Container, error) { + rtc, err := rt.GetConfig() + if err != nil { + return nil, err + } + + // If joining a pod, retrieve the pod for use. + var pod *libpod.Pod + if s.Pod != "" { + foundPod, err := rt.LookupPod(s.Pod) + if err != nil { + return nil, errors.Wrapf(err, "error retrieving pod %s", s.Pod) + } + pod = foundPod + } + + // Set defaults for unset namespaces + if s.PidNS.IsDefault() { + defaultNS, err := GetDefaultNamespaceMode("pid", rtc, pod) + if err != nil { + return nil, err + } + s.PidNS = defaultNS + } + if s.IpcNS.IsDefault() { + defaultNS, err := GetDefaultNamespaceMode("ipc", rtc, pod) + if err != nil { + return nil, err + } + s.IpcNS = defaultNS + } + if s.UtsNS.IsDefault() { + defaultNS, err := GetDefaultNamespaceMode("uts", rtc, pod) + if err != nil { + return nil, err + } + s.UtsNS = defaultNS + } + if s.UserNS.IsDefault() { + defaultNS, err := GetDefaultNamespaceMode("user", rtc, pod) + if err != nil { + return nil, err + } + s.UserNS = defaultNS + } + if s.NetNS.IsDefault() { + defaultNS, err := GetDefaultNamespaceMode("net", rtc, pod) + if err != nil { + return nil, err + } + s.NetNS = defaultNS + } + if s.CgroupNS.IsDefault() { + defaultNS, err := GetDefaultNamespaceMode("cgroup", rtc, pod) + if err != nil { + return nil, err + } + s.CgroupNS = defaultNS + } + + options := []libpod.CtrCreateOption{} + + var newImage *image.Image + if s.Rootfs != "" { + options = append(options, libpod.WithRootFS(s.Rootfs)) + } else { + newImage, err = rt.ImageRuntime().NewFromLocal(s.Image) + if err != nil { + return nil, err + } + options = append(options, libpod.WithRootFSFromImage(newImage.ID(), s.Image, s.RawImageName)) + } if err := s.Validate(); err != nil { return nil, errors.Wrap(err, "invalid config provided") } - rtc, err := rt.GetConfig() + + finalMounts, finalVolumes, err := finalizeMounts(ctx, s, rt, rtc, newImage) if err != nil { return nil, err } - options, err := createContainerOptions(rt, s) + opts, err := createContainerOptions(rt, s, pod, finalVolumes) if err != nil { return nil, err } + options = append(options, opts...) podmanPath, err := os.Executable() if err != nil { return nil, err } options = append(options, createExitCommandOption(s, rt.StorageConfig(), rtc, podmanPath)) - newImage, err := rt.ImageRuntime().NewFromLocal(s.Image) - if err != nil { - return nil, err - } - - options = append(options, libpod.WithRootFSFromImage(newImage.ID(), s.Image, s.RawImageName)) - runtimeSpec, err := SpecGenToOCI(s, rt, newImage) + runtimeSpec, err := SpecGenToOCI(ctx, s, rt, rtc, newImage, finalMounts) if err != nil { return nil, err } - return rt.NewContainer(context.Background(), runtimeSpec, options...) + return rt.NewContainer(ctx, runtimeSpec, options...) } -func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator) ([]libpod.CtrCreateOption, error) { +func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator, pod *libpod.Pod, volumes []*specgen.NamedVolume) ([]libpod.CtrCreateOption, error) { var options []libpod.CtrCreateOption var err error @@ -74,21 +142,21 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator) ([]lib for _, mount := range s.Mounts { destinations = append(destinations, mount.Destination) } - for _, volume := range s.Volumes { + for _, volume := range volumes { destinations = append(destinations, volume.Dest) } options = append(options, libpod.WithUserVolumes(destinations)) - if len(s.Volumes) != 0 { - var volumes []*libpod.ContainerNamedVolume - for _, v := range s.Volumes { - volumes = append(volumes, &libpod.ContainerNamedVolume{ + if len(volumes) != 0 { + var vols []*libpod.ContainerNamedVolume + for _, v := range volumes { + vols = append(vols, &libpod.ContainerNamedVolume{ Name: v.Name, Dest: v.Dest, Options: v.Options, }) } - options = append(options, libpod.WithNamedVolumes(volumes)) + options = append(options, libpod.WithNamedVolumes(vols)) } if len(s.Command) != 0 { @@ -123,7 +191,7 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator) ([]lib options = append(options, libpod.WithPrivileged(s.Privileged)) // Get namespace related options - namespaceOptions, err := GenerateNamespaceContainerOpts(s, rt) + namespaceOptions, err := GenerateNamespaceOptions(s, rt, pod) if err != nil { return nil, err } diff --git a/pkg/specgen/generate/namespaces.go b/pkg/specgen/generate/namespaces.go index cdd7d86da..a8b74b504 100644 --- a/pkg/specgen/generate/namespaces.go +++ b/pkg/specgen/generate/namespaces.go @@ -2,317 +2,408 @@ package generate import ( "os" + "strings" - "github.com/containers/common/pkg/capabilities" + "github.com/containers/common/pkg/config" "github.com/containers/libpod/libpod" - "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/cgroups" + "github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/pkg/specgen" - "github.com/cri-o/ocicni/pkg/ocicni" + "github.com/containers/libpod/pkg/util" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-tools/generate" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) -func GenerateNamespaceContainerOpts(s *specgen.SpecGenerator, rt *libpod.Runtime) ([]libpod.CtrCreateOption, error) { - var portBindings []ocicni.PortMapping - options := make([]libpod.CtrCreateOption, 0) +// Get the default namespace mode for any given namespace type. +func GetDefaultNamespaceMode(nsType string, cfg *config.Config, pod *libpod.Pod) (specgen.Namespace, error) { + // The default for most is private + toReturn := specgen.Namespace{} + toReturn.NSMode = specgen.Private - // Cgroups - switch { - case s.CgroupNS.IsPrivate(): - ns := s.CgroupNS.Value - if _, err := os.Stat(ns); err != nil { - return nil, err + // Ensure case insensitivity + nsType = strings.ToLower(nsType) + + // If the pod is not nil - check shared namespaces + if pod != nil && pod.HasInfraContainer() { + podMode := false + switch { + case nsType == "pid" && pod.SharesPID(): + podMode = true + case nsType == "ipc" && pod.SharesIPC(): + podMode = true + case nsType == "uts" && pod.SharesUTS(): + podMode = true + case nsType == "user" && pod.SharesUser(): + podMode = true + case nsType == "net" && pod.SharesNet(): + podMode = true + case nsType == "cgroup" && pod.SharesCgroup(): + podMode = true } - case s.CgroupNS.IsContainer(): - connectedCtr, err := rt.LookupContainer(s.CgroupNS.Value) - if err != nil { - return nil, errors.Wrapf(err, "container %q not found", s.CgroupNS.Value) + if podMode { + toReturn.NSMode = specgen.FromPod + return toReturn, nil } - options = append(options, libpod.WithCgroupNSFrom(connectedCtr)) - // TODO - //default: - // return nil, errors.New("cgroup name only supports private and container") } - if s.CgroupParent != "" { - options = append(options, libpod.WithCgroupParent(s.CgroupParent)) + // If we have containers.conf and are not using cgroupns, use that. + if cfg != nil && nsType != "cgroup" { + switch nsType { + case "pid": + return specgen.ParseNamespace(cfg.Containers.PidNS) + case "ipc": + return specgen.ParseNamespace(cfg.Containers.IPCNS) + case "uts": + return specgen.ParseNamespace(cfg.Containers.UTSNS) + case "user": + return specgen.ParseUserNamespace(cfg.Containers.UserNS) + case "net": + ns, _, err := specgen.ParseNetworkNamespace(cfg.Containers.NetNS) + return ns, err + } } - if s.CgroupsMode != "" { - options = append(options, libpod.WithCgroupsMode(s.CgroupsMode)) + switch nsType { + case "pid", "ipc", "uts": + // PID, IPC, UTS both default to private, do nothing + case "user": + // User namespace always defaults to host + toReturn.NSMode = specgen.Host + case "net": + // Net defaults to Slirp on rootless, Bridge otherwise. + if rootless.IsRootless() { + toReturn.NSMode = specgen.Slirp + } else { + toReturn.NSMode = specgen.Bridge + } + case "cgroup": + // Cgroup is host for v1, private for v2. + // We can't trust c/common for this, as it only assumes private. + cgroupsv2, err := cgroups.IsCgroup2UnifiedMode() + if err != nil { + return toReturn, err + } + if !cgroupsv2 { + toReturn.NSMode = specgen.Host + } + default: + return toReturn, errors.Wrapf(define.ErrInvalidArg, "invalid namespace type %s passed", nsType) } - // ipc - switch { - case s.IpcNS.IsHost(): - options = append(options, libpod.WithShmDir("/dev/shm")) - case s.IpcNS.IsContainer(): - connectedCtr, err := rt.LookupContainer(s.IpcNS.Value) + return toReturn, nil +} + +// GenerateNamespaceOptions generates container creation options for all +// namespaces in a SpecGenerator. +// Pod is the pod the container will join. May be nil is the container is not +// joining a pod. +// TODO: Consider grouping options that are not directly attached to a namespace +// elsewhere. +func GenerateNamespaceOptions(s *specgen.SpecGenerator, rt *libpod.Runtime, pod *libpod.Pod) ([]libpod.CtrCreateOption, error) { + toReturn := []libpod.CtrCreateOption{} + + // If pod is not nil, get infra container. + var infraCtr *libpod.Container + if pod != nil { + infraID, err := pod.InfraContainerID() if err != nil { - return nil, errors.Wrapf(err, "container %q not found", s.IpcNS.Value) + // This is likely to be of the fatal kind (pod was + // removed) so hard fail + return nil, errors.Wrapf(err, "error looking up pod %s infra container", pod.ID()) + } + if infraID != "" { + ctr, err := rt.GetContainer(infraID) + if err != nil { + return nil, errors.Wrapf(err, "error retrieving pod %s infra container %s", pod.ID(), infraID) + } + infraCtr = ctr } - options = append(options, libpod.WithIPCNSFrom(connectedCtr)) - options = append(options, libpod.WithShmDir(connectedCtr.ShmDir())) } - // pid - if s.PidNS.IsContainer() { - connectedCtr, err := rt.LookupContainer(s.PidNS.Value) + errNoInfra := errors.Wrapf(define.ErrInvalidArg, "cannot use pod namespace as container is not joining a pod or pod has no infra container") + + // PID + switch s.PidNS.NSMode { + case specgen.FromPod: + if pod == nil || infraCtr == nil { + return nil, errNoInfra + } + toReturn = append(toReturn, libpod.WithPIDNSFrom(infraCtr)) + case specgen.FromContainer: + pidCtr, err := rt.LookupContainer(s.PidNS.Value) if err != nil { - return nil, errors.Wrapf(err, "container %q not found", s.PidNS.Value) + return nil, errors.Wrapf(err, "error looking up container to share pid namespace with") } - options = append(options, libpod.WithPIDNSFrom(connectedCtr)) + toReturn = append(toReturn, libpod.WithPIDNSFrom(pidCtr)) } - // uts - switch { - case s.UtsNS.IsPod(): - connectedPod, err := rt.LookupPod(s.UtsNS.Value) - if err != nil { - return nil, errors.Wrapf(err, "pod %q not found", s.UtsNS.Value) + // IPC + switch s.IpcNS.NSMode { + case specgen.Host: + // Force use of host /dev/shm for host namespace + toReturn = append(toReturn, libpod.WithShmDir("/dev/shm")) + case specgen.FromPod: + if pod == nil || infraCtr == nil { + return nil, errNoInfra } - options = append(options, libpod.WithUTSNSFromPod(connectedPod)) - case s.UtsNS.IsContainer(): - connectedCtr, err := rt.LookupContainer(s.UtsNS.Value) + toReturn = append(toReturn, libpod.WithIPCNSFrom(infraCtr)) + case specgen.FromContainer: + ipcCtr, err := rt.LookupContainer(s.IpcNS.Value) if err != nil { - return nil, errors.Wrapf(err, "container %q not found", s.UtsNS.Value) + return nil, errors.Wrapf(err, "error looking up container to share ipc namespace with") } - - options = append(options, libpod.WithUTSNSFrom(connectedCtr)) + toReturn = append(toReturn, libpod.WithIPCNSFrom(ipcCtr)) + toReturn = append(toReturn, libpod.WithShmDir(ipcCtr.ShmDir())) } - if s.UseImageHosts { - options = append(options, libpod.WithUseImageHosts()) - } else if len(s.HostAdd) > 0 { - options = append(options, libpod.WithHosts(s.HostAdd)) + // UTS + switch s.UtsNS.NSMode { + case specgen.FromPod: + if pod == nil || infraCtr == nil { + return nil, errNoInfra + } + toReturn = append(toReturn, libpod.WithUTSNSFrom(infraCtr)) + case specgen.FromContainer: + utsCtr, err := rt.LookupContainer(s.UtsNS.Value) + if err != nil { + return nil, errors.Wrapf(err, "error looking up container to share uts namespace with") + } + toReturn = append(toReturn, libpod.WithUTSNSFrom(utsCtr)) } // User - - switch { - case s.UserNS.IsPath(): - ns := s.UserNS.Value - if ns == "" { - return nil, errors.Errorf("invalid empty user-defined user namespace") - } - _, err := os.Stat(ns) - if err != nil { - return nil, err + switch s.UserNS.NSMode { + case specgen.KeepID: + if rootless.IsRootless() { + s.User = "" + } else { + // keep-id as root doesn't need a user namespace + s.UserNS.NSMode = specgen.Host } - if s.IDMappings != nil { - options = append(options, libpod.WithIDMappings(*s.IDMappings)) + case specgen.FromPod: + if pod == nil || infraCtr == nil { + return nil, errNoInfra } - case s.UserNS.IsContainer(): - connectedCtr, err := rt.LookupContainer(s.UserNS.Value) + toReturn = append(toReturn, libpod.WithUserNSFrom(infraCtr)) + case specgen.FromContainer: + userCtr, err := rt.LookupContainer(s.UserNS.Value) if err != nil { - return nil, errors.Wrapf(err, "container %q not found", s.UserNS.Value) - } - options = append(options, libpod.WithUserNSFrom(connectedCtr)) - default: - if s.IDMappings != nil { - options = append(options, libpod.WithIDMappings(*s.IDMappings)) + return nil, errors.Wrapf(err, "error looking up container to share user namespace with") } + toReturn = append(toReturn, libpod.WithUserNSFrom(userCtr)) } - options = append(options, libpod.WithUser(s.User)) - options = append(options, libpod.WithGroups(s.Groups)) - - if len(s.PortMappings) > 0 { - portBindings = s.PortMappings + if s.IDMappings != nil { + toReturn = append(toReturn, libpod.WithIDMappings(*s.IDMappings)) + } + if s.User != "" { + toReturn = append(toReturn, libpod.WithUser(s.User)) + } + if len(s.Groups) > 0 { + toReturn = append(toReturn, libpod.WithGroups(s.Groups)) } - switch { - case s.NetNS.IsPath(): - ns := s.NetNS.Value - if ns == "" { - return nil, errors.Errorf("invalid empty user-defined network namespace") + // Cgroup + switch s.CgroupNS.NSMode { + case specgen.FromPod: + if pod == nil || infraCtr == nil { + return nil, errNoInfra } - _, err := os.Stat(ns) + toReturn = append(toReturn, libpod.WithCgroupNSFrom(infraCtr)) + case specgen.FromContainer: + cgroupCtr, err := rt.LookupContainer(s.CgroupNS.Value) if err != nil { - return nil, err + return nil, errors.Wrapf(err, "error looking up container to share cgroup namespace with") } - case s.NetNS.IsContainer(): - connectedCtr, err := rt.LookupContainer(s.NetNS.Value) + toReturn = append(toReturn, libpod.WithCgroupNSFrom(cgroupCtr)) + } + + if s.CgroupParent != "" { + toReturn = append(toReturn, libpod.WithCgroupParent(s.CgroupParent)) + } + + if s.CgroupsMode != "" { + toReturn = append(toReturn, libpod.WithCgroupsMode(s.CgroupsMode)) + } + + // Net + // TODO image ports + // TODO validate CNINetworks, StaticIP, StaticIPv6 are only set if we + // are in bridge mode. + postConfigureNetNS := !s.UserNS.IsHost() + switch s.NetNS.NSMode { + case specgen.FromPod: + if pod == nil || infraCtr == nil { + return nil, errNoInfra + } + toReturn = append(toReturn, libpod.WithNetNSFrom(infraCtr)) + case specgen.FromContainer: + netCtr, err := rt.LookupContainer(s.NetNS.Value) if err != nil { - return nil, errors.Wrapf(err, "container %q not found", s.NetNS.Value) + return nil, errors.Wrapf(err, "error looking up container to share net namespace with") } - options = append(options, libpod.WithNetNSFrom(connectedCtr)) - case !s.NetNS.IsHost() && s.NetNS.NSMode != specgen.NoNetwork: - postConfigureNetNS := !s.UserNS.IsHost() - options = append(options, libpod.WithNetNS(portBindings, postConfigureNetNS, string(s.NetNS.NSMode), s.CNINetworks)) + toReturn = append(toReturn, libpod.WithNetNSFrom(netCtr)) + case specgen.Slirp: + toReturn = append(toReturn, libpod.WithNetNS(s.PortMappings, postConfigureNetNS, "slirp4netns", nil)) + case specgen.Bridge: + toReturn = append(toReturn, libpod.WithNetNS(s.PortMappings, postConfigureNetNS, "bridge", s.CNINetworks)) } - if len(s.DNSSearch) > 0 { - options = append(options, libpod.WithDNSSearch(s.DNSSearch)) + if s.UseImageHosts { + toReturn = append(toReturn, libpod.WithUseImageHosts()) + } else if len(s.HostAdd) > 0 { + toReturn = append(toReturn, libpod.WithHosts(s.HostAdd)) } - if len(s.DNSServer) > 0 { - // TODO I'm not sure how we are going to handle this given the input - if len(s.DNSServer) == 1 { //&& strings.ToLower(s.DNSServer[0].) == "none" { - options = append(options, libpod.WithUseImageResolvConf()) - } else { - var dnsServers []string - for _, d := range s.DNSServer { - dnsServers = append(dnsServers, d.String()) - } - options = append(options, libpod.WithDNS(dnsServers)) + if len(s.DNSSearch) > 0 { + toReturn = append(toReturn, libpod.WithDNSSearch(s.DNSSearch)) + } + if s.UseImageResolvConf { + toReturn = append(toReturn, libpod.WithUseImageResolvConf()) + } else if len(s.DNSServers) > 0 { + var dnsServers []string + for _, d := range s.DNSServers { + dnsServers = append(dnsServers, d.String()) } + toReturn = append(toReturn, libpod.WithDNS(dnsServers)) } - if len(s.DNSOption) > 0 { - options = append(options, libpod.WithDNSOption(s.DNSOption)) + if len(s.DNSOptions) > 0 { + toReturn = append(toReturn, libpod.WithDNSOption(s.DNSOptions)) } if s.StaticIP != nil { - options = append(options, libpod.WithStaticIP(*s.StaticIP)) + toReturn = append(toReturn, libpod.WithStaticIP(*s.StaticIP)) } - if s.StaticMAC != nil { - options = append(options, libpod.WithStaticMAC(*s.StaticMAC)) + toReturn = append(toReturn, libpod.WithStaticMAC(*s.StaticMAC)) } - return options, nil + + return toReturn, nil } -func pidConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator) error { - if s.PidNS.IsPath() { - return g.AddOrReplaceLinuxNamespace(string(spec.PIDNamespace), s.PidNS.Value) - } - if s.PidNS.IsHost() { - return g.RemoveLinuxNamespace(string(spec.PIDNamespace)) +func specConfigureNamespaces(s *specgen.SpecGenerator, g *generate.Generator, rt *libpod.Runtime) error { + // PID + switch s.PidNS.NSMode { + case specgen.Path: + if _, err := os.Stat(s.PidNS.Value); err != nil { + return errors.Wrapf(err, "cannot find specified PID namespace path %q", s.PidNS.Value) + } + if err := g.AddOrReplaceLinuxNamespace(string(spec.PIDNamespace), s.PidNS.Value); err != nil { + return err + } + case specgen.Host: + if err := g.RemoveLinuxNamespace(string(spec.PIDNamespace)); err != nil { + return err + } + case specgen.Private: + if err := g.AddOrReplaceLinuxNamespace(string(spec.PIDNamespace), ""); err != nil { + return err + } } - if s.PidNS.IsContainer() { - logrus.Debugf("using container %s pidmode", s.PidNS.Value) + + // IPC + switch s.IpcNS.NSMode { + case specgen.Path: + if _, err := os.Stat(s.IpcNS.Value); err != nil { + return errors.Wrapf(err, "cannot find specified IPC namespace path %q", s.IpcNS.Value) + } + if err := g.AddOrReplaceLinuxNamespace(string(spec.IPCNamespace), s.IpcNS.Value); err != nil { + return err + } + case specgen.Host: + if err := g.RemoveLinuxNamespace(string(spec.IPCNamespace)); err != nil { + return err + } + case specgen.Private: + if err := g.AddOrReplaceLinuxNamespace(string(spec.IPCNamespace), ""); err != nil { + return err + } } - if s.PidNS.IsPod() { - logrus.Debug("using pod pidmode") + + // UTS + switch s.UtsNS.NSMode { + case specgen.Path: + if _, err := os.Stat(s.UtsNS.Value); err != nil { + return errors.Wrapf(err, "cannot find specified UTS namespace path %q", s.UtsNS.Value) + } + if err := g.AddOrReplaceLinuxNamespace(string(spec.UTSNamespace), s.UtsNS.Value); err != nil { + return err + } + case specgen.Host: + if err := g.RemoveLinuxNamespace(string(spec.UTSNamespace)); err != nil { + return err + } + case specgen.Private: + if err := g.AddOrReplaceLinuxNamespace(string(spec.UTSNamespace), ""); err != nil { + return err + } } - return nil -} -func utsConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator, runtime *libpod.Runtime) error { hostname := s.Hostname - var err error if hostname == "" { switch { - case s.UtsNS.IsContainer(): - utsCtr, err := runtime.LookupContainer(s.UtsNS.Value) + case s.UtsNS.NSMode == specgen.FromContainer: + utsCtr, err := rt.LookupContainer(s.UtsNS.Value) if err != nil { - return errors.Wrapf(err, "unable to retrieve hostname from dependency container %s", s.UtsNS.Value) + return errors.Wrapf(err, "error looking up container to share uts namespace with") } hostname = utsCtr.Hostname() - case s.NetNS.IsHost() || s.UtsNS.IsHost(): - hostname, err = os.Hostname() + case s.NetNS.NSMode == specgen.Host || s.UtsNS.NSMode == specgen.Host: + tmpHostname, err := os.Hostname() if err != nil { return errors.Wrap(err, "unable to retrieve hostname of the host") } + hostname = tmpHostname default: logrus.Debug("No hostname set; container's hostname will default to runtime default") } } + g.RemoveHostname() - if s.Hostname != "" || !s.UtsNS.IsHost() { - // Set the hostname in the OCI configuration only - // if specified by the user or if we are creating - // a new UTS namespace. + if s.Hostname != "" || s.UtsNS.NSMode != specgen.Host { + // Set the hostname in the OCI configuration only if specified by + // the user or if we are creating a new UTS namespace. + // TODO: Should we be doing this for pod or container shared + // namespaces? g.SetHostname(hostname) } g.AddProcessEnv("HOSTNAME", hostname) - if s.UtsNS.IsPath() { - return g.AddOrReplaceLinuxNamespace(string(spec.UTSNamespace), s.UtsNS.Value) - } - if s.UtsNS.IsHost() { - return g.RemoveLinuxNamespace(string(spec.UTSNamespace)) - } - if s.UtsNS.IsContainer() { - logrus.Debugf("using container %s utsmode", s.UtsNS.Value) - } - return nil -} - -func ipcConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator) error { - if s.IpcNS.IsPath() { - return g.AddOrReplaceLinuxNamespace(string(spec.IPCNamespace), s.IpcNS.Value) - } - if s.IpcNS.IsHost() { - return g.RemoveLinuxNamespace(s.IpcNS.Value) - } - if s.IpcNS.IsContainer() { - logrus.Debugf("Using container %s ipcmode", s.IpcNS.Value) - } - return nil -} - -func cgroupConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator) error { - if s.CgroupNS.IsPath() { - return g.AddOrReplaceLinuxNamespace(string(spec.CgroupNamespace), s.CgroupNS.Value) - } - if s.CgroupNS.IsHost() { - return g.RemoveLinuxNamespace(s.CgroupNS.Value) - } - if s.CgroupNS.IsPrivate() { - return g.AddOrReplaceLinuxNamespace(string(spec.CgroupNamespace), "") - } - if s.CgroupNS.IsContainer() { - logrus.Debugf("Using container %s cgroup mode", s.CgroupNS.Value) - } - return nil -} - -func networkConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator) error { - switch { - case s.NetNS.IsHost(): - logrus.Debug("Using host netmode") - if err := g.RemoveLinuxNamespace(string(spec.NetworkNamespace)); err != nil { - return err - } - - case s.NetNS.NSMode == specgen.NoNetwork: - logrus.Debug("Using none netmode") - case s.NetNS.NSMode == specgen.Bridge: - logrus.Debug("Using bridge netmode") - case s.NetNS.IsContainer(): - logrus.Debugf("using container %s netmode", s.NetNS.Value) - case s.NetNS.IsPath(): - logrus.Debug("Using ns netmode") - if err := g.AddOrReplaceLinuxNamespace(string(spec.NetworkNamespace), s.NetNS.Value); err != nil { - return err + // User + switch s.UserNS.NSMode { + case specgen.Path: + if _, err := os.Stat(s.UserNS.Value); err != nil { + return errors.Wrapf(err, "cannot find specified user namespace path %s", s.UserNS.Value) } - case s.NetNS.IsPod(): - logrus.Debug("Using pod netmode, unless pod is not sharing") - case s.NetNS.NSMode == specgen.Slirp: - logrus.Debug("Using slirp4netns netmode") - default: - return errors.Errorf("unknown network mode") - } - - if g.Config.Annotations == nil { - g.Config.Annotations = make(map[string]string) - } - - if s.PublishImagePorts { - g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseTrue - } else { - g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseFalse - } - - return nil -} - -func userConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator) error { - if s.UserNS.IsPath() { if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), s.UserNS.Value); err != nil { return err } // runc complains if no mapping is specified, even if we join another ns. So provide a dummy mapping g.AddLinuxUIDMapping(uint32(0), uint32(0), uint32(1)) g.AddLinuxGIDMapping(uint32(0), uint32(0), uint32(1)) - } - - if s.IDMappings != nil { - if (len(s.IDMappings.UIDMap) > 0 || len(s.IDMappings.GIDMap) > 0) && !s.UserNS.IsHost() { - if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), ""); err != nil { - return err - } + case specgen.Host: + if err := g.RemoveLinuxNamespace(string(spec.UserNamespace)); err != nil { + return err + } + case specgen.KeepID: + var ( + err error + uid, gid int + ) + s.IDMappings, uid, gid, err = util.GetKeepIDMapping() + if err != nil { + return err + } + g.SetProcessUID(uint32(uid)) + g.SetProcessGID(uint32(gid)) + fallthrough + case specgen.Private: + if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), ""); err != nil { + return err + } + if s.IDMappings == nil || (len(s.IDMappings.UIDMap) == 0 && len(s.IDMappings.GIDMap) == 0) { + return errors.Errorf("must provide at least one UID or GID mapping to configure a user namespace") } for _, uidmap := range s.IDMappings.UIDMap { g.AddLinuxUIDMapping(uint32(uidmap.HostID), uint32(uidmap.ContainerID), uint32(uidmap.Size)) @@ -321,64 +412,52 @@ func userConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator) err g.AddLinuxGIDMapping(uint32(gidmap.HostID), uint32(gidmap.ContainerID), uint32(gidmap.Size)) } } - return nil -} - -func securityConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator, newImage *image.Image) error { - // HANDLE CAPABILITIES - // NOTE: Must happen before SECCOMP - if s.Privileged { - g.SetupPrivileged(true) - } - useNotRoot := func(user string) bool { - if user == "" || user == "root" || user == "0" { - return false + // Cgroup + switch s.CgroupNS.NSMode { + case specgen.Path: + if _, err := os.Stat(s.CgroupNS.Value); err != nil { + return errors.Wrapf(err, "cannot find specified cgroup namespace path %s", s.CgroupNS.Value) } - return true - } - configSpec := g.Config - var err error - var caplist []string - bounding := configSpec.Process.Capabilities.Bounding - if useNotRoot(s.User) { - configSpec.Process.Capabilities.Bounding = caplist - } - caplist, err = capabilities.MergeCapabilities(configSpec.Process.Capabilities.Bounding, s.CapAdd, s.CapDrop) - if err != nil { - return err - } - - configSpec.Process.Capabilities.Bounding = caplist - configSpec.Process.Capabilities.Permitted = caplist - configSpec.Process.Capabilities.Inheritable = caplist - configSpec.Process.Capabilities.Effective = caplist - configSpec.Process.Capabilities.Ambient = caplist - if useNotRoot(s.User) { - caplist, err = capabilities.MergeCapabilities(bounding, s.CapAdd, s.CapDrop) - if err != nil { + if err := g.AddOrReplaceLinuxNamespace(string(spec.CgroupNamespace), s.CgroupNS.Value); err != nil { + return err + } + case specgen.Host: + if err := g.RemoveLinuxNamespace(string(spec.CgroupNamespace)); err != nil { + return err + } + case specgen.Private: + if err := g.AddOrReplaceLinuxNamespace(string(spec.CgroupNamespace), ""); err != nil { return err } } - configSpec.Process.Capabilities.Bounding = caplist - // HANDLE SECCOMP - if s.SeccompProfilePath != "unconfined" { - seccompConfig, err := getSeccompConfig(s, configSpec, newImage) - if err != nil { + // Net + switch s.NetNS.NSMode { + case specgen.Path: + if _, err := os.Stat(s.NetNS.Value); err != nil { + return errors.Wrapf(err, "cannot find specified network namespace path %s", s.NetNS.Value) + } + if err := g.AddOrReplaceLinuxNamespace(string(spec.NetworkNamespace), s.NetNS.Value); err != nil { + return err + } + case specgen.Host: + if err := g.RemoveLinuxNamespace(string(spec.NetworkNamespace)); err != nil { + return err + } + case specgen.Private, specgen.NoNetwork: + if err := g.AddOrReplaceLinuxNamespace(string(spec.NetworkNamespace), ""); err != nil { return err } - configSpec.Linux.Seccomp = seccompConfig } - // Clear default Seccomp profile from Generator for privileged containers - if s.SeccompProfilePath == "unconfined" || s.Privileged { - configSpec.Linux.Seccomp = nil + if g.Config.Annotations == nil { + g.Config.Annotations = make(map[string]string) } - - g.SetRootReadonly(s.ReadOnlyFilesystem) - for sysctlKey, sysctlVal := range s.Sysctl { - g.AddLinuxSysctl(sysctlKey, sysctlVal) + if s.PublishImagePorts { + g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseTrue + } else { + g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseFalse } return nil diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go index 0ed091f9a..87262684e 100644 --- a/pkg/specgen/generate/oci.go +++ b/pkg/specgen/generate/oci.go @@ -1,8 +1,10 @@ package generate import ( + "context" "strings" + "github.com/containers/common/pkg/config" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/rootless" @@ -10,9 +12,90 @@ import ( "github.com/opencontainers/runc/libcontainer/user" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-tools/generate" + "github.com/pkg/errors" ) -func SpecGenToOCI(s *specgen.SpecGenerator, rt *libpod.Runtime, newImage *image.Image) (*spec.Spec, error) { +func addRlimits(s *specgen.SpecGenerator, g *generate.Generator) error { + var ( + kernelMax uint64 = 1048576 + isRootless = rootless.IsRootless() + nofileSet = false + nprocSet = false + ) + + if s.Rlimits == nil { + g.Config.Process.Rlimits = nil + return nil + } + + for _, u := range s.Rlimits { + name := "RLIMIT_" + strings.ToUpper(u.Type) + if name == "RLIMIT_NOFILE" { + nofileSet = true + } else if name == "RLIMIT_NPROC" { + nprocSet = true + } + g.AddProcessRlimits(name, u.Hard, u.Soft) + } + + // If not explicitly overridden by the user, default number of open + // files and number of processes to the maximum they can be set to + // (without overriding a sysctl) + if !nofileSet && !isRootless { + g.AddProcessRlimits("RLIMIT_NOFILE", kernelMax, kernelMax) + } + if !nprocSet && !isRootless { + g.AddProcessRlimits("RLIMIT_NPROC", kernelMax, kernelMax) + } + + return nil +} + +// Produce the final command for the container. +func makeCommand(ctx context.Context, s *specgen.SpecGenerator, img *image.Image, rtc *config.Config) ([]string, error) { + finalCommand := []string{} + + entrypoint := s.Entrypoint + if len(entrypoint) == 0 && img != nil { + newEntry, err := img.Entrypoint(ctx) + if err != nil { + return nil, err + } + entrypoint = newEntry + } + + finalCommand = append(finalCommand, entrypoint...) + + command := s.Command + if len(command) == 0 && img != nil { + newCmd, err := img.Cmd(ctx) + if err != nil { + return nil, err + } + command = newCmd + } + + finalCommand = append(finalCommand, command...) + + if len(finalCommand) == 0 { + return nil, errors.Errorf("no command or entrypoint provided, and no CMD or ENTRYPOINT from image") + } + + if s.Init { + initPath := s.InitPath + if initPath == "" && rtc != nil { + initPath = rtc.Engine.InitPath + } + if initPath == "" { + return nil, errors.Errorf("no path to init binary found but container requested an init") + } + finalCommand = append([]string{initPath, "--"}, finalCommand...) + } + + return finalCommand, nil +} + +func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runtime, rtc *config.Config, newImage *image.Image, mounts []spec.Mount) (*spec.Spec, error) { var ( inUserNS bool ) @@ -137,7 +220,13 @@ func SpecGenToOCI(s *specgen.SpecGenerator, rt *libpod.Runtime, newImage *image. g.AddMount(cgroupMnt) } g.SetProcessCwd(s.WorkDir) - g.SetProcessArgs(s.Command) + + finalCmd, err := makeCommand(ctx, s, newImage, rtc) + if err != nil { + return nil, err + } + g.SetProcessArgs(finalCmd) + g.SetProcessTerminal(s.Terminal) for key, val := range s.Annotations { @@ -176,35 +265,12 @@ func SpecGenToOCI(s *specgen.SpecGenerator, rt *libpod.Runtime, newImage *image. g.AddProcessEnv(name, val) } - // TODO rlimits and ulimits needs further refinement by someone more - // familiar with the code. - //if err := addRlimits(config, &g); err != nil { - // return nil, err - //} - - // NAMESPACES - - if err := pidConfigureGenerator(s, &g); err != nil { - return nil, err - } - - if err := userConfigureGenerator(s, &g); err != nil { - return nil, err - } - - if err := networkConfigureGenerator(s, &g); err != nil { + if err := addRlimits(s, &g); err != nil { return nil, err } - if err := utsConfigureGenerator(s, &g, rt); err != nil { - return nil, err - } - - if err := ipcConfigureGenerator(s, &g); err != nil { - return nil, err - } - - if err := cgroupConfigureGenerator(s, &g); err != nil { + // NAMESPACES + if err := specConfigureNamespaces(s, &g, rt); err != nil { return nil, err } configSpec := g.Config @@ -214,7 +280,7 @@ func SpecGenToOCI(s *specgen.SpecGenerator, rt *libpod.Runtime, newImage *image. } // BIND MOUNTS - configSpec.Mounts = SupercedeUserMounts(s.Mounts, configSpec.Mounts) + configSpec.Mounts = SupercedeUserMounts(mounts, configSpec.Mounts) // Process mounts to ensure correct options if err := InitFSMounts(configSpec.Mounts); err != nil { return nil, err diff --git a/pkg/specgen/generate/pod_create.go b/pkg/specgen/generate/pod_create.go index 292f9b155..babfba9bc 100644 --- a/pkg/specgen/generate/pod_create.go +++ b/pkg/specgen/generate/pod_create.go @@ -46,6 +46,13 @@ func createPodOptions(p *specgen.PodSpecGenerator) ([]libpod.PodCreateOption, er if len(p.HostAdd) > 0 { options = append(options, libpod.WithPodHosts(p.HostAdd)) } + if len(p.DNSServer) > 0 { + var dnsServers []string + for _, d := range p.DNSServer { + dnsServers = append(dnsServers, d.String()) + } + options = append(options, libpod.WithPodDNS(dnsServers)) + } if len(p.DNSOption) > 0 { options = append(options, libpod.WithPodDNSOption(p.DNSOption)) } diff --git a/pkg/specgen/generate/security.go b/pkg/specgen/generate/security.go index ef4b3b47a..e2da9e976 100644 --- a/pkg/specgen/generate/security.go +++ b/pkg/specgen/generate/security.go @@ -1,15 +1,22 @@ package generate import ( + "strings" + + "github.com/containers/common/pkg/capabilities" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/specgen" + "github.com/containers/libpod/pkg/util" + "github.com/opencontainers/runtime-tools/generate" "github.com/opencontainers/selinux/go-selinux/label" "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) -// SetLabelOpts sets the label options of the SecurityConfig according to the +// setLabelOpts sets the label options of the SecurityConfig according to the // input. -func SetLabelOpts(s *specgen.SpecGenerator, runtime *libpod.Runtime, pidConfig specgen.Namespace, ipcConfig specgen.Namespace) error { +func setLabelOpts(s *specgen.SpecGenerator, runtime *libpod.Runtime, pidConfig specgen.Namespace, ipcConfig specgen.Namespace) error { if !runtime.EnableLabeling() || s.Privileged { s.SelinuxOpts = label.DisableSecOpt() return nil @@ -48,12 +55,10 @@ func SetLabelOpts(s *specgen.SpecGenerator, runtime *libpod.Runtime, pidConfig s return nil } -// ConfigureGenerator configures the generator according to the input. -/* -func (c *SecurityConfig) ConfigureGenerator(g *generate.Generator, user *UserConfig) error { +func securityConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator, newImage *image.Image) error { // HANDLE CAPABILITIES // NOTE: Must happen before SECCOMP - if c.Privileged { + if s.Privileged { g.SetupPrivileged(true) } @@ -63,56 +68,66 @@ func (c *SecurityConfig) ConfigureGenerator(g *generate.Generator, user *UserCon } return true } - configSpec := g.Config var err error - var defaultCaplist []string + var caplist []string bounding := configSpec.Process.Capabilities.Bounding - if useNotRoot(user.User) { - configSpec.Process.Capabilities.Bounding = defaultCaplist + if useNotRoot(s.User) { + configSpec.Process.Capabilities.Bounding = caplist } - defaultCaplist, err = capabilities.MergeCapabilities(configSpec.Process.Capabilities.Bounding, c.CapAdd, c.CapDrop) + caplist, err = capabilities.MergeCapabilities(configSpec.Process.Capabilities.Bounding, s.CapAdd, s.CapDrop) if err != nil { return err } + privCapsRequired := []string{} + + // If the container image specifies an label with a + // capabilities.ContainerImageLabel then split the comma separated list + // of capabilities and record them. This list indicates the only + // capabilities, required to run the container. + var capsRequiredRequested []string + for key, val := range s.Labels { + if util.StringInSlice(key, capabilities.ContainerImageLabels) { + capsRequiredRequested = strings.Split(val, ",") + } + } + if !s.Privileged && len(capsRequiredRequested) > 0 { - privCapRequired := []string{} - - if !c.Privileged && len(c.CapRequired) > 0 { - // Pass CapRequired in CapAdd field to normalize capabilities names - capRequired, err := capabilities.MergeCapabilities(nil, c.CapRequired, nil) + // Pass capRequiredRequested in CapAdd field to normalize capabilities names + capsRequired, err := capabilities.MergeCapabilities(nil, capsRequiredRequested, nil) if err != nil { - logrus.Errorf("capabilities requested by user or image are not valid: %q", strings.Join(c.CapRequired, ",")) + logrus.Errorf("capabilities requested by user or image are not valid: %q", strings.Join(capsRequired, ",")) } else { - // Verify all capRequiered are in the defaultCapList - for _, cap := range capRequired { - if !util.StringInSlice(cap, defaultCaplist) { - privCapRequired = append(privCapRequired, cap) + // Verify all capRequiered are in the capList + for _, cap := range capsRequired { + if !util.StringInSlice(cap, caplist) { + privCapsRequired = append(privCapsRequired, cap) } } } - if len(privCapRequired) == 0 { - defaultCaplist = capRequired + if len(privCapsRequired) == 0 { + caplist = capsRequired } else { - logrus.Errorf("capabilities requested by user or image are not allowed by default: %q", strings.Join(privCapRequired, ",")) + logrus.Errorf("capabilities requested by user or image are not allowed by default: %q", strings.Join(privCapsRequired, ",")) } } - configSpec.Process.Capabilities.Bounding = defaultCaplist - configSpec.Process.Capabilities.Permitted = defaultCaplist - configSpec.Process.Capabilities.Inheritable = defaultCaplist - configSpec.Process.Capabilities.Effective = defaultCaplist - configSpec.Process.Capabilities.Ambient = defaultCaplist - if useNotRoot(user.User) { - defaultCaplist, err = capabilities.MergeCapabilities(bounding, c.CapAdd, c.CapDrop) + + configSpec.Process.Capabilities.Bounding = caplist + configSpec.Process.Capabilities.Permitted = caplist + configSpec.Process.Capabilities.Inheritable = caplist + configSpec.Process.Capabilities.Effective = caplist + configSpec.Process.Capabilities.Ambient = caplist + if useNotRoot(s.User) { + caplist, err = capabilities.MergeCapabilities(bounding, s.CapAdd, s.CapDrop) if err != nil { return err } } - configSpec.Process.Capabilities.Bounding = defaultCaplist + configSpec.Process.Capabilities.Bounding = caplist // HANDLE SECCOMP - if c.SeccompProfilePath != "unconfined" { - seccompConfig, err := getSeccompConfig(c, configSpec) + if s.SeccompProfilePath != "unconfined" { + seccompConfig, err := getSeccompConfig(s, configSpec, newImage) if err != nil { return err } @@ -120,35 +135,14 @@ func (c *SecurityConfig) ConfigureGenerator(g *generate.Generator, user *UserCon } // Clear default Seccomp profile from Generator for privileged containers - if c.SeccompProfilePath == "unconfined" || c.Privileged { + if s.SeccompProfilePath == "unconfined" || s.Privileged { configSpec.Linux.Seccomp = nil } - for _, opt := range c.SecurityOpts { - // Split on both : and = - splitOpt := strings.Split(opt, "=") - if len(splitOpt) == 1 { - splitOpt = strings.Split(opt, ":") - } - if len(splitOpt) < 2 { - continue - } - switch splitOpt[0] { - case "label": - configSpec.Annotations[libpod.InspectAnnotationLabel] = splitOpt[1] - case "seccomp": - configSpec.Annotations[libpod.InspectAnnotationSeccomp] = splitOpt[1] - case "apparmor": - configSpec.Annotations[libpod.InspectAnnotationApparmor] = splitOpt[1] - } - } - - g.SetRootReadonly(c.ReadOnlyRootfs) - for sysctlKey, sysctlVal := range c.Sysctl { + g.SetRootReadonly(s.ReadOnlyFilesystem) + for sysctlKey, sysctlVal := range s.Sysctl { g.AddLinuxSysctl(sysctlKey, sysctlVal) } return nil } - -*/ diff --git a/pkg/specgen/generate/storage.go b/pkg/specgen/generate/storage.go index c9a36ed46..241c9adeb 100644 --- a/pkg/specgen/generate/storage.go +++ b/pkg/specgen/generate/storage.go @@ -1,15 +1,16 @@ package generate -//nolint - import ( + "context" "fmt" + "os" "path" "path/filepath" "strings" - "github.com/containers/buildah/pkg/parse" + "github.com/containers/common/pkg/config" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/specgen" "github.com/containers/libpod/pkg/util" spec "github.com/opencontainers/runtime-spec/specs-go" @@ -17,6 +18,7 @@ import ( "github.com/sirupsen/logrus" ) +// TODO unify this in one place - maybe libpod/define const ( // TypeBind is the type for mounting host dir TypeBind = "bind" @@ -27,788 +29,278 @@ const ( ) var ( - errDuplicateDest = errors.Errorf("duplicate mount destination") //nolint - optionArgError = errors.Errorf("must provide an argument for option") //nolint - noDestError = errors.Errorf("must set volume destination") //nolint + errDuplicateDest = errors.Errorf("duplicate mount destination") ) -// Parse all volume-related options in the create config into a set of mounts -// and named volumes to add to the container. -// Handles --volumes-from, --volumes, --tmpfs, --init, and --init-path flags. -// TODO: Named volume options - should we default to rprivate? It bakes into a -// bind mount under the hood... -// TODO: handle options parsing/processing via containers/storage/pkg/mount -func parseVolumes(s *specgen.SpecGenerator, mounts, volMounts, tmpMounts []string) error { //nolint - - // TODO this needs to come from the image and erquires a runtime - - // Add image volumes. - //baseMounts, baseVolumes, err := config.getImageVolumes() - //if err != nil { - // return nil, nil, err - //} - - // Add --volumes-from. - // Overrides image volumes unconditionally. - //vFromMounts, vFromVolumes, err := config.getVolumesFrom(runtime) - //if err != nil { - // return nil, nil, err - //} - //for dest, mount := range vFromMounts { - // baseMounts[dest] = mount - //} - //for dest, volume := range vFromVolumes { - // baseVolumes[dest] = volume - //} - - // Next mounts from the --mounts flag. - // Do not override yet. - //unifiedMounts, _, err := getMounts(mounts) - //if err != nil { - // return err - //} - // - //// Next --volumes flag. - //// Do not override yet. - //volumeMounts, _ , err := getVolumeMounts(volMounts) - //if err != nil { - // return err - //} - // - //// Next --tmpfs flag. - //// Do not override yet. - //tmpfsMounts, err := getTmpfsMounts(tmpMounts) - //if err != nil { - // return err - //} - - //// Unify mounts from --mount, --volume, --tmpfs. - //// Also add mounts + volumes directly from createconfig. - //// Start with --volume. - //for dest, mount := range volumeMounts { - // if _, ok := unifiedMounts[dest]; ok { - // return nil, nil, errors.Wrapf(errDuplicateDest, dest) - // } - // unifiedMounts[dest] = mount - //} - //for dest, volume := range volumeVolumes { - // if _, ok := unifiedVolumes[dest]; ok { - // return nil, nil, errors.Wrapf(errDuplicateDest, dest) - // } - // unifiedVolumes[dest] = volume - //} - //// Now --tmpfs - //for dest, tmpfs := range tmpfsMounts { - // if _, ok := unifiedMounts[dest]; ok { - // return nil, nil, errors.Wrapf(errDuplicateDest, dest) - // } - // unifiedMounts[dest] = tmpfs - //} - //// Now spec mounts and volumes - //for _, mount := range config.Mounts { - // dest := mount.Destination - // if _, ok := unifiedMounts[dest]; ok { - // return nil, nil, errors.Wrapf(errDuplicateDest, dest) - // } - // unifiedMounts[dest] = mount - //} - //for _, volume := range config.NamedVolumes { - // dest := volume.Dest - // if _, ok := unifiedVolumes[dest]; ok { - // return nil, nil, errors.Wrapf(errDuplicateDest, dest) - // } - // unifiedVolumes[dest] = volume - //} - // - //// If requested, add container init binary - //if config.Init { - // initPath := config.InitPath - // if initPath == "" { - // rtc, err := runtime.GetConfig() - // if err != nil { - // return nil, nil, err - // } - // initPath = rtc.Engine.InitPath - // } - // initMount, err := config.addContainerInitBinary(initPath) - // if err != nil { - // return nil, nil, err - // } - // if _, ok := unifiedMounts[initMount.Destination]; ok { - // return nil, nil, errors.Wrapf(errDuplicateDest, "conflict with mount added by --init to %q", initMount.Destination) - // } - // unifiedMounts[initMount.Destination] = initMount - //} - // - //// Before superseding, we need to find volume mounts which conflict with - //// named volumes, and vice versa. - //// We'll delete the conflicts here as we supersede. - //for dest := range unifiedMounts { - // if _, ok := baseVolumes[dest]; ok { - // delete(baseVolumes, dest) - // } - //} - //for dest := range unifiedVolumes { - // if _, ok := baseMounts[dest]; ok { - // delete(baseMounts, dest) - // } - //} - // - //// Supersede volumes-from/image volumes with unified volumes from above. - //// This is an unconditional replacement. - //for dest, mount := range unifiedMounts { - // baseMounts[dest] = mount - //} - //for dest, volume := range unifiedVolumes { - // baseVolumes[dest] = volume - //} - // - //// If requested, add tmpfs filesystems for read-only containers. - //if config.Security.ReadOnlyRootfs && config.Security.ReadOnlyTmpfs { - // readonlyTmpfs := []string{"/tmp", "/var/tmp", "/run"} - // options := []string{"rw", "rprivate", "nosuid", "nodev", "tmpcopyup"} - // for _, dest := range readonlyTmpfs { - // if _, ok := baseMounts[dest]; ok { - // continue - // } - // if _, ok := baseVolumes[dest]; ok { - // continue - // } - // localOpts := options - // if dest == "/run" { - // localOpts = append(localOpts, "noexec", "size=65536k") - // } else { - // localOpts = append(localOpts, "exec") - // } - // baseMounts[dest] = spec.Mount{ - // Destination: dest, - // Type: "tmpfs", - // Source: "tmpfs", - // Options: localOpts, - // } - // } - //} - // - //// Check for conflicts between named volumes and mounts - //for dest := range baseMounts { - // if _, ok := baseVolumes[dest]; ok { - // return nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest) - // } - //} - //for dest := range baseVolumes { - // if _, ok := baseMounts[dest]; ok { - // return nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest) - // } - //} - // - //// Final step: maps to arrays - //finalMounts := make([]spec.Mount, 0, len(baseMounts)) - //for _, mount := range baseMounts { - // if mount.Type == TypeBind { - // absSrc, err := filepath.Abs(mount.Source) - // if err != nil { - // return nil, nil, errors.Wrapf(err, "error getting absolute path of %s", mount.Source) - // } - // mount.Source = absSrc - // } - // finalMounts = append(finalMounts, mount) - //} - //finalVolumes := make([]*define.ContainerNamedVolume, 0, len(baseVolumes)) - //for _, volume := range baseVolumes { - // finalVolumes = append(finalVolumes, volume) - //} - - //return finalMounts, finalVolumes, nil - return nil -} - -// Parse volumes from - a set of containers whose volumes we will mount in. -// Grab the containers, retrieve any user-created spec mounts and all named -// volumes, and return a list of them. -// Conflicts are resolved simply - the last container specified wins. -// Container names may be suffixed by mount options after a colon. -// TODO: We should clean these paths if possible -// TODO deferred baude -func getVolumesFrom() (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) { //nolint - // Both of these are maps of mount destination to mount type. - // We ensure that each destination is only mounted to once in this way. - //finalMounts := make(map[string]spec.Mount) - //finalNamedVolumes := make(map[string]*define.ContainerNamedVolume) - // - //for _, vol := range config.VolumesFrom { - // var ( - // options = []string{} - // err error - // splitVol = strings.SplitN(vol, ":", 2) - // ) - // if len(splitVol) == 2 { - // splitOpts := strings.Split(splitVol[1], ",") - // for _, checkOpt := range splitOpts { - // switch checkOpt { - // case "z", "ro", "rw": - // // Do nothing, these are valid options - // default: - // return nil, nil, errors.Errorf("invalid options %q, can only specify 'ro', 'rw', and 'z'", splitVol[1]) - // } - // } - // - // if options, err = parse.ValidateVolumeOpts(splitOpts); err != nil { - // return nil, nil, err - // } - // } - // ctr, err := runtime.LookupContainer(splitVol[0]) - // if err != nil { - // return nil, nil, errors.Wrapf(err, "error looking up container %q for volumes-from", splitVol[0]) - // } - // - // logrus.Debugf("Adding volumes from container %s", ctr.ID()) - // - // // Look up the container's user volumes. This gets us the - // // destinations of all mounts the user added to the container. - // userVolumesArr := ctr.UserVolumes() - // - // // We're going to need to access them a lot, so convert to a map - // // to reduce looping. - // // We'll also use the map to indicate if we missed any volumes along the way. - // userVolumes := make(map[string]bool) - // for _, dest := range userVolumesArr { - // userVolumes[dest] = false - // } - // - // // Now we get the container's spec and loop through its volumes - // // and append them in if we can find them. - // spec := ctr.Spec() - // if spec == nil { - // return nil, nil, errors.Errorf("error retrieving container %s spec for volumes-from", ctr.ID()) - // } - // for _, mnt := range spec.Mounts { - // if mnt.Type != TypeBind { - // continue - // } - // if _, exists := userVolumes[mnt.Destination]; exists { - // userVolumes[mnt.Destination] = true - // - // if len(options) != 0 { - // mnt.Options = options - // } - // - // if _, ok := finalMounts[mnt.Destination]; ok { - // logrus.Debugf("Overriding mount to %s with new mount from container %s", mnt.Destination, ctr.ID()) - // } - // finalMounts[mnt.Destination] = mnt - // } - // } - // - // // We're done with the spec mounts. Add named volumes. - // // Add these unconditionally - none of them are automatically - // // part of the container, as some spec mounts are. - // namedVolumes := ctr.NamedVolumes() - // for _, namedVol := range namedVolumes { - // if _, exists := userVolumes[namedVol.Dest]; exists { - // userVolumes[namedVol.Dest] = true - // } - // - // if len(options) != 0 { - // namedVol.Options = options - // } - // - // if _, ok := finalMounts[namedVol.Dest]; ok { - // logrus.Debugf("Overriding named volume mount to %s with new named volume from container %s", namedVol.Dest, ctr.ID()) - // } - // finalNamedVolumes[namedVol.Dest] = namedVol - // } - // - // // Check if we missed any volumes - // for volDest, found := range userVolumes { - // if !found { - // logrus.Warnf("Unable to match volume %s from container %s for volumes-from", volDest, ctr.ID()) - // } - // } - //} - // - //return finalMounts, finalNamedVolumes, nil - return nil, nil, nil -} +// Produce final mounts and named volumes for a container +func finalizeMounts(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runtime, rtc *config.Config, img *image.Image) ([]spec.Mount, []*specgen.NamedVolume, error) { + // Get image volumes + baseMounts, baseVolumes, err := getImageVolumes(ctx, img, s) + if err != nil { + return nil, nil, err + } -// getMounts takes user-provided input from the --mount flag and creates OCI -// spec mounts and Libpod named volumes. -// podman run --mount type=bind,src=/etc/resolv.conf,target=/etc/resolv.conf ... -// podman run --mount type=tmpfs,target=/dev/shm ... -// podman run --mount type=volume,source=test-volume, ... -func getMounts(mounts []string) (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) { //nolint - finalMounts := make(map[string]spec.Mount) - finalNamedVolumes := make(map[string]*libpod.ContainerNamedVolume) + // Get volumes-from mounts + volFromMounts, volFromVolumes, err := getVolumesFrom(s.VolumesFrom, rt) + if err != nil { + return nil, nil, err + } - errInvalidSyntax := errors.Errorf("incorrect mount format: should be --mount type=<bind|tmpfs|volume>,[src=<host-dir|volume-name>,]target=<ctr-dir>[,options]") + // Supercede from --volumes-from. + for dest, mount := range volFromMounts { + baseMounts[dest] = mount + } + for dest, volume := range volFromVolumes { + baseVolumes[dest] = volume + } - // TODO(vrothberg): the manual parsing can be replaced with a regular expression - // to allow a more robust parsing of the mount format and to give - // precise errors regarding supported format versus supported options. - for _, mount := range mounts { - arr := strings.SplitN(mount, ",", 2) - if len(arr) < 2 { - return nil, nil, errors.Wrapf(errInvalidSyntax, "%q", mount) + // Need to make map forms of specgen mounts/volumes. + unifiedMounts := map[string]spec.Mount{} + unifiedVolumes := map[string]*specgen.NamedVolume{} + for _, m := range s.Mounts { + if _, ok := unifiedMounts[m.Destination]; ok { + return nil, nil, errors.Wrapf(errDuplicateDest, "conflict in specified mounts - multiple mounts at %q", m.Destination) } - kv := strings.Split(arr[0], "=") - // TODO: type is not explicitly required in Docker. - // If not specified, it defaults to "volume". - if len(kv) != 2 || kv[0] != "type" { - return nil, nil, errors.Wrapf(errInvalidSyntax, "%q", mount) + unifiedMounts[m.Destination] = m + } + for _, v := range s.Volumes { + if _, ok := unifiedVolumes[v.Dest]; ok { + return nil, nil, errors.Wrapf(errDuplicateDest, "conflict in specified volumes - multiple volumes at %q", v.Dest) } + unifiedVolumes[v.Dest] = v + } - tokens := strings.Split(arr[1], ",") - switch kv[1] { - case TypeBind: - mount, err := getBindMount(tokens) - if err != nil { - return nil, nil, err - } - if _, ok := finalMounts[mount.Destination]; ok { - return nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination) - } - finalMounts[mount.Destination] = mount - case TypeTmpfs: - mount, err := getTmpfsMount(tokens) - if err != nil { - return nil, nil, err - } - if _, ok := finalMounts[mount.Destination]; ok { - return nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination) - } - finalMounts[mount.Destination] = mount - case "volume": - volume, err := getNamedVolume(tokens) - if err != nil { - return nil, nil, err - } - if _, ok := finalNamedVolumes[volume.Dest]; ok { - return nil, nil, errors.Wrapf(errDuplicateDest, volume.Dest) - } - finalNamedVolumes[volume.Dest] = volume - default: - return nil, nil, errors.Errorf("invalid filesystem type %q", kv[1]) + // If requested, add container init binary + if s.Init { + initPath := s.InitPath + if initPath == "" && rtc != nil { + initPath = rtc.Engine.InitPath } + initMount, err := addContainerInitBinary(s, initPath) + if err != nil { + return nil, nil, err + } + if _, ok := unifiedMounts[initMount.Destination]; ok { + return nil, nil, errors.Wrapf(errDuplicateDest, "conflict with mount added by --init to %q", initMount.Destination) + } + unifiedMounts[initMount.Destination] = initMount } - return finalMounts, finalNamedVolumes, nil -} - -// Parse a single bind mount entry from the --mount flag. -func getBindMount(args []string) (spec.Mount, error) { //nolint - newMount := spec.Mount{ - Type: TypeBind, + // Before superseding, we need to find volume mounts which conflict with + // named volumes, and vice versa. + // We'll delete the conflicts here as we supersede. + for dest := range unifiedMounts { + if _, ok := baseVolumes[dest]; ok { + delete(baseVolumes, dest) + } } - - var setSource, setDest, setRORW, setSuid, setDev, setExec, setRelabel bool - - for _, val := range args { - kv := strings.Split(val, "=") - switch kv[0] { - case "bind-nonrecursive": - newMount.Options = append(newMount.Options, "bind") - case "ro", "rw": - if setRORW { - return newMount, errors.Wrapf(optionArgError, "cannot pass 'ro' or 'rw' options more than once") - } - setRORW = true - // Can be formatted as one of: - // ro - // ro=[true|false] - // rw - // rw=[true|false] - switch len(kv) { - case 1: - newMount.Options = append(newMount.Options, kv[0]) - case 2: - switch strings.ToLower(kv[1]) { - case "true": - newMount.Options = append(newMount.Options, kv[0]) - case "false": - // Set the opposite only for rw - // ro's opposite is the default - if kv[0] == "rw" { - newMount.Options = append(newMount.Options, "ro") - } - default: - return newMount, errors.Wrapf(optionArgError, "%s must be set to true or false, instead received %q", kv[0], kv[1]) - } - default: - return newMount, errors.Wrapf(optionArgError, "badly formatted option %q", val) - } - case "nosuid", "suid": - if setSuid { - return newMount, errors.Wrapf(optionArgError, "cannot pass 'nosuid' and 'suid' options more than once") - } - setSuid = true - newMount.Options = append(newMount.Options, kv[0]) - case "nodev", "dev": - if setDev { - return newMount, errors.Wrapf(optionArgError, "cannot pass 'nodev' and 'dev' options more than once") - } - setDev = true - newMount.Options = append(newMount.Options, kv[0]) - case "noexec", "exec": - if setExec { - return newMount, errors.Wrapf(optionArgError, "cannot pass 'noexec' and 'exec' options more than once") - } - setExec = true - newMount.Options = append(newMount.Options, kv[0]) - case "shared", "rshared", "private", "rprivate", "slave", "rslave", "Z", "z": - newMount.Options = append(newMount.Options, kv[0]) - case "bind-propagation": - if len(kv) == 1 { - return newMount, errors.Wrapf(optionArgError, kv[0]) - } - newMount.Options = append(newMount.Options, kv[1]) - case "src", "source": - if len(kv) == 1 { - return newMount, errors.Wrapf(optionArgError, kv[0]) - } - if err := parse.ValidateVolumeHostDir(kv[1]); err != nil { - return newMount, err - } - newMount.Source = kv[1] - setSource = true - case "target", "dst", "destination": - if len(kv) == 1 { - return newMount, errors.Wrapf(optionArgError, kv[0]) - } - if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil { - return newMount, err - } - newMount.Destination = filepath.Clean(kv[1]) - setDest = true - case "relabel": - if setRelabel { - return newMount, errors.Wrapf(optionArgError, "cannot pass 'relabel' option more than once") - } - setRelabel = true - if len(kv) != 2 { - return newMount, errors.Wrapf(util.ErrBadMntOption, "%s mount option must be 'private' or 'shared'", kv[0]) - } - switch kv[1] { - case "private": - newMount.Options = append(newMount.Options, "z") - case "shared": - newMount.Options = append(newMount.Options, "Z") - default: - return newMount, errors.Wrapf(util.ErrBadMntOption, "%s mount option must be 'private' or 'shared'", kv[0]) - } - default: - return newMount, errors.Wrapf(util.ErrBadMntOption, kv[0]) + for dest := range unifiedVolumes { + if _, ok := baseMounts[dest]; ok { + delete(baseMounts, dest) } } - if !setDest { - return newMount, noDestError + // Supersede volumes-from/image volumes with unified volumes from above. + // This is an unconditional replacement. + for dest, mount := range unifiedMounts { + baseMounts[dest] = mount } - - if !setSource { - newMount.Source = newMount.Destination + for dest, volume := range unifiedVolumes { + baseVolumes[dest] = volume } - options, err := parse.ValidateVolumeOpts(newMount.Options) - if err != nil { - return newMount, err - } - newMount.Options = options - return newMount, nil -} + // TODO: Investigate moving readonlyTmpfs into here. Would be more + // correct. -// Parse a single tmpfs mount entry from the --mount flag -func getTmpfsMount(args []string) (spec.Mount, error) { //nolint - newMount := spec.Mount{ - Type: TypeTmpfs, - Source: TypeTmpfs, + // Check for conflicts between named volumes and mounts + for dest := range baseMounts { + if _, ok := baseVolumes[dest]; ok { + return nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest) + } } - - var setDest, setRORW, setSuid, setDev, setExec, setTmpcopyup bool - - for _, val := range args { - kv := strings.Split(val, "=") - switch kv[0] { - case "tmpcopyup", "notmpcopyup": - if setTmpcopyup { - return newMount, errors.Wrapf(optionArgError, "cannot pass 'tmpcopyup' and 'notmpcopyup' options more than once") - } - setTmpcopyup = true - newMount.Options = append(newMount.Options, kv[0]) - case "ro", "rw": - if setRORW { - return newMount, errors.Wrapf(optionArgError, "cannot pass 'ro' and 'rw' options more than once") - } - setRORW = true - newMount.Options = append(newMount.Options, kv[0]) - case "nosuid", "suid": - if setSuid { - return newMount, errors.Wrapf(optionArgError, "cannot pass 'nosuid' and 'suid' options more than once") - } - setSuid = true - newMount.Options = append(newMount.Options, kv[0]) - case "nodev", "dev": - if setDev { - return newMount, errors.Wrapf(optionArgError, "cannot pass 'nodev' and 'dev' options more than once") - } - setDev = true - newMount.Options = append(newMount.Options, kv[0]) - case "noexec", "exec": - if setExec { - return newMount, errors.Wrapf(optionArgError, "cannot pass 'noexec' and 'exec' options more than once") - } - setExec = true - newMount.Options = append(newMount.Options, kv[0]) - case "tmpfs-mode": - if len(kv) == 1 { - return newMount, errors.Wrapf(optionArgError, kv[0]) - } - newMount.Options = append(newMount.Options, fmt.Sprintf("mode=%s", kv[1])) - case "tmpfs-size": - if len(kv) == 1 { - return newMount, errors.Wrapf(optionArgError, kv[0]) - } - newMount.Options = append(newMount.Options, fmt.Sprintf("size=%s", kv[1])) - case "src", "source": - return newMount, errors.Errorf("source is not supported with tmpfs mounts") - case "target", "dst", "destination": - if len(kv) == 1 { - return newMount, errors.Wrapf(optionArgError, kv[0]) - } - if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil { - return newMount, err + for dest := range baseVolumes { + if _, ok := baseMounts[dest]; ok { + return nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest) + } + } + // Final step: maps to arrays + finalMounts := make([]spec.Mount, 0, len(baseMounts)) + for _, mount := range baseMounts { + if mount.Type == TypeBind { + absSrc, err := filepath.Abs(mount.Source) + if err != nil { + return nil, nil, errors.Wrapf(err, "error getting absolute path of %s", mount.Source) } - newMount.Destination = filepath.Clean(kv[1]) - setDest = true - default: - return newMount, errors.Wrapf(util.ErrBadMntOption, kv[0]) + mount.Source = absSrc } + finalMounts = append(finalMounts, mount) } - - if !setDest { - return newMount, noDestError + finalVolumes := make([]*specgen.NamedVolume, 0, len(baseVolumes)) + for _, volume := range baseVolumes { + finalVolumes = append(finalVolumes, volume) } - return newMount, nil + return finalMounts, finalVolumes, nil } -// Parse a single volume mount entry from the --mount flag. -// Note that the volume-label option for named volumes is currently NOT supported. -// TODO: add support for --volume-label -func getNamedVolume(args []string) (*libpod.ContainerNamedVolume, error) { //nolint - newVolume := new(libpod.ContainerNamedVolume) +// Get image volumes from the given image +func getImageVolumes(ctx context.Context, img *image.Image, s *specgen.SpecGenerator) (map[string]spec.Mount, map[string]*specgen.NamedVolume, error) { + mounts := make(map[string]spec.Mount) + volumes := make(map[string]*specgen.NamedVolume) - var setSource, setDest, setRORW, setSuid, setDev, setExec bool + mode := strings.ToLower(s.ImageVolumeMode) - for _, val := range args { - kv := strings.Split(val, "=") - switch kv[0] { - case "ro", "rw": - if setRORW { - return nil, errors.Wrapf(optionArgError, "cannot pass 'ro' and 'rw' options more than once") - } - setRORW = true - newVolume.Options = append(newVolume.Options, kv[0]) - case "nosuid", "suid": - if setSuid { - return nil, errors.Wrapf(optionArgError, "cannot pass 'nosuid' and 'suid' options more than once") - } - setSuid = true - newVolume.Options = append(newVolume.Options, kv[0]) - case "nodev", "dev": - if setDev { - return nil, errors.Wrapf(optionArgError, "cannot pass 'nodev' and 'dev' options more than once") - } - setDev = true - newVolume.Options = append(newVolume.Options, kv[0]) - case "noexec", "exec": - if setExec { - return nil, errors.Wrapf(optionArgError, "cannot pass 'noexec' and 'exec' options more than once") - } - setExec = true - newVolume.Options = append(newVolume.Options, kv[0]) - case "volume-label": - return nil, errors.Errorf("the --volume-label option is not presently implemented") - case "src", "source": - if len(kv) == 1 { - return nil, errors.Wrapf(optionArgError, kv[0]) - } - newVolume.Name = kv[1] - setSource = true - case "target", "dst", "destination": - if len(kv) == 1 { - return nil, errors.Wrapf(optionArgError, kv[0]) - } - if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil { - return nil, err - } - newVolume.Dest = filepath.Clean(kv[1]) - setDest = true - default: - return nil, errors.Wrapf(util.ErrBadMntOption, kv[0]) - } + // Image may be nil (rootfs in use), or image volume mode may be ignore. + if img == nil || mode == "ignore" { + return mounts, volumes, nil } - if !setSource { - return nil, errors.Errorf("must set source volume") + inspect, err := img.InspectNoSize(ctx) + if err != nil { + return nil, nil, errors.Wrapf(err, "error inspecting image to get image volumes") } - if !setDest { - return nil, noDestError + for volume := range inspect.Config.Volumes { + logrus.Debugf("Image has volume at %q", volume) + cleanDest := filepath.Clean(volume) + switch mode { + case "", "anonymous": + // Anonymous volumes have no name. + newVol := new(specgen.NamedVolume) + newVol.Dest = cleanDest + newVol.Options = []string{"rprivate", "rw", "nodev", "exec"} + volumes[cleanDest] = newVol + logrus.Debugf("Adding anonymous image volume at %q", cleanDest) + case "tmpfs": + mount := spec.Mount{ + Destination: cleanDest, + Source: TypeTmpfs, + Type: TypeTmpfs, + Options: []string{"rprivate", "rw", "nodev", "exec"}, + } + mounts[cleanDest] = mount + logrus.Debugf("Adding tmpfs image volume at %q", cleanDest) + } } - return newVolume, nil + return mounts, volumes, nil } -func getVolumeMounts(vols []string) (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) { //nolint - mounts := make(map[string]spec.Mount) - volumes := make(map[string]*libpod.ContainerNamedVolume) - - volumeFormatErr := errors.Errorf("incorrect volume format, should be [host-dir:]ctr-dir[:option]") +func getVolumesFrom(volumesFrom []string, runtime *libpod.Runtime) (map[string]spec.Mount, map[string]*specgen.NamedVolume, error) { + finalMounts := make(map[string]spec.Mount) + finalNamedVolumes := make(map[string]*specgen.NamedVolume) - for _, vol := range vols { - var ( - options []string - src string - dest string - err error - ) + for _, volume := range volumesFrom { + var options []string - splitVol := strings.Split(vol, ":") - if len(splitVol) > 3 { - return nil, nil, errors.Wrapf(volumeFormatErr, vol) + splitVol := strings.SplitN(volume, ":", 2) + if len(splitVol) == 2 { + splitOpts := strings.Split(splitVol[1], ",") + for _, opt := range splitOpts { + setRORW := false + setZ := false + switch opt { + case "z": + if setZ { + return nil, nil, errors.Errorf("cannot set :z more than once in mount options") + } + setZ = true + case "ro", "rw": + if setRORW { + return nil, nil, errors.Errorf("cannot set ro or rw options more than once") + } + setRORW = true + default: + return nil, nil, errors.Errorf("invalid option %q specified - volumes from another container can only use z,ro,rw options", opt) + } + } + options = splitOpts } - src = splitVol[0] - if len(splitVol) == 1 { - // This is an anonymous named volume. Only thing given - // is destination. - // Name/source will be blank, and populated by libpod. - src = "" - dest = splitVol[0] - } else if len(splitVol) > 1 { - dest = splitVol[1] - } - if len(splitVol) > 2 { - if options, err = parse.ValidateVolumeOpts(strings.Split(splitVol[2], ",")); err != nil { - return nil, nil, err - } + ctr, err := runtime.LookupContainer(splitVol[0]) + if err != nil { + return nil, nil, errors.Wrapf(err, "error looking up container %q for volumes-from", splitVol[0]) } - // Do not check source dir for anonymous volumes - if len(splitVol) > 1 { - if err := parse.ValidateVolumeHostDir(src); err != nil { - return nil, nil, err - } + logrus.Debugf("Adding volumes from container %s", ctr.ID()) + + // Look up the container's user volumes. This gets us the + // destinations of all mounts the user added to the container. + userVolumesArr := ctr.UserVolumes() + + // We're going to need to access them a lot, so convert to a map + // to reduce looping. + // We'll also use the map to indicate if we missed any volumes along the way. + userVolumes := make(map[string]bool) + for _, dest := range userVolumesArr { + userVolumes[dest] = false } - if err := parse.ValidateVolumeCtrDir(dest); err != nil { - return nil, nil, err + + // Now we get the container's spec and loop through its volumes + // and append them in if we can find them. + spec := ctr.Spec() + if spec == nil { + return nil, nil, errors.Errorf("error retrieving container %s spec for volumes-from", ctr.ID()) } + for _, mnt := range spec.Mounts { + if mnt.Type != TypeBind { + continue + } + if _, exists := userVolumes[mnt.Destination]; exists { + userVolumes[mnt.Destination] = true - cleanDest := filepath.Clean(dest) + if len(options) != 0 { + mnt.Options = options + } - if strings.HasPrefix(src, "/") || strings.HasPrefix(src, ".") { - // This is not a named volume - newMount := spec.Mount{ - Destination: cleanDest, - Type: string(TypeBind), - Source: src, - Options: options, - } - if _, ok := mounts[newMount.Destination]; ok { - return nil, nil, errors.Wrapf(errDuplicateDest, newMount.Destination) - } - mounts[newMount.Destination] = newMount - } else { - // This is a named volume - newNamedVol := new(libpod.ContainerNamedVolume) - newNamedVol.Name = src - newNamedVol.Dest = cleanDest - newNamedVol.Options = options - - if _, ok := volumes[newNamedVol.Dest]; ok { - return nil, nil, errors.Wrapf(errDuplicateDest, newNamedVol.Dest) + if _, ok := finalMounts[mnt.Destination]; ok { + logrus.Debugf("Overriding mount to %s with new mount from container %s", mnt.Destination, ctr.ID()) + } + finalMounts[mnt.Destination] = mnt } - volumes[newNamedVol.Dest] = newNamedVol } - logrus.Debugf("User mount %s:%s options %v", src, dest, options) - } + // We're done with the spec mounts. Add named volumes. + // Add these unconditionally - none of them are automatically + // part of the container, as some spec mounts are. + namedVolumes := ctr.NamedVolumes() + for _, namedVol := range namedVolumes { + if _, exists := userVolumes[namedVol.Dest]; exists { + userVolumes[namedVol.Dest] = true + } - return mounts, volumes, nil -} + if len(options) != 0 { + namedVol.Options = options + } -// Get mounts for container's image volumes -// TODO deferred baude -func getImageVolumes() (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) { //nolint - //mounts := make(map[string]spec.Mount) - //volumes := make(map[string]*define.ContainerNamedVolume) - // - //if config.ImageVolumeType == "ignore" { - // return mounts, volumes, nil - //} - // - //for vol := range config.BuiltinImgVolumes { - // cleanDest := filepath.Clean(vol) - // logrus.Debugf("Adding image volume at %s", cleanDest) - // if config.ImageVolumeType == "tmpfs" { - // // Tmpfs image volumes are handled as mounts - // mount := spec.Mount{ - // Destination: cleanDest, - // Source: TypeTmpfs, - // Type: TypeTmpfs, - // Options: []string{"rprivate", "rw", "nodev", "exec"}, - // } - // mounts[cleanDest] = mount - // } else { - // // Anonymous volumes have no name. - // namedVolume := new(define.ContainerNamedVolume) - // namedVolume.Options = []string{"rprivate", "rw", "nodev", "exec"} - // namedVolume.Dest = cleanDest - // volumes[cleanDest] = namedVolume - // } - //} - // - //return mounts, volumes, nil - return nil, nil, nil -} + if _, ok := finalMounts[namedVol.Dest]; ok { + logrus.Debugf("Overriding named volume mount to %s with new named volume from container %s", namedVol.Dest, ctr.ID()) + } -// GetTmpfsMounts creates spec.Mount structs for user-requested tmpfs mounts -func getTmpfsMounts(mounts []string) (map[string]spec.Mount, error) { //nolint - m := make(map[string]spec.Mount) - for _, i := range mounts { - // Default options if nothing passed - var options []string - spliti := strings.Split(i, ":") - destPath := spliti[0] - if err := parse.ValidateVolumeCtrDir(spliti[0]); err != nil { - return nil, err - } - if len(spliti) > 1 { - options = strings.Split(spliti[1], ",") - } + newVol := new(specgen.NamedVolume) + newVol.Dest = namedVol.Dest + newVol.Options = namedVol.Options + newVol.Name = namedVol.Name - if _, ok := m[destPath]; ok { - return nil, errors.Wrapf(errDuplicateDest, destPath) + finalNamedVolumes[namedVol.Dest] = newVol } - mount := spec.Mount{ - Destination: filepath.Clean(destPath), - Type: string(TypeTmpfs), - Options: options, - Source: string(TypeTmpfs), + // Check if we missed any volumes + for volDest, found := range userVolumes { + if !found { + logrus.Warnf("Unable to match volume %s from container %s for volumes-from", volDest, ctr.ID()) + } } - m[destPath] = mount } - return m, nil + + return finalMounts, finalNamedVolumes, nil } // AddContainerInitBinary adds the init binary specified by path iff the // container will run in a private PID namespace that is not shared with the // host or another pre-existing container, where an init-like process is // already running. -// -// Note that AddContainerInitBinary prepends "/dev/init" "--" to the command -// to execute the bind-mounted binary as PID 1. -// TODO this needs to be worked on to work in new env -func addContainerInitBinary(path string) (spec.Mount, error) { //nolint +// This does *NOT* modify the container command - that must be done elsewhere. +func addContainerInitBinary(s *specgen.SpecGenerator, path string) (spec.Mount, error) { mount := spec.Mount{ Destination: "/dev/init", Type: TypeBind, @@ -816,19 +308,18 @@ func addContainerInitBinary(path string) (spec.Mount, error) { //nolint Options: []string{TypeBind, "ro"}, } - //if path == "" { - // return mount, fmt.Errorf("please specify a path to the container-init binary") - //} - //if !config.Pid.PidMode.IsPrivate() { - // return mount, fmt.Errorf("cannot add init binary as PID 1 (PID namespace isn't private)") - //} - //if config.Systemd { - // return mount, fmt.Errorf("cannot use container-init binary with systemd") - //} - //if _, err := os.Stat(path); os.IsNotExist(err) { - // return mount, errors.Wrap(err, "container-init binary not found on the host") - //} - //config.Command = append([]string{"/dev/init", "--"}, config.Command...) + if path == "" { + return mount, fmt.Errorf("please specify a path to the container-init binary") + } + if !s.PidNS.IsPrivate() { + return mount, fmt.Errorf("cannot add init binary as PID 1 (PID namespace isn't private)") + } + if s.Systemd == "true" || s.Systemd == "always" { + return mount, fmt.Errorf("cannot use container-init binary with systemd") + } + if _, err := os.Stat(path); os.IsNotExist(err) { + return mount, errors.Wrap(err, "container-init binary not found on the host") + } return mount, nil } diff --git a/pkg/specgen/namespaces.go b/pkg/specgen/namespaces.go index 2e7f80fe8..f0161a793 100644 --- a/pkg/specgen/namespaces.go +++ b/pkg/specgen/namespaces.go @@ -1,6 +1,8 @@ package specgen import ( + "strings" + "github.com/pkg/errors" ) @@ -31,6 +33,11 @@ const ( // Slirp indicates that a slirp4netns network stack should // be used Slirp NamespaceMode = "slirp4netns" + // KeepId indicates a user namespace to keep the owner uid inside + // of the namespace itself + KeepID NamespaceMode = "keep-id" + // KeepId indicates to automatically create a user namespace + Auto NamespaceMode = "auto" ) // Namespace describes the namespace @@ -39,6 +46,12 @@ type Namespace struct { Value string `json:"string,omitempty"` } +// IsDefault returns whether the namespace is set to the default setting (which +// also includes the empty string). +func (n *Namespace) IsDefault() bool { + return n.NSMode == Default || n.NSMode == "" +} + // IsHost returns a bool if the namespace is host based func (n *Namespace) IsHost() bool { return n.NSMode == Host @@ -64,16 +77,50 @@ func (n *Namespace) IsPrivate() bool { return n.NSMode == Private } +// IsAuto indicates the namespace is auto +func (n *Namespace) IsAuto() bool { + return n.NSMode == Auto +} + +// IsKeepID indicates the namespace is KeepID +func (n *Namespace) IsKeepID() bool { + return n.NSMode == KeepID +} + +func validateUserNS(n *Namespace) error { + if n == nil { + return nil + } + switch n.NSMode { + case Auto, KeepID: + return nil + } + return n.validate() +} + func validateNetNS(n *Namespace) error { if n == nil { return nil } switch n.NSMode { - case Host, Path, FromContainer, FromPod, Private, NoNetwork, Bridge, Slirp: + case "", Default, Host, Path, FromContainer, FromPod, Private, NoNetwork, Bridge, Slirp: break default: return errors.Errorf("invalid network %q", n.NSMode) } + + // Path and From Container MUST have a string value set + if n.NSMode == Path || n.NSMode == FromContainer { + 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 + if len(n.Value) > 0 { + return errors.Errorf("namespace value %s cannot be provided with namespace mode %s", n.Value, n.NSMode) + } + } + return nil } @@ -83,6 +130,15 @@ func (n *Namespace) validate() error { if n == nil { return nil } + switch n.NSMode { + case "", Default, Host, Path, FromContainer, FromPod, Private: + // Valid, do nothing + case NoNetwork, Bridge, Slirp: + return errors.Errorf("cannot use network modes with non-network namespace") + default: + return errors.Errorf("invalid namespace type %s specified", n.NSMode) + } + // Path and From Container MUST have a string value set if n.NSMode == Path || n.NSMode == FromContainer { if len(n.Value) < 1 { @@ -96,3 +152,100 @@ func (n *Namespace) validate() error { } return nil } + +// ParseNamespace parses a namespace in string form. +// This is not intended for the network namespace, which has a separate +// function. +func ParseNamespace(ns string) (Namespace, error) { + toReturn := Namespace{} + switch { + case ns == "pod": + toReturn.NSMode = FromPod + case ns == "host": + toReturn.NSMode = Host + case ns == "private": + toReturn.NSMode = Private + case strings.HasPrefix(ns, "ns:"): + split := strings.SplitN(ns, ":", 2) + if len(split) != 2 { + return toReturn, errors.Errorf("must provide a path to a namespace when specifying ns:") + } + toReturn.NSMode = Path + toReturn.Value = split[1] + case strings.HasPrefix(ns, "container:"): + split := strings.SplitN(ns, ":", 2) + if len(split) != 2 { + return toReturn, errors.Errorf("must provide name or ID or a container when specifying container:") + } + toReturn.NSMode = FromContainer + toReturn.Value = split[1] + default: + return toReturn, errors.Errorf("unrecognized namespace mode %s passed", ns) + } + + return toReturn, nil +} + +// ParseUserNamespace parses a user namespace specification in string +// form. +func ParseUserNamespace(ns string) (Namespace, error) { + toReturn := Namespace{} + switch { + case ns == "auto": + toReturn.NSMode = Auto + return toReturn, nil + case strings.HasPrefix(ns, "auto:"): + split := strings.SplitN(ns, ":", 2) + if len(split) != 2 { + return toReturn, errors.Errorf("invalid setting for auto: mode") + } + toReturn.NSMode = Auto + toReturn.Value = split[1] + return toReturn, nil + case ns == "keep-id": + toReturn.NSMode = KeepID + return toReturn, nil + } + return ParseNamespace(ns) +} + +// ParseNetworkNamespace parses a network namespace specification in string +// form. +// Returns a namespace and (optionally) a list of CNI networks to join. +func ParseNetworkNamespace(ns string) (Namespace, []string, error) { + toReturn := Namespace{} + var cniNetworks []string + switch { + case ns == "pod": + toReturn.NSMode = FromPod + case ns == "bridge": + toReturn.NSMode = Bridge + case ns == "none": + toReturn.NSMode = NoNetwork + case ns == "host": + toReturn.NSMode = Host + case ns == "private": + toReturn.NSMode = Private + case strings.HasPrefix(ns, "ns:"): + split := strings.SplitN(ns, ":", 2) + if len(split) != 2 { + return toReturn, nil, errors.Errorf("must provide a path to a namespace when specifying ns:") + } + toReturn.NSMode = Path + toReturn.Value = split[1] + case strings.HasPrefix(ns, "container:"): + split := strings.SplitN(ns, ":", 2) + if len(split) != 2 { + return toReturn, nil, errors.Errorf("must provide name or ID or a container when specifying container:") + } + toReturn.NSMode = FromContainer + toReturn.Value = split[1] + default: + // Assume we have been given a list of CNI networks. + // Which only works in bridge mode, so set that. + cniNetworks = strings.Split(ns, ",") + toReturn.NSMode = Bridge + } + + return toReturn, cniNetworks, nil +} diff --git a/pkg/specgen/pod_validate.go b/pkg/specgen/pod_validate.go index 9e9659fa9..98d59549e 100644 --- a/pkg/specgen/pod_validate.go +++ b/pkg/specgen/pod_validate.go @@ -1,14 +1,16 @@ package specgen import ( - "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/rootless" + "github.com/containers/libpod/pkg/util" "github.com/pkg/errors" ) var ( // ErrInvalidPodSpecConfig describes an error given when the podspecgenerator is invalid ErrInvalidPodSpecConfig error = errors.New("invalid pod spec") + // containerConfig has the default configurations defined in containers.conf + containerConfig = util.DefaultContainerConfig() ) func exclusivePodOptions(opt1, opt2 string) error { @@ -60,7 +62,7 @@ func (p *PodSpecGenerator) Validate() error { return exclusivePodOptions("NoInfra", "NoManageResolvConf") } } - if p.NetNS.NSMode != Bridge { + if p.NetNS.NSMode != "" && p.NetNS.NSMode != Bridge && p.NetNS.NSMode != Default { if len(p.PortMappings) > 0 { return errors.New("PortMappings can only be used with Bridge mode networking") } @@ -96,10 +98,10 @@ func (p *PodSpecGenerator) Validate() error { } } if len(p.InfraImage) < 1 { - p.InfraImage = define.DefaultInfraImage + p.InfraImage = containerConfig.Engine.InfraImage } if len(p.InfraCommand) < 1 { - p.InfraCommand = []string{define.DefaultInfraCommand} + p.InfraCommand = []string{containerConfig.Engine.InfraCommand} } return nil } diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go index 1a05733f9..20c8f8800 100644 --- a/pkg/specgen/specgen.go +++ b/pkg/specgen/specgen.go @@ -5,7 +5,6 @@ import ( "syscall" "github.com/containers/image/v5/manifest" - "github.com/containers/libpod/pkg/rootless" "github.com/containers/storage" "github.com/cri-o/ocicni/pkg/ocicni" spec "github.com/opencontainers/runtime-spec/specs-go" @@ -155,14 +154,23 @@ type ContainerStorageConfig struct { // ImageVolumeMode indicates how image volumes will be created. // Supported modes are "ignore" (do not create), "tmpfs" (create as // tmpfs), and "anonymous" (create as anonymous volumes). - // The default is anonymous. + // The default if unset is anonymous. // Optional. ImageVolumeMode string `json:"image_volume_mode,omitempty"` - // VolumesFrom is a list of containers whose volumes will be added to - // this container. Supported mount options may be added after the - // container name with a : and include "ro" and "rw". - // Optional. + // VolumesFrom is a set of containers whose volumes will be added to + // this container. The name or ID of the container must be provided, and + // may optionally be followed by a : and then one or more + // comma-separated options. Valid options are 'ro', 'rw', and 'z'. + // Options will be used for all volumes sourced from the container. VolumesFrom []string `json:"volumes_from,omitempty"` + // Init specifies that an init binary will be mounted into the + // container, and will be used as PID1. + Init bool `json:"init,omitempty"` + // InitPath specifies the path to the init binary that will be added if + // Init is specified above. If not specified, the default set in the + // Libpod config will be used. Ignored if Init above is not set. + // Optional. + InitPath string `json:"init_path,omitempty"` // Mounts are mounts that will be added to the container. // These will supersede Image Volumes and VolumesFrom volumes where // there are conflicts. @@ -172,7 +180,7 @@ type ContainerStorageConfig struct { // These will supersede Image Volumes and VolumesFrom volumes where // there are conflicts. // Optional. - Volumes []*Volumes `json:"volumes,omitempty"` + Volumes []*NamedVolume `json:"volumes,omitempty"` // Devices are devices that will be added to the container. // Optional. Devices []spec.LinuxDevice `json:"devices,omitempty"` @@ -283,25 +291,20 @@ type ContainerNetworkConfig struct { // namespace. // Mandatory. NetNS Namespace `json:"netns,omitempty"` - // ConfigureNetNS is whether Libpod will configure the container's - // network namespace to send and receive traffic. - // Only available is NetNS is private - conflicts with other NetNS - // modes. - ConfigureNetNS bool `json:"configure_netns,omitempty"` // StaticIP is the a IPv4 address of the container. - // Only available if ConfigureNetNS is true. + // Only available if NetNS is set to Bridge. // Optional. StaticIP *net.IP `json:"static_ip,omitempty"` // StaticIPv6 is a static IPv6 address to set in the container. - // Only available if ConfigureNetNS is true. + // Only available if NetNS is set to Bridge. // Optional. StaticIPv6 *net.IP `json:"static_ipv6,omitempty"` // StaticMAC is a static MAC address to set in the container. - // Only available if ConfigureNetNS is true. + // Only available if NetNS is set to bridge. // Optional. StaticMAC *net.HardwareAddr `json:"static_mac,omitempty"` // PortBindings is a set of ports to map into the container. - // Only available if ConfigureNetNS is true. + // Only available if NetNS is set to bridge or slirp. // Optional. PortMappings []ocicni.PortMapping `json:"portmappings,omitempty"` // PublishImagePorts will publish ports specified in the image to random @@ -312,31 +315,31 @@ type ContainerNetworkConfig struct { // If this list is empty, the default CNI network will be joined // instead. If at least one entry is present, we will not join the // default network (unless it is part of this list). - // Only available if ConfigureNetNS is true. + // Only available if NetNS is set to bridge. // Optional. CNINetworks []string `json:"cni_networks,omitempty"` // UseImageResolvConf indicates that resolv.conf should not be managed // by Podman, but instead sourced from the image. // Conflicts with DNSServer, DNSSearch, DNSOption. UseImageResolvConf bool `json:"use_image_resolve_conf,omitempty"` - // DNSServer is a set of DNS servers that will be used in the + // DNSServers is a set of DNS servers that will be used in the // container's resolv.conf, replacing the host's DNS Servers which are // used by default. // Conflicts with UseImageResolvConf. // Optional. - DNSServer []net.IP `json:"dns_server,omitempty"` + DNSServers []net.IP `json:"dns_server,omitempty"` // DNSSearch is a set of DNS search domains that will be used in the // container's resolv.conf, replacing the host's DNS search domains // which are used by default. // Conflicts with UseImageResolvConf. // Optional. DNSSearch []string `json:"dns_search,omitempty"` - // DNSOption is a set of DNS options that will be used in the + // DNSOptions is a set of DNS options that will be used in the // container's resolv.conf, replacing the host's DNS options which are // used by default. // Conflicts with UseImageResolvConf. // Optional. - DNSOption []string `json:"dns_option,omitempty"` + DNSOptions []string `json:"dns_option,omitempty"` // UseImageHosts indicates that /etc/hosts should not be managed by // Podman, and instead sourced from the image. // Conflicts with HostAdd. @@ -393,27 +396,30 @@ type SpecGenerator struct { ContainerHealthCheckConfig } -// Volumes is a temporary struct to hold input from the User -type Volumes struct { - Name string - Dest string +// NamedVolume holds information about a named volume that will be mounted into +// the container. +type NamedVolume struct { + // Name is the name of the named volume to be mounted. May be empty. + // If empty, a new named volume with a pseudorandomly generated name + // will be mounted at the given destination. + Name string + // Destination to mount the named volume within the container. Must be + // an absolute path. Path will be created if it does not exist. + Dest string + // Options are options that the named volume will be mounted with. Options []string } // NewSpecGenerator returns a SpecGenerator struct given one of two mandatory inputs -func NewSpecGenerator(image string) *SpecGenerator { - networkConfig := ContainerNetworkConfig{ - NetNS: Namespace{ - NSMode: Bridge, - }, - } - csc := ContainerStorageConfig{Image: image} - if rootless.IsRootless() { - networkConfig.NetNS.NSMode = Slirp +func NewSpecGenerator(arg string, rootfs bool) *SpecGenerator { + csc := ContainerStorageConfig{} + if rootfs { + csc.Rootfs = arg + } else { + csc.Image = arg } return &SpecGenerator{ ContainerStorageConfig: csc, - ContainerNetworkConfig: networkConfig, } } diff --git a/pkg/sysinfo/README.md b/pkg/sysinfo/README.md deleted file mode 100644 index c1530cef0..000000000 --- a/pkg/sysinfo/README.md +++ /dev/null @@ -1 +0,0 @@ -SysInfo stores information about which features a kernel supports. diff --git a/pkg/sysinfo/numcpu.go b/pkg/sysinfo/numcpu.go deleted file mode 100644 index aeb1a3a80..000000000 --- a/pkg/sysinfo/numcpu.go +++ /dev/null @@ -1,12 +0,0 @@ -// +build !linux,!windows - -package sysinfo - -import ( - "runtime" -) - -// NumCPU returns the number of CPUs -func NumCPU() int { - return runtime.NumCPU() -} diff --git a/pkg/sysinfo/numcpu_linux.go b/pkg/sysinfo/numcpu_linux.go deleted file mode 100644 index f1d2d9db3..000000000 --- a/pkg/sysinfo/numcpu_linux.go +++ /dev/null @@ -1,44 +0,0 @@ -// +build linux - -package sysinfo - -import ( - "runtime" - "unsafe" - - "golang.org/x/sys/unix" -) - -// numCPU queries the system for the count of threads available -// for use to this process. -// -// Issues two syscalls. -// Returns 0 on errors. Use |runtime.NumCPU| in that case. -func numCPU() int { - // Gets the affinity mask for a process: The very one invoking this function. - pid, _, _ := unix.RawSyscall(unix.SYS_GETPID, 0, 0, 0) - - var mask [1024 / 64]uintptr - _, _, err := unix.RawSyscall(unix.SYS_SCHED_GETAFFINITY, pid, uintptr(len(mask)*8), uintptr(unsafe.Pointer(&mask[0]))) - if err != 0 { - return 0 - } - - // For every available thread a bit is set in the mask. - ncpu := 0 - for _, e := range mask { - if e == 0 { - continue - } - ncpu += int(popcnt(uint64(e))) - } - return ncpu -} - -// NumCPU returns the number of CPUs which are currently online -func NumCPU() int { - if ncpu := numCPU(); ncpu > 0 { - return ncpu - } - return runtime.NumCPU() -} diff --git a/pkg/sysinfo/numcpu_windows.go b/pkg/sysinfo/numcpu_windows.go deleted file mode 100644 index 1d89dd550..000000000 --- a/pkg/sysinfo/numcpu_windows.go +++ /dev/null @@ -1,37 +0,0 @@ -// +build windows - -package sysinfo - -import ( - "runtime" - "unsafe" - - "golang.org/x/sys/windows" -) - -var ( - kernel32 = windows.NewLazySystemDLL("kernel32.dll") - getCurrentProcess = kernel32.NewProc("GetCurrentProcess") - getProcessAffinityMask = kernel32.NewProc("GetProcessAffinityMask") -) - -func numCPU() int { - // Gets the affinity mask for a process - var mask, sysmask uintptr - currentProcess, _, _ := getCurrentProcess.Call() - ret, _, _ := getProcessAffinityMask.Call(currentProcess, uintptr(unsafe.Pointer(&mask)), uintptr(unsafe.Pointer(&sysmask))) - if ret == 0 { - return 0 - } - // For every available thread a bit is set in the mask. - ncpu := int(popcnt(uint64(mask))) - return ncpu -} - -// NumCPU returns the number of CPUs which are currently online -func NumCPU() int { - if ncpu := numCPU(); ncpu > 0 { - return ncpu - } - return runtime.NumCPU() -} diff --git a/pkg/sysinfo/sysinfo.go b/pkg/sysinfo/sysinfo.go deleted file mode 100644 index 686f66ce5..000000000 --- a/pkg/sysinfo/sysinfo.go +++ /dev/null @@ -1,153 +0,0 @@ -package sysinfo - -import "github.com/docker/docker/pkg/parsers" - -// SysInfo stores information about which features a kernel supports. -// TODO Windows: Factor out platform specific capabilities. -type SysInfo struct { - // Whether the kernel supports AppArmor or not - AppArmor bool - // Whether the kernel supports Seccomp or not - Seccomp bool - - cgroupMemInfo - cgroupCPUInfo - cgroupBlkioInfo - cgroupCpusetInfo - cgroupPids - - // Whether IPv4 forwarding is supported or not, if this was disabled, networking will not work - IPv4ForwardingDisabled bool - - // Whether bridge-nf-call-iptables is supported or not - BridgeNFCallIPTablesDisabled bool - - // Whether bridge-nf-call-ip6tables is supported or not - BridgeNFCallIP6TablesDisabled bool - - // Whether the cgroup has the mountpoint of "devices" or not - CgroupDevicesEnabled bool -} - -type cgroupMemInfo struct { - // Whether memory limit is supported or not - MemoryLimit bool - - // Whether swap limit is supported or not - SwapLimit bool - - // Whether soft limit is supported or not - MemoryReservation bool - - // Whether OOM killer disable is supported or not - OomKillDisable bool - - // Whether memory swappiness is supported or not - MemorySwappiness bool - - // Whether kernel memory limit is supported or not - KernelMemory bool -} - -type cgroupCPUInfo struct { - // Whether CPU shares is supported or not - CPUShares bool - - // Whether CPU CFS(Completely Fair Scheduler) period is supported or not - CPUCfsPeriod bool - - // Whether CPU CFS(Completely Fair Scheduler) quota is supported or not - CPUCfsQuota bool - - // Whether CPU real-time period is supported or not - CPURealtimePeriod bool - - // Whether CPU real-time runtime is supported or not - CPURealtimeRuntime bool -} - -type cgroupBlkioInfo struct { - // Whether Block IO weight is supported or not - BlkioWeight bool - - // Whether Block IO weight_device is supported or not - BlkioWeightDevice bool - - // Whether Block IO read limit in bytes per second is supported or not - BlkioReadBpsDevice bool - - // Whether Block IO write limit in bytes per second is supported or not - BlkioWriteBpsDevice bool - - // Whether Block IO read limit in IO per second is supported or not - BlkioReadIOpsDevice bool - - // Whether Block IO write limit in IO per second is supported or not - BlkioWriteIOpsDevice bool -} - -type cgroupCpusetInfo struct { - // Whether Cpuset is supported or not - Cpuset bool - - // Available Cpuset's cpus - Cpus string - - // Available Cpuset's memory nodes - Mems string -} - -type cgroupPids struct { - // Whether Pids Limit is supported or not - PidsLimit bool -} - -// IsCpusetCpusAvailable returns `true` if the provided string set is contained -// in cgroup's cpuset.cpus set, `false` otherwise. -// If error is not nil a parsing error occurred. -func (c cgroupCpusetInfo) IsCpusetCpusAvailable(provided string) (bool, error) { - return isCpusetListAvailable(provided, c.Cpus) -} - -// IsCpusetMemsAvailable returns `true` if the provided string set is contained -// in cgroup's cpuset.mems set, `false` otherwise. -// If error is not nil a parsing error occurred. -func (c cgroupCpusetInfo) IsCpusetMemsAvailable(provided string) (bool, error) { - return isCpusetListAvailable(provided, c.Mems) -} - -func isCpusetListAvailable(provided, available string) (bool, error) { - parsedProvided, err := parsers.ParseUintList(provided) - if err != nil { - return false, err - } - parsedAvailable, err := parsers.ParseUintList(available) - if err != nil { - return false, err - } - for k := range parsedProvided { - if !parsedAvailable[k] { - return false, nil - } - } - return true, nil -} - -// Returns bit count of 1, used by NumCPU -func popcnt(x uint64) (n byte) { - x -= (x >> 1) & 0x5555555555555555 - x = (x>>2)&0x3333333333333333 + x&0x3333333333333333 - x += x >> 4 - x &= 0x0f0f0f0f0f0f0f0f - x *= 0x0101010101010101 - return byte(x >> 56) -} - -// GetDefaultPidsLimit returns the default pids limit to run containers with -func GetDefaultPidsLimit() int64 { - sysInfo := New(true) - if !sysInfo.PidsLimit { - return 0 - } - return 4096 -} diff --git a/pkg/sysinfo/sysinfo_linux.go b/pkg/sysinfo/sysinfo_linux.go deleted file mode 100644 index 76bda23c6..000000000 --- a/pkg/sysinfo/sysinfo_linux.go +++ /dev/null @@ -1,261 +0,0 @@ -package sysinfo - -import ( - "fmt" - "io/ioutil" - "os" - "path" - "strings" - - cg "github.com/containers/libpod/pkg/cgroups" - "github.com/opencontainers/runc/libcontainer/cgroups" - "github.com/sirupsen/logrus" - "golang.org/x/sys/unix" -) - -func findCgroupMountpoints() (map[string]string, error) { - cgMounts, err := cgroups.GetCgroupMounts(false) - if err != nil { - return nil, fmt.Errorf("failed to parse cgroup information: %v", err) - } - mps := make(map[string]string) - for _, m := range cgMounts { - for _, ss := range m.Subsystems { - mps[ss] = m.Mountpoint - } - } - return mps, nil -} - -// New returns a new SysInfo, using the filesystem to detect which features -// the kernel supports. If `quiet` is `false` warnings are printed in logs -// whenever an error occurs or misconfigurations are present. -func New(quiet bool) *SysInfo { - sysInfo := &SysInfo{} - cgMounts, err := findCgroupMountpoints() - if err != nil { - logrus.Warnf("Failed to parse cgroup information: %v", err) - } else { - sysInfo.cgroupMemInfo = checkCgroupMem(cgMounts, quiet) - sysInfo.cgroupCPUInfo = checkCgroupCPU(cgMounts, quiet) - sysInfo.cgroupBlkioInfo = checkCgroupBlkioInfo(cgMounts, quiet) - sysInfo.cgroupCpusetInfo = checkCgroupCpusetInfo(cgMounts, quiet) - sysInfo.cgroupPids = checkCgroupPids(quiet) - } - - _, ok := cgMounts["devices"] - sysInfo.CgroupDevicesEnabled = ok - - sysInfo.IPv4ForwardingDisabled = !readProcBool("/proc/sys/net/ipv4/ip_forward") - sysInfo.BridgeNFCallIPTablesDisabled = !readProcBool("/proc/sys/net/bridge/bridge-nf-call-iptables") - sysInfo.BridgeNFCallIP6TablesDisabled = !readProcBool("/proc/sys/net/bridge/bridge-nf-call-ip6tables") - - // Check if AppArmor is supported. - if _, err := os.Stat("/sys/kernel/security/apparmor"); !os.IsNotExist(err) { - sysInfo.AppArmor = true - } - - // Check if Seccomp is supported, via CONFIG_SECCOMP. - if err := unix.Prctl(unix.PR_GET_SECCOMP, 0, 0, 0, 0); err != unix.EINVAL { - // Make sure the kernel has CONFIG_SECCOMP_FILTER. - if err := unix.Prctl(unix.PR_SET_SECCOMP, unix.SECCOMP_MODE_FILTER, 0, 0, 0); err != unix.EINVAL { - sysInfo.Seccomp = true - } - } - - return sysInfo -} - -// checkCgroupMem reads the memory information from the memory cgroup mount point. -func checkCgroupMem(cgMounts map[string]string, quiet bool) cgroupMemInfo { - mountPoint, ok := cgMounts["memory"] - if !ok { - if !quiet { - logrus.Warn("Your kernel does not support cgroup memory limit") - } - return cgroupMemInfo{} - } - - swapLimit := cgroupEnabled(mountPoint, "memory.memsw.limit_in_bytes") - if !quiet && !swapLimit { - logrus.Warn("Your kernel does not support swap memory limit") - } - memoryReservation := cgroupEnabled(mountPoint, "memory.soft_limit_in_bytes") - if !quiet && !memoryReservation { - logrus.Warn("Your kernel does not support memory reservation") - } - oomKillDisable := cgroupEnabled(mountPoint, "memory.oom_control") - if !quiet && !oomKillDisable { - logrus.Warn("Your kernel does not support oom control") - } - memorySwappiness := cgroupEnabled(mountPoint, "memory.swappiness") - if !quiet && !memorySwappiness { - logrus.Warn("Your kernel does not support memory swappiness") - } - kernelMemory := cgroupEnabled(mountPoint, "memory.kmem.limit_in_bytes") - if !quiet && !kernelMemory { - logrus.Warn("Your kernel does not support kernel memory limit") - } - - return cgroupMemInfo{ - MemoryLimit: true, - SwapLimit: swapLimit, - MemoryReservation: memoryReservation, - OomKillDisable: oomKillDisable, - MemorySwappiness: memorySwappiness, - KernelMemory: kernelMemory, - } -} - -// checkCgroupCPU reads the cpu information from the cpu cgroup mount point. -func checkCgroupCPU(cgMounts map[string]string, quiet bool) cgroupCPUInfo { - mountPoint, ok := cgMounts["cpu"] - if !ok { - if !quiet { - logrus.Warn("Unable to find cpu cgroup in mounts") - } - return cgroupCPUInfo{} - } - - cpuShares := cgroupEnabled(mountPoint, "cpu.shares") - if !quiet && !cpuShares { - logrus.Warn("Your kernel does not support cgroup cpu shares") - } - - cpuCfsPeriod := cgroupEnabled(mountPoint, "cpu.cfs_period_us") - if !quiet && !cpuCfsPeriod { - logrus.Warn("Your kernel does not support cgroup cfs period") - } - - cpuCfsQuota := cgroupEnabled(mountPoint, "cpu.cfs_quota_us") - if !quiet && !cpuCfsQuota { - logrus.Warn("Your kernel does not support cgroup cfs quotas") - } - - cpuRealtimePeriod := cgroupEnabled(mountPoint, "cpu.rt_period_us") - if !quiet && !cpuRealtimePeriod { - logrus.Warn("Your kernel does not support cgroup rt period") - } - - cpuRealtimeRuntime := cgroupEnabled(mountPoint, "cpu.rt_runtime_us") - if !quiet && !cpuRealtimeRuntime { - logrus.Warn("Your kernel does not support cgroup rt runtime") - } - - return cgroupCPUInfo{ - CPUShares: cpuShares, - CPUCfsPeriod: cpuCfsPeriod, - CPUCfsQuota: cpuCfsQuota, - CPURealtimePeriod: cpuRealtimePeriod, - CPURealtimeRuntime: cpuRealtimeRuntime, - } -} - -// checkCgroupBlkioInfo reads the blkio information from the blkio cgroup mount point. -func checkCgroupBlkioInfo(cgMounts map[string]string, quiet bool) cgroupBlkioInfo { - mountPoint, ok := cgMounts["blkio"] - if !ok { - if !quiet { - logrus.Warn("Unable to find blkio cgroup in mounts") - } - return cgroupBlkioInfo{} - } - - weight := cgroupEnabled(mountPoint, "blkio.weight") - if !quiet && !weight { - logrus.Warn("Your kernel does not support cgroup blkio weight") - } - - weightDevice := cgroupEnabled(mountPoint, "blkio.weight_device") - if !quiet && !weightDevice { - logrus.Warn("Your kernel does not support cgroup blkio weight_device") - } - - readBpsDevice := cgroupEnabled(mountPoint, "blkio.throttle.read_bps_device") - if !quiet && !readBpsDevice { - logrus.Warn("Your kernel does not support cgroup blkio throttle.read_bps_device") - } - - writeBpsDevice := cgroupEnabled(mountPoint, "blkio.throttle.write_bps_device") - if !quiet && !writeBpsDevice { - logrus.Warn("Your kernel does not support cgroup blkio throttle.write_bps_device") - } - readIOpsDevice := cgroupEnabled(mountPoint, "blkio.throttle.read_iops_device") - if !quiet && !readIOpsDevice { - logrus.Warn("Your kernel does not support cgroup blkio throttle.read_iops_device") - } - - writeIOpsDevice := cgroupEnabled(mountPoint, "blkio.throttle.write_iops_device") - if !quiet && !writeIOpsDevice { - logrus.Warn("Your kernel does not support cgroup blkio throttle.write_iops_device") - } - return cgroupBlkioInfo{ - BlkioWeight: weight, - BlkioWeightDevice: weightDevice, - BlkioReadBpsDevice: readBpsDevice, - BlkioWriteBpsDevice: writeBpsDevice, - BlkioReadIOpsDevice: readIOpsDevice, - BlkioWriteIOpsDevice: writeIOpsDevice, - } -} - -// checkCgroupCpusetInfo reads the cpuset information from the cpuset cgroup mount point. -func checkCgroupCpusetInfo(cgMounts map[string]string, quiet bool) cgroupCpusetInfo { - mountPoint, ok := cgMounts["cpuset"] - if !ok { - if !quiet { - logrus.Warn("Unable to find cpuset cgroup in mounts") - } - return cgroupCpusetInfo{} - } - - cpus, err := ioutil.ReadFile(path.Join(mountPoint, "cpuset.cpus")) - if err != nil { - return cgroupCpusetInfo{} - } - - mems, err := ioutil.ReadFile(path.Join(mountPoint, "cpuset.mems")) - if err != nil { - return cgroupCpusetInfo{} - } - - return cgroupCpusetInfo{ - Cpuset: true, - Cpus: strings.TrimSpace(string(cpus)), - Mems: strings.TrimSpace(string(mems)), - } -} - -// checkCgroupPids reads the pids information from the pids cgroup mount point. -func checkCgroupPids(quiet bool) cgroupPids { - cgroup2, err := cg.IsCgroup2UnifiedMode() - if err != nil { - logrus.Errorf("Failed to check cgroups version: %v", err) - } - if !cgroup2 { - _, err := cgroups.FindCgroupMountpoint("", "pids") - if err != nil { - if !quiet { - logrus.Warn(err) - } - return cgroupPids{} - } - } - - return cgroupPids{ - PidsLimit: true, - } -} - -func cgroupEnabled(mountPoint, name string) bool { - _, err := os.Stat(path.Join(mountPoint, name)) - return err == nil -} - -func readProcBool(path string) bool { - val, err := ioutil.ReadFile(path) - if err != nil { - return false - } - return strings.TrimSpace(string(val)) == "1" -} diff --git a/pkg/sysinfo/sysinfo_linux_test.go b/pkg/sysinfo/sysinfo_linux_test.go deleted file mode 100644 index 860784f2a..000000000 --- a/pkg/sysinfo/sysinfo_linux_test.go +++ /dev/null @@ -1,104 +0,0 @@ -package sysinfo - -import ( - "io/ioutil" - "os" - "path" - "path/filepath" - "testing" - - "github.com/stretchr/testify/require" - "golang.org/x/sys/unix" -) - -func TestReadProcBool(t *testing.T) { - tmpDir, err := ioutil.TempDir("", "test-sysinfo-proc") - require.NoError(t, err) - defer os.RemoveAll(tmpDir) - - procFile := filepath.Join(tmpDir, "read-proc-bool") - err = ioutil.WriteFile(procFile, []byte("1"), 0644) - require.NoError(t, err) - - if !readProcBool(procFile) { - t.Fatal("expected proc bool to be true, got false") - } - - if err := ioutil.WriteFile(procFile, []byte("0"), 0644); err != nil { - t.Fatal(err) - } - if readProcBool(procFile) { - t.Fatal("expected proc bool to be false, got true") - } - - if readProcBool(path.Join(tmpDir, "no-exist")) { - t.Fatal("should be false for non-existent entry") - } - -} - -func TestCgroupEnabled(t *testing.T) { - cgroupDir, err := ioutil.TempDir("", "cgroup-test") - require.NoError(t, err) - defer os.RemoveAll(cgroupDir) - - if cgroupEnabled(cgroupDir, "test") { - t.Fatal("cgroupEnabled should be false") - } - - err = ioutil.WriteFile(path.Join(cgroupDir, "test"), []byte{}, 0644) - require.NoError(t, err) - - if !cgroupEnabled(cgroupDir, "test") { - t.Fatal("cgroupEnabled should be true") - } -} - -func TestNew(t *testing.T) { - sysInfo := New(false) - require.NotNil(t, sysInfo) - checkSysInfo(t, sysInfo) - - sysInfo = New(true) - require.NotNil(t, sysInfo) - checkSysInfo(t, sysInfo) -} - -func checkSysInfo(t *testing.T, sysInfo *SysInfo) { - // Check if Seccomp is supported, via CONFIG_SECCOMP.then sysInfo.Seccomp must be TRUE , else FALSE - if err := unix.Prctl(unix.PR_GET_SECCOMP, 0, 0, 0, 0); err != unix.EINVAL { - // Make sure the kernel has CONFIG_SECCOMP_FILTER. - if err := unix.Prctl(unix.PR_SET_SECCOMP, unix.SECCOMP_MODE_FILTER, 0, 0, 0); err != unix.EINVAL { - require.True(t, sysInfo.Seccomp) - } - } else { - require.False(t, sysInfo.Seccomp) - } -} - -func TestNewAppArmorEnabled(t *testing.T) { - // Check if AppArmor is supported. then it must be TRUE , else FALSE - if _, err := os.Stat("/sys/kernel/security/apparmor"); err != nil { - t.Skip("App Armor Must be Enabled") - } - - sysInfo := New(true) - require.True(t, sysInfo.AppArmor) -} - -func TestNewAppArmorDisabled(t *testing.T) { - // Check if AppArmor is supported. then it must be TRUE , else FALSE - if _, err := os.Stat("/sys/kernel/security/apparmor"); !os.IsNotExist(err) { - t.Skip("App Armor Must be Disabled") - } - - sysInfo := New(true) - require.False(t, sysInfo.AppArmor) -} - -func TestNumCPU(t *testing.T) { - cpuNumbers := NumCPU() - if cpuNumbers <= 0 { - t.Fatal("CPU returned must be greater than zero") - } -} diff --git a/pkg/sysinfo/sysinfo_solaris.go b/pkg/sysinfo/sysinfo_solaris.go deleted file mode 100644 index 7463cdd8f..000000000 --- a/pkg/sysinfo/sysinfo_solaris.go +++ /dev/null @@ -1,122 +0,0 @@ -// +build solaris,cgo - -package sysinfo - -import ( - "bytes" - "os/exec" - "strconv" - "strings" -) - -/* -#cgo LDFLAGS: -llgrp -#cgo CFLAGS: -Wall -Werror -#include <unistd.h> -#include <stdlib.h> -#include <sys/lgrp_user.h> -int getLgrpCount() { - lgrp_cookie_t lgrpcookie = LGRP_COOKIE_NONE; - uint_t nlgrps; - - if ((lgrpcookie = lgrp_init(LGRP_VIEW_OS)) == LGRP_COOKIE_NONE) { - return -1; - } - nlgrps = lgrp_nlgrps(lgrpcookie); - return nlgrps; -} -*/ -import "C" - -// IsCPUSharesAvailable returns whether CPUShares setting is supported. -// We need FSS to be set as default scheduling class to support CPU Shares -func IsCPUSharesAvailable() bool { - cmd := exec.Command("/usr/sbin/dispadmin", "-d") - outBuf := new(bytes.Buffer) - errBuf := new(bytes.Buffer) - cmd.Stderr = errBuf - cmd.Stdout = outBuf - - if err := cmd.Run(); err != nil { - return false - } - return (strings.Contains(outBuf.String(), "FSS")) -} - -// New returns a new SysInfo, using the filesystem to detect which features -// the kernel supports. -//NOTE Solaris: If we change the below capabilities be sure -// to update verifyPlatformContainerSettings() in daemon_solaris.go -func New(quiet bool) *SysInfo { - sysInfo := &SysInfo{} - sysInfo.cgroupMemInfo = setCgroupMem(quiet) - sysInfo.cgroupCPUInfo = setCgroupCPU(quiet) - sysInfo.cgroupBlkioInfo = setCgroupBlkioInfo(quiet) - sysInfo.cgroupCpusetInfo = setCgroupCPUsetInfo(quiet) - - sysInfo.IPv4ForwardingDisabled = false - - sysInfo.AppArmor = false - - return sysInfo -} - -// setCgroupMem reads the memory information for Solaris. -func setCgroupMem(quiet bool) cgroupMemInfo { - - return cgroupMemInfo{ - MemoryLimit: true, - SwapLimit: true, - MemoryReservation: false, - OomKillDisable: false, - MemorySwappiness: false, - KernelMemory: false, - } -} - -// setCgroupCPU reads the cpu information for Solaris. -func setCgroupCPU(quiet bool) cgroupCPUInfo { - - return cgroupCPUInfo{ - CPUShares: true, - CPUCfsPeriod: false, - CPUCfsQuota: true, - CPURealtimePeriod: false, - CPURealtimeRuntime: false, - } -} - -// blkio switches are not supported in Solaris. -func setCgroupBlkioInfo(quiet bool) cgroupBlkioInfo { - - return cgroupBlkioInfo{ - BlkioWeight: false, - BlkioWeightDevice: false, - } -} - -// setCgroupCPUsetInfo reads the cpuset information for Solaris. -func setCgroupCPUsetInfo(quiet bool) cgroupCpusetInfo { - - return cgroupCpusetInfo{ - Cpuset: true, - Cpus: getCPUCount(), - Mems: getLgrpCount(), - } -} - -func getCPUCount() string { - ncpus := C.sysconf(C._SC_NPROCESSORS_ONLN) - if ncpus <= 0 { - return "" - } - return strconv.FormatInt(int64(ncpus), 16) -} - -func getLgrpCount() string { - nlgrps := C.getLgrpCount() - if nlgrps <= 0 { - return "" - } - return strconv.FormatInt(int64(nlgrps), 16) -} diff --git a/pkg/sysinfo/sysinfo_test.go b/pkg/sysinfo/sysinfo_test.go deleted file mode 100644 index 895828f26..000000000 --- a/pkg/sysinfo/sysinfo_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package sysinfo - -import "testing" - -func TestIsCpusetListAvailable(t *testing.T) { - cases := []struct { - provided string - available string - res bool - err bool - }{ - {"1", "0-4", true, false}, - {"01,3", "0-4", true, false}, - {"", "0-7", true, false}, - {"1--42", "0-7", false, true}, - {"1-42", "00-1,8,,9", false, true}, - {"1,41-42", "43,45", false, false}, - {"0-3", "", false, false}, - } - for _, c := range cases { - r, err := isCpusetListAvailable(c.provided, c.available) - if (c.err && err == nil) && r != c.res { - t.Fatalf("Expected pair: %v, %v for %s, %s. Got %v, %v instead", c.res, c.err, c.provided, c.available, c.err && err == nil, r) - } - } -} diff --git a/pkg/sysinfo/sysinfo_unix.go b/pkg/sysinfo/sysinfo_unix.go deleted file mode 100644 index 45f3ef1c6..000000000 --- a/pkg/sysinfo/sysinfo_unix.go +++ /dev/null @@ -1,9 +0,0 @@ -// +build !linux,!solaris,!windows - -package sysinfo - -// New returns an empty SysInfo for non linux nor solaris for now. -func New(quiet bool) *SysInfo { - sysInfo := &SysInfo{} - return sysInfo -} diff --git a/pkg/sysinfo/sysinfo_windows.go b/pkg/sysinfo/sysinfo_windows.go deleted file mode 100644 index 4e6255bc5..000000000 --- a/pkg/sysinfo/sysinfo_windows.go +++ /dev/null @@ -1,9 +0,0 @@ -// +build windows - -package sysinfo - -// New returns an empty SysInfo for windows for now. -func New(quiet bool) *SysInfo { - sysInfo := &SysInfo{} - return sysInfo -} diff --git a/pkg/systemd/activation.go b/pkg/systemd/activation.go index c8b2389dc..8f75f9cca 100644 --- a/pkg/systemd/activation.go +++ b/pkg/systemd/activation.go @@ -3,38 +3,33 @@ package systemd import ( "os" "strconv" - "strings" ) // SocketActivated determine if podman is running under the socket activation protocol +// Criteria is based on the expectations of "github.com/coreos/go-systemd/v22/activation" func SocketActivated() bool { - pid, pid_found := os.LookupEnv("LISTEN_PID") - fds, fds_found := os.LookupEnv("LISTEN_FDS") - fdnames, fdnames_found := os.LookupEnv("LISTEN_FDNAMES") - - if !(pid_found && fds_found && fdnames_found) { + pid, found := os.LookupEnv("LISTEN_PID") + if !found { return false } - p, err := strconv.Atoi(pid) if err != nil || p != os.Getpid() { return false } + fds, found := os.LookupEnv("LISTEN_FDS") + if !found { + return false + } nfds, err := strconv.Atoi(fds) - if err != nil || nfds < 1 { + if err != nil || nfds == 0 { return false } - // First available file descriptor is always 3. - if nfds > 1 { - names := strings.Split(fdnames, ":") - for _, n := range names { - if strings.Contains(n, "podman") { - return true - } - } + // "github.com/coreos/go-systemd/v22/activation" will use and validate this variable's + // value. We're just providing a fast fail + if _, found = os.LookupEnv("LISTEN_FDNAMES"); !found { + return false } - return true } diff --git a/pkg/util/utils.go b/pkg/util/utils.go index babf7dfc9..917f57742 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -13,6 +13,7 @@ import ( "time" "github.com/BurntSushi/toml" + "github.com/containers/common/pkg/config" "github.com/containers/image/v5/types" "github.com/containers/libpod/pkg/errorhandling" "github.com/containers/libpod/pkg/namespaces" @@ -21,12 +22,22 @@ import ( "github.com/containers/storage" "github.com/containers/storage/pkg/idtools" v1 "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/opencontainers/selinux/go-selinux" "github.com/pkg/errors" "github.com/sirupsen/logrus" "golang.org/x/crypto/ssh/terminal" ) +var containerConfig *config.Config + +func init() { + var err error + containerConfig, err = config.Default() + if err != nil { + logrus.Error(err) + os.Exit(1) + } +} + // Helper function to determine the username/password passed // in the creds string. It could be either or both. func parseCreds(creds string) (string, string) { @@ -319,6 +330,58 @@ func ParseSignal(rawSignal string) (syscall.Signal, error) { return sig, nil } +// GetKeepIDMapping returns the mappings and the user to use when keep-id is used +func GetKeepIDMapping() (*storage.IDMappingOptions, int, int, error) { + options := storage.IDMappingOptions{ + HostUIDMapping: true, + HostGIDMapping: true, + } + uid, gid := 0, 0 + if rootless.IsRootless() { + min := func(a, b int) int { + if a < b { + return a + } + return b + } + + uid = rootless.GetRootlessUID() + gid = rootless.GetRootlessGID() + + uids, gids, err := rootless.GetConfiguredMappings() + if err != nil { + return nil, -1, -1, errors.Wrapf(err, "cannot read mappings") + } + maxUID, maxGID := 0, 0 + for _, u := range uids { + maxUID += u.Size + } + for _, g := range gids { + maxGID += g.Size + } + + options.UIDMap, options.GIDMap = nil, nil + + options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: 0, HostID: 1, Size: min(uid, maxUID)}) + options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid, HostID: 0, Size: 1}) + if maxUID > uid { + options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid + 1, HostID: uid + 1, Size: maxUID - uid}) + } + + options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: 0, HostID: 1, Size: min(gid, maxGID)}) + options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid, HostID: 0, Size: 1}) + if maxGID > gid { + options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid + 1, HostID: gid + 1, Size: maxGID - gid}) + } + + options.HostUIDMapping = false + options.HostGIDMapping = false + + } + // Simply ignore the setting and do not setup an inner namespace for root as it is a no-op + return &options, uid, gid, nil +} + // ParseIDMapping takes idmappings and subuid and subgid maps and returns a storage mapping func ParseIDMapping(mode namespaces.UsernsMode, uidMapSlice, gidMapSlice []string, subUIDMap, subGIDMap string) (*storage.IDMappingOptions, error) { options := storage.IDMappingOptions{ @@ -339,53 +402,8 @@ func ParseIDMapping(mode namespaces.UsernsMode, uidMapSlice, gidMapSlice []strin return &options, nil } if mode.IsKeepID() { - if len(uidMapSlice) > 0 || len(gidMapSlice) > 0 { - return nil, errors.New("cannot specify custom mappings with --userns=keep-id") - } - if len(subUIDMap) > 0 || len(subGIDMap) > 0 { - return nil, errors.New("cannot specify subuidmap or subgidmap with --userns=keep-id") - } - if rootless.IsRootless() { - min := func(a, b int) int { - if a < b { - return a - } - return b - } - - uid := rootless.GetRootlessUID() - gid := rootless.GetRootlessGID() - - uids, gids, err := rootless.GetConfiguredMappings() - if err != nil { - return nil, errors.Wrapf(err, "cannot read mappings") - } - maxUID, maxGID := 0, 0 - for _, u := range uids { - maxUID += u.Size - } - for _, g := range gids { - maxGID += g.Size - } - - options.UIDMap, options.GIDMap = nil, nil - - options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: 0, HostID: 1, Size: min(uid, maxUID)}) - options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid, HostID: 0, Size: 1}) - if maxUID > uid { - options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid + 1, HostID: uid + 1, Size: maxUID - uid}) - } - - options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: 0, HostID: 1, Size: min(gid, maxGID)}) - options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid, HostID: 0, Size: 1}) - if maxGID > gid { - options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid + 1, HostID: gid + 1, Size: maxGID - gid}) - } - - options.HostUIDMapping = false - options.HostGIDMapping = false - } - // Simply ignore the setting and do not setup an inner namespace for root as it is a no-op + options.HostUIDMapping = false + options.HostGIDMapping = false return &options, nil } @@ -635,37 +653,6 @@ func ValidateSysctls(strSlice []string) (map[string]string, error) { return sysctl, nil } -// SELinuxKVMLabel returns labels for running kvm isolated containers -func SELinuxKVMLabel(cLabel string) (string, error) { - if cLabel == "" { - // selinux is disabled - return "", nil - } - processLabel, _ := selinux.KVMContainerLabels() - selinux.ReleaseLabel(processLabel) - return swapSELinuxLabel(cLabel, processLabel) -} - -// SELinuxInitLabel returns labels for running systemd based containers -func SELinuxInitLabel(cLabel string) (string, error) { - if cLabel == "" { - // selinux is disabled - return "", nil - } - processLabel, _ := selinux.InitContainerLabels() - selinux.ReleaseLabel(processLabel) - return swapSELinuxLabel(cLabel, processLabel) -} - -func swapSELinuxLabel(cLabel, processLabel string) (string, error) { - dcon, err := selinux.NewContext(cLabel) - if err != nil { - return "", err - } - scon, err := selinux.NewContext(processLabel) - if err != nil { - return "", err - } - dcon["type"] = scon["type"] - return dcon.Get(), nil +func DefaultContainerConfig() *config.Config { + return containerConfig } diff --git a/pkg/varlinkapi/create.go b/pkg/varlinkapi/create.go index 63d5072c6..571ce6115 100644 --- a/pkg/varlinkapi/create.go +++ b/pkg/varlinkapi/create.go @@ -13,6 +13,7 @@ import ( "syscall" "time" + "github.com/containers/common/pkg/sysinfo" "github.com/containers/image/v5/manifest" "github.com/containers/libpod/cmd/podman/parse" "github.com/containers/libpod/libpod" @@ -28,7 +29,6 @@ import ( "github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/pkg/seccomp" cc "github.com/containers/libpod/pkg/spec" - "github.com/containers/libpod/pkg/sysinfo" systemdGen "github.com/containers/libpod/pkg/systemd/generate" "github.com/containers/libpod/pkg/util" "github.com/docker/go-connections/nat" diff --git a/pkg/varlinkapi/intermediate_varlink.go b/pkg/varlinkapi/intermediate_varlink.go index 21c57d4f4..bd0c45b33 100644 --- a/pkg/varlinkapi/intermediate_varlink.go +++ b/pkg/varlinkapi/intermediate_varlink.go @@ -331,7 +331,7 @@ func intFromVarlink(v *int64, flagName string, defaultValue *int) CRInt { // structure. func VarlinkCreateToGeneric(opts iopodman.Create) GenericCLIResults { // FIXME this will need to be fixed!!!!! With containers conf - //defaultContainerConfig := cliconfig.GetDefaultConfig() + //containerConfig := cliconfig.GetDefaultConfig() // TODO | WARN // We do not get a default network over varlink. Unlike the other default values for some cli // elements, it seems it gets set to the default anyway. |