summaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
Diffstat (limited to 'pkg')
-rw-r--r--pkg/api/handlers/compat/containers_create.go8
-rw-r--r--pkg/api/handlers/compat/events.go4
-rw-r--r--pkg/api/handlers/compat/info.go2
-rw-r--r--pkg/api/handlers/libpod/containers_create.go3
-rw-r--r--pkg/api/handlers/libpod/images.go32
-rw-r--r--pkg/api/handlers/libpod/pods.go42
-rw-r--r--pkg/api/handlers/swagger/swagger.go16
-rw-r--r--pkg/api/handlers/types.go60
-rw-r--r--pkg/api/handlers/utils/errors.go3
-rw-r--r--pkg/api/server/register_images.go32
-rw-r--r--pkg/api/server/register_pods.go29
-rw-r--r--pkg/api/server/server.go44
-rw-r--r--pkg/api/types/types.go9
-rw-r--r--pkg/apparmor/apparmor.go19
-rw-r--r--pkg/apparmor/apparmor_linux.go289
-rw-r--r--pkg/apparmor/apparmor_linux_template.go49
-rw-r--r--pkg/apparmor/apparmor_linux_test.go140
-rw-r--r--pkg/apparmor/apparmor_unsupported.go31
-rw-r--r--pkg/bindings/connection.go6
-rw-r--r--pkg/bindings/images/images.go29
-rw-r--r--pkg/bindings/pods/pods.go29
-rw-r--r--pkg/bindings/system/system.go5
-rw-r--r--pkg/bindings/test/common_test.go4
-rw-r--r--pkg/bindings/test/containers_test.go4
-rw-r--r--pkg/bindings/test/create_test.go2
-rw-r--r--pkg/bindings/test/info_test.go2
-rw-r--r--pkg/bindings/test/pods_test.go29
-rw-r--r--pkg/domain/entities/container_ps.go30
-rw-r--r--pkg/domain/entities/containers.go25
-rw-r--r--pkg/domain/entities/engine_container.go9
-rw-r--r--pkg/domain/entities/engine_image.go7
-rw-r--r--pkg/domain/entities/events.go61
-rw-r--r--pkg/domain/entities/images.go37
-rw-r--r--pkg/domain/entities/manifest.go16
-rw-r--r--pkg/domain/entities/pods.go50
-rw-r--r--pkg/domain/entities/types.go23
-rw-r--r--pkg/domain/infra/abi/containers.go66
-rw-r--r--pkg/domain/infra/abi/cp.go433
-rw-r--r--pkg/domain/infra/abi/events.go2
-rw-r--r--pkg/domain/infra/abi/healthcheck.go2
-rw-r--r--pkg/domain/infra/abi/images.go256
-rw-r--r--pkg/domain/infra/abi/images_list.go2
-rw-r--r--pkg/domain/infra/abi/manifest.go102
-rw-r--r--pkg/domain/infra/abi/pods.go27
-rw-r--r--pkg/domain/infra/abi/pods_stats.go85
-rw-r--r--pkg/domain/infra/abi/runtime.go6
-rw-r--r--pkg/domain/infra/abi/system.go38
-rw-r--r--pkg/domain/infra/abi/terminal/sigproxy_linux.go2
-rw-r--r--pkg/domain/infra/abi/terminal/terminal.go2
-rw-r--r--pkg/domain/infra/abi/terminal/terminal_linux.go2
-rw-r--r--pkg/domain/infra/runtime_libpod.go36
-rw-r--r--pkg/domain/infra/tunnel/containers.go16
-rw-r--r--pkg/domain/infra/tunnel/events.go5
-rw-r--r--pkg/domain/infra/tunnel/images.go39
-rw-r--r--pkg/domain/infra/tunnel/manifest.go64
-rw-r--r--pkg/domain/infra/tunnel/pods.go4
-rw-r--r--pkg/domain/infra/tunnel/system.go4
-rw-r--r--pkg/namespaces/namespaces.go7
-rw-r--r--pkg/ps/ps.go1
-rw-r--r--pkg/rootless/rootless_linux.c45
-rw-r--r--pkg/rootless/rootless_linux.go113
-rw-r--r--pkg/selinux/selinux.go40
-rw-r--r--pkg/spec/spec.go2
-rw-r--r--pkg/spec/spec_test.go2
-rw-r--r--pkg/specgen/container_validate.go15
-rw-r--r--pkg/specgen/generate/config_linux_cgo.go3
-rw-r--r--pkg/specgen/generate/container.go106
-rw-r--r--pkg/specgen/generate/container_create.go106
-rw-r--r--pkg/specgen/generate/namespaces.go619
-rw-r--r--pkg/specgen/generate/oci.go124
-rw-r--r--pkg/specgen/generate/pod_create.go7
-rw-r--r--pkg/specgen/generate/security.go110
-rw-r--r--pkg/specgen/generate/storage.go961
-rw-r--r--pkg/specgen/namespaces.go155
-rw-r--r--pkg/specgen/pod_validate.go10
-rw-r--r--pkg/specgen/specgen.go76
-rw-r--r--pkg/sysinfo/README.md1
-rw-r--r--pkg/sysinfo/numcpu.go12
-rw-r--r--pkg/sysinfo/numcpu_linux.go44
-rw-r--r--pkg/sysinfo/numcpu_windows.go37
-rw-r--r--pkg/sysinfo/sysinfo.go153
-rw-r--r--pkg/sysinfo/sysinfo_linux.go261
-rw-r--r--pkg/sysinfo/sysinfo_linux_test.go104
-rw-r--r--pkg/sysinfo/sysinfo_solaris.go122
-rw-r--r--pkg/sysinfo/sysinfo_test.go26
-rw-r--r--pkg/sysinfo/sysinfo_unix.go9
-rw-r--r--pkg/sysinfo/sysinfo_windows.go9
-rw-r--r--pkg/systemd/activation.go29
-rw-r--r--pkg/util/utils.go149
-rw-r--r--pkg/varlinkapi/create.go2
-rw-r--r--pkg/varlinkapi/intermediate_varlink.go2
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.