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/containers_prune.go35
-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.go34
-rw-r--r--pkg/api/handlers/libpod/pods.go65
-rw-r--r--pkg/api/handlers/libpod/swagger.go7
-rw-r--r--pkg/api/handlers/libpod/system.go71
-rw-r--r--pkg/api/handlers/libpod/volumes.go15
-rw-r--r--pkg/api/handlers/swagger/swagger.go16
-rw-r--r--pkg/api/handlers/types.go62
-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.go35
-rw-r--r--pkg/api/server/register_system.go17
-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.go54
-rw-r--r--pkg/bindings/pods/pods.go41
-rw-r--r--pkg/bindings/system/system.go30
-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.go39
-rw-r--r--pkg/bindings/test/system_test.go106
-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.go10
-rw-r--r--pkg/domain/entities/events.go61
-rw-r--r--pkg/domain/entities/generate.go22
-rw-r--r--pkg/domain/entities/images.go62
-rw-r--r--pkg/domain/entities/manifest.go16
-rw-r--r--pkg/domain/entities/pods.go59
-rw-r--r--pkg/domain/entities/system.go14
-rw-r--r--pkg/domain/entities/types.go45
-rw-r--r--pkg/domain/infra/abi/containers.go78
-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/generate.go174
-rw-r--r--pkg/domain/infra/abi/healthcheck.go2
-rw-r--r--pkg/domain/infra/abi/images.go304
-rw-r--r--pkg/domain/infra/abi/images_list.go13
-rw-r--r--pkg/domain/infra/abi/manifest.go102
-rw-r--r--pkg/domain/infra/abi/pods.go44
-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_abi.go4
-rw-r--r--pkg/domain/infra/runtime_image_proxy.go2
-rw-r--r--pkg/domain/infra/runtime_libpod.go50
-rw-r--r--pkg/domain/infra/runtime_proxy.go2
-rw-r--r--pkg/domain/infra/runtime_tunnel.go4
-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/generate.go12
-rw-r--r--pkg/domain/infra/tunnel/images.go57
-rw-r--r--pkg/domain/infra/tunnel/manifest.go64
-rw-r--r--pkg/domain/infra/tunnel/pods.go8
-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.c63
-rw-r--r--pkg/rootless/rootless_linux.go113
-rw-r--r--pkg/rootlessport/rootlessport_linux.go26
-rw-r--r--pkg/selinux/selinux.go40
-rw-r--r--pkg/spec/spec.go11
-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.go113
-rw-r--r--pkg/specgen/generate/container_create.go107
-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.go157
-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
106 files changed, 3478 insertions, 3193 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/containers_prune.go b/pkg/api/handlers/compat/containers_prune.go
index b4e98ac1f..9d77f612b 100644
--- a/pkg/api/handlers/compat/containers_prune.go
+++ b/pkg/api/handlers/compat/containers_prune.go
@@ -38,21 +38,24 @@ func PruneContainers(w http.ResponseWriter, r *http.Request) {
filterFuncs = append(filterFuncs, generatedFunc)
}
}
- prunedContainers, pruneErrors, err := runtime.PruneContainers(filterFuncs)
- if err != nil {
- utils.InternalServerError(w, err)
- return
- }
// Libpod response differs
if utils.IsLibpodRequest(r) {
- report := &entities.ContainerPruneReport{
- Err: pruneErrors,
- ID: prunedContainers,
+ report, err := PruneContainersHelper(w, r, filterFuncs)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
}
+
utils.WriteResponse(w, http.StatusOK, report)
return
}
+
+ prunedContainers, pruneErrors, err := runtime.PruneContainers(filterFuncs)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
for ctrID, size := range prunedContainers {
if pruneErrors[ctrID] == nil {
space += size
@@ -65,3 +68,19 @@ func PruneContainers(w http.ResponseWriter, r *http.Request) {
}
utils.WriteResponse(w, http.StatusOK, report)
}
+
+func PruneContainersHelper(w http.ResponseWriter, r *http.Request, filterFuncs []libpod.ContainerFilter) (
+ *entities.ContainerPruneReport, error) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ prunedContainers, pruneErrors, err := runtime.PruneContainers(filterFuncs)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return nil, err
+ }
+
+ report := &entities.ContainerPruneReport{
+ Err: pruneErrors,
+ ID: prunedContainers,
+ }
+ return report, nil
+}
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 a42d06205..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"
@@ -111,7 +112,7 @@ func GetImages(w http.ResponseWriter, r *http.Request) {
return
}
// libpod has additional fields that we need to populate.
- is.Created = img.Created().Unix()
+ is.Created = img.Created()
is.ReadOnly = img.IsReadOnly()
summaries[j] = is
}
@@ -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 92556bb61..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"
@@ -230,15 +231,30 @@ func PodRestart(w http.ResponseWriter, r *http.Request) {
}
func PodPrune(w http.ResponseWriter, r *http.Request) {
+ reports, err := PodPruneHelper(w, r)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, reports)
+}
+
+func PodPruneHelper(w http.ResponseWriter, r *http.Request) ([]*entities.PodPruneReport, error) {
var (
runtime = r.Context().Value("runtime").(*libpod.Runtime)
+ reports []*entities.PodPruneReport
)
- pruned, err := runtime.PrunePods()
+ responses, err := runtime.PrunePods(r.Context())
if err != nil {
- utils.InternalServerError(w, err)
- return
+ return nil, err
}
- utils.WriteResponse(w, http.StatusOK, pruned)
+ for k, v := range responses {
+ reports = append(reports, &entities.PodPruneReport{
+ Err: v,
+ Id: k,
+ })
+ }
+ return reports, nil
}
func PodPause(w http.ResponseWriter, r *http.Request) {
@@ -412,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/libpod/swagger.go b/pkg/api/handlers/libpod/swagger.go
index ed19462c6..46426eb6b 100644
--- a/pkg/api/handlers/libpod/swagger.go
+++ b/pkg/api/handlers/libpod/swagger.go
@@ -70,6 +70,13 @@ type swagStartPodResponse struct {
Body entities.PodStartReport
}
+// Prune pod
+// swagger:response PodPruneReport
+type swagPrunePodResponse struct {
+ // in:body
+ Body entities.PodPruneReport
+}
+
// Rm pod
// swagger:response PodRmReport
type swagRmPodResponse struct {
diff --git a/pkg/api/handlers/libpod/system.go b/pkg/api/handlers/libpod/system.go
new file mode 100644
index 000000000..98e33bf10
--- /dev/null
+++ b/pkg/api/handlers/libpod/system.go
@@ -0,0 +1,71 @@
+package libpod
+
+import (
+ "net/http"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/api/handlers/compat"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+)
+
+// SystemPrune removes unused data
+func SystemPrune(w http.ResponseWriter, r *http.Request) {
+ var (
+ decoder = r.Context().Value("decoder").(*schema.Decoder)
+ runtime = r.Context().Value("runtime").(*libpod.Runtime)
+ systemPruneReport = new(entities.SystemPruneReport)
+ )
+ query := struct {
+ All bool `schema:"all"`
+ Volumes bool `schema:"volumes"`
+ }{}
+
+ 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
+ }
+
+ podPruneReport, err := PodPruneHelper(w, r)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ systemPruneReport.PodPruneReport = podPruneReport
+
+ // We could parallelize this, should we?
+ containerPruneReport, err := compat.PruneContainersHelper(w, r, nil)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ systemPruneReport.ContainerPruneReport = containerPruneReport
+
+ results, err := runtime.ImageRuntime().PruneImages(r.Context(), query.All, nil)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+
+ report := entities.ImagePruneReport{
+ Report: entities.Report{
+ Id: results,
+ Err: nil,
+ },
+ }
+
+ systemPruneReport.ImagePruneReport = &report
+
+ if query.Volumes {
+ volumePruneReport, err := pruneVolumesHelper(w, r)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ systemPruneReport.VolumePruneReport = volumePruneReport
+ }
+ utils.WriteResponse(w, http.StatusOK, systemPruneReport)
+}
diff --git a/pkg/api/handlers/libpod/volumes.go b/pkg/api/handlers/libpod/volumes.go
index 18c561a0d..c42ca407b 100644
--- a/pkg/api/handlers/libpod/volumes.go
+++ b/pkg/api/handlers/libpod/volumes.go
@@ -147,14 +147,22 @@ func ListVolumes(w http.ResponseWriter, r *http.Request) {
}
func PruneVolumes(w http.ResponseWriter, r *http.Request) {
+ reports, err := pruneVolumesHelper(w, r)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, reports)
+}
+
+func pruneVolumesHelper(w http.ResponseWriter, r *http.Request) ([]*entities.VolumePruneReport, error) {
var (
runtime = r.Context().Value("runtime").(*libpod.Runtime)
reports []*entities.VolumePruneReport
)
pruned, err := runtime.PruneVolumes(r.Context())
if err != nil {
- utils.InternalServerError(w, err)
- return
+ return nil, err
}
for k, v := range pruned {
reports = append(reports, &entities.VolumePruneReport{
@@ -162,9 +170,8 @@ func PruneVolumes(w http.ResponseWriter, r *http.Request) {
Id: k,
})
}
- utils.WriteResponse(w, http.StatusOK, reports)
+ return reports, nil
}
-
func RemoveVolume(w http.ResponseWriter, r *http.Request) {
var (
runtime = r.Context().Value("runtime").(*libpod.Runtime)
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 f402da064..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 {
@@ -262,7 +220,7 @@ func ImageToImageSummary(l *libpodImage.Image) (*entities.ImageSummary, error) {
ID: l.ID(),
ParentId: l.Parent,
RepoTags: repoTags,
- Created: l.Created().Unix(),
+ Created: l.Created(),
Size: int64(*size),
SharedSize: 0,
VirtualSize: l.VirtualSize,
@@ -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 a49bf1f63..4156dd86b 100644
--- a/pkg/api/server/register_pods.go
+++ b/pkg/api/server/register_pods.go
@@ -53,11 +53,7 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error {
// - application/json
// responses:
// 200:
- // description: tbd
- // schema:
- // type: object
- // additionalProperties:
- // type: string
+ // $ref: '#/responses/PodPruneReport'
// 400:
// $ref: "#/responses/BadParamError"
// 409:
@@ -290,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/register_system.go b/pkg/api/server/register_system.go
index 708ccd39b..7375a75c1 100644
--- a/pkg/api/server/register_system.go
+++ b/pkg/api/server/register_system.go
@@ -4,6 +4,7 @@ import (
"net/http"
"github.com/containers/libpod/pkg/api/handlers/compat"
+ "github.com/containers/libpod/pkg/api/handlers/libpod"
"github.com/gorilla/mux"
)
@@ -11,5 +12,21 @@ func (s *APIServer) registerSystemHandlers(r *mux.Router) error {
r.Handle(VersionedPath("/system/df"), s.APIHandler(compat.GetDiskUsage)).Methods(http.MethodGet)
// Added non version path to URI to support docker non versioned paths
r.Handle("/system/df", s.APIHandler(compat.GetDiskUsage)).Methods(http.MethodGet)
+ // Swagger:operation POST /libpod/system/prune libpod pruneSystem
+ // ---
+ // tags:
+ // - system
+ // summary: Prune unused data
+ // produces:
+ // - application/json
+ // responses:
+ // 200:
+ // $ref: '#/responses/SystemPruneReport'
+ // 400:
+ // $ref: "#/responses/BadParamError"
+ // 500:
+ // $ref: "#/responses/InternalError"
+ r.Handle(VersionedPath("/libpod/system/prune"), s.APIHandler(libpod.SystemPrune)).Methods(http.MethodPost)
+
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..4d8ae6a6e 100644
--- a/pkg/bindings/images/images.go
+++ b/pkg/bindings/images/images.go
@@ -57,7 +57,7 @@ func List(ctx context.Context, all *bool, filters map[string][]string) ([]*entit
// Get performs an image inspect. To have the on-disk size of the image calculated, you can
// use the optional size parameter.
-func GetImage(ctx context.Context, nameOrID string, size *bool) (*entities.ImageData, error) {
+func GetImage(ctx context.Context, nameOrID string, size *bool) (*entities.ImageInspectReport, error) {
conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
@@ -66,7 +66,7 @@ func GetImage(ctx context.Context, nameOrID string, size *bool) (*entities.Image
if size != nil {
params.Set("size", strconv.FormatBool(*size))
}
- inspectedData := entities.ImageData{}
+ inspectedData := entities.ImageInspectReport{}
response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/json", params, nameOrID)
if err != nil {
return &inspectedData, err
@@ -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
@@ -262,9 +273,10 @@ func Pull(ctx context.Context, rawImage string, options entities.ImagePullOption
params.Set("credentials", options.Credentials)
params.Set("overrideArch", options.OverrideArch)
params.Set("overrideOS", options.OverrideOS)
- if options.TLSVerify != types.OptionalBoolUndefined {
- val := bool(options.TLSVerify == types.OptionalBoolTrue)
- params.Set("tlsVerify", strconv.FormatBool(val))
+ if options.SkipTLSVerify != types.OptionalBoolUndefined {
+ // Note: we have to verify if skipped is false.
+ verifyTLS := bool(options.SkipTLSVerify == types.OptionalBoolFalse)
+ params.Set("tlsVerify", strconv.FormatBool(verifyTLS))
}
params.Set("allTags", strconv.FormatBool(options.AllTags))
@@ -299,9 +311,10 @@ func Push(ctx context.Context, source string, destination string, options entiti
params := url.Values{}
params.Set("credentials", options.Credentials)
params.Set("destination", destination)
- if options.TLSVerify != types.OptionalBoolUndefined {
- val := bool(options.TLSVerify == types.OptionalBoolTrue)
- params.Set("tlsVerify", strconv.FormatBool(val))
+ if options.SkipTLSVerify != types.OptionalBoolUndefined {
+ // Note: we have to verify if skipped is false.
+ verifyTLS := bool(options.SkipTLSVerify == types.OptionalBoolFalse)
+ params.Set("tlsVerify", strconv.FormatBool(verifyTLS))
}
path := fmt.Sprintf("/images/%s/push", source)
@@ -322,9 +335,10 @@ func Search(ctx context.Context, term string, opts entities.ImageSearchOptions)
params.Set("filters", f)
}
- if opts.TLSVerify != types.OptionalBoolUndefined {
- val := bool(opts.TLSVerify == types.OptionalBoolTrue)
- params.Set("tlsVerify", strconv.FormatBool(val))
+ if opts.SkipTLSVerify != types.OptionalBoolUndefined {
+ // Note: we have to verify if skipped is false.
+ verifyTLS := bool(opts.SkipTLSVerify == types.OptionalBoolFalse)
+ params.Set("tlsVerify", strconv.FormatBool(verifyTLS))
}
response, err := conn.DoRequest(nil, http.MethodGet, "/images/search", params)
diff --git a/pkg/bindings/pods/pods.go b/pkg/bindings/pods/pods.go
index 83847614a..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"
@@ -98,17 +99,19 @@ func Pause(ctx context.Context, nameOrID string) (*entities.PodPauseReport, erro
return &report, response.Process(&report)
}
-// Prune removes all non-running pods in local storage.
-func Prune(ctx context.Context) error {
+// Prune by default removes all non-running pods in local storage.
+// And with force set true removes all pods.
+func Prune(ctx context.Context) ([]*entities.PodPruneReport, error) {
+ var reports []*entities.PodPruneReport
conn, err := bindings.GetClient(ctx)
if err != nil {
- return err
+ return nil, err
}
response, err := conn.DoRequest(nil, http.MethodPost, "/pods/prune", nil)
if err != nil {
- return err
+ return nil, err
}
- return response.Process(nil)
+ return reports, response.Process(&reports)
}
// List returns all pods in local storage. The optional filters parameter can
@@ -187,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) {
@@ -262,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 fce8bbb8e..df6b529de 100644
--- a/pkg/bindings/system/system.go
+++ b/pkg/bindings/system/system.go
@@ -6,9 +6,10 @@ import (
"io"
"net/http"
"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"
"github.com/sirupsen/logrus"
)
@@ -16,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
@@ -48,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
@@ -59,3 +60,26 @@ func Events(ctx context.Context, eventChan chan (handlers.Event), cancelChan cha
}
return nil
}
+
+// Prune removes all unused system data.
+func Prune(ctx context.Context, all, volumes *bool) (*entities.SystemPruneReport, error) {
+ var (
+ report entities.SystemPruneReport
+ )
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+ params := url.Values{}
+ if all != nil {
+ params.Set("All", strconv.FormatBool(*all))
+ }
+ if volumes != nil {
+ params.Set("Volumes", strconv.FormatBool(*volumes))
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/system/prune", params)
+ if err != nil {
+ return nil, err
+ }
+ return &report, response.Process(&report)
+}
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 579161b26..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))
@@ -262,7 +257,7 @@ var _ = Describe("Podman pods", func() {
var newpod2 string = "newpod2"
bt.Podcreate(&newpod2)
// No pods pruned since no pod in exited state
- err = pods.Prune(bt.conn)
+ pruneResponse, err := pods.Prune(bt.conn)
Expect(err).To(BeNil())
podSummary, err := pods.List(bt.conn, nil)
Expect(err).To(BeNil())
@@ -277,15 +272,21 @@ 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))
- err = pods.Prune(bt.conn)
+ 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).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())
Expect(len(podSummary)).To(Equal(1))
- // Test prune all pods in exited state.
+ // Test prune multiple pods.
bt.Podcreate(&newpod)
_, err = pods.Start(bt.conn, newpod)
Expect(err).To(BeNil())
@@ -295,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))
@@ -305,13 +305,12 @@ 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))
}
- err = pods.Prune(bt.conn)
+ _, err = pods.Prune(bt.conn)
Expect(err).To(BeNil())
podSummary, err = pods.List(bt.conn, nil)
Expect(err).To(BeNil())
diff --git a/pkg/bindings/test/system_test.go b/pkg/bindings/test/system_test.go
index 3abc26b34..87e6d56dc 100644
--- a/pkg/bindings/test/system_test.go
+++ b/pkg/bindings/test/system_test.go
@@ -4,7 +4,12 @@ import (
"time"
"github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/bindings"
+ "github.com/containers/libpod/pkg/bindings/containers"
+ "github.com/containers/libpod/pkg/bindings/pods"
"github.com/containers/libpod/pkg/bindings/system"
+ "github.com/containers/libpod/pkg/bindings/volumes"
+ "github.com/containers/libpod/pkg/domain/entities"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gexec"
@@ -12,13 +17,16 @@ import (
var _ = Describe("Podman system", func() {
var (
- bt *bindingTest
- s *gexec.Session
+ bt *bindingTest
+ s *gexec.Session
+ newpod string
)
BeforeEach(func() {
bt = newBindingTest()
bt.RestoreImagesFromCache()
+ newpod = "newpod"
+ bt.Podcreate(&newpod)
s = bt.startAPIService()
time.Sleep(1 * time.Second)
err := bt.NewConnection()
@@ -48,4 +56,98 @@ var _ = Describe("Podman system", func() {
cancelChan <- true
Expect(len(messages)).To(BeNumerically("==", 3))
})
+
+ It("podman system prune - pod,container stopped", func() {
+ // Start and stop a pod to enter in exited state.
+ _, err := pods.Start(bt.conn, newpod)
+ Expect(err).To(BeNil())
+ _, err = pods.Stop(bt.conn, newpod, nil)
+ Expect(err).To(BeNil())
+ // Start and stop a container to enter in exited state.
+ var name = "top"
+ _, err = bt.RunTopContainer(&name, &bindings.PFalse, nil)
+ Expect(err).To(BeNil())
+ err = containers.Stop(bt.conn, name, nil)
+ Expect(err).To(BeNil())
+
+ systemPruneResponse, err := system.Prune(bt.conn, &bindings.PTrue, &bindings.PFalse)
+ Expect(err).To(BeNil())
+ Expect(len(systemPruneResponse.PodPruneReport)).To(Equal(1))
+ Expect(len(systemPruneResponse.ContainerPruneReport.ID)).To(Equal(1))
+ Expect(len(systemPruneResponse.ImagePruneReport.Report.Id)).
+ To(BeNumerically(">", 0))
+ Expect(systemPruneResponse.ImagePruneReport.Report.Id).
+ To(ContainElement("docker.io/library/alpine:latest"))
+ Expect(len(systemPruneResponse.VolumePruneReport)).To(Equal(0))
+ })
+
+ It("podman system prune running alpine container", func() {
+ // Start and stop a pod to enter in exited state.
+ _, err := pods.Start(bt.conn, newpod)
+ Expect(err).To(BeNil())
+ _, err = pods.Stop(bt.conn, newpod, nil)
+ Expect(err).To(BeNil())
+
+ // Start and stop a container to enter in exited state.
+ var name = "top"
+ _, err = bt.RunTopContainer(&name, &bindings.PFalse, nil)
+ Expect(err).To(BeNil())
+ err = containers.Stop(bt.conn, name, nil)
+ Expect(err).To(BeNil())
+
+ // Start container and leave in running
+ var name2 = "top2"
+ _, err = bt.RunTopContainer(&name2, &bindings.PFalse, nil)
+ Expect(err).To(BeNil())
+
+ // Adding an unused volume
+ _, err = volumes.Create(bt.conn, entities.VolumeCreateOptions{})
+ Expect(err).To(BeNil())
+
+ systemPruneResponse, err := system.Prune(bt.conn, &bindings.PTrue, &bindings.PFalse)
+ Expect(err).To(BeNil())
+ Expect(len(systemPruneResponse.PodPruneReport)).To(Equal(1))
+ Expect(len(systemPruneResponse.ContainerPruneReport.ID)).To(Equal(1))
+ Expect(len(systemPruneResponse.ImagePruneReport.Report.Id)).
+ To(BeNumerically(">", 0))
+ // Alpine image should not be pruned as used by running container
+ Expect(systemPruneResponse.ImagePruneReport.Report.Id).
+ ToNot(ContainElement("docker.io/library/alpine:latest"))
+ // Though unsed volume is available it should not be pruned as flag set to false.
+ Expect(len(systemPruneResponse.VolumePruneReport)).To(Equal(0))
+ })
+
+ It("podman system prune running alpine container volume prune", func() {
+ // Start a pod and leave it running
+ _, err := pods.Start(bt.conn, newpod)
+ Expect(err).To(BeNil())
+
+ // Start and stop a container to enter in exited state.
+ var name = "top"
+ _, err = bt.RunTopContainer(&name, &bindings.PFalse, nil)
+ Expect(err).To(BeNil())
+ err = containers.Stop(bt.conn, name, nil)
+ Expect(err).To(BeNil())
+
+ // Start second container and leave in running
+ var name2 = "top2"
+ _, err = bt.RunTopContainer(&name2, &bindings.PFalse, nil)
+ Expect(err).To(BeNil())
+
+ // Adding an unused volume should work
+ _, err = volumes.Create(bt.conn, entities.VolumeCreateOptions{})
+ Expect(err).To(BeNil())
+
+ systemPruneResponse, err := system.Prune(bt.conn, &bindings.PTrue, &bindings.PTrue)
+ Expect(err).To(BeNil())
+ Expect(len(systemPruneResponse.PodPruneReport)).To(Equal(0))
+ Expect(len(systemPruneResponse.ContainerPruneReport.ID)).To(Equal(1))
+ Expect(len(systemPruneResponse.ImagePruneReport.Report.Id)).
+ To(BeNumerically(">", 0))
+ // Alpine image should not be pruned as used by running container
+ Expect(systemPruneResponse.ImagePruneReport.Report.Id).
+ ToNot(ContainElement("docker.io/library/alpine:latest"))
+ // Volume should be pruned now as flag set true
+ Expect(len(systemPruneResponse.VolumePruneReport)).To(Equal(1))
+ })
})
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 02938413a..eebf4c033 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)
@@ -39,6 +41,7 @@ type ContainerEngine interface {
ContainerUnpause(ctx context.Context, namesOrIds []string, options PauseUnPauseOptions) ([]*PauseUnpauseReport, error)
ContainerWait(ctx context.Context, namesOrIds []string, options WaitOptions) ([]WaitReport, error)
Events(ctx context.Context, opts EventsOptions) error
+ GenerateSystemd(ctx context.Context, nameOrID string, opts GenerateSystemdOptions) (*GenerateSystemdReport, error)
HealthCheckRun(ctx context.Context, nameOrId string, options HealthCheckOptions) (*define.HealthCheckResults, error)
Info(ctx context.Context) (*define.Info, error)
PodCreate(ctx context.Context, opts PodCreateOptions) (*PodCreateReport, error)
@@ -46,15 +49,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)
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 e3b606550..46a96ca20 100644
--- a/pkg/domain/entities/engine_image.go
+++ b/pkg/domain/entities/engine_image.go
@@ -7,20 +7,26 @@ 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)
Import(ctx context.Context, opts ImageImportOptions) (*ImageImportReport, error)
- Inspect(ctx context.Context, names []string, opts InspectOptions) (*ImageInspectReport, error)
+ Inspect(ctx context.Context, namesOrIDs []string, opts InspectOptions) ([]*ImageInspectReport, error)
List(ctx context.Context, opts ImageListOptions) ([]*ImageSummary, error)
Load(ctx context.Context, opts ImageLoadOptions) (*ImageLoadReport, error)
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/generate.go b/pkg/domain/entities/generate.go
new file mode 100644
index 000000000..6d65b52f8
--- /dev/null
+++ b/pkg/domain/entities/generate.go
@@ -0,0 +1,22 @@
+package entities
+
+// GenerateSystemdOptions control the generation of systemd unit files.
+type GenerateSystemdOptions struct {
+ // Files - generate files instead of printing to stdout.
+ Files bool
+ // Name - use container/pod name instead of its ID.
+ Name bool
+ // New - create a new container instead of starting a new one.
+ New bool
+ // RestartPolicy - systemd restart policy.
+ RestartPolicy string
+ // StopTimeout - time when stopping the container.
+ StopTimeout *uint
+}
+
+// GenerateSystemdReport
+type GenerateSystemdReport struct {
+ // Output of the generate process. Either the generated files or their
+ // entire content.
+ Output string
+}
diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go
index 78ebb8805..74f27e25f 100644
--- a/pkg/domain/entities/images.go
+++ b/pkg/domain/entities/images.go
@@ -50,10 +50,10 @@ func (i *Image) Id() string {
}
type ImageSummary struct {
- ID string `json:"Id"`
+ ID string
ParentId string `json:",omitempty"`
RepoTags []string `json:",omitempty"`
- Created int64 `json:",omitempty"`
+ Created time.Time `json:",omitempty"`
Size int64 `json:",omitempty"`
SharedSize int `json:",omitempty"`
VirtualSize int64 `json:",omitempty"`
@@ -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{}
@@ -136,8 +141,8 @@ type ImagePullOptions struct {
Quiet bool
// SignaturePolicy to use when pulling. Ignored for remote calls.
SignaturePolicy string
- // TLSVerify to enable/disable HTTPS and certificate verification.
- TLSVerify types.OptionalBool
+ // SkipTLSVerify to skip HTTPS and certificate verification.
+ SkipTLSVerify types.OptionalBool
}
// ImagePullReport is the response from pulling one or more images.
@@ -178,8 +183,8 @@ type ImagePushOptions struct {
// SignBy adds a signature at the destination using the specified key.
// Ignored for remote calls.
SignBy string
- // TLSVerify to enable/disable HTTPS and certificate verification.
- TLSVerify types.OptionalBool
+ // SkipTLSVerify to skip HTTPS and certificate verification.
+ SkipTLSVerify types.OptionalBool
}
// ImageSearchOptions are the arguments for searching images.
@@ -193,8 +198,8 @@ type ImageSearchOptions struct {
Limit int
// NoTrunc will not truncate the output.
NoTrunc bool
- // TLSVerify to enable/disable HTTPS and certificate verification.
- TLSVerify types.OptionalBool
+ // SkipTLSVerify to skip HTTPS and certificate verification.
+ SkipTLSVerify types.OptionalBool
}
// ImageSearchReport is the response from searching images.
@@ -213,6 +218,7 @@ type ImageSearchReport struct {
Automated string
}
+// Image List Options
type ImageListOptions struct {
All bool `json:"all" schema:"all"`
Filter []string `json:"Filter,omitempty"`
@@ -233,13 +239,9 @@ type ImagePruneReport struct {
type ImageTagOptions struct{}
type ImageUntagOptions struct{}
-type ImageData struct {
- *inspect.ImageData
-}
-
+// ImageInspectReport is the data when inspecting an image.
type ImageInspectReport struct {
- Images []*ImageData
- Errors map[string]error
+ *inspect.ImageData
}
type ImageLoadOptions struct {
@@ -251,7 +253,7 @@ type ImageLoadOptions struct {
}
type ImageLoadReport struct {
- Name string
+ Names []string
}
type ImageImportOptions struct {
@@ -273,3 +275,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 b280203de..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
@@ -147,6 +148,15 @@ func (p PodCreateOptions) ToPodSpecGen(s *specgen.PodSpecGenerator) {
s.CgroupParent = p.CGroupParent
}
+type PodPruneOptions struct {
+ Force bool `json:"force" schema:"force"`
+}
+
+type PodPruneReport struct {
+ Err error
+ Id string
+}
+
type PodTopOptions struct {
// CLI flags.
ListDescriptors bool
@@ -179,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/system.go b/pkg/domain/entities/system.go
index 3ddc04293..de93a382f 100644
--- a/pkg/domain/entities/system.go
+++ b/pkg/domain/entities/system.go
@@ -12,3 +12,17 @@ type ServiceOptions struct {
Timeout time.Duration // duration of inactivity the service should wait before shutting down
Command *cobra.Command // CLI command provided. Used in V1 code
}
+
+// SystemPruneOptions provides options to prune system.
+type SystemPruneOptions struct {
+ All bool
+ Volume bool
+}
+
+// SystemPruneReport provides report after system prune is executed.
+type SystemPruneReport struct {
+ PodPruneReport []*PodPruneReport
+ *ContainerPruneReport
+ *ImagePruneReport
+ VolumePruneReport []*VolumePruneReport
+}
diff --git a/pkg/domain/entities/types.go b/pkg/domain/entities/types.go
index 31a05f5d3..9fbe04c9a 100644
--- a/pkg/domain/entities/types.go
+++ b/pkg/domain/entities/types.go
@@ -4,6 +4,7 @@ import (
"errors"
"net"
+ "github.com/containers/buildah/imagebuildah"
"github.com/containers/libpod/libpod/events"
"github.com/containers/libpod/pkg/specgen"
"github.com/containers/storage/pkg/archive"
@@ -24,33 +25,36 @@ type Report struct {
}
type PodDeleteReport struct{ Report }
-type PodPruneOptions struct{}
-type PodPruneReport struct{ Report }
type VolumeDeleteOptions struct{}
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
type InspectOptions struct {
+ // Format - change the output to JSON or a Go template.
Format string `json:",omitempty"`
- Latest bool `json:",omitempty"`
- Size bool `json:",omitempty"`
+ // Latest - inspect the latest container Podman is aware of.
+ Latest bool `json:",omitempty"`
+ // Size (containers only) - display total file size.
+ Size bool `json:",omitempty"`
+ // Type -- return JSON for specified type.
+ Type string `json:",omitempty"`
}
// All API and CLI diff commands and diff sub-commands use the same options
@@ -104,3 +108,14 @@ func (e ErrorModel) Cause() error {
func (e ErrorModel) Code() int {
return e.ResponseCode
}
+
+// BuildOptions describe the options for building container images.
+type BuildOptions struct {
+ imagebuildah.BuildOptions
+}
+
+// BuildReport is the image-build report.
+type BuildReport struct {
+ // ID of the image.
+ ID string
+}
diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go
index c9df72f2d..82bf82bf0 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
}
@@ -505,7 +514,7 @@ func (ic *ContainerEngine) ContainerAttach(ctx context.Context, nameOrId string,
}
// If the container is in a pod, also set to recursively start dependencies
- if err := terminal.StartAttachCtr(ctx, ctr, options.Stdin, options.Stderr, options.Stdin, options.DetachKeys, options.SigProxy, false, ctr.PodID() != ""); err != nil && errors.Cause(err) != define.ErrDetach {
+ if err := terminal.StartAttachCtr(ctx, ctr, options.Stdout, options.Stderr, options.Stdin, options.DetachKeys, options.SigProxy, false, ctr.PodID() != ""); err != nil && errors.Cause(err) != define.ErrDetach {
return errors.Wrapf(err, "error attaching to container %s", ctr.ID())
}
return nil
@@ -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
}
@@ -736,6 +745,16 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta
} else {
report.ExitCode = int(ecode)
}
+ if opts.Rm {
+ if err := ic.Libpod.RemoveContainer(ctx, ctr, false, true); err != nil {
+ if errors.Cause(err) == define.ErrNoSuchCtr ||
+ errors.Cause(err) == define.ErrCtrRemoved {
+ logrus.Warnf("Container %s does not exist: %v", ctr.ID(), err)
+ } else {
+ logrus.Errorf("Error removing container %s: %v", ctr.ID(), err)
+ }
+ }
+ }
return &report, nil
}
@@ -829,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
@@ -921,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, options.Latest, []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/generate.go b/pkg/domain/infra/abi/generate.go
new file mode 100644
index 000000000..f69ba560e
--- /dev/null
+++ b/pkg/domain/infra/abi/generate.go
@@ -0,0 +1,174 @@
+package abi
+
+import (
+ "context"
+ "fmt"
+ "strings"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/systemd/generate"
+ "github.com/pkg/errors"
+)
+
+func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string, options entities.GenerateSystemdOptions) (*entities.GenerateSystemdReport, error) {
+ opts := generate.Options{
+ Files: options.Files,
+ New: options.New,
+ }
+
+ // First assume it's a container.
+ if info, found, err := ic.generateSystemdgenContainerInfo(nameOrID, nil, options); found && err != nil {
+ return nil, err
+ } else if found && err == nil {
+ output, err := generate.CreateContainerSystemdUnit(info, opts)
+ if err != nil {
+ return nil, err
+ }
+ return &entities.GenerateSystemdReport{Output: output}, nil
+ }
+
+ // --new does not support pods.
+ if options.New {
+ return nil, errors.Errorf("error generating systemd unit files: cannot generate generic files for a pod")
+ }
+
+ // We're either having a pod or garbage.
+ pod, err := ic.Libpod.LookupPod(nameOrID)
+ if err != nil {
+ return nil, err
+ }
+
+ // Error out if the pod has no infra container, which we require to be the
+ // main service.
+ if !pod.HasInfraContainer() {
+ return nil, fmt.Errorf("error generating systemd unit files: Pod %q has no infra container", pod.Name())
+ }
+
+ // Generate a systemdgen.ContainerInfo for the infra container. This
+ // ContainerInfo acts as the main service of the pod.
+ infraID, err := pod.InfraContainerID()
+ if err != nil {
+ return nil, nil
+ }
+ podInfo, _, err := ic.generateSystemdgenContainerInfo(infraID, pod, options)
+ if err != nil {
+ return nil, err
+ }
+
+ // Compute the container-dependency graph for the Pod.
+ containers, err := pod.AllContainers()
+ if err != nil {
+ return nil, err
+ }
+ if len(containers) == 0 {
+ return nil, fmt.Errorf("error generating systemd unit files: Pod %q has no containers", pod.Name())
+ }
+ graph, err := libpod.BuildContainerGraph(containers)
+ if err != nil {
+ return nil, err
+ }
+
+ // Traverse the dependency graph and create systemdgen.ContainerInfo's for
+ // each container.
+ containerInfos := []*generate.ContainerInfo{podInfo}
+ for ctr, dependencies := range graph.DependencyMap() {
+ // Skip the infra container as we already generated it.
+ if ctr.ID() == infraID {
+ continue
+ }
+ ctrInfo, _, err := ic.generateSystemdgenContainerInfo(ctr.ID(), nil, options)
+ if err != nil {
+ return nil, err
+ }
+ // Now add the container's dependencies and at the container as a
+ // required service of the infra container.
+ for _, dep := range dependencies {
+ if dep.ID() == infraID {
+ ctrInfo.BoundToServices = append(ctrInfo.BoundToServices, podInfo.ServiceName)
+ } else {
+ _, serviceName := generateServiceName(dep, nil, options)
+ ctrInfo.BoundToServices = append(ctrInfo.BoundToServices, serviceName)
+ }
+ }
+ podInfo.RequiredServices = append(podInfo.RequiredServices, ctrInfo.ServiceName)
+ containerInfos = append(containerInfos, ctrInfo)
+ }
+
+ // Now generate the systemd service for all containers.
+ builder := strings.Builder{}
+ for i, info := range containerInfos {
+ if i > 0 {
+ builder.WriteByte('\n')
+ }
+ out, err := generate.CreateContainerSystemdUnit(info, opts)
+ if err != nil {
+ return nil, err
+ }
+ builder.WriteString(out)
+ }
+
+ return &entities.GenerateSystemdReport{Output: builder.String()}, nil
+}
+
+// generateSystemdgenContainerInfo is a helper to generate a
+// systemdgen.ContainerInfo for `GenerateSystemd`.
+func (ic *ContainerEngine) generateSystemdgenContainerInfo(nameOrID string, pod *libpod.Pod, options entities.GenerateSystemdOptions) (*generate.ContainerInfo, bool, error) {
+ ctr, err := ic.Libpod.LookupContainer(nameOrID)
+ if err != nil {
+ return nil, false, err
+ }
+
+ timeout := ctr.StopTimeout()
+ if options.StopTimeout != nil {
+ timeout = *options.StopTimeout
+ }
+
+ config := ctr.Config()
+ conmonPidFile := config.ConmonPidFile
+ if conmonPidFile == "" {
+ return nil, true, errors.Errorf("conmon PID file path is empty, try to recreate the container with --conmon-pidfile flag")
+ }
+
+ createCommand := []string{}
+ if config.CreateCommand != nil {
+ createCommand = config.CreateCommand
+ } else if options.New {
+ return nil, true, errors.Errorf("cannot use --new on container %q: no create command found", nameOrID)
+ }
+
+ name, serviceName := generateServiceName(ctr, pod, options)
+ info := &generate.ContainerInfo{
+ ServiceName: serviceName,
+ ContainerName: name,
+ RestartPolicy: options.RestartPolicy,
+ PIDFile: conmonPidFile,
+ StopTimeout: timeout,
+ GenerateTimestamp: true,
+ CreateCommand: createCommand,
+ }
+
+ return info, true, nil
+}
+
+// generateServiceName generates the container name and the service name for systemd service.
+func generateServiceName(ctr *libpod.Container, pod *libpod.Pod, options entities.GenerateSystemdOptions) (string, string) {
+ var kind, name, ctrName string
+ if pod == nil {
+ kind = "container"
+ name = ctr.ID()
+ if options.Name {
+ name = ctr.Name()
+ }
+ ctrName = name
+ } else {
+ kind = "pod"
+ name = pod.ID()
+ ctrName = ctr.ID()
+ if options.Name {
+ name = pod.Name()
+ ctrName = ctr.Name()
+ }
+ }
+ return ctrName, fmt.Sprintf("%s-%s", kind, name)
+}
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 9467c14d4..be788b2bf 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 {
@@ -117,7 +46,6 @@ func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOption
Id: results,
Err: nil,
},
- Size: 0,
}
return &report, nil
}
@@ -172,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
}
@@ -190,13 +118,13 @@ func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entiti
DockerCertPath: options.CertDir,
OSChoice: options.OverrideOS,
ArchitectureChoice: options.OverrideArch,
- DockerInsecureSkipTLSVerify: options.TLSVerify,
+ DockerInsecureSkipTLSVerify: options.SkipTLSVerify,
}
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
}
@@ -237,34 +165,29 @@ 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
}
-func (ir *ImageEngine) Inspect(ctx context.Context, names []string, opts entities.InspectOptions) (*entities.ImageInspectReport, error) {
- report := entities.ImageInspectReport{
- Errors: make(map[string]error),
- }
-
- for _, id := range names {
- img, err := ir.Libpod.ImageRuntime().NewFromLocal(id)
+func (ir *ImageEngine) Inspect(ctx context.Context, namesOrIDs []string, opts entities.InspectOptions) ([]*entities.ImageInspectReport, error) {
+ reports := []*entities.ImageInspectReport{}
+ for _, i := range namesOrIDs {
+ img, err := ir.Libpod.ImageRuntime().NewFromLocal(i)
if err != nil {
- report.Errors[id] = err
- continue
+ return nil, err
}
-
- results, err := img.Inspect(ctx)
+ result, err := img.Inspect(ctx)
if err != nil {
- report.Errors[id] = err
- continue
+ return nil, err
}
-
- cookedResults := entities.ImageData{}
- _ = domainUtils.DeepCopy(&cookedResults, results)
- report.Images = append(report.Images, &cookedResults)
+ report := entities.ImageInspectReport{}
+ if err := domainUtils.DeepCopy(&report, result); err != nil {
+ return nil, err
+ }
+ reports = append(reports, &report)
}
- return &report, nil
+ return reports, nil
}
func (ir *ImageEngine) Push(ctx context.Context, source string, destination string, options entities.ImagePushOptions) error {
@@ -298,7 +221,7 @@ func (ir *ImageEngine) Push(ctx context.Context, source string, destination stri
dockerRegistryOptions := image.DockerRegistryOptions{
DockerRegistryCreds: registryCreds,
DockerCertPath: options.CertDir,
- DockerInsecureSkipTLSVerify: options.TLSVerify,
+ DockerInsecureSkipTLSVerify: options.SkipTLSVerify,
}
signOptions := image.SigningOptions{
@@ -374,6 +297,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
@@ -393,16 +320,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) {
@@ -440,7 +370,7 @@ func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.Im
Filter: *filter,
Limit: opts.Limit,
NoTrunc: opts.NoTrunc,
- InsecureSkipTLSVerify: opts.TLSVerify,
+ InsecureSkipTLSVerify: opts.SkipTLSVerify,
}
searchResults, err := image.SearchImages(term, searchOpts)
@@ -468,3 +398,167 @@ func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.Im
func (ir *ImageEngine) Config(_ context.Context) (*config.Config, error) {
return ir.Libpod.GetConfig()
}
+
+func (ir *ImageEngine) Build(ctx context.Context, containerFiles []string, opts entities.BuildOptions) (*entities.BuildReport, error) {
+ id, _, err := ir.Libpod.Build(ctx, opts.BuildOptions, containerFiles...)
+ if err != nil {
+ return nil, err
+ }
+ 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 define.ErrNoSuchImage:
+ inUseErrors = true // ExitCode is expected
+ 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, errors.Wrapf(err, "failed to remove image '%s'", id))
+ 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 2f4020374..c559e250c 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 (
@@ -27,8 +25,8 @@ func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions)
return nil, err
}
- summaries := make([]*entities.ImageSummary, len(images))
- for i, img := range images {
+ var summaries []*entities.ImageSummary
+ for _, img := range images {
var repoTags []string
if opts.All {
pairs, err := libpodImage.ReposToMap(img.Names())
@@ -43,6 +41,9 @@ func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions)
}
} else {
repoTags, _ = img.RepoTags()
+ if len(repoTags) == 0 {
+ continue
+ }
}
digests := make([]string, len(img.Digests()))
@@ -54,7 +55,7 @@ func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions)
ID: img.ID(),
ConfigDigest: string(img.ConfigDigest),
- Created: img.Created().Unix(),
+ Created: img.Created(),
Dangling: img.Dangling(),
Digest: string(img.Digest()),
Digests: digests,
@@ -74,7 +75,7 @@ func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions)
sz, _ := img.Size(context.TODO())
e.Size = int64(*sz)
- summaries[i] = &e
+ summaries = append(summaries, &e)
}
return summaries, nil
}
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 59bf0f636..b286bcf0d 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,13 +236,29 @@ 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
- continue
}
reports = append(reports, &report)
}
return reports, nil
}
+func (ic *ContainerEngine) PodPrune(ctx context.Context, options entities.PodPruneOptions) ([]*entities.PodPruneReport, error) {
+ var (
+ reports []*entities.PodPruneReport
+ )
+ response, err := ic.Libpod.PrunePods(ctx)
+ if err != nil {
+ return nil, err
+ }
+ for k, v := range response {
+ reports = append(reports, &entities.PodPruneReport{
+ Err: v,
+ Id: k,
+ })
+ }
+ return reports, nil
+}
+
func (ic *ContainerEngine) PodCreate(ctx context.Context, opts entities.PodCreateOptions) (*entities.PodCreateReport, error) {
podSpec := specgen.NewPodSpecGenerator()
opts.ToPodSpecGen(podSpec)
@@ -277,9 +293,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)
@@ -290,10 +309,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_abi.go b/pkg/domain/infra/runtime_abi.go
index 0dbcf2ad2..7aa6986a7 100644
--- a/pkg/domain/infra/runtime_abi.go
+++ b/pkg/domain/infra/runtime_abi.go
@@ -12,7 +12,7 @@ import (
)
// NewContainerEngine factory provides a libpod runtime for container-related operations
-func NewContainerEngine(facts entities.PodmanConfig) (entities.ContainerEngine, error) {
+func NewContainerEngine(facts *entities.PodmanConfig) (entities.ContainerEngine, error) {
switch facts.EngineMode {
case entities.ABIMode:
r, err := NewLibpodRuntime(facts.FlagSet, facts)
@@ -25,7 +25,7 @@ func NewContainerEngine(facts entities.PodmanConfig) (entities.ContainerEngine,
}
// NewContainerEngine factory provides a libpod runtime for image-related operations
-func NewImageEngine(facts entities.PodmanConfig) (entities.ImageEngine, error) {
+func NewImageEngine(facts *entities.PodmanConfig) (entities.ImageEngine, error) {
switch facts.EngineMode {
case entities.ABIMode:
r, err := NewLibpodImageRuntime(facts.FlagSet, facts)
diff --git a/pkg/domain/infra/runtime_image_proxy.go b/pkg/domain/infra/runtime_image_proxy.go
index 535fba858..ea5d0e6f2 100644
--- a/pkg/domain/infra/runtime_image_proxy.go
+++ b/pkg/domain/infra/runtime_image_proxy.go
@@ -12,7 +12,7 @@ import (
// ContainerEngine Image Proxy will be EOL'ed after podman is separated from libpod repo
-func NewLibpodImageRuntime(flags *pflag.FlagSet, opts entities.PodmanConfig) (entities.ImageEngine, error) {
+func NewLibpodImageRuntime(flags *pflag.FlagSet, opts *entities.PodmanConfig) (entities.ImageEngine, error) {
r, err := GetRuntime(context.Background(), flags, opts)
if err != nil {
return nil, err
diff --git a/pkg/domain/infra/runtime_libpod.go b/pkg/domain/infra/runtime_libpod.go
index e335dd560..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,17 +19,25 @@ 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
migrate bool
noStore bool
withFDS bool
- config entities.PodmanConfig
+ config *entities.PodmanConfig
}
// GetRuntimeMigrate gets a libpod runtime that will perform a migration of existing containers
-func GetRuntimeMigrate(ctx context.Context, fs *flag.FlagSet, cfg entities.PodmanConfig, newRuntime string) (*libpod.Runtime, error) {
+func GetRuntimeMigrate(ctx context.Context, fs *flag.FlagSet, cfg *entities.PodmanConfig, newRuntime string) (*libpod.Runtime, error) {
return getRuntime(ctx, fs, &engineOpts{
name: newRuntime,
renumber: false,
@@ -40,7 +49,7 @@ func GetRuntimeMigrate(ctx context.Context, fs *flag.FlagSet, cfg entities.Podma
}
// GetRuntimeDisableFDs gets a libpod runtime that will disable sd notify
-func GetRuntimeDisableFDs(ctx context.Context, fs *flag.FlagSet, cfg entities.PodmanConfig) (*libpod.Runtime, error) {
+func GetRuntimeDisableFDs(ctx context.Context, fs *flag.FlagSet, cfg *entities.PodmanConfig) (*libpod.Runtime, error) {
return getRuntime(ctx, fs, &engineOpts{
renumber: false,
migrate: false,
@@ -51,7 +60,7 @@ func GetRuntimeDisableFDs(ctx context.Context, fs *flag.FlagSet, cfg entities.Po
}
// GetRuntimeRenumber gets a libpod runtime that will perform a lock renumber
-func GetRuntimeRenumber(ctx context.Context, fs *flag.FlagSet, cfg entities.PodmanConfig) (*libpod.Runtime, error) {
+func GetRuntimeRenumber(ctx context.Context, fs *flag.FlagSet, cfg *entities.PodmanConfig) (*libpod.Runtime, error) {
return getRuntime(ctx, fs, &engineOpts{
renumber: true,
migrate: false,
@@ -62,18 +71,21 @@ func GetRuntimeRenumber(ctx context.Context, fs *flag.FlagSet, cfg entities.Podm
}
// 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,
+func GetRuntime(ctx context.Context, flags *flag.FlagSet, cfg *entities.PodmanConfig) (*libpod.Runtime, error) {
+ 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
-func GetRuntimeNoStore(ctx context.Context, fs *flag.FlagSet, cfg entities.PodmanConfig) (*libpod.Runtime, error) {
+func GetRuntimeNoStore(ctx context.Context, fs *flag.FlagSet, cfg *entities.PodmanConfig) (*libpod.Runtime, error) {
return getRuntime(ctx, fs, &engineOpts{
renumber: false,
migrate: false,
@@ -160,7 +172,7 @@ func getRuntime(ctx context.Context, fs *flag.FlagSet, opts *engineOpts) (*libpo
}
if fs.Changed("runtime") {
- options = append(options, libpod.WithOCIRuntime(cfg.Engine.OCIRuntime))
+ options = append(options, libpod.WithOCIRuntime(cfg.RuntimePath))
}
if fs.Changed("conmon") {
@@ -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/runtime_proxy.go b/pkg/domain/infra/runtime_proxy.go
index fbe45ea8f..41193fd89 100644
--- a/pkg/domain/infra/runtime_proxy.go
+++ b/pkg/domain/infra/runtime_proxy.go
@@ -12,7 +12,7 @@ import (
// ContainerEngine Proxy will be EOL'ed after podman is separated from libpod repo
-func NewLibpodRuntime(flags *flag.FlagSet, opts entities.PodmanConfig) (entities.ContainerEngine, error) {
+func NewLibpodRuntime(flags *flag.FlagSet, opts *entities.PodmanConfig) (entities.ContainerEngine, error) {
r, err := GetRuntime(context.Background(), flags, opts)
if err != nil {
return nil, err
diff --git a/pkg/domain/infra/runtime_tunnel.go b/pkg/domain/infra/runtime_tunnel.go
index 129fdeb2c..752218aaf 100644
--- a/pkg/domain/infra/runtime_tunnel.go
+++ b/pkg/domain/infra/runtime_tunnel.go
@@ -11,7 +11,7 @@ import (
"github.com/containers/libpod/pkg/domain/infra/tunnel"
)
-func NewContainerEngine(facts entities.PodmanConfig) (entities.ContainerEngine, error) {
+func NewContainerEngine(facts *entities.PodmanConfig) (entities.ContainerEngine, error) {
switch facts.EngineMode {
case entities.ABIMode:
return nil, fmt.Errorf("direct runtime not supported")
@@ -23,7 +23,7 @@ func NewContainerEngine(facts entities.PodmanConfig) (entities.ContainerEngine,
}
// NewImageEngine factory provides a libpod runtime for image-related operations
-func NewImageEngine(facts entities.PodmanConfig) (entities.ImageEngine, error) {
+func NewImageEngine(facts *entities.PodmanConfig) (entities.ImageEngine, error) {
switch facts.EngineMode {
case entities.ABIMode:
return nil, fmt.Errorf("direct image runtime not supported")
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/generate.go b/pkg/domain/infra/tunnel/generate.go
new file mode 100644
index 000000000..3cd483053
--- /dev/null
+++ b/pkg/domain/infra/tunnel/generate.go
@@ -0,0 +1,12 @@
+package tunnel
+
+import (
+ "context"
+
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/pkg/errors"
+)
+
+func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string, options entities.GenerateSystemdOptions) (*entities.GenerateSystemdReport, error) {
+ return nil, errors.New("not implemented for tunnel")
+}
diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go
index 7d40e0327..dcc5fc3e7 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
@@ -150,16 +143,16 @@ func (ir *ImageEngine) Untag(ctx context.Context, nameOrId string, tags []string
return nil
}
-func (ir *ImageEngine) Inspect(_ context.Context, names []string, opts entities.InspectOptions) (*entities.ImageInspectReport, error) {
- report := entities.ImageInspectReport{}
- for _, id := range names {
- r, err := images.GetImage(ir.ClientCxt, id, &opts.Size)
+func (ir *ImageEngine) Inspect(ctx context.Context, namesOrIDs []string, opts entities.InspectOptions) ([]*entities.ImageInspectReport, error) {
+ reports := []*entities.ImageInspectReport{}
+ for _, i := range namesOrIDs {
+ r, err := images.GetImage(ir.ClientCxt, i, &opts.Size)
if err != nil {
- report.Errors[id] = err
+ return nil, err
}
- report.Images = append(report.Images, r)
+ reports = append(reports, r)
}
- return &report, nil
+ return reports, nil
}
func (ir *ImageEngine) Load(ctx context.Context, opts entities.ImageLoadOptions) (*entities.ImageLoadReport, error) {
@@ -259,3 +252,15 @@ func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.Im
func (ir *ImageEngine) Config(_ context.Context) (*config.Config, error) {
return config.Default()
}
+
+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 dad77284f..c193c6752 100644
--- a/pkg/domain/infra/tunnel/pods.go
+++ b/pkg/domain/infra/tunnel/pods.go
@@ -173,6 +173,10 @@ func (ic *ContainerEngine) PodRm(ctx context.Context, namesOrIds []string, optio
return reports, nil
}
+func (ic *ContainerEngine) PodPrune(ctx context.Context, opts entities.PodPruneOptions) ([]*entities.PodPruneReport, error) {
+ return pods.Prune(ic.ClientCxt)
+}
+
func (ic *ContainerEngine) PodCreate(ctx context.Context, opts entities.PodCreateOptions) (*entities.PodCreateReport, error) {
podSpec := specgen.NewPodSpecGenerator()
opts.ToPodSpecGen(podSpec)
@@ -207,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..716db81dc 100644
--- a/pkg/rootless/rootless_linux.c
+++ b/pkg/rootless/rootless_linux.c
@@ -535,13 +535,41 @@ create_pause_process (const char *pause_pid_file_path, char **argv)
}
}
+static int
+open_namespace (int pid_to_join, const char *ns_file)
+{
+ char ns_path[PATH_MAX];
+ int ret;
+
+ 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");
+ return -1;
+ }
+
+ return open (ns_path, O_CLOEXEC | O_RDONLY);
+}
+
+static void
+join_namespace_or_die (const char *name, int ns_fd)
+{
+ if (setns (ns_fd, 0) < 0)
+ {
+ fprintf (stderr, "cannot set %s namespace\n", name);
+ _exit (EXIT_FAILURE);
+ }
+}
+
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];
char **argv;
int pid;
+ int mnt_ns = -1;
+ int user_ns = -1;
char *cwd = getcwd (NULL, 0);
sigset_t sigset, oldsigset;
@@ -561,14 +589,28 @@ reexec_userns_join (int userns, int mountns, char *pause_pid_file_path)
_exit (EXIT_FAILURE);
}
+ user_ns = open_namespace (pid_to_join, "user");
+ if (user_ns < 0)
+ return user_ns;
+ mnt_ns = open_namespace (pid_to_join, "mnt");
+ if (mnt_ns < 0)
+ {
+ close (user_ns);
+ return mnt_ns;
+ }
+
pid = fork ();
if (pid < 0)
fprintf (stderr, "cannot fork: %s\n", strerror (errno));
if (pid)
{
- /* We passed down these fds, close them. */
int f;
+
+ /* We passed down these fds, close them. */
+ close (user_ns);
+ close (mnt_ns);
+
for (f = 3; f < open_files_max_fd; f++)
if (open_files_set == NULL || FD_ISSET (f % FD_SETSIZE, &(open_files_set[f / FD_SETSIZE])))
close (f);
@@ -606,19 +648,10 @@ 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 ("user", user_ns);
+ join_namespace_or_die ("mnt", mnt_ns);
+ close (user_ns);
+ close (mnt_ns);
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/rootlessport/rootlessport_linux.go b/pkg/rootlessport/rootlessport_linux.go
index 1c1ed39df..c686d80fc 100644
--- a/pkg/rootlessport/rootlessport_linux.go
+++ b/pkg/rootlessport/rootlessport_linux.go
@@ -102,25 +102,27 @@ func parent() error {
return err
}
- sigC := make(chan os.Signal, 1)
- signal.Notify(sigC, unix.SIGPIPE)
- defer func() {
- // dummy signal to terminate the goroutine
- sigC <- unix.SIGKILL
- }()
+ exitC := make(chan os.Signal, 1)
+ defer close(exitC)
+
go func() {
+ sigC := make(chan os.Signal, 1)
+ signal.Notify(sigC, unix.SIGPIPE)
defer func() {
signal.Stop(sigC)
close(sigC)
}()
- s := <-sigC
- if s == unix.SIGPIPE {
- if f, err := os.OpenFile("/dev/null", os.O_WRONLY, 0755); err == nil {
- unix.Dup2(int(f.Fd()), 1) // nolint:errcheck
- unix.Dup2(int(f.Fd()), 2) // nolint:errcheck
- f.Close()
+ select {
+ case s := <-sigC:
+ if s == unix.SIGPIPE {
+ if f, err := os.OpenFile("/dev/null", os.O_WRONLY, 0755); err == nil {
+ unix.Dup2(int(f.Fd()), 1) // nolint:errcheck
+ unix.Dup2(int(f.Fd()), 2) // nolint:errcheck
+ f.Close()
+ }
}
+ case <-exitC:
}
}()
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..a62344640 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"
@@ -326,10 +326,6 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM
}
defaultEnv = env.Join(env.DefaultEnvVariables, defaultEnv)
}
- config.Env = env.Join(defaultEnv, config.Env)
- for name, val := range config.Env {
- g.AddProcessEnv(name, val)
- }
if err := addRlimits(config, &g); err != nil {
return nil, err
@@ -360,6 +356,11 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM
if err := config.Cgroup.ConfigureGenerator(&g); err != nil {
return nil, err
}
+
+ config.Env = env.Join(defaultEnv, config.Env)
+ for name, val := range config.Env {
+ g.AddProcessEnv(name, val)
+ }
configSpec := g.Config
// If the container image specifies an label with a
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..92a2b4d35 100644
--- a/pkg/specgen/generate/container.go
+++ b/pkg/specgen/generate/container.go
@@ -3,34 +3,60 @@ package generate
import (
"context"
+ "github.com/containers/image/v5/manifest"
"github.com/containers/libpod/libpod"
ann "github.com/containers/libpod/pkg/annotations"
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
}
+ _, mediaType, err := newImage.Manifest(ctx)
+ if err != nil {
+ return err
+ }
+
+ if s.HealthConfig == nil && mediaType == manifest.DockerV2Schema2MediaType {
+ 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 +67,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 +98,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
- }
- //
- // Next, add annotations from the image
- annotations, err := newImage.Annotations(ctx)
- if err != nil {
- return err
+ annotations[ann.SandboxID] = s.Pod
+ annotations[ann.ContainerType] = ann.ContainerTypeContainer
}
- 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 +127,10 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat
s.SeccompProfilePath = p
}
- if user := s.User; len(user) == 0 {
- switch {
- // 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):
- s.User = "0"
- default:
- s.User = newImage.Config.User
+ if len(s.User) == 0 {
+ s.User, err = newImage.User(ctx)
+ if err != nil {
+ return err
}
}
if err := finishThrottleDevices(s); err != nil {
@@ -116,7 +139,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..01ddcf9c8 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,108 @@ 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{}
+ options = append(options, libpod.WithCreateCommand())
+
+ 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 +143,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 +192,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..396563267 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,102 @@ 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 == "slirp4netns":
+ toReturn.NSMode = Slirp
+ 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.