summaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
Diffstat (limited to 'pkg')
-rw-r--r--pkg/api/handlers/compat/containers_top.go81
-rw-r--r--pkg/api/handlers/compat/images_build.go15
-rw-r--r--pkg/api/handlers/compat/images_search.go2
-rw-r--r--pkg/api/handlers/compat/images_tag.go5
-rw-r--r--pkg/api/handlers/libpod/play.go2
-rw-r--r--pkg/api/handlers/libpod/pods.go95
-rw-r--r--pkg/api/handlers/libpod/volumes.go17
-rw-r--r--pkg/api/handlers/types.go12
-rw-r--r--pkg/api/handlers/utils/handler.go8
-rw-r--r--pkg/api/handlers/utils/handler_test.go48
-rw-r--r--pkg/api/server/handler_rid.go9
-rw-r--r--pkg/api/server/register_containers.go17
-rw-r--r--pkg/api/server/register_images.go12
-rw-r--r--pkg/api/server/register_pods.go15
-rw-r--r--pkg/api/server/server.go23
-rw-r--r--pkg/api/server/swagger.go14
-rw-r--r--pkg/bindings/connection.go16
-rw-r--r--pkg/bindings/containers/attach.go4
-rw-r--r--pkg/bindings/containers/logs.go2
-rw-r--r--pkg/bindings/errors.go29
-rw-r--r--pkg/bindings/images/types.go2
-rw-r--r--pkg/bindings/images/types_search_options.go15
-rw-r--r--pkg/bindings/play/types.go2
-rw-r--r--pkg/bindings/play/types_kube_options.go15
-rw-r--r--pkg/bindings/pods/pods.go14
-rw-r--r--pkg/bindings/test/common_test.go17
-rw-r--r--pkg/bindings/test/containers_test.go7
-rw-r--r--pkg/bindings/test/pods_test.go26
-rw-r--r--pkg/cgroups/cgroups.go16
-rw-r--r--pkg/checkpoint/checkpoint_restore.go4
-rw-r--r--pkg/domain/entities/container_ps.go2
-rw-r--r--pkg/domain/entities/containers.go2
-rw-r--r--pkg/domain/entities/engine_image.go1
-rw-r--r--pkg/domain/entities/images.go7
-rw-r--r--pkg/domain/entities/play.go2
-rw-r--r--pkg/domain/entities/pods.go9
-rw-r--r--pkg/domain/entities/reports/prune.go6
-rw-r--r--pkg/domain/entities/system.go2
-rw-r--r--pkg/domain/entities/volumes.go4
-rw-r--r--pkg/domain/infra/abi/containers.go9
-rw-r--r--pkg/domain/infra/abi/images.go76
-rw-r--r--pkg/domain/infra/abi/play.go2
-rw-r--r--pkg/domain/infra/abi/system.go10
-rw-r--r--pkg/domain/infra/tunnel/containers.go2
-rw-r--r--pkg/domain/infra/tunnel/images.go11
-rw-r--r--pkg/domain/infra/tunnel/network.go2
-rw-r--r--pkg/domain/infra/tunnel/play.go3
-rw-r--r--pkg/domain/infra/tunnel/secrets.go4
-rw-r--r--pkg/domain/infra/tunnel/volumes.go2
-rw-r--r--pkg/errorhandling/errorhandling.go14
-rw-r--r--pkg/machine/config.go1
-rw-r--r--pkg/machine/ignition.go18
-rw-r--r--pkg/machine/pull.go2
-rw-r--r--pkg/machine/qemu/config.go2
-rw-r--r--pkg/machine/qemu/machine.go4
-rw-r--r--pkg/rootlessport/rootlessport_linux.go349
-rw-r--r--pkg/specgen/generate/container_create.go3
-rw-r--r--pkg/specgen/generate/kube/kube.go26
-rw-r--r--pkg/specgen/generate/pod_create.go101
-rw-r--r--pkg/specgen/generate/ports.go589
-rw-r--r--pkg/specgen/generate/ports_bench_test.go197
-rw-r--r--pkg/specgen/generate/ports_test.go989
-rw-r--r--pkg/specgen/generate/storage.go3
-rw-r--r--pkg/specgen/podspecgen.go3
-rw-r--r--pkg/specgen/specgen.go14
-rw-r--r--pkg/specgen/specgen_test.go25
-rw-r--r--pkg/specgenutil/specgen.go27
-rw-r--r--pkg/specgenutil/volumes.go12
-rw-r--r--pkg/systemd/dbus.go2
69 files changed, 2236 insertions, 845 deletions
diff --git a/pkg/api/handlers/compat/containers_top.go b/pkg/api/handlers/compat/containers_top.go
index b5debd37d..545320ad9 100644
--- a/pkg/api/handlers/compat/containers_top.go
+++ b/pkg/api/handlers/compat/containers_top.go
@@ -1,8 +1,11 @@
package compat
import (
+ "encoding/json"
+ "fmt"
"net/http"
"strings"
+ "time"
"github.com/containers/podman/v3/libpod"
"github.com/containers/podman/v3/pkg/api/handlers"
@@ -10,20 +13,24 @@ import (
api "github.com/containers/podman/v3/pkg/api/types"
"github.com/gorilla/schema"
"github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
)
func TopContainer(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
- defaultValue := "-ef"
+ psArgs := "-ef"
if utils.IsLibpodRequest(r) {
- defaultValue = ""
+ psArgs = ""
}
query := struct {
+ Delay int `schema:"delay"`
PsArgs string `schema:"ps_args"`
+ Stream bool `schema:"stream"`
}{
- PsArgs: defaultValue,
+ Delay: 5,
+ PsArgs: psArgs,
}
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
@@ -31,6 +38,12 @@ func TopContainer(w http.ResponseWriter, r *http.Request) {
return
}
+ if query.Delay < 1 {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ fmt.Errorf("\"delay\" parameter of value %d < 1", query.Delay))
+ return
+ }
+
name := utils.GetName(r)
c, err := runtime.LookupContainer(name)
if err != nil {
@@ -38,26 +51,56 @@ func TopContainer(w http.ResponseWriter, r *http.Request) {
return
}
- output, err := c.Top([]string{query.PsArgs})
- if err != nil {
- utils.InternalServerError(w, err)
- return
+ // We are committed now - all errors logged but not reported to client, ship has sailed
+ w.WriteHeader(http.StatusOK)
+ w.Header().Set("Content-Type", "application/json")
+ if f, ok := w.(http.Flusher); ok {
+ f.Flush()
}
- var body = handlers.ContainerTopOKBody{}
- if len(output) > 0 {
- body.Titles = strings.Split(output[0], "\t")
- for i := range body.Titles {
- body.Titles[i] = strings.TrimSpace(body.Titles[i])
- }
+ encoder := json.NewEncoder(w)
+
+loop: // break out of for/select infinite` loop
+ for {
+ select {
+ case <-r.Context().Done():
+ break loop
+ default:
+ output, err := c.Top([]string{query.PsArgs})
+ if err != nil {
+ logrus.Infof("Error from %s %q : %v", r.Method, r.URL, err)
+ break loop
+ }
+
+ if len(output) > 0 {
+ body := handlers.ContainerTopOKBody{}
+ body.Titles = strings.Split(output[0], "\t")
+ for i := range body.Titles {
+ body.Titles[i] = strings.TrimSpace(body.Titles[i])
+ }
+
+ for _, line := range output[1:] {
+ process := strings.Split(line, "\t")
+ for i := range process {
+ process[i] = strings.TrimSpace(process[i])
+ }
+ body.Processes = append(body.Processes, process)
+ }
+
+ if err := encoder.Encode(body); err != nil {
+ logrus.Infof("Error from %s %q : %v", r.Method, r.URL, err)
+ break loop
+ }
+ if f, ok := w.(http.Flusher); ok {
+ f.Flush()
+ }
+ }
- for _, line := range output[1:] {
- process := strings.Split(line, "\t")
- for i := range process {
- process[i] = strings.TrimSpace(process[i])
+ if query.Stream {
+ time.Sleep(time.Duration(query.Delay) * time.Second)
+ } else {
+ break loop
}
- body.Processes = append(body.Processes, process)
}
}
- utils.WriteJSON(w, http.StatusOK, body)
}
diff --git a/pkg/api/handlers/compat/images_build.go b/pkg/api/handlers/compat/images_build.go
index 606c52e41..6152f1c02 100644
--- a/pkg/api/handlers/compat/images_build.go
+++ b/pkg/api/handlers/compat/images_build.go
@@ -151,22 +151,19 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
var m = []string{}
if err := json.Unmarshal([]byte(query.Dockerfile), &m); err != nil {
// it's not json, assume just a string
- m = append(m, query.Dockerfile)
+ m = []string{filepath.Join(contextDirectory, query.Dockerfile)}
}
containerFiles = m
} else {
- containerFiles = []string{"Dockerfile"}
+ containerFiles = []string{filepath.Join(contextDirectory, "Dockerfile")}
if utils.IsLibpodRequest(r) {
- containerFiles = []string{"Containerfile"}
- if _, err = os.Stat(filepath.Join(contextDirectory, "Containerfile")); err != nil {
- if _, err1 := os.Stat(filepath.Join(contextDirectory, "Dockerfile")); err1 == nil {
- containerFiles = []string{"Dockerfile"}
- } else {
+ containerFiles = []string{filepath.Join(contextDirectory, "Containerfile")}
+ if _, err = os.Stat(containerFiles[0]); err != nil {
+ containerFiles = []string{filepath.Join(contextDirectory, "Dockerfile")}
+ if _, err1 := os.Stat(containerFiles[0]); err1 != nil {
utils.BadRequest(w, "dockerfile", query.Dockerfile, err)
}
}
- } else {
- containerFiles = []string{"Dockerfile"}
}
}
diff --git a/pkg/api/handlers/compat/images_search.go b/pkg/api/handlers/compat/images_search.go
index 01282513e..e9cc3e2b6 100644
--- a/pkg/api/handlers/compat/images_search.go
+++ b/pkg/api/handlers/compat/images_search.go
@@ -22,7 +22,6 @@ func SearchImages(w http.ResponseWriter, r *http.Request) {
query := struct {
Term string `json:"term"`
Limit int `json:"limit"`
- NoTrunc bool `json:"noTrunc"`
Filters map[string][]string `json:"filters"`
TLSVerify bool `json:"tlsVerify"`
ListTags bool `json:"listTags"`
@@ -50,7 +49,6 @@ func SearchImages(w http.ResponseWriter, r *http.Request) {
options := entities.ImageSearchOptions{
Authfile: authfile,
Limit: query.Limit,
- NoTrunc: query.NoTrunc,
ListTags: query.ListTags,
Filters: filters,
}
diff --git a/pkg/api/handlers/compat/images_tag.go b/pkg/api/handlers/compat/images_tag.go
index 7858298be..5d413a821 100644
--- a/pkg/api/handlers/compat/images_tag.go
+++ b/pkg/api/handlers/compat/images_tag.go
@@ -4,6 +4,7 @@ import (
"fmt"
"net/http"
+ "github.com/containers/common/libimage"
"github.com/containers/podman/v3/libpod"
"github.com/containers/podman/v3/pkg/api/handlers/utils"
api "github.com/containers/podman/v3/pkg/api/types"
@@ -16,7 +17,9 @@ func TagImage(w http.ResponseWriter, r *http.Request) {
// /v1.xx/images/(name)/tag
name := utils.GetName(r)
- newImage, _, err := runtime.LibimageRuntime().LookupImage(name, nil)
+ // Allow tagging manifest list instead of resolving instances from manifest
+ lookupOptions := &libimage.LookupImageOptions{ManifestList: true}
+ newImage, _, err := runtime.LibimageRuntime().LookupImage(name, lookupOptions)
if err != nil {
utils.ImageNotFound(w, name, errors.Wrapf(err, "failed to find image %s", name))
return
diff --git a/pkg/api/handlers/libpod/play.go b/pkg/api/handlers/libpod/play.go
index 851e0f6c8..f943fc240 100644
--- a/pkg/api/handlers/libpod/play.go
+++ b/pkg/api/handlers/libpod/play.go
@@ -26,6 +26,7 @@ func PlayKube(w http.ResponseWriter, r *http.Request) {
Network string `schema:"network"`
TLSVerify bool `schema:"tlsVerify"`
LogDriver string `schema:"logDriver"`
+ LogOptions []string `schema:"logOptions"`
Start bool `schema:"start"`
StaticIPs []string `schema:"staticIPs"`
StaticMACs []string `schema:"staticMACs"`
@@ -106,6 +107,7 @@ func PlayKube(w http.ResponseWriter, r *http.Request) {
NoHosts: query.NoHosts,
Quiet: true,
LogDriver: query.LogDriver,
+ LogOptions: query.LogOptions,
StaticIPs: staticIPs,
StaticMACs: staticMACs,
}
diff --git a/pkg/api/handlers/libpod/pods.go b/pkg/api/handlers/libpod/pods.go
index 77d026550..2ba292579 100644
--- a/pkg/api/handlers/libpod/pods.go
+++ b/pkg/api/handlers/libpod/pods.go
@@ -1,15 +1,13 @@
package libpod
import (
- "context"
"encoding/json"
"fmt"
"net/http"
"strings"
+ "time"
- "github.com/containers/common/libimage"
"github.com/containers/common/pkg/config"
- "github.com/containers/image/v5/transports/alltransports"
"github.com/containers/podman/v3/libpod"
"github.com/containers/podman/v3/libpod/define"
"github.com/containers/podman/v3/pkg/api/handlers"
@@ -67,20 +65,6 @@ func PodCreate(w http.ResponseWriter, r *http.Request) {
imageName = config.DefaultInfraImage
rawImageName = config.DefaultInfraImage
}
- curr := infraOptions.Quiet
- infraOptions.Quiet = true
- pullOptions := &libimage.PullOptions{}
- pulledImages, err := runtime.LibimageRuntime().Pull(context.Background(), imageName, config.PullPolicyMissing, pullOptions)
- if err != nil {
- utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "could not pull image"))
- return
- }
- if _, err := alltransports.ParseImageName(imageName); err == nil {
- if len(pulledImages) != 0 {
- imageName = pulledImages[0].ID()
- }
- }
- infraOptions.Quiet = curr
psg.InfraImage = imageName
psg.InfraContainerSpec.Image = imageName
psg.InfraContainerSpec.RawImageName = rawImageName
@@ -380,10 +364,17 @@ func PodTop(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
+ psArgs := "-ef"
+ if utils.IsLibpodRequest(r) {
+ psArgs = ""
+ }
query := struct {
+ Delay int `schema:"delay"`
PsArgs string `schema:"ps_args"`
+ Stream bool `schema:"stream"`
}{
- PsArgs: "",
+ Delay: 5,
+ PsArgs: psArgs,
}
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
@@ -391,6 +382,12 @@ func PodTop(w http.ResponseWriter, r *http.Request) {
return
}
+ if query.Delay < 1 {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ fmt.Errorf("\"delay\" parameter of value %d < 1", query.Delay))
+ return
+ }
+
name := utils.GetName(r)
pod, err := runtime.LookupPod(name)
if err != nil {
@@ -398,24 +395,58 @@ func PodTop(w http.ResponseWriter, r *http.Request) {
return
}
- args := []string{}
- if query.PsArgs != "" {
- args = append(args, query.PsArgs)
- }
- output, err := pod.GetPodPidInformation(args)
- if err != nil {
- utils.InternalServerError(w, err)
- return
+ // We are committed now - all errors logged but not reported to client, ship has sailed
+ w.WriteHeader(http.StatusOK)
+ w.Header().Set("Content-Type", "application/json")
+ if f, ok := w.(http.Flusher); ok {
+ f.Flush()
}
- var body = handlers.PodTopOKBody{}
- if len(output) > 0 {
- body.Titles = strings.Split(output[0], "\t")
- for _, line := range output[1:] {
- body.Processes = append(body.Processes, strings.Split(line, "\t"))
+ encoder := json.NewEncoder(w)
+
+loop: // break out of for/select infinite` loop
+ for {
+ select {
+ case <-r.Context().Done():
+ break loop
+ default:
+ output, err := pod.GetPodPidInformation([]string{query.PsArgs})
+ if err != nil {
+ logrus.Infof("Error from %s %q : %v", r.Method, r.URL, err)
+ break loop
+ }
+
+ if len(output) > 0 {
+ var body = handlers.PodTopOKBody{}
+ body.Titles = strings.Split(output[0], "\t")
+ for i := range body.Titles {
+ body.Titles[i] = strings.TrimSpace(body.Titles[i])
+ }
+
+ for _, line := range output[1:] {
+ process := strings.Split(line, "\t")
+ for i := range process {
+ process[i] = strings.TrimSpace(process[i])
+ }
+ body.Processes = append(body.Processes, process)
+ }
+
+ if err := encoder.Encode(body); err != nil {
+ logrus.Infof("Error from %s %q : %v", r.Method, r.URL, err)
+ break loop
+ }
+ if f, ok := w.(http.Flusher); ok {
+ f.Flush()
+ }
+ }
+
+ if query.Stream {
+ time.Sleep(time.Duration(query.Delay) * time.Second)
+ } else {
+ break loop
+ }
}
}
- utils.WriteJSON(w, http.StatusOK, body)
}
func PodKill(w http.ResponseWriter, r *http.Request) {
diff --git a/pkg/api/handlers/libpod/volumes.go b/pkg/api/handlers/libpod/volumes.go
index 3ba39b860..ffdb30551 100644
--- a/pkg/api/handlers/libpod/volumes.go
+++ b/pkg/api/handlers/libpod/volumes.go
@@ -29,12 +29,13 @@ func CreateVolume(w http.ResponseWriter, r *http.Request) {
}{
// override any golang type defaults
}
- input := entities.VolumeCreateOptions{}
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
utils.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError,
errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
return
}
+
+ input := entities.VolumeCreateOptions{}
// decode params from body
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
@@ -47,9 +48,19 @@ func CreateVolume(w http.ResponseWriter, r *http.Request) {
if len(input.Driver) > 0 {
volumeOptions = append(volumeOptions, libpod.WithVolumeDriver(input.Driver))
}
- if len(input.Label) > 0 {
- volumeOptions = append(volumeOptions, libpod.WithVolumeLabels(input.Label))
+
+ // Label provided for compatibility.
+ labels := make(map[string]string, len(input.Label)+len(input.Labels))
+ for k, v := range input.Label {
+ labels[k] = v
}
+ for k, v := range input.Labels {
+ labels[k] = v
+ }
+ if len(labels) > 0 {
+ volumeOptions = append(volumeOptions, libpod.WithVolumeLabels(labels))
+ }
+
if len(input.Options) > 0 {
parsedOptions, err := parse.VolumeOptions(input.Options)
if err != nil {
diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go
index b90154e30..35120a1a5 100644
--- a/pkg/api/handlers/types.go
+++ b/pkg/api/handlers/types.go
@@ -42,9 +42,15 @@ type ContainersPruneReport struct {
}
type LibpodContainersPruneReport struct {
- ID string `json:"id"`
- SpaceReclaimed int64 `json:"space"`
- PruneError string `json:"error"`
+ ID string `json:"Id"`
+ SpaceReclaimed int64 `json:"Size"`
+ // Error which occurred during prune operation (if any).
+ // This field is optional and may be omitted if no error occurred.
+ //
+ // Extensions:
+ // x-omitempty: true
+ // x-nullable: true
+ PruneError string `json:"Err,omitempty"`
}
type Info struct {
diff --git a/pkg/api/handlers/utils/handler.go b/pkg/api/handlers/utils/handler.go
index 29139a98e..96b7a957c 100644
--- a/pkg/api/handlers/utils/handler.go
+++ b/pkg/api/handlers/utils/handler.go
@@ -145,12 +145,12 @@ func MarshalErrorSliceJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) {
}
}
-func MarshalErrorJSONIsEmpty(_ unsafe.Pointer) bool {
- return false
+func MarshalErrorJSONIsEmpty(ptr unsafe.Pointer) bool {
+ return *((*error)(ptr)) == nil
}
-func MarshalErrorSliceJSONIsEmpty(_ unsafe.Pointer) bool {
- return false
+func MarshalErrorSliceJSONIsEmpty(ptr unsafe.Pointer) bool {
+ return len(*((*[]error)(ptr))) <= 0
}
// WriteJSON writes an interface value encoded as JSON to w
diff --git a/pkg/api/handlers/utils/handler_test.go b/pkg/api/handlers/utils/handler_test.go
index 18a1d2678..5957e7d74 100644
--- a/pkg/api/handlers/utils/handler_test.go
+++ b/pkg/api/handlers/utils/handler_test.go
@@ -138,3 +138,51 @@ func TestEqualVersion(t *testing.T) {
rr.Body.String(), expected)
}
}
+
+func TestErrorEncoderFuncOmit(t *testing.T) {
+ data, err := json.Marshal(struct {
+ Err error `json:"err,omitempty"`
+ Errs []error `json:"errs,omitempty"`
+ }{})
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ dataAsMap := make(map[string]interface{})
+ err = json.Unmarshal(data, &dataAsMap)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ _, ok := dataAsMap["err"]
+ if ok {
+ t.Errorf("the `err` field should have been omitted")
+ }
+ _, ok = dataAsMap["errs"]
+ if ok {
+ t.Errorf("the `errs` field should have been omitted")
+ }
+
+ dataAsMap = make(map[string]interface{})
+ data, err = json.Marshal(struct {
+ Err error `json:"err"`
+ Errs []error `json:"errs"`
+ }{})
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = json.Unmarshal(data, &dataAsMap)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ _, ok = dataAsMap["err"]
+ if !ok {
+ t.Errorf("the `err` field shouldn't have been omitted")
+ }
+ _, ok = dataAsMap["errs"]
+ if !ok {
+ t.Errorf("the `errs` field shouldn't have been omitted")
+ }
+}
diff --git a/pkg/api/server/handler_rid.go b/pkg/api/server/handler_rid.go
index b624b99a6..7dcf436f7 100644
--- a/pkg/api/server/handler_rid.go
+++ b/pkg/api/server/handler_rid.go
@@ -2,6 +2,7 @@ package server
import (
"fmt"
+ "io/ioutil"
"net/http"
"github.com/containers/podman/v3/pkg/api/types"
@@ -15,7 +16,13 @@ import (
// and Apache style request logging
func referenceIDHandler() mux.MiddlewareFunc {
return func(h http.Handler) http.Handler {
- return handlers.CombinedLoggingHandler(logrus.StandardLogger().Out,
+ // Only log Apache access_log-like entries at Info level or below
+ out := ioutil.Discard
+ if logrus.IsLevelEnabled(logrus.InfoLevel) {
+ out = logrus.StandardLogger().Out
+ }
+
+ return handlers.CombinedLoggingHandler(out,
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
rid := r.Header.Get("X-Reference-Id")
if rid == "" {
diff --git a/pkg/api/server/register_containers.go b/pkg/api/server/register_containers.go
index 8dcea1301..c4919182b 100644
--- a/pkg/api/server/register_containers.go
+++ b/pkg/api/server/register_containers.go
@@ -442,6 +442,7 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
// - in: query
// name: ps_args
// type: string
+ // default: -ef
// description: arguments to pass to ps such as aux. Requires ps(1) to be installed in the container if no ps(1) compatible AIX descriptors are used.
// produces:
// - application/json
@@ -1142,19 +1143,23 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
// name: name
// type: string
// required: true
- // description: |
- // Name of container to query for processes
- // (As of version 1.xx)
+ // description: Name of container to query for processes (As of version 1.xx)
// - in: query
// name: stream
// type: boolean
- // default: true
- // description: Stream the output
+ // description: when true, repeatedly stream the latest output (As of version 4.0)
+ // - in: query
+ // name: delay
+ // type: integer
+ // description: if streaming, delay in seconds between updates. Must be >1. (As of version 4.0)
+ // default: 5
// - in: query
// name: ps_args
// type: string
// default: -ef
- // description: arguments to pass to ps such as aux. Requires ps(1) to be installed in the container if no ps(1) compatible AIX descriptors are used.
+ // description: |
+ // arguments to pass to ps such as aux.
+ // Requires ps(1) to be installed in the container if no ps(1) compatible AIX descriptors are used.
// produces:
// - application/json
// responses:
diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go
index aa573eaa6..38ceea271 100644
--- a/pkg/api/server/register_images.go
+++ b/pkg/api/server/register_images.go
@@ -103,7 +103,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// - application/json
// responses:
// 200:
- // $ref: "#/responses/DockerImageSummary"
+ // $ref: "#/responses/DockerImageSummaryResponse"
// 500:
// $ref: '#/responses/InternalError'
r.Handle(VersionedPath("/images/json"), s.APIHandler(compat.GetImages)).Methods(http.MethodGet)
@@ -837,7 +837,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// - application/json
// responses:
// 200:
- // $ref: "#/responses/DockerImageSummary"
+ // $ref: "#/responses/LibpodImageSummaryResponse"
// 500:
// $ref: '#/responses/InternalError'
r.Handle(VersionedPath("/libpod/images/json"), s.APIHandler(libpod.GetImages)).Methods(http.MethodGet)
@@ -967,7 +967,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// - application/json
// responses:
// 200:
- // $ref: "#/responses/DocsImageDeleteResponse"
+ // $ref: "#/responses/DocsLibpodImagesRemoveResponse"
// 400:
// $ref: "#/responses/BadParamError"
// 404:
@@ -1069,7 +1069,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// - application/json
// responses:
// 200:
- // $ref: "#/responses/DocsImageDeleteResponse"
+ // $ref: "#/responses/DocsLibpodPruneResponse"
// 500:
// $ref: '#/responses/InternalError'
r.Handle(VersionedPath("/libpod/images/prune"), s.APIHandler(libpod.PruneImages)).Methods(http.MethodPost)
@@ -1090,10 +1090,6 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// default: 25
// description: maximum number of results
// - in: query
- // name: noTrunc
- // type: boolean
- // description: do not truncate any of the result strings
- // - in: query
// name: filters
// type: string
// description: |
diff --git a/pkg/api/server/register_pods.go b/pkg/api/server/register_pods.go
index de3669a0a..16a7bbb4c 100644
--- a/pkg/api/server/register_pods.go
+++ b/pkg/api/server/register_pods.go
@@ -296,18 +296,23 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error {
// name: name
// type: string
// required: true
- // description: |
- // Name of pod to query for processes
+ // description: Name of pod to query for processes
// - in: query
// name: stream
// type: boolean
- // default: true
- // description: Stream the output
+ // description: when true, repeatedly stream the latest output (As of version 4.0)
+ // - in: query
+ // name: delay
+ // type: integer
+ // description: if streaming, delay in seconds between updates. Must be >1. (As of version 4.0)
+ // default: 5
// - in: query
// name: ps_args
// type: string
// default: -ef
- // description: arguments to pass to ps such as aux. Requires ps(1) to be installed in the container if no ps(1) compatible AIX descriptors are used.
+ // description: |
+ // arguments to pass to ps such as aux.
+ // Requires ps(1) to be installed in the container if no ps(1) compatible AIX descriptors are used.
// responses:
// 200:
// $ref: "#/responses/DocsPodTopResponse"
diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go
index 6e9578cd1..8c5c7aeeb 100644
--- a/pkg/api/server/server.go
+++ b/pkg/api/server/server.go
@@ -207,7 +207,7 @@ func (s *APIServer) setupSystemd() {
func (s *APIServer) Serve() error {
s.setupPprof()
- if err := shutdown.Register("server", func(sig os.Signal) error {
+ if err := shutdown.Register("service", func(sig os.Signal) error {
return s.Shutdown(true)
}); err != nil {
return err
@@ -272,20 +272,24 @@ func (s *APIServer) setupPprof() {
// Shutdown is a clean shutdown waiting on existing clients
func (s *APIServer) Shutdown(halt bool) error {
- if s.idleTracker.Duration == UnlimitedServiceDuration && !halt {
- logrus.Debug("API service shutdown request ignored as Duration is UnlimitedService")
+ switch {
+ case halt:
+ logrus.Debug("API service forced shutdown, ignoring timeout Duration")
+ case s.idleTracker.Duration == UnlimitedServiceDuration:
+ logrus.Debug("API service shutdown request ignored as timeout Duration is UnlimitedService")
return nil
}
shutdownOnce.Do(func() {
- if logrus.IsLevelEnabled(logrus.DebugLevel) {
- _, file, line, _ := runtime.Caller(1)
- logrus.Debugf("API service shutdown by %s:%d, %d/%d connection(s)",
- file, line, s.idleTracker.ActiveConnections(), s.idleTracker.TotalConnections())
- }
+ logrus.Debugf("API service shutdown, %d/%d connection(s)",
+ s.idleTracker.ActiveConnections(), s.idleTracker.TotalConnections())
// Gracefully shutdown server(s), duration of wait same as idle window
- ctx, cancel := context.WithTimeout(context.Background(), s.idleTracker.Duration)
+ deadline := 1 * time.Second
+ if s.idleTracker.Duration > 0 {
+ deadline = s.idleTracker.Duration
+ }
+ ctx, cancel := context.WithTimeout(context.Background(), deadline)
go func() {
defer cancel()
@@ -296,7 +300,6 @@ func (s *APIServer) Shutdown(halt bool) error {
}()
<-ctx.Done()
})
-
return nil
}
diff --git a/pkg/api/server/swagger.go b/pkg/api/server/swagger.go
index 0fd66652e..3f8f6f9c5 100644
--- a/pkg/api/server/swagger.go
+++ b/pkg/api/server/swagger.go
@@ -6,6 +6,7 @@ import (
"github.com/containers/podman/v3/pkg/domain/entities"
"github.com/containers/podman/v3/pkg/domain/entities/reports"
"github.com/containers/podman/v3/pkg/errorhandling"
+ docker "github.com/docker/docker/api/types"
)
// No such image
@@ -134,9 +135,16 @@ type swagPodAlreadyStopped struct {
}
}
-// Image summary
-// swagger:response DockerImageSummary
-type swagImageSummary struct {
+// Image summary for compat API
+// swagger:response DockerImageSummaryResponse
+type swagDockerImageSummaryResponse struct {
+ // in:body
+ Body []docker.ImageSummary
+}
+
+// Image summary for libpod API
+// swagger:response LibpodImageSummaryResponse
+type swagLibpodImageSummaryResponse struct {
// in:body
Body []entities.ImageSummary
}
diff --git a/pkg/bindings/connection.go b/pkg/bindings/connection.go
index e2c46e481..a2be44ab4 100644
--- a/pkg/bindings/connection.go
+++ b/pkg/bindings/connection.go
@@ -349,6 +349,17 @@ func (c *Connection) DoRequest(httpBody io.Reader, httpMethod, endpoint string,
return &APIResponse{response, req}, err
}
+// Get raw Transport.DialContext from client
+func (c *Connection) GetDialer(ctx context.Context) (net.Conn, error) {
+ client := c.Client
+ transport := client.Transport.(*http.Transport)
+ if transport.DialContext != nil && transport.TLSClientConfig == nil {
+ return transport.DialContext(ctx, c.URI.Scheme, c.URI.String())
+ }
+
+ return nil, errors.New("Unable to get dial context")
+}
+
// FiltersToString converts our typical filter format of a
// map[string][]string to a query/html safe string.
func FiltersToString(filters map[string][]string) (string, error) {
@@ -379,6 +390,11 @@ func (h *APIResponse) IsClientError() bool {
return h.Response.StatusCode/100 == 4
}
+// IsConflictError returns true if the response code is 409
+func (h *APIResponse) IsConflictError() bool {
+ return h.Response.StatusCode == 409
+}
+
// IsServerError returns true if the response code is 5xx
func (h *APIResponse) IsServerError() bool {
return h.Response.StatusCode/100 == 5
diff --git a/pkg/bindings/containers/attach.go b/pkg/bindings/containers/attach.go
index c5f54c1af..47de89b33 100644
--- a/pkg/bindings/containers/attach.go
+++ b/pkg/bindings/containers/attach.go
@@ -214,7 +214,7 @@ func Attach(ctx context.Context, nameOrID string, stdin io.Reader, stdout io.Wri
// Read multiplexed channels and write to appropriate stream
fd, l, err := DemuxHeader(socket, buffer)
if err != nil {
- if errors.Is(err, io.EOF) {
+ if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
return nil
}
return err
@@ -531,7 +531,7 @@ func ExecStartAndAttach(ctx context.Context, sessionID string, options *ExecStar
// Read multiplexed channels and write to appropriate stream
fd, l, err := DemuxHeader(socket, buffer)
if err != nil {
- if errors.Is(err, io.EOF) {
+ if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
return nil
}
return err
diff --git a/pkg/bindings/containers/logs.go b/pkg/bindings/containers/logs.go
index 67db94487..37ffdf0a5 100644
--- a/pkg/bindings/containers/logs.go
+++ b/pkg/bindings/containers/logs.go
@@ -39,7 +39,7 @@ func Logs(ctx context.Context, nameOrID string, options *LogOptions, stdoutChan,
for {
fd, l, err := DemuxHeader(response.Body, buffer)
if err != nil {
- if errors.Is(err, io.EOF) {
+ if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
return nil
}
return err
diff --git a/pkg/bindings/errors.go b/pkg/bindings/errors.go
index 9c311d912..ec837b39c 100644
--- a/pkg/bindings/errors.go
+++ b/pkg/bindings/errors.go
@@ -12,17 +12,22 @@ var (
ErrNotImplemented = errors.New("function not implemented")
)
-func handleError(data []byte) error {
- e := errorhandling.ErrorModel{}
- if err := json.Unmarshal(data, &e); err != nil {
+func handleError(data []byte, unmarshalErrorInto interface{}) error {
+ if err := json.Unmarshal(data, unmarshalErrorInto); err != nil {
return err
}
- return e
+ return unmarshalErrorInto.(error)
}
// Process drains the response body, and processes the HTTP status code
// Note: Closing the response.Body is left to the caller
func (h APIResponse) Process(unmarshalInto interface{}) error {
+ return h.ProcessWithError(unmarshalInto, &errorhandling.ErrorModel{})
+}
+
+// Process drains the response body, and processes the HTTP status code
+// Note: Closing the response.Body is left to the caller
+func (h APIResponse) ProcessWithError(unmarshalInto interface{}, unmarshalErrorInto interface{}) error {
data, err := ioutil.ReadAll(h.Response.Body)
if err != nil {
return errors.Wrap(err, "unable to process API response")
@@ -33,14 +38,22 @@ func (h APIResponse) Process(unmarshalInto interface{}) error {
}
return nil
}
+
+ if h.IsConflictError() {
+ return handleError(data, unmarshalErrorInto)
+ }
+
// TODO should we add a debug here with the response code?
- return handleError(data)
+ return handleError(data, &errorhandling.ErrorModel{})
}
func CheckResponseCode(inError error) (int, error) {
- e, ok := inError.(errorhandling.ErrorModel)
- if !ok {
+ switch e := inError.(type) {
+ case *errorhandling.ErrorModel:
+ return e.Code(), nil
+ case *errorhandling.PodConflictErrorModel:
+ return e.Code(), nil
+ default:
return -1, errors.New("error is not type ErrorModel")
}
- return e.Code(), nil
}
diff --git a/pkg/bindings/images/types.go b/pkg/bindings/images/types.go
index dc6bd91c3..a44a3527f 100644
--- a/pkg/bindings/images/types.go
+++ b/pkg/bindings/images/types.go
@@ -133,8 +133,6 @@ type SearchOptions struct {
Filters map[string][]string
// Limit the number of results.
Limit *int
- // NoTrunc will not truncate the output.
- NoTrunc *bool
// SkipTLSVerify to skip HTTPS and certificate verification.
SkipTLSVerify *bool
// ListTags search the available tags of the repository
diff --git a/pkg/bindings/images/types_search_options.go b/pkg/bindings/images/types_search_options.go
index e38ef9fb1..4424f1504 100644
--- a/pkg/bindings/images/types_search_options.go
+++ b/pkg/bindings/images/types_search_options.go
@@ -62,21 +62,6 @@ func (o *SearchOptions) GetLimit() int {
return *o.Limit
}
-// WithNoTrunc set field NoTrunc to given value
-func (o *SearchOptions) WithNoTrunc(value bool) *SearchOptions {
- o.NoTrunc = &value
- return o
-}
-
-// GetNoTrunc returns value of field NoTrunc
-func (o *SearchOptions) GetNoTrunc() bool {
- if o.NoTrunc == nil {
- var z bool
- return z
- }
- return *o.NoTrunc
-}
-
// WithSkipTLSVerify set field SkipTLSVerify to given value
func (o *SearchOptions) WithSkipTLSVerify(value bool) *SearchOptions {
o.SkipTLSVerify = &value
diff --git a/pkg/bindings/play/types.go b/pkg/bindings/play/types.go
index fdfc4a6fa..011f7f9ca 100644
--- a/pkg/bindings/play/types.go
+++ b/pkg/bindings/play/types.go
@@ -37,6 +37,8 @@ type KubeOptions struct {
ConfigMaps *[]string
// LogDriver for the container. For example: journald
LogDriver *string
+ // LogOptions for the container. For example: journald
+ LogOptions *[]string
// Start - don't start the pod if false
Start *bool
}
diff --git a/pkg/bindings/play/types_kube_options.go b/pkg/bindings/play/types_kube_options.go
index 1a6324302..344771e0c 100644
--- a/pkg/bindings/play/types_kube_options.go
+++ b/pkg/bindings/play/types_kube_options.go
@@ -228,6 +228,21 @@ func (o *KubeOptions) GetLogDriver() string {
return *o.LogDriver
}
+// WithLogOptions set field LogOptions to given value
+func (o *KubeOptions) WithLogOptions(value []string) *KubeOptions {
+ o.LogOptions = &value
+ return o
+}
+
+// GetLogOptions returns value of field LogOptions
+func (o *KubeOptions) GetLogOptions() []string {
+ if o.LogOptions == nil {
+ var z []string
+ return z
+ }
+ return *o.LogOptions
+}
+
// WithStart set field Start to given value
func (o *KubeOptions) WithStart(value bool) *KubeOptions {
o.Start = &value
diff --git a/pkg/bindings/pods/pods.go b/pkg/bindings/pods/pods.go
index a1a431a3b..3b5832373 100644
--- a/pkg/bindings/pods/pods.go
+++ b/pkg/bindings/pods/pods.go
@@ -9,6 +9,7 @@ import (
"github.com/containers/podman/v3/pkg/api/handlers"
"github.com/containers/podman/v3/pkg/bindings"
"github.com/containers/podman/v3/pkg/domain/entities"
+ "github.com/containers/podman/v3/pkg/errorhandling"
jsoniter "github.com/json-iterator/go"
)
@@ -97,7 +98,7 @@ func Kill(ctx context.Context, nameOrID string, options *KillOptions) (*entities
}
defer response.Body.Close()
- return &report, response.Process(&report)
+ return &report, response.ProcessWithError(&report, &errorhandling.PodConflictErrorModel{})
}
// Pause pauses all running containers in a given pod.
@@ -117,7 +118,7 @@ func Pause(ctx context.Context, nameOrID string, options *PauseOptions) (*entiti
}
defer response.Body.Close()
- return &report, response.Process(&report)
+ return &report, response.ProcessWithError(&report, &errorhandling.PodConflictErrorModel{})
}
// Prune by default removes all non-running pods in local storage.
@@ -184,7 +185,7 @@ func Restart(ctx context.Context, nameOrID string, options *RestartOptions) (*en
}
defer response.Body.Close()
- return &report, response.Process(&report)
+ return &report, response.ProcessWithError(&report, &errorhandling.PodConflictErrorModel{})
}
// Remove deletes a Pod from from local storage. The optional force parameter denotes
@@ -232,7 +233,8 @@ func Start(ctx context.Context, nameOrID string, options *StartOptions) (*entiti
report.Id = nameOrID
return &report, nil
}
- return &report, response.Process(&report)
+
+ return &report, response.ProcessWithError(&report, &errorhandling.PodConflictErrorModel{})
}
// Stop stops all containers in a Pod. The optional timeout parameter can be
@@ -260,7 +262,7 @@ func Stop(ctx context.Context, nameOrID string, options *StopOptions) (*entities
report.Id = nameOrID
return &report, nil
}
- return &report, response.Process(&report)
+ return &report, response.ProcessWithError(&report, &errorhandling.PodConflictErrorModel{})
}
// Top gathers statistics about the running processes in a pod. The nameOrID can be a pod name
@@ -316,7 +318,7 @@ func Unpause(ctx context.Context, nameOrID string, options *UnpauseOptions) (*en
}
defer response.Body.Close()
- return &report, response.Process(&report)
+ return &report, response.ProcessWithError(&report, &errorhandling.PodConflictErrorModel{})
}
// Stats display resource-usage statistics of one or more pods.
diff --git a/pkg/bindings/test/common_test.go b/pkg/bindings/test/common_test.go
index 91ebe21fc..d996595bf 100644
--- a/pkg/bindings/test/common_test.go
+++ b/pkg/bindings/test/common_test.go
@@ -225,12 +225,23 @@ func (b *bindingTest) RunTopContainer(containerName *string, podName *string) (s
// This method creates a pod with the given pod name.
// Podname is an optional parameter
func (b *bindingTest) Podcreate(name *string) {
+ b.PodcreateAndExpose(name, nil)
+}
+
+// This method creates a pod with the given pod name and publish port.
+// Podname is an optional parameter
+// port is an optional parameter
+func (b *bindingTest) PodcreateAndExpose(name *string, port *string) {
+ command := []string{"pod", "create"}
if name != nil {
podname := *name
- b.runPodman([]string{"pod", "create", "--name", podname}).Wait(45)
- } else {
- b.runPodman([]string{"pod", "create"}).Wait(45)
+ command = append(command, "--name", podname)
+ }
+ if port != nil {
+ podport := *port
+ command = append(command, "--publish", podport)
}
+ b.runPodman(command).Wait(45)
}
// StringInSlice returns a boolean based on whether a given
diff --git a/pkg/bindings/test/containers_test.go b/pkg/bindings/test/containers_test.go
index b9ed67255..0f535bc31 100644
--- a/pkg/bindings/test/containers_test.go
+++ b/pkg/bindings/test/containers_test.go
@@ -259,6 +259,7 @@ var _ = Describe("Podman containers ", func() {
_, err = bt.RunTopContainer(&name, nil)
Expect(err).To(BeNil())
go func() {
+ defer GinkgoRecover()
exitCode, err = containers.Wait(bt.conn, name, nil)
errChan <- err
close(errChan)
@@ -281,6 +282,7 @@ var _ = Describe("Podman containers ", func() {
_, err := bt.RunTopContainer(&name, nil)
Expect(err).To(BeNil())
go func() {
+ defer GinkgoRecover()
exitCode, err = containers.Wait(bt.conn, name, new(containers.WaitOptions).WithCondition([]define.ContainerStatus{pause}))
errChan <- err
close(errChan)
@@ -366,7 +368,10 @@ var _ = Describe("Podman containers ", func() {
opts := new(containers.LogOptions).WithStdout(true).WithFollow(true)
go func() {
- containers.Logs(bt.conn, r.ID, opts, stdoutChan, nil)
+ defer GinkgoRecover()
+ err := containers.Logs(bt.conn, r.ID, opts, stdoutChan, nil)
+ close(stdoutChan)
+ Expect(err).ShouldNot(HaveOccurred())
}()
o := <-stdoutChan
o = strings.TrimSpace(o)
diff --git a/pkg/bindings/test/pods_test.go b/pkg/bindings/test/pods_test.go
index 5331cf439..879d4d00d 100644
--- a/pkg/bindings/test/pods_test.go
+++ b/pkg/bindings/test/pods_test.go
@@ -1,6 +1,7 @@
package test_bindings
import (
+ "fmt"
"net/http"
"strings"
"time"
@@ -9,7 +10,9 @@ import (
"github.com/containers/podman/v3/pkg/bindings"
"github.com/containers/podman/v3/pkg/bindings/pods"
"github.com/containers/podman/v3/pkg/domain/entities"
+ "github.com/containers/podman/v3/pkg/errorhandling"
"github.com/containers/podman/v3/pkg/specgen"
+ "github.com/containers/podman/v3/utils"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gexec"
@@ -208,6 +211,29 @@ var _ = Describe("Podman pods", func() {
}
})
+ It("start pod with port conflict", func() {
+ randomport, err := utils.GetRandomPort()
+ Expect(err).To(BeNil())
+
+ portPublish := fmt.Sprintf("%d:%d", randomport, randomport)
+ var podwithport string = "newpodwithport"
+ bt.PodcreateAndExpose(&podwithport, &portPublish)
+
+ // Start pod and expose port 12345
+ _, err = pods.Start(bt.conn, podwithport, nil)
+ Expect(err).To(BeNil())
+
+ // Start another pod and expose same port 12345
+ var podwithport2 string = "newpodwithport2"
+ bt.PodcreateAndExpose(&podwithport2, &portPublish)
+
+ _, err = pods.Start(bt.conn, podwithport2, nil)
+ Expect(err).ToNot(BeNil())
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", http.StatusConflict))
+ Expect(err).To(BeAssignableToTypeOf(&errorhandling.PodConflictErrorModel{}))
+ })
+
It("start stop restart pod", func() {
// Start an invalid pod
_, err = pods.Start(bt.conn, "dummyName", nil)
diff --git a/pkg/cgroups/cgroups.go b/pkg/cgroups/cgroups.go
index 4bb8de69b..d0c090012 100644
--- a/pkg/cgroups/cgroups.go
+++ b/pkg/cgroups/cgroups.go
@@ -129,8 +129,8 @@ func init() {
func getAvailableControllers(exclude map[string]controllerHandler, cgroup2 bool) ([]controller, error) {
if cgroup2 {
controllers := []controller{}
- subtreeControl := cgroupRoot + "/cgroup.subtree_control"
- // rootless cgroupv2: check available controllers for current user ,systemd or servicescope will inherit
+ controllersFile := cgroupRoot + "/cgroup.controllers"
+ // rootless cgroupv2: check available controllers for current user, systemd or servicescope will inherit
if rootless.IsRootless() {
userSlice, err := getCgroupPathForCurrentProcess()
if err != nil {
@@ -138,13 +138,13 @@ func getAvailableControllers(exclude map[string]controllerHandler, cgroup2 bool)
}
//userSlice already contains '/' so not adding here
basePath := cgroupRoot + userSlice
- subtreeControl = fmt.Sprintf("%s/cgroup.subtree_control", basePath)
+ controllersFile = fmt.Sprintf("%s/cgroup.controllers", basePath)
}
- subtreeControlBytes, err := ioutil.ReadFile(subtreeControl)
+ controllersFileBytes, err := ioutil.ReadFile(controllersFile)
if err != nil {
- return nil, errors.Wrapf(err, "failed while reading controllers for cgroup v2 from %q", subtreeControl)
+ return nil, errors.Wrapf(err, "failed while reading controllers for cgroup v2 from %q", controllersFile)
}
- for _, controllerName := range strings.Fields(string(subtreeControlBytes)) {
+ for _, controllerName := range strings.Fields(string(controllersFileBytes)) {
c := controller{
name: controllerName,
symlink: false,
@@ -461,10 +461,10 @@ func (c *CgroupControl) CreateSystemdUnit(path string) error {
return systemdCreate(path, conn)
}
-// GetUserConnection returns an user connection to D-BUS
+// GetUserConnection returns a user connection to D-BUS
func GetUserConnection(uid int) (*systemdDbus.Conn, error) {
return systemdDbus.NewConnection(func() (*dbus.Conn, error) {
- return dbusAuthConnection(uid, dbus.SessionBusPrivate)
+ return dbusAuthConnection(uid, dbus.SessionBusPrivateNoAutoStartup)
})
}
diff --git a/pkg/checkpoint/checkpoint_restore.go b/pkg/checkpoint/checkpoint_restore.go
index f53e31f9b..da82c9745 100644
--- a/pkg/checkpoint/checkpoint_restore.go
+++ b/pkg/checkpoint/checkpoint_restore.go
@@ -6,6 +6,7 @@ import (
"os"
metadata "github.com/checkpoint-restore/checkpointctl/lib"
+ "github.com/checkpoint-restore/go-criu/v5/stats"
"github.com/containers/common/libimage"
"github.com/containers/common/pkg/config"
"github.com/containers/podman/v3/libpod"
@@ -39,6 +40,7 @@ func CRImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, restoreOpt
"volumes",
"ctr.log",
"artifacts",
+ stats.StatsDump,
metadata.RootFsDiffTar,
metadata.DeletedFilesFile,
metadata.NetworkStatusFile,
@@ -193,7 +195,7 @@ func CRImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, restoreOpt
}
if len(restoreOptions.PublishPorts) > 0 {
- ports, _, _, err := generate.ParsePortMapping(restoreOptions.PublishPorts)
+ ports, err := generate.ParsePortMapping(restoreOptions.PublishPorts, nil)
if err != nil {
return nil, err
}
diff --git a/pkg/domain/entities/container_ps.go b/pkg/domain/entities/container_ps.go
index 58f231a2f..d018d373f 100644
--- a/pkg/domain/entities/container_ps.go
+++ b/pkg/domain/entities/container_ps.go
@@ -54,7 +54,7 @@ type ListContainer struct {
// boolean to be set
PodName string
// Port mappings
- Ports []types.OCICNIPortMapping
+ Ports []types.PortMapping
// Size of the container rootfs. Requires the size boolean to be true
Size *define.ContainerSize
// Time when container started
diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go
index deae85fe1..869c616ea 100644
--- a/pkg/domain/entities/containers.go
+++ b/pkg/domain/entities/containers.go
@@ -422,7 +422,7 @@ type ContainerPortOptions struct {
// the CLI to output ports
type ContainerPortReport struct {
Id string //nolint
- Ports []nettypes.OCICNIPortMapping
+ Ports []nettypes.PortMapping
}
// ContainerCpOptions describes input options for cp.
diff --git a/pkg/domain/entities/engine_image.go b/pkg/domain/entities/engine_image.go
index b0f9ae408..d72f64b5e 100644
--- a/pkg/domain/entities/engine_image.go
+++ b/pkg/domain/entities/engine_image.go
@@ -27,6 +27,7 @@ type ImageEngine interface {
ShowTrust(ctx context.Context, args []string, options ShowTrustOptions) (*ShowTrustReport, error)
Shutdown(ctx context.Context)
Tag(ctx context.Context, nameOrID string, tags []string, options ImageTagOptions) error
+ Transfer(ctx context.Context, scpOpts ImageScpOptions) error
Tree(ctx context.Context, nameOrID string, options ImageTreeOptions) (*ImageTreeReport, error)
Unmount(ctx context.Context, images []string, options ImageUnmountOptions) ([]*ImageUnmountReport, error)
Untag(ctx context.Context, nameOrID string, tags []string, options ImageUntagOptions) error
diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go
index 2822b1ad7..7583ce442 100644
--- a/pkg/domain/entities/images.go
+++ b/pkg/domain/entities/images.go
@@ -50,6 +50,7 @@ func (i *Image) Id() string { // nolint
return i.ID
}
+// swagger:model LibpodImageSummary
type ImageSummary struct {
ID string `json:"Id"`
ParentId string // nolint
@@ -218,8 +219,6 @@ type ImageSearchOptions struct {
Filters []string
// Limit the number of results.
Limit int
- // NoTrunc will not truncate the output.
- NoTrunc bool
// SkipTLSVerify to skip HTTPS and certificate verification.
SkipTLSVerify types.OptionalBool
// ListTags search the available tags of the repository
@@ -330,6 +329,10 @@ type ImageScpOptions struct {
Save ImageSaveOptions
// Load options used for the second half of the scp operation
Load ImageLoadOptions
+ // Rootless determines whether we are loading locally from root storage to rootless storage
+ Rootless bool
+ // User is used in conjunction with Rootless to determine which user to use to obtain the uid
+ User string
}
// ImageTreeOptions provides options for ImageEngine.Tree()
diff --git a/pkg/domain/entities/play.go b/pkg/domain/entities/play.go
index 715d8acaf..ad35dfe25 100644
--- a/pkg/domain/entities/play.go
+++ b/pkg/domain/entities/play.go
@@ -46,6 +46,8 @@ type PlayKubeOptions struct {
ConfigMaps []string
// LogDriver for the container. For example: journald
LogDriver string
+ // LogOptions for the log driver for the container.
+ LogOptions []string
// Start - don't start the pod if false
Start types.OptionalBool
}
diff --git a/pkg/domain/entities/pods.go b/pkg/domain/entities/pods.go
index 309677396..1df18be58 100644
--- a/pkg/domain/entities/pods.go
+++ b/pkg/domain/entities/pods.go
@@ -5,7 +5,9 @@ import (
"strings"
"time"
+ commonFlag "github.com/containers/common/pkg/flag"
"github.com/containers/podman/v3/libpod/define"
+ "github.com/containers/podman/v3/libpod/network/types"
"github.com/containers/podman/v3/pkg/specgen"
"github.com/containers/podman/v3/pkg/util"
"github.com/opencontainers/runtime-spec/specs-go"
@@ -235,13 +237,13 @@ type ContainerCreateOptions struct {
SignaturePolicy string
StopSignal string
StopTimeout uint
- StorageOpt []string
+ StorageOpts []string
SubUIDName string
SubGIDName string
Sysctl []string
Systemd string
Timeout uint
- TLSVerify bool
+ TLSVerify commonFlag.OptionalBool
TmpFS []string
TTY bool
Timezone string
@@ -317,7 +319,8 @@ func ToPodSpecGen(s specgen.PodSpecGenerator, p *PodCreateOptions) (*specgen.Pod
if p.Net != nil {
s.NetNS = p.Net.Network
s.StaticIP = p.Net.StaticIP
- s.StaticMAC = p.Net.StaticMAC
+ // type cast to types.HardwareAddr
+ s.StaticMAC = (*types.HardwareAddr)(p.Net.StaticMAC)
s.PortMappings = p.Net.PublishPorts
s.CNINetworks = p.Net.CNINetworks
s.NetworkOptions = p.Net.NetworkOptions
diff --git a/pkg/domain/entities/reports/prune.go b/pkg/domain/entities/reports/prune.go
index 5494ac3ae..219e35b67 100644
--- a/pkg/domain/entities/reports/prune.go
+++ b/pkg/domain/entities/reports/prune.go
@@ -1,9 +1,9 @@
package reports
type PruneReport struct {
- Id string //nolint
- Err error
- Size uint64
+ Id string `json:"Id"` //nolint
+ Err error `json:"Err,omitempty"`
+ Size uint64 `json:"Size"`
}
func PruneReportsIds(r []*PruneReport) []string {
diff --git a/pkg/domain/entities/system.go b/pkg/domain/entities/system.go
index fe041dec8..49f0c2323 100644
--- a/pkg/domain/entities/system.go
+++ b/pkg/domain/entities/system.go
@@ -100,7 +100,7 @@ type SystemVersionReport struct {
// SystemUnshareOptions describes the options for the unshare command
type SystemUnshareOptions struct {
- RootlessCNI bool
+ RootlessNetNS bool
}
type ComponentVersion struct {
diff --git a/pkg/domain/entities/volumes.go b/pkg/domain/entities/volumes.go
index 2ecfb4446..9b2a170e2 100644
--- a/pkg/domain/entities/volumes.go
+++ b/pkg/domain/entities/volumes.go
@@ -78,8 +78,10 @@ type VolumeCreateOptions struct {
Name string `schema:"name"`
// Volume driver to use
Driver string `schema:"driver"`
- // User-defined key/value metadata.
+ // User-defined key/value metadata. Provided for compatibility
Label map[string]string `schema:"label"`
+ // User-defined key/value metadata. Preferred field, will override Label
+ Labels map[string]string `schema:"labels"`
// Mapping of driver options and values.
Options map[string]string `schema:"opts"`
}
diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go
index 6ca142618..c30129001 100644
--- a/pkg/domain/infra/abi/containers.go
+++ b/pkg/domain/infra/abi/containers.go
@@ -1319,6 +1319,15 @@ func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []stri
if options.Interval < 1 {
return nil, errors.New("Invalid interval, must be a positive number greater zero")
}
+ if rootless.IsRootless() {
+ unified, err := cgroups.IsCgroup2UnifiedMode()
+ if err != nil {
+ return nil, err
+ }
+ if !unified {
+ return nil, errors.New("stats is not supported in rootless mode without cgroups v2")
+ }
+ }
statsChan = make(chan entities.ContainerStatsReport, 1)
containerFunc := ic.Libpod.GetRunningContainers
diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go
index 8a0b87cab..5c0227986 100644
--- a/pkg/domain/infra/abi/images.go
+++ b/pkg/domain/infra/abi/images.go
@@ -6,9 +6,12 @@ import (
"io/ioutil"
"net/url"
"os"
+ "os/exec"
+ "os/user"
"path"
"path/filepath"
"strconv"
+ "strings"
"github.com/containers/common/libimage"
"github.com/containers/common/pkg/config"
@@ -18,6 +21,7 @@ import (
"github.com/containers/image/v5/signature"
"github.com/containers/image/v5/transports"
"github.com/containers/image/v5/transports/alltransports"
+ "github.com/containers/podman/v3/libpod/define"
"github.com/containers/podman/v3/pkg/domain/entities"
"github.com/containers/podman/v3/pkg/domain/entities/reports"
domainUtils "github.com/containers/podman/v3/pkg/domain/utils"
@@ -57,7 +61,7 @@ func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOption
pruneOptions.Filters = append(pruneOptions.Filters, "containers=false")
}
- var pruneReports []*reports.PruneReport
+ pruneReports := make([]*reports.PruneReport, 0)
// Now prune all images until we converge.
numPreviouslyRemovedImages := 1
@@ -330,8 +334,71 @@ func (ir *ImageEngine) Push(ctx context.Context, source string, destination stri
return pushError
}
+// Transfer moves images from root to rootless storage so the user specified in the scp call can access and use the image modified by root
+func (ir *ImageEngine) Transfer(ctx context.Context, scpOpts entities.ImageScpOptions) error {
+ if scpOpts.User == "" {
+ return errors.Wrapf(define.ErrInvalidArg, "you must define a user when transferring from root to rootless storage")
+ }
+ var u *user.User
+ scpOpts.User = strings.Split(scpOpts.User, ":")[0] // split in case provided with uid:gid
+ _, err := strconv.Atoi(scpOpts.User)
+ if err != nil {
+ u, err = user.Lookup(scpOpts.User)
+ if err != nil {
+ return err
+ }
+ } else {
+ u, err = user.LookupId(scpOpts.User)
+ if err != nil {
+ return err
+ }
+ }
+ uid, err := strconv.Atoi(u.Uid)
+ if err != nil {
+ return err
+ }
+ gid, err := strconv.Atoi(u.Gid)
+ if err != nil {
+ return err
+ }
+ err = os.Chown(scpOpts.Save.Output, uid, gid) // chown the output because was created by root so we need to give th euser read access
+ if err != nil {
+ return err
+ }
+
+ podman, err := os.Executable()
+ if err != nil {
+ return err
+ }
+ machinectl, err := exec.LookPath("machinectl")
+ if err != nil {
+ logrus.Warn("defaulting to su since machinectl is not available, su will fail if no user session is available")
+ cmd := exec.Command("su", "-l", u.Username, "--command", podman+" --log-level="+logrus.GetLevel().String()+" --cgroup-manager=cgroupfs load --input="+scpOpts.Save.Output) // load the new image to the rootless storage
+ cmd.Stderr = os.Stderr
+ cmd.Stdout = os.Stdout
+ logrus.Debug("Executing load command su")
+ err = cmd.Run()
+ if err != nil {
+ return err
+ }
+ } else {
+ cmd := exec.Command(machinectl, "shell", "-q", u.Username+"@.host", podman, "--log-level="+logrus.GetLevel().String(), "--cgroup-manager=cgroupfs", "load", "--input", scpOpts.Save.Output) // load the new image to the rootless storage
+ cmd.Stderr = os.Stderr
+ cmd.Stdout = os.Stdout
+ logrus.Debug("Executing load command machinectl")
+ err = cmd.Run()
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
func (ir *ImageEngine) Tag(ctx context.Context, nameOrID string, tags []string, options entities.ImageTagOptions) error {
- image, _, err := ir.Libpod.LibimageRuntime().LookupImage(nameOrID, nil)
+ // Allow tagging manifest list instead of resolving instances from manifest
+ lookupOptions := &libimage.LookupImageOptions{ManifestList: true}
+ image, _, err := ir.Libpod.LibimageRuntime().LookupImage(nameOrID, lookupOptions)
if err != nil {
return err
}
@@ -417,6 +484,7 @@ func (ir *ImageEngine) Import(ctx context.Context, options entities.ImageImportO
return &entities.ImageImportReport{Id: imageID}, nil
}
+// Search for images using term and filters
func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.ImageSearchOptions) ([]entities.ImageSearchReport, error) {
filter, err := libimage.ParseSearchFilter(opts.Filters)
if err != nil {
@@ -427,7 +495,7 @@ func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.Im
Authfile: opts.Authfile,
Filter: *filter,
Limit: opts.Limit,
- NoTrunc: opts.NoTrunc,
+ NoTrunc: true,
InsecureSkipTLSVerify: opts.SkipTLSVerify,
ListTags: opts.ListTags,
}
@@ -454,7 +522,7 @@ func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.Im
return reports, nil
}
-// GetConfig returns a copy of the configuration used by the runtime
+// Config returns a copy of the configuration used by the runtime
func (ir *ImageEngine) Config(_ context.Context) (*config.Config, error) {
return ir.Libpod.GetConfig()
}
diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go
index 751d6cc05..4d21751d1 100644
--- a/pkg/domain/infra/abi/play.go
+++ b/pkg/domain/infra/abi/play.go
@@ -333,6 +333,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
NetNSIsHost: p.NetNS.IsHost(),
SecretsManager: secretsManager,
LogDriver: options.LogDriver,
+ LogOptions: options.LogOptions,
Labels: labels,
InitContainerType: define.AlwaysInitContainer,
}
@@ -371,6 +372,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
NetNSIsHost: p.NetNS.IsHost(),
SecretsManager: secretsManager,
LogDriver: options.LogDriver,
+ LogOptions: options.LogOptions,
Labels: labels,
}
specGen, err := kube.ToSpecGen(ctx, &specgenOpts)
diff --git a/pkg/domain/infra/abi/system.go b/pkg/domain/infra/abi/system.go
index e326f26a8..7da7754f2 100644
--- a/pkg/domain/infra/abi/system.go
+++ b/pkg/domain/infra/abi/system.go
@@ -360,15 +360,15 @@ func (ic *ContainerEngine) Unshare(ctx context.Context, args []string, options e
return cmd.Run()
}
- if options.RootlessCNI {
- rootlesscni, err := ic.Libpod.GetRootlessCNINetNs(true)
+ if options.RootlessNetNS {
+ rootlessNetNS, err := ic.Libpod.GetRootlessNetNs(true)
if err != nil {
return err
}
// make sure to unlock, unshare can run for a long time
- rootlesscni.Lock.Unlock()
- defer rootlesscni.Cleanup(ic.Libpod)
- return rootlesscni.Do(unshare)
+ rootlessNetNS.Lock.Unlock()
+ defer rootlessNetNS.Cleanup(ic.Libpod)
+ return rootlessNetNS.Do(unshare)
}
return unshare()
}
diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go
index 3f78ba7bc..5b5a1912c 100644
--- a/pkg/domain/infra/tunnel/containers.go
+++ b/pkg/domain/infra/tunnel/containers.go
@@ -228,7 +228,7 @@ func (ic *ContainerEngine) ContainerInspect(ctx context.Context, namesOrIds []st
for _, name := range namesOrIds {
inspect, err := containers.Inspect(ic.ClientCtx, name, options)
if err != nil {
- errModel, ok := err.(errorhandling.ErrorModel)
+ errModel, ok := err.(*errorhandling.ErrorModel)
if !ok {
return nil, nil, err
}
diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go
index d41a20348..fde57972f 100644
--- a/pkg/domain/infra/tunnel/images.go
+++ b/pkg/domain/infra/tunnel/images.go
@@ -12,7 +12,8 @@ import (
"github.com/containers/common/pkg/config"
"github.com/containers/image/v5/docker/reference"
"github.com/containers/image/v5/types"
- images "github.com/containers/podman/v3/pkg/bindings/images"
+ "github.com/containers/podman/v3/libpod/define"
+ "github.com/containers/podman/v3/pkg/bindings/images"
"github.com/containers/podman/v3/pkg/domain/entities"
"github.com/containers/podman/v3/pkg/domain/entities/reports"
"github.com/containers/podman/v3/pkg/domain/utils"
@@ -122,6 +123,10 @@ func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, opts entities.
return &entities.ImagePullReport{Images: pulledImages}, nil
}
+func (ir *ImageEngine) Transfer(ctx context.Context, scpOpts entities.ImageScpOptions) error {
+ return errors.Wrapf(define.ErrNotImplemented, "cannot use the remote client to transfer images between root and rootless storage")
+}
+
func (ir *ImageEngine) Tag(ctx context.Context, nameOrID string, tags []string, opt entities.ImageTagOptions) error {
options := new(images.TagOptions)
for _, newTag := range tags {
@@ -188,7 +193,7 @@ func (ir *ImageEngine) Inspect(ctx context.Context, namesOrIDs []string, opts en
for _, i := range namesOrIDs {
r, err := images.GetImage(ir.ClientCtx, i, options)
if err != nil {
- errModel, ok := err.(errorhandling.ErrorModel)
+ errModel, ok := err.(*errorhandling.ErrorModel)
if !ok {
return nil, nil, err
}
@@ -323,7 +328,7 @@ func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.Im
options := new(images.SearchOptions)
options.WithAuthfile(opts.Authfile).WithFilters(mappedFilters).WithLimit(opts.Limit)
- options.WithListTags(opts.ListTags).WithNoTrunc(opts.NoTrunc)
+ options.WithListTags(opts.ListTags)
if s := opts.SkipTLSVerify; s != types.OptionalBoolUndefined {
if s == types.OptionalBoolTrue {
options.WithSkipTLSVerify(true)
diff --git a/pkg/domain/infra/tunnel/network.go b/pkg/domain/infra/tunnel/network.go
index 79fba1943..069982d30 100644
--- a/pkg/domain/infra/tunnel/network.go
+++ b/pkg/domain/infra/tunnel/network.go
@@ -25,7 +25,7 @@ func (ic *ContainerEngine) NetworkInspect(ctx context.Context, namesOrIds []stri
for _, name := range namesOrIds {
report, err := network.Inspect(ic.ClientCtx, name, options)
if err != nil {
- errModel, ok := err.(errorhandling.ErrorModel)
+ errModel, ok := err.(*errorhandling.ErrorModel)
if !ok {
return nil, nil, err
}
diff --git a/pkg/domain/infra/tunnel/play.go b/pkg/domain/infra/tunnel/play.go
index 0b1c3d2ca..75952ce2c 100644
--- a/pkg/domain/infra/tunnel/play.go
+++ b/pkg/domain/infra/tunnel/play.go
@@ -13,6 +13,9 @@ func (ic *ContainerEngine) PlayKube(ctx context.Context, path string, opts entit
options.WithCertDir(opts.CertDir).WithQuiet(opts.Quiet).WithSignaturePolicy(opts.SignaturePolicy).WithConfigMaps(opts.ConfigMaps)
options.WithLogDriver(opts.LogDriver).WithNetwork(opts.Network).WithSeccompProfileRoot(opts.SeccompProfileRoot)
options.WithStaticIPs(opts.StaticIPs).WithStaticMACs(opts.StaticMACs)
+ if len(opts.LogOptions) > 0 {
+ options.WithLogOptions(opts.LogOptions)
+ }
options.WithNoHosts(opts.NoHosts)
if s := opts.SkipTLSVerify; s != types.OptionalBoolUndefined {
options.WithSkipTLSVerify(s == types.OptionalBoolTrue)
diff --git a/pkg/domain/infra/tunnel/secrets.go b/pkg/domain/infra/tunnel/secrets.go
index 6337c7fbe..e5fa200bd 100644
--- a/pkg/domain/infra/tunnel/secrets.go
+++ b/pkg/domain/infra/tunnel/secrets.go
@@ -28,7 +28,7 @@ func (ic *ContainerEngine) SecretInspect(ctx context.Context, nameOrIDs []string
for _, name := range nameOrIDs {
inspected, err := secrets.Inspect(ic.ClientCtx, name, nil)
if err != nil {
- errModel, ok := err.(errorhandling.ErrorModel)
+ errModel, ok := err.(*errorhandling.ErrorModel)
if !ok {
return nil, nil, err
}
@@ -67,7 +67,7 @@ func (ic *ContainerEngine) SecretRm(ctx context.Context, nameOrIDs []string, opt
for _, name := range nameOrIDs {
secret, err := secrets.Inspect(ic.ClientCtx, name, nil)
if err != nil {
- errModel, ok := err.(errorhandling.ErrorModel)
+ errModel, ok := err.(*errorhandling.ErrorModel)
if !ok {
return nil, err
}
diff --git a/pkg/domain/infra/tunnel/volumes.go b/pkg/domain/infra/tunnel/volumes.go
index cfd1574c3..ccb363935 100644
--- a/pkg/domain/infra/tunnel/volumes.go
+++ b/pkg/domain/infra/tunnel/volumes.go
@@ -59,7 +59,7 @@ func (ic *ContainerEngine) VolumeInspect(ctx context.Context, namesOrIds []strin
for _, id := range namesOrIds {
data, err := volumes.Inspect(ic.ClientCtx, id, nil)
if err != nil {
- errModel, ok := err.(errorhandling.ErrorModel)
+ errModel, ok := err.(*errorhandling.ErrorModel)
if !ok {
return nil, nil, err
}
diff --git a/pkg/errorhandling/errorhandling.go b/pkg/errorhandling/errorhandling.go
index 44a0c3efd..04110b62a 100644
--- a/pkg/errorhandling/errorhandling.go
+++ b/pkg/errorhandling/errorhandling.go
@@ -83,6 +83,12 @@ func Contains(err error, sub error) bool {
return strings.Contains(err.Error(), sub.Error())
}
+// PodConflictErrorModel is used in remote connections with podman
+type PodConflictErrorModel struct {
+ Errs []string
+ Id string //nolint
+}
+
// ErrorModel is used in remote connections with podman
type ErrorModel struct {
// API root cause formatted for automated parsing
@@ -106,3 +112,11 @@ func (e ErrorModel) Cause() error {
func (e ErrorModel) Code() int {
return e.ResponseCode
}
+
+func (e PodConflictErrorModel) Error() string {
+ return strings.Join(e.Errs, ",")
+}
+
+func (e PodConflictErrorModel) Code() int {
+ return 409
+}
diff --git a/pkg/machine/config.go b/pkg/machine/config.go
index 3ff5c7fe7..55d5dd7b4 100644
--- a/pkg/machine/config.go
+++ b/pkg/machine/config.go
@@ -57,6 +57,7 @@ type ListResponse struct {
CreatedAt time.Time
LastUp time.Time
Running bool
+ Stream string
VMType string
CPUs uint64
Memory uint64
diff --git a/pkg/machine/ignition.go b/pkg/machine/ignition.go
index e211f5ea6..42d729458 100644
--- a/pkg/machine/ignition.go
+++ b/pkg/machine/ignition.go
@@ -304,6 +304,24 @@ machine_enabled=true
},
})
+ setDockerHost := `export DOCKER_HOST="unix://$(podman info -f "{{.Host.RemoteSocket.Path}}")"
+`
+
+ files = append(files, File{
+ Node: Node{
+ Group: getNodeGrp("root"),
+ Path: "/etc/profile.d/docker-host.sh",
+ User: getNodeUsr("root"),
+ },
+ FileEmbedded1: FileEmbedded1{
+ Append: nil,
+ Contents: Resource{
+ Source: encodeDataURLPtr(setDockerHost),
+ },
+ Mode: intToPtr(0644),
+ },
+ })
+
return files
}
diff --git a/pkg/machine/pull.go b/pkg/machine/pull.go
index f79ac6ec4..3c8422a30 100644
--- a/pkg/machine/pull.go
+++ b/pkg/machine/pull.go
@@ -15,7 +15,7 @@ import (
"time"
"github.com/containers/image/v5/pkg/compression"
- "github.com/docker/docker/pkg/archive"
+ "github.com/containers/storage/pkg/archive"
"github.com/sirupsen/logrus"
"github.com/vbauerster/mpb/v6"
"github.com/vbauerster/mpb/v6/decor"
diff --git a/pkg/machine/qemu/config.go b/pkg/machine/qemu/config.go
index 9f5f45b58..c04773450 100644
--- a/pkg/machine/qemu/config.go
+++ b/pkg/machine/qemu/config.go
@@ -13,6 +13,8 @@ type MachineVM struct {
IdentityPath string
// IgnitionFilePath is the fq path to the .ign file
IgnitionFilePath string
+ // ImageStream is the update stream for the image
+ ImageStream string
// ImagePath is the fq path to
ImagePath string
// Memory in megabytes assigned to the vm
diff --git a/pkg/machine/qemu/machine.go b/pkg/machine/qemu/machine.go
index 727b3cda4..a7174aac3 100644
--- a/pkg/machine/qemu/machine.go
+++ b/pkg/machine/qemu/machine.go
@@ -143,6 +143,7 @@ func (v *MachineVM) Init(opts machine.InitOptions) error {
switch opts.ImagePath {
case "testing", "next", "stable", "":
// Get image as usual
+ v.ImageStream = opts.ImagePath
dd, err := machine.NewFcosDownloader(vmtype, v.Name, opts.ImagePath)
if err != nil {
return err
@@ -154,6 +155,7 @@ func (v *MachineVM) Init(opts machine.InitOptions) error {
default:
// The user has provided an alternate image which can be a file path
// or URL.
+ v.ImageStream = "custom"
g, err := machine.NewGenericDownloader(vmtype, v.Name, opts.ImagePath)
if err != nil {
return err
@@ -396,7 +398,6 @@ func (v *MachineVM) Stop(name string, _ machine.StopOptions) error {
return err
}
- fmt.Printf("Successfully stopped machine: %s", name)
return nil
}
@@ -595,6 +596,7 @@ func GetVMInfos() ([]*machine.ListResponse, error) {
listEntry := new(machine.ListResponse)
listEntry.Name = vm.Name
+ listEntry.Stream = vm.ImageStream
listEntry.VMType = "qemu"
listEntry.CPUs = vm.CPUs
listEntry.Memory = vm.Memory
diff --git a/pkg/rootlessport/rootlessport_linux.go b/pkg/rootlessport/rootlessport_linux.go
index 37fb7ce79..7e6075789 100644
--- a/pkg/rootlessport/rootlessport_linux.go
+++ b/pkg/rootlessport/rootlessport_linux.go
@@ -12,39 +12,18 @@
package rootlessport
import (
- "context"
- "encoding/json"
- "fmt"
- "io"
- "io/ioutil"
- "net"
- "os"
- "os/exec"
- "path/filepath"
-
- "github.com/containernetworking/plugins/pkg/ns"
"github.com/containers/podman/v3/libpod/network/types"
- "github.com/containers/storage/pkg/reexec"
- "github.com/pkg/errors"
- rkport "github.com/rootless-containers/rootlesskit/pkg/port"
- rkbuiltin "github.com/rootless-containers/rootlesskit/pkg/port/builtin"
- rkportutil "github.com/rootless-containers/rootlesskit/pkg/port/portutil"
- "github.com/sirupsen/logrus"
- "golang.org/x/sys/unix"
)
const (
- // ReexecKey is the reexec key for the parent process.
- ReexecKey = "containers-rootlessport"
- // reexecChildKey is used internally for the second reexec
- reexecChildKey = "containers-rootlessport-child"
- reexecChildEnvOpaque = "_CONTAINERS_ROOTLESSPORT_CHILD_OPAQUE"
+ // BinaryName is the binary name for the parent process.
+ BinaryName = "rootlessport"
)
// Config needs to be provided to the process via stdin as a JSON string.
// stdin needs to be closed after the message has been written.
type Config struct {
- Mappings []types.OCICNIPortMapping
+ Mappings []types.PortMapping
NetNSPath string
ExitFD int
ReadyFD int
@@ -53,325 +32,3 @@ type Config struct {
ContainerID string
RootlessCNI bool
}
-
-func init() {
- reexec.Register(ReexecKey, func() {
- if err := parent(); err != nil {
- fmt.Println(err)
- os.Exit(1)
- }
- })
- reexec.Register(reexecChildKey, func() {
- if err := child(); err != nil {
- fmt.Println(err)
- os.Exit(1)
- }
- })
-}
-
-func loadConfig(r io.Reader) (*Config, io.ReadCloser, io.WriteCloser, error) {
- stdin, err := ioutil.ReadAll(r)
- if err != nil {
- return nil, nil, nil, err
- }
- var cfg Config
- if err := json.Unmarshal(stdin, &cfg); err != nil {
- return nil, nil, nil, err
- }
- if cfg.NetNSPath == "" {
- return nil, nil, nil, errors.New("missing NetNSPath")
- }
- if cfg.ExitFD <= 0 {
- return nil, nil, nil, errors.New("missing ExitFD")
- }
- exitFile := os.NewFile(uintptr(cfg.ExitFD), "exitfile")
- if exitFile == nil {
- return nil, nil, nil, errors.New("invalid ExitFD")
- }
- if cfg.ReadyFD <= 0 {
- return nil, nil, nil, errors.New("missing ReadyFD")
- }
- readyFile := os.NewFile(uintptr(cfg.ReadyFD), "readyfile")
- if readyFile == nil {
- return nil, nil, nil, errors.New("invalid ReadyFD")
- }
- return &cfg, exitFile, readyFile, nil
-}
-
-func parent() error {
- // load config from stdin
- cfg, exitR, readyW, err := loadConfig(os.Stdin)
- if err != nil {
- return err
- }
-
- socketDir := filepath.Join(cfg.TmpDir, "rp")
- err = os.MkdirAll(socketDir, 0700)
- if err != nil {
- return err
- }
-
- // create the parent driver
- stateDir, err := ioutil.TempDir(cfg.TmpDir, "rootlessport")
- if err != nil {
- return err
- }
- defer os.RemoveAll(stateDir)
- driver, err := rkbuiltin.NewParentDriver(&logrusWriter{prefix: "parent: "}, stateDir)
- if err != nil {
- return err
- }
- initComplete := make(chan struct{})
- quit := make(chan struct{})
- errCh := make(chan error)
- // start the parent driver. initComplete will be closed when the child connected to the parent.
- logrus.Infof("Starting parent driver")
- go func() {
- driverErr := driver.RunParentDriver(initComplete, quit, nil)
- if driverErr != nil {
- logrus.WithError(driverErr).Warn("Parent driver exited")
- }
- errCh <- driverErr
- close(errCh)
- }()
- opaque := driver.OpaqueForChild()
- logrus.Infof("Opaque=%+v", opaque)
- opaqueJSON, err := json.Marshal(opaque)
- if err != nil {
- return err
- }
- childQuitR, childQuitW, err := os.Pipe()
- if err != nil {
- return err
- }
- defer func() {
- // stop the child
- logrus.Info("Stopping child driver")
- if err := childQuitW.Close(); err != nil {
- logrus.WithError(err).Warn("Unable to close childQuitW")
- }
- }()
-
- // reexec the child process in the child netns
- cmd := exec.Command("/proc/self/exe")
- cmd.Args = []string{reexecChildKey}
- cmd.Stdin = childQuitR
- cmd.Stdout = &logrusWriter{prefix: "child"}
- cmd.Stderr = cmd.Stdout
- cmd.Env = append(os.Environ(), reexecChildEnvOpaque+"="+string(opaqueJSON))
- childNS, err := ns.GetNS(cfg.NetNSPath)
- if err != nil {
- return err
- }
- if err := childNS.Do(func(_ ns.NetNS) error {
- logrus.Infof("Starting child driver in child netns (%q %v)", cmd.Path, cmd.Args)
- return cmd.Start()
- }); err != nil {
- return err
- }
-
- childErrCh := make(chan error)
- go func() {
- err := cmd.Wait()
- childErrCh <- err
- close(childErrCh)
- }()
-
- defer func() {
- if err := unix.Kill(cmd.Process.Pid, unix.SIGTERM); err != nil {
- logrus.WithError(err).Warn("Kill child process")
- }
- }()
-
- logrus.Info("Waiting for initComplete")
- // wait for the child to connect to the parent
-outer:
- for {
- select {
- case <-initComplete:
- logrus.Infof("initComplete is closed; parent and child established the communication channel")
- break outer
- case err := <-childErrCh:
- if err != nil {
- return err
- }
- case err := <-errCh:
- if err != nil {
- return err
- }
- }
- }
-
- defer func() {
- logrus.Info("Stopping parent driver")
- quit <- struct{}{}
- if err := <-errCh; err != nil {
- logrus.WithError(err).Warn("Parent driver returned error on exit")
- }
- }()
-
- // let parent expose ports
- logrus.Infof("Exposing ports %v", cfg.Mappings)
- if err := exposePorts(driver, cfg.Mappings, cfg.ChildIP); err != nil {
- return err
- }
-
- // we only need to have a socket to reload ports when we run under rootless cni
- if cfg.RootlessCNI {
- socketfile := filepath.Join(socketDir, cfg.ContainerID)
- // make sure to remove the file if it exists to prevent EADDRINUSE
- _ = os.Remove(socketfile)
- // workaround to bypass the 108 char socket path limit
- // open the fd and use the path to the fd as bind argument
- fd, err := unix.Open(socketDir, unix.O_PATH, 0)
- if err != nil {
- return err
- }
- socket, err := net.ListenUnix("unixpacket", &net.UnixAddr{Name: fmt.Sprintf("/proc/self/fd/%d/%s", fd, cfg.ContainerID), Net: "unixpacket"})
- if err != nil {
- return err
- }
- err = unix.Close(fd)
- // remove the socket file on exit
- defer os.Remove(socketfile)
- if err != nil {
- logrus.Warnf("Failed to close the socketDir fd: %v", err)
- }
- defer socket.Close()
- go serve(socket, driver)
- }
-
- logrus.Info("Ready")
-
- // https://github.com/containers/podman/issues/11248
- // Copy /dev/null to stdout and stderr to prevent SIGPIPE errors
- 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()
- }
- // write and close ReadyFD (convention is same as slirp4netns --ready-fd)
- if _, err := readyW.Write([]byte("1")); err != nil {
- return err
- }
- if err := readyW.Close(); err != nil {
- return err
- }
-
- // wait for ExitFD to be closed
- logrus.Info("Waiting for exitfd to be closed")
- if _, err := ioutil.ReadAll(exitR); err != nil {
- return err
- }
- return nil
-}
-
-func serve(listener net.Listener, pm rkport.Manager) {
- for {
- conn, err := listener.Accept()
- if err != nil {
- // we cannot log this error, stderr is already closed
- continue
- }
- ctx := context.TODO()
- err = handler(ctx, conn, pm)
- if err != nil {
- conn.Write([]byte(err.Error()))
- } else {
- conn.Write([]byte("OK"))
- }
- conn.Close()
- }
-}
-
-func handler(ctx context.Context, conn io.Reader, pm rkport.Manager) error {
- var childIP string
- dec := json.NewDecoder(conn)
- err := dec.Decode(&childIP)
- if err != nil {
- return errors.Wrap(err, "rootless port failed to decode ports")
- }
- portStatus, err := pm.ListPorts(ctx)
- if err != nil {
- return errors.Wrap(err, "rootless port failed to list ports")
- }
- for _, status := range portStatus {
- err = pm.RemovePort(ctx, status.ID)
- if err != nil {
- return errors.Wrap(err, "rootless port failed to remove port")
- }
- }
- // add the ports with the new child IP
- for _, status := range portStatus {
- // set the new child IP
- status.Spec.ChildIP = childIP
- _, err = pm.AddPort(ctx, status.Spec)
- if err != nil {
- return errors.Wrap(err, "rootless port failed to add port")
- }
- }
- return nil
-}
-
-func exposePorts(pm rkport.Manager, portMappings []types.OCICNIPortMapping, childIP string) error {
- ctx := context.TODO()
- for _, i := range portMappings {
- hostIP := i.HostIP
- if hostIP == "" {
- hostIP = "0.0.0.0"
- }
- spec := rkport.Spec{
- Proto: i.Protocol,
- ParentIP: hostIP,
- ParentPort: int(i.HostPort),
- ChildPort: int(i.ContainerPort),
- ChildIP: childIP,
- }
- if err := rkportutil.ValidatePortSpec(spec, nil); err != nil {
- return err
- }
- if _, err := pm.AddPort(ctx, spec); err != nil {
- return err
- }
- }
- return nil
-}
-
-func child() error {
- // load the config from the parent
- var opaque map[string]string
- if err := json.Unmarshal([]byte(os.Getenv(reexecChildEnvOpaque)), &opaque); err != nil {
- return err
- }
-
- // start the child driver
- quit := make(chan struct{})
- errCh := make(chan error)
- go func() {
- d := rkbuiltin.NewChildDriver(os.Stderr)
- dErr := d.RunChildDriver(opaque, quit)
- errCh <- dErr
- }()
- defer func() {
- logrus.Info("Stopping child driver")
- quit <- struct{}{}
- if err := <-errCh; err != nil {
- logrus.WithError(err).Warn("Child driver returned error on exit")
- }
- }()
-
- // wait for stdin to be closed
- if _, err := ioutil.ReadAll(os.Stdin); err != nil {
- return err
- }
- return nil
-}
-
-type logrusWriter struct {
- prefix string
-}
-
-func (w *logrusWriter) Write(p []byte) (int, error) {
- logrus.Infof("%s%s", w.prefix, string(p))
- return len(p), nil
-}
diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go
index 92c0f22d9..9f398a0ed 100644
--- a/pkg/specgen/generate/container_create.go
+++ b/pkg/specgen/generate/container_create.go
@@ -366,6 +366,9 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen.
if s.Entrypoint != nil {
options = append(options, libpod.WithEntrypoint(s.Entrypoint))
}
+ if len(s.ContainerStorageConfig.StorageOpts) > 0 {
+ options = append(options, libpod.WithStorageOpts(s.StorageOpts))
+ }
// If the user did not specify a workdir on the CLI, let's extract it
// from the image.
if s.WorkDir == "" && imageData != nil {
diff --git a/pkg/specgen/generate/kube/kube.go b/pkg/specgen/generate/kube/kube.go
index e93462008..c502a6e62 100644
--- a/pkg/specgen/generate/kube/kube.go
+++ b/pkg/specgen/generate/kube/kube.go
@@ -19,6 +19,7 @@ import (
"github.com/containers/podman/v3/pkg/specgen"
"github.com/containers/podman/v3/pkg/specgen/generate"
"github.com/containers/podman/v3/pkg/util"
+ "github.com/docker/go-units"
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
v1 "k8s.io/api/core/v1"
@@ -116,6 +117,8 @@ type CtrSpecGenOptions struct {
SecretsManager *secrets.SecretsManager
// LogDriver which should be used for the container
LogDriver string
+ // LogOptions log options which should be used for the container
+ LogOptions []string
// Labels define key-value pairs of metadata
Labels map[string]string
//
@@ -144,6 +147,27 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener
Driver: opts.LogDriver,
}
+ for _, o := range opts.LogOptions {
+ split := strings.SplitN(o, "=", 2)
+ if len(split) < 2 {
+ return nil, errors.Errorf("invalid log option %q", o)
+ }
+ switch strings.ToLower(split[0]) {
+ case "driver":
+ s.LogConfiguration.Driver = split[1]
+ case "path":
+ s.LogConfiguration.Path = split[1]
+ case "max-size":
+ logSize, err := units.FromHumanSize(split[1])
+ if err != nil {
+ return nil, err
+ }
+ s.LogConfiguration.Size = logSize
+ default:
+ s.LogConfiguration.Options[split[0]] = split[1]
+ }
+ }
+
s.InitContainerType = opts.InitContainerType
setupSecurityContext(s, opts.Container)
@@ -257,7 +281,7 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener
// Environment Variables
envs := map[string]string{}
for _, env := range imageData.Config.Env {
- keyval := strings.Split(env, "=")
+ keyval := strings.SplitN(env, "=", 2)
envs[keyval[0]] = keyval[1]
}
diff --git a/pkg/specgen/generate/pod_create.go b/pkg/specgen/generate/pod_create.go
index a4027eae7..501bce05d 100644
--- a/pkg/specgen/generate/pod_create.go
+++ b/pkg/specgen/generate/pod_create.go
@@ -2,8 +2,12 @@ package generate
import (
"context"
+ "fmt"
+ "io/ioutil"
"net"
+ "os"
+ buildahDefine "github.com/containers/buildah/define"
"github.com/containers/common/pkg/config"
"github.com/containers/podman/v3/libpod"
"github.com/containers/podman/v3/libpod/define"
@@ -14,10 +18,102 @@ import (
"github.com/sirupsen/logrus"
)
+func buildPauseImage(rt *libpod.Runtime, rtConfig *config.Config) (string, error) {
+ version, err := define.GetVersion()
+ if err != nil {
+ return "", err
+ }
+ imageName := fmt.Sprintf("localhost/podman-pause:%s-%d", version.Version, version.Built)
+
+ // First check if the image has already been built.
+ if _, _, err := rt.LibimageRuntime().LookupImage(imageName, nil); err == nil {
+ return imageName, nil
+ }
+
+ // NOTE: Having the pause binary in its own directory keeps the door
+ // open for replacing the image building with using an overlay root FS.
+ // The latter turned out to be complex and error prone (see #11956) but
+ // we may be able to come up with a proper solution at a later point in
+ // time.
+ pausePath, err := rtConfig.FindHelperBinary("pause/pause", false)
+ if err != nil {
+ return "", fmt.Errorf("finding pause binary: %w", err)
+ }
+
+ buildContent := fmt.Sprintf(`FROM scratch
+COPY %s /pause
+ENTRYPOINT ["/pause"]`, pausePath)
+
+ tmpF, err := ioutil.TempFile("", "pause.containerfile")
+ if err != nil {
+ return "", err
+ }
+ if _, err := tmpF.WriteString(buildContent); err != nil {
+ return "", err
+ }
+ if err := tmpF.Close(); err != nil {
+ return "", err
+ }
+ defer os.Remove(tmpF.Name())
+
+ buildOptions := buildahDefine.BuildOptions{
+ CommonBuildOpts: &buildahDefine.CommonBuildOptions{},
+ Output: imageName,
+ Quiet: true,
+ IIDFile: "/dev/null", // prevents Buildah from writing the ID on stdout
+ }
+ if _, _, err := rt.Build(context.Background(), buildOptions, tmpF.Name()); err != nil {
+ return "", err
+ }
+
+ return imageName, nil
+}
+
+func pullOrBuildInfraImage(p *entities.PodSpec, rt *libpod.Runtime) error {
+ if p.PodSpecGen.NoInfra {
+ return nil
+ }
+
+ rtConfig, err := rt.GetConfigNoCopy()
+ if err != nil {
+ return err
+ }
+
+ // NOTE: we need pull down the infra image if it was explicitly set by
+ // the user (or containers.conf) to the non-default one.
+ imageName := p.PodSpecGen.InfraImage
+ if imageName == "" {
+ imageName = rtConfig.Engine.InfraImage
+ }
+
+ if imageName != config.DefaultInfraImage {
+ _, err := rt.LibimageRuntime().Pull(context.Background(), imageName, config.PullPolicyMissing, nil)
+ if err != nil {
+ return err
+ }
+ } else {
+ name, err := buildPauseImage(rt, rtConfig)
+ if err != nil {
+ return fmt.Errorf("building local pause image: %w", err)
+ }
+ imageName = name
+ }
+
+ p.PodSpecGen.InfraImage = imageName
+ p.PodSpecGen.InfraContainerSpec.RawImageName = imageName
+
+ return nil
+}
+
func MakePod(p *entities.PodSpec, rt *libpod.Runtime) (*libpod.Pod, error) {
if err := p.PodSpecGen.Validate(); err != nil {
return nil, err
}
+
+ if err := pullOrBuildInfraImage(p, rt); err != nil {
+ return nil, err
+ }
+
if !p.PodSpecGen.NoInfra && p.PodSpecGen.InfraContainerSpec != nil {
var err error
p.PodSpecGen.InfraContainerSpec, err = MapSpec(&p.PodSpecGen)
@@ -35,7 +131,6 @@ func MakePod(p *entities.PodSpec, rt *libpod.Runtime) (*libpod.Pod, error) {
return nil, err
}
if !p.PodSpecGen.NoInfra && p.PodSpecGen.InfraContainerSpec != nil {
- p.PodSpecGen.InfraContainerSpec.ContainerCreateCommand = []string{} // we do NOT want os.Args as the command, will display the pod create cmd
if p.PodSpecGen.InfraContainerSpec.Name == "" {
p.PodSpecGen.InfraContainerSpec.Name = pod.ID()[:12] + "-infra"
}
@@ -109,11 +204,11 @@ func createPodOptions(p *specgen.PodSpecGenerator, rt *libpod.Runtime, infraSpec
// replacing necessary values with those specified in pod creation
func MapSpec(p *specgen.PodSpecGenerator) (*specgen.SpecGenerator, error) {
if len(p.PortMappings) > 0 {
- ports, _, _, err := ParsePortMapping(p.PortMappings)
+ ports, err := ParsePortMapping(p.PortMappings, nil)
if err != nil {
return nil, err
}
- p.InfraContainerSpec.PortMappings = libpod.WithInfraContainerPorts(ports, p.InfraContainerSpec)
+ p.InfraContainerSpec.PortMappings = ports
}
switch p.NetNS.NSMode {
case specgen.Default, "":
diff --git a/pkg/specgen/generate/ports.go b/pkg/specgen/generate/ports.go
index 992b4a8e9..53a5e5697 100644
--- a/pkg/specgen/generate/ports.go
+++ b/pkg/specgen/generate/ports.go
@@ -2,7 +2,9 @@ package generate
import (
"context"
+ "fmt"
"net"
+ "sort"
"strconv"
"strings"
@@ -11,6 +13,7 @@ import (
"github.com/containers/podman/v3/utils"
"github.com/containers/podman/v3/pkg/specgen"
+ "github.com/containers/podman/v3/pkg/util"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
@@ -21,252 +24,323 @@ const (
protoSCTP = "sctp"
)
-// Parse port maps to OCICNI port mappings.
-// Returns a set of OCICNI port mappings, and maps of utilized container and
+// joinTwoPortsToRangePortIfPossible will expect two ports the previous port one must have a lower or equal hostPort than the current port.
+func joinTwoPortsToRangePortIfPossible(ports *[]types.PortMapping, allHostPorts, allContainerPorts, currentHostPorts *[65536]bool,
+ previousPort *types.PortMapping, port types.PortMapping) (*types.PortMapping, error) {
+ // no previous port just return the current one
+ if previousPort == nil {
+ return &port, nil
+ }
+ if previousPort.HostPort+previousPort.Range >= port.HostPort {
+ // check if the port range matches the host and container ports
+ portDiff := port.HostPort - previousPort.HostPort
+ if portDiff == port.ContainerPort-previousPort.ContainerPort {
+ // calc the new range use the old range and add the difference between the ports
+ newRange := port.Range + portDiff
+ // if the newRange is greater than the old range use it
+ // this is important otherwise we would could lower the range
+ if newRange > previousPort.Range {
+ previousPort.Range = newRange
+ }
+ return previousPort, nil
+ }
+ // if both host port ranges overlap and the container port range did not match
+ // we have to error because we cannot assign the same host port to more than one container port
+ if previousPort.HostPort+previousPort.Range-1 > port.HostPort {
+ return nil, errors.Errorf("conflicting port mappings for host port %d (protocol %s)", port.HostPort, port.Protocol)
+ }
+ }
+ // we could not join the ports so we append the old one to the list
+ // and return the current port as previous port
+ addPortToUsedPorts(ports, allHostPorts, allContainerPorts, currentHostPorts, previousPort)
+ return &port, nil
+}
+
+// joinTwoContainerPortsToRangePortIfPossible will expect two ports with both no host port set,
+// the previous port one must have a lower or equal containerPort than the current port.
+func joinTwoContainerPortsToRangePortIfPossible(ports *[]types.PortMapping, allHostPorts, allContainerPorts, currentHostPorts *[65536]bool,
+ previousPort *types.PortMapping, port types.PortMapping) (*types.PortMapping, error) {
+ // no previous port just return the current one
+ if previousPort == nil {
+ return &port, nil
+ }
+ if previousPort.ContainerPort+previousPort.Range > port.ContainerPort {
+ // calc the new range use the old range and add the difference between the ports
+ newRange := port.ContainerPort - previousPort.ContainerPort + port.Range
+ // if the newRange is greater than the old range use it
+ // this is important otherwise we would could lower the range
+ if newRange > previousPort.Range {
+ previousPort.Range = newRange
+ }
+ return previousPort, nil
+ }
+ // we could not join the ports so we append the old one to the list
+ // and return the current port as previous port
+ newPort, err := getRandomHostPort(currentHostPorts, *previousPort)
+ if err != nil {
+ return nil, err
+ }
+ addPortToUsedPorts(ports, allHostPorts, allContainerPorts, currentHostPorts, &newPort)
+ return &port, nil
+}
+
+func addPortToUsedPorts(ports *[]types.PortMapping, allHostPorts, allContainerPorts, currentHostPorts *[65536]bool, port *types.PortMapping) {
+ for i := uint16(0); i < port.Range; i++ {
+ h := port.HostPort + i
+ allHostPorts[h] = true
+ currentHostPorts[h] = true
+ c := port.ContainerPort + i
+ allContainerPorts[c] = true
+ }
+ *ports = append(*ports, *port)
+}
+
+// getRandomHostPort get a random host port mapping for the given port
+// the caller has to supply a array with he already used ports
+func getRandomHostPort(hostPorts *[65536]bool, port types.PortMapping) (types.PortMapping, error) {
+outer:
+ for i := 0; i < 15; i++ {
+ ranPort, err := utils.GetRandomPort()
+ if err != nil {
+ return port, err
+ }
+
+ // if port range is exceeds max port we cannot use it
+ if ranPort+int(port.Range) > 65535 {
+ continue
+ }
+
+ // check if there is a port in the range which is used
+ for j := 0; j < int(port.Range); j++ {
+ // port already used
+ if hostPorts[ranPort+j] {
+ continue outer
+ }
+ }
+
+ port.HostPort = uint16(ranPort)
+ return port, nil
+ }
+
+ // add range to error message if needed
+ rangePort := ""
+ if port.Range > 1 {
+ rangePort = fmt.Sprintf("with range %d ", port.Range)
+ }
+
+ return port, errors.Errorf("failed to find an open port to expose container port %d %son the host", port.ContainerPort, rangePort)
+}
+
+// Parse port maps to port mappings.
+// Returns a set of port mappings, and maps of utilized container and
// host ports.
-func ParsePortMapping(portMappings []types.PortMapping) ([]types.OCICNIPortMapping, map[string]map[string]map[uint16]uint16, map[string]map[string]map[uint16]uint16, error) {
- // First, we need to validate the ports passed in the specgen, and then
- // convert them into CNI port mappings.
- type tempMapping struct {
- mapping types.OCICNIPortMapping
- startOfRange bool
- isInRange bool
+func ParsePortMapping(portMappings []types.PortMapping, exposePorts map[uint16][]string) ([]types.PortMapping, error) {
+ if len(portMappings) == 0 && len(exposePorts) == 0 {
+ return nil, nil
}
- tempMappings := []tempMapping{}
-
- // To validate, we need two maps: one for host ports, one for container
- // ports.
- // Each is a map of protocol to map of IP address to map of port to
- // port (for hostPortValidate, it's host port to container port;
- // for containerPortValidate, container port to host port.
- // These will ensure no collisions.
- hostPortValidate := make(map[string]map[string]map[uint16]uint16)
- containerPortValidate := make(map[string]map[string]map[uint16]uint16)
-
- // Initialize the first level of maps (we can't really guess keys for
- // the rest).
- for _, proto := range []string{protoTCP, protoUDP, protoSCTP} {
- hostPortValidate[proto] = make(map[string]map[uint16]uint16)
- containerPortValidate[proto] = make(map[string]map[uint16]uint16)
+
+ // tempMapping stores the ports without ip and protocol
+ type tempMapping struct {
+ hostPort uint16
+ containerPort uint16
+ rangePort uint16
}
- postAssignHostPort := false
+ // portMap is a temporary structure to sort all ports
+ // the map is hostIp -> protocol -> array of mappings
+ portMap := make(map[string]map[string][]tempMapping)
+
+ // allUsedContainerPorts stores all used ports for each protocol
+ // the key is the protocol and the array is 65536 elements long for each port.
+ allUsedContainerPortsMap := make(map[string][65536]bool)
+ allUsedHostPortsMap := make(map[string][65536]bool)
- // Iterate through all port mappings, generating OCICNI PortMapping
- // structs and validating there is no overlap.
+ // First, we need to validate the ports passed in the specgen
for _, port := range portMappings {
// First, check proto
protocols, err := checkProtocol(port.Protocol, true)
if err != nil {
- return nil, nil, nil, err
- }
-
- // Validate host IP
- hostIP := port.HostIP
- if hostIP == "" {
- hostIP = "0.0.0.0"
+ return nil, err
}
- if ip := net.ParseIP(hostIP); ip == nil {
- return nil, nil, nil, errors.Errorf("invalid IP address %s in port mapping", port.HostIP)
+ if port.HostIP != "" {
+ if ip := net.ParseIP(port.HostIP); ip == nil {
+ return nil, errors.Errorf("invalid IP address %q in port mapping", port.HostIP)
+ }
}
// Validate port numbers and range.
- len := port.Range
- if len == 0 {
- len = 1
+ portRange := port.Range
+ if portRange == 0 {
+ portRange = 1
}
containerPort := port.ContainerPort
if containerPort == 0 {
- return nil, nil, nil, errors.Errorf("container port number must be non-0")
+ return nil, errors.Errorf("container port number must be non-0")
}
hostPort := port.HostPort
- if uint32(len-1)+uint32(containerPort) > 65535 {
- return nil, nil, nil, errors.Errorf("container port range exceeds maximum allowable port number")
+ if uint32(portRange-1)+uint32(containerPort) > 65535 {
+ return nil, errors.Errorf("container port range exceeds maximum allowable port number")
}
- if uint32(len-1)+uint32(hostPort) > 65536 {
- return nil, nil, nil, errors.Errorf("host port range exceeds maximum allowable port number")
+ if uint32(portRange-1)+uint32(hostPort) > 65535 {
+ return nil, errors.Errorf("host port range exceeds maximum allowable port number")
}
- // Iterate through ports, populating maps to check for conflicts
- // and generating CNI port mappings.
- for _, p := range protocols {
- hostIPMap := hostPortValidate[p]
- ctrIPMap := containerPortValidate[p]
-
- hostPortMap, ok := hostIPMap[hostIP]
- if !ok {
- hostPortMap = make(map[uint16]uint16)
- hostIPMap[hostIP] = hostPortMap
+ hostProtoMap, ok := portMap[port.HostIP]
+ if !ok {
+ hostProtoMap = make(map[string][]tempMapping)
+ for _, proto := range []string{protoTCP, protoUDP, protoSCTP} {
+ hostProtoMap[proto] = make([]tempMapping, 0)
}
- ctrPortMap, ok := ctrIPMap[hostIP]
- if !ok {
- ctrPortMap = make(map[uint16]uint16)
- ctrIPMap[hostIP] = ctrPortMap
- }
-
- // Iterate through all port numbers in the requested
- // range.
- var index uint16
- for index = 0; index < len; index++ {
- cPort := containerPort + index
- hPort := hostPort
- // Only increment host port if it's not 0.
- if hostPort != 0 {
- hPort += index
- }
-
- if cPort == 0 {
- return nil, nil, nil, errors.Errorf("container port cannot be 0")
- }
+ portMap[port.HostIP] = hostProtoMap
+ }
- // Host port is allowed to be 0. If it is, we
- // select a random port on the host.
- // This will happen *after* all other ports are
- // placed, to ensure we don't accidentally
- // select a port that a later mapping wanted.
- if hPort == 0 {
- // If we already have a host port
- // assigned to their container port -
- // just use that.
- if ctrPortMap[cPort] != 0 {
- hPort = ctrPortMap[cPort]
- } else {
- postAssignHostPort = true
- }
- } else {
- testHPort := hostPortMap[hPort]
- if testHPort != 0 && testHPort != cPort {
- return nil, nil, nil, errors.Errorf("conflicting port mappings for host port %d (protocol %s)", hPort, p)
- }
- hostPortMap[hPort] = cPort
-
- // Mapping a container port to multiple
- // host ports is allowed.
- // We only store the latest of these in
- // the container port map - we don't
- // need to know all of them, just one.
- testCPort := ctrPortMap[cPort]
- ctrPortMap[cPort] = hPort
-
- // If we have an exact duplicate, just continue
- if testCPort == hPort && testHPort == cPort {
- continue
- }
- }
+ p := tempMapping{
+ hostPort: port.HostPort,
+ containerPort: port.ContainerPort,
+ rangePort: portRange,
+ }
- // We appear to be clear. Make an OCICNI port
- // struct.
- // Don't use hostIP - we want to preserve the
- // empty string hostIP by default for compat.
- cniPort := types.OCICNIPortMapping{
- HostPort: int32(hPort),
- ContainerPort: int32(cPort),
- Protocol: p,
- HostIP: port.HostIP,
- }
- tempMappings = append(
- tempMappings,
- tempMapping{
- mapping: cniPort,
- startOfRange: port.Range > 1 && index == 0,
- isInRange: port.Range > 1,
- },
- )
- }
+ for _, proto := range protocols {
+ hostProtoMap[proto] = append(hostProtoMap[proto], p)
}
}
- // Handle any 0 host ports now by setting random container ports.
- if postAssignHostPort {
- remadeMappings := make([]types.OCICNIPortMapping, 0, len(tempMappings))
-
- var (
- candidate int
- err error
- )
-
- // Iterate over all
- for _, tmp := range tempMappings {
- p := tmp.mapping
+ // we do no longer need the original port mappings
+ // set it to 0 length so we can resuse it to populate
+ // the slice again while keeping the underlying capacity
+ portMappings = portMappings[:0]
- if p.HostPort != 0 {
- remadeMappings = append(remadeMappings, p)
+ for hostIP, protoMap := range portMap {
+ for protocol, ports := range protoMap {
+ ports := ports
+ if len(ports) == 0 {
continue
}
-
- hostIPMap := hostPortValidate[p.Protocol]
- ctrIPMap := containerPortValidate[p.Protocol]
-
- hostPortMap, ok := hostIPMap[p.HostIP]
- if !ok {
- hostPortMap = make(map[uint16]uint16)
- hostIPMap[p.HostIP] = hostPortMap
+ // 1. sort the ports by host port
+ // use a small hack to make sure ports with host port 0 are sorted last
+ sort.Slice(ports, func(i, j int) bool {
+ if ports[i].hostPort == ports[j].hostPort {
+ return ports[i].containerPort < ports[j].containerPort
+ }
+ if ports[i].hostPort == 0 {
+ return false
+ }
+ if ports[j].hostPort == 0 {
+ return true
+ }
+ return ports[i].hostPort < ports[j].hostPort
+ })
+
+ allUsedContainerPorts := allUsedContainerPortsMap[protocol]
+ allUsedHostPorts := allUsedHostPortsMap[protocol]
+ var usedHostPorts [65536]bool
+
+ var previousPort *types.PortMapping
+ var i int
+ for i = 0; i < len(ports); i++ {
+ if ports[i].hostPort == 0 {
+ // because the ports are sorted and host port 0 is last
+ // we can break when we hit 0
+ // we will fit them in afterwards
+ break
+ }
+ p := types.PortMapping{
+ HostIP: hostIP,
+ Protocol: protocol,
+ HostPort: ports[i].hostPort,
+ ContainerPort: ports[i].containerPort,
+ Range: ports[i].rangePort,
+ }
+ var err error
+ previousPort, err = joinTwoPortsToRangePortIfPossible(&portMappings, &allUsedHostPorts,
+ &allUsedContainerPorts, &usedHostPorts, previousPort, p)
+ if err != nil {
+ return nil, err
+ }
}
- ctrPortMap, ok := ctrIPMap[p.HostIP]
- if !ok {
- ctrPortMap = make(map[uint16]uint16)
- ctrIPMap[p.HostIP] = ctrPortMap
+ if previousPort != nil {
+ addPortToUsedPorts(&portMappings, &allUsedHostPorts,
+ &allUsedContainerPorts, &usedHostPorts, previousPort)
}
- // See if container port has been used elsewhere
- if ctrPortMap[uint16(p.ContainerPort)] != 0 {
- // Duplicate definition. Let's not bother
- // including it.
- continue
+ // now take care of the hostPort = 0 ports
+ previousPort = nil
+ for i < len(ports) {
+ p := types.PortMapping{
+ HostIP: hostIP,
+ Protocol: protocol,
+ ContainerPort: ports[i].containerPort,
+ Range: ports[i].rangePort,
+ }
+ var err error
+ previousPort, err = joinTwoContainerPortsToRangePortIfPossible(&portMappings, &allUsedHostPorts,
+ &allUsedContainerPorts, &usedHostPorts, previousPort, p)
+ if err != nil {
+ return nil, err
+ }
+ i++
+ }
+ if previousPort != nil {
+ newPort, err := getRandomHostPort(&usedHostPorts, *previousPort)
+ if err != nil {
+ return nil, err
+ }
+ addPortToUsedPorts(&portMappings, &allUsedHostPorts,
+ &allUsedContainerPorts, &usedHostPorts, &newPort)
}
- // Max retries to ensure we don't loop forever.
- for i := 0; i < 15; i++ {
- // Only get a random candidate for single entries or the start
- // of a range. Otherwise we just increment the candidate.
- if !tmp.isInRange || tmp.startOfRange {
- candidate, err = utils.GetRandomPort()
+ allUsedContainerPortsMap[protocol] = allUsedContainerPorts
+ allUsedHostPortsMap[protocol] = allUsedHostPorts
+ }
+ }
+
+ if len(exposePorts) > 0 {
+ logrus.Debugf("Adding exposed ports")
+
+ for port, protocols := range exposePorts {
+ newProtocols := make([]string, 0, len(protocols))
+ for _, protocol := range protocols {
+ if !allUsedContainerPortsMap[protocol][port] {
+ p := types.PortMapping{
+ ContainerPort: port,
+ Protocol: protocol,
+ Range: 1,
+ }
+ allPorts := allUsedContainerPortsMap[protocol]
+ p, err := getRandomHostPort(&allPorts, p)
if err != nil {
- return nil, nil, nil, errors.Wrapf(err, "error getting candidate host port for container port %d", p.ContainerPort)
+ return nil, err
}
+ portMappings = append(portMappings, p)
} else {
- candidate++
- }
-
- if hostPortMap[uint16(candidate)] == 0 {
- logrus.Debugf("Successfully assigned container port %d to host port %d (IP %s Protocol %s)", p.ContainerPort, candidate, p.HostIP, p.Protocol)
- hostPortMap[uint16(candidate)] = uint16(p.ContainerPort)
- ctrPortMap[uint16(p.ContainerPort)] = uint16(candidate)
- p.HostPort = int32(candidate)
- break
+ newProtocols = append(newProtocols, protocol)
}
}
- if p.HostPort == 0 {
- return nil, nil, nil, errors.Errorf("could not find open host port to map container port %d to", p.ContainerPort)
+ // make sure to delete the key from the map if there are no protocols left
+ if len(newProtocols) == 0 {
+ delete(exposePorts, port)
+ } else {
+ exposePorts[port] = newProtocols
}
- remadeMappings = append(remadeMappings, p)
}
- return remadeMappings, containerPortValidate, hostPortValidate, nil
}
+ return portMappings, nil
+}
- finalMappings := []types.OCICNIPortMapping{}
- for _, m := range tempMappings {
- finalMappings = append(finalMappings, m.mapping)
+func appendProtocolsNoDuplicates(slice []string, protocols []string) []string {
+ for _, proto := range protocols {
+ if util.StringInSlice(proto, slice) {
+ continue
+ }
+ slice = append(slice, proto)
}
-
- return finalMappings, containerPortValidate, hostPortValidate, nil
+ return slice
}
// Make final port mappings for the container
-func createPortMappings(ctx context.Context, s *specgen.SpecGenerator, imageData *libimage.ImageData) ([]types.OCICNIPortMapping, map[uint16][]string, error) {
- finalMappings, containerPortValidate, hostPortValidate, err := ParsePortMapping(s.PortMappings)
- if err != nil {
- return nil, nil, err
- }
-
- // No exposed ports so return the port mappings we've made so far.
- if len(s.Expose) == 0 && imageData == nil {
- return finalMappings, nil, nil
- }
-
- logrus.Debugf("Adding exposed ports")
-
+func createPortMappings(ctx context.Context, s *specgen.SpecGenerator, imageData *libimage.ImageData) ([]types.PortMapping, map[uint16][]string, error) {
expose := make(map[uint16]string)
+ var err error
if imageData != nil {
expose, err = GenExposedPorts(imageData.Config.ExposedPorts)
if err != nil {
@@ -274,103 +348,30 @@ func createPortMappings(ctx context.Context, s *specgen.SpecGenerator, imageData
}
}
- // We need to merge s.Expose into image exposed ports
- for k, v := range s.Expose {
- expose[k] = v
- }
- // There's been a request to expose some ports. Let's do that.
- // Start by figuring out what needs to be exposed.
- // This is a map of container port number to protocols to expose.
- toExpose := make(map[uint16][]string)
- for port, proto := range expose {
- // Validate protocol first
- protocols, err := checkProtocol(proto, false)
- if err != nil {
- return nil, nil, errors.Wrapf(err, "error validating protocols for exposed port %d", port)
- }
-
- if port == 0 {
- return nil, nil, errors.Errorf("cannot expose 0 as it is not a valid port number")
- }
-
- // Check to see if the port is already present in existing
- // mappings.
- for _, p := range protocols {
- ctrPortMap, ok := containerPortValidate[p]["0.0.0.0"]
- if !ok {
- ctrPortMap = make(map[uint16]uint16)
- containerPortValidate[p]["0.0.0.0"] = ctrPortMap
+ toExpose := make(map[uint16][]string, len(s.Expose)+len(expose))
+ for _, expose := range []map[uint16]string{expose, s.Expose} {
+ for port, proto := range expose {
+ if port == 0 {
+ return nil, nil, errors.Errorf("cannot expose 0 as it is not a valid port number")
}
-
- if portNum := ctrPortMap[port]; portNum == 0 {
- // We want to expose this port for this protocol
- exposeProto, ok := toExpose[port]
- if !ok {
- exposeProto = []string{}
- }
- exposeProto = append(exposeProto, p)
- toExpose[port] = exposeProto
+ protocols, err := checkProtocol(proto, false)
+ if err != nil {
+ return nil, nil, errors.Wrapf(err, "error validating protocols for exposed port %d", port)
}
+ toExpose[port] = appendProtocolsNoDuplicates(toExpose[port], protocols)
}
}
- // If not publishing exposed ports return mappings and exposed ports.
+ publishPorts := toExpose
if !s.PublishExposedPorts {
- return finalMappings, toExpose, nil
+ publishPorts = nil
}
- // We now have a final list of ports that we want exposed.
- // Let's find empty, unallocated host ports for them.
- for port, protocols := range toExpose {
- for _, p := range protocols {
- // Find an open port on the host.
- // I see a faint possibility that this will infinite
- // loop trying to find a valid open port, so I've
- // included a max-tries counter.
- hostPort := 0
- tries := 15
- for hostPort == 0 && tries > 0 {
- // We can't select a specific protocol, which is
- // unfortunate for the UDP case.
- candidate, err := utils.GetRandomPort()
- if err != nil {
- return nil, nil, err
- }
-
- // Check if the host port is already bound
- hostPortMap, ok := hostPortValidate[p]["0.0.0.0"]
- if !ok {
- hostPortMap = make(map[uint16]uint16)
- hostPortValidate[p]["0.0.0.0"] = hostPortMap
- }
-
- if checkPort := hostPortMap[uint16(candidate)]; checkPort != 0 {
- // Host port is already allocated, try again
- tries--
- continue
- }
-
- hostPortMap[uint16(candidate)] = port
- hostPort = candidate
- logrus.Debugf("Mapping exposed port %d/%s to host port %d", port, p, hostPort)
-
- // Make a CNI port mapping
- cniPort := types.OCICNIPortMapping{
- HostPort: int32(candidate),
- ContainerPort: int32(port),
- Protocol: p,
- HostIP: "",
- }
- finalMappings = append(finalMappings, cniPort)
- }
- if tries == 0 && hostPort == 0 {
- // We failed to find an open port.
- return nil, nil, errors.Errorf("failed to find an open port to expose container port %d on the host", port)
- }
- }
+ finalMappings, err := ParsePortMapping(s.PortMappings, publishPorts)
+ if err != nil {
+ return nil, nil, err
}
-
- return finalMappings, nil, nil
+ return finalMappings, toExpose, nil
}
// Check a string to ensure it is a comma-separated set of valid protocols
@@ -409,7 +410,7 @@ func checkProtocol(protocol string, allowSCTP bool) ([]string, error) {
}
func GenExposedPorts(exposedPorts map[string]struct{}) (map[uint16]string, error) {
- expose := make(map[uint16]string)
+ expose := make(map[uint16]string, len(exposedPorts))
for imgExpose := range exposedPorts {
// Expose format is portNumber[/protocol]
splitExpose := strings.SplitN(imgExpose, "/", 2)
@@ -420,12 +421,20 @@ func GenExposedPorts(exposedPorts map[string]struct{}) (map[uint16]string, error
if num > 65535 || num < 1 {
return nil, errors.Errorf("%d from image EXPOSE statement %q is not a valid port number", num, imgExpose)
}
- // No need to validate protocol, we'll do it below.
- if len(splitExpose) == 1 {
- expose[uint16(num)] = "tcp"
+
+ // No need to validate protocol, we'll do it later.
+ newProto := "tcp"
+ if len(splitExpose) == 2 {
+ newProto = splitExpose[1]
+ }
+
+ proto := expose[uint16(num)]
+ if len(proto) > 1 {
+ proto = proto + "," + newProto
} else {
- expose[uint16(num)] = splitExpose[1]
+ proto = newProto
}
+ expose[uint16(num)] = proto
}
return expose, nil
}
diff --git a/pkg/specgen/generate/ports_bench_test.go b/pkg/specgen/generate/ports_bench_test.go
new file mode 100644
index 000000000..06f02acda
--- /dev/null
+++ b/pkg/specgen/generate/ports_bench_test.go
@@ -0,0 +1,197 @@
+package generate
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/containers/podman/v3/libpod/network/types"
+)
+
+func benchmarkParsePortMapping(b *testing.B, ports []types.PortMapping) {
+ for n := 0; n < b.N; n++ {
+ ParsePortMapping(ports, nil)
+ }
+}
+
+func BenchmarkParsePortMappingNoPorts(b *testing.B) {
+ benchmarkParsePortMapping(b, nil)
+}
+
+func BenchmarkParsePortMapping1(b *testing.B) {
+ benchmarkParsePortMapping(b, []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ },
+ })
+}
+
+func BenchmarkParsePortMapping100(b *testing.B) {
+ ports := make([]types.PortMapping, 0, 100)
+ for i := uint16(8080); i < 8180; i++ {
+ ports = append(ports, types.PortMapping{
+ HostPort: i,
+ ContainerPort: i,
+ Protocol: "tcp",
+ })
+ }
+ b.ResetTimer()
+ benchmarkParsePortMapping(b, ports)
+}
+
+func BenchmarkParsePortMapping1k(b *testing.B) {
+ ports := make([]types.PortMapping, 0, 1000)
+ for i := uint16(8080); i < 9080; i++ {
+ ports = append(ports, types.PortMapping{
+ HostPort: i,
+ ContainerPort: i,
+ Protocol: "tcp",
+ })
+ }
+ b.ResetTimer()
+ benchmarkParsePortMapping(b, ports)
+}
+
+func BenchmarkParsePortMapping10k(b *testing.B) {
+ ports := make([]types.PortMapping, 0, 30000)
+ for i := uint16(8080); i < 18080; i++ {
+ ports = append(ports, types.PortMapping{
+ HostPort: i,
+ ContainerPort: i,
+ Protocol: "tcp",
+ })
+ }
+ b.ResetTimer()
+ benchmarkParsePortMapping(b, ports)
+}
+
+func BenchmarkParsePortMapping1m(b *testing.B) {
+ ports := make([]types.PortMapping, 0, 1000000)
+ for j := 0; j < 20; j++ {
+ for i := uint16(1); i <= 50000; i++ {
+ ports = append(ports, types.PortMapping{
+ HostPort: i,
+ ContainerPort: i,
+ Protocol: "tcp",
+ HostIP: fmt.Sprintf("192.168.1.%d", j),
+ })
+ }
+ }
+ b.ResetTimer()
+ benchmarkParsePortMapping(b, ports)
+}
+
+func BenchmarkParsePortMappingReverse100(b *testing.B) {
+ ports := make([]types.PortMapping, 0, 100)
+ for i := uint16(8180); i > 8080; i-- {
+ ports = append(ports, types.PortMapping{
+ HostPort: i,
+ ContainerPort: i,
+ Protocol: "tcp",
+ })
+ }
+ b.ResetTimer()
+ benchmarkParsePortMapping(b, ports)
+}
+
+func BenchmarkParsePortMappingReverse1k(b *testing.B) {
+ ports := make([]types.PortMapping, 0, 1000)
+ for i := uint16(9080); i > 8080; i-- {
+ ports = append(ports, types.PortMapping{
+ HostPort: i,
+ ContainerPort: i,
+ Protocol: "tcp",
+ })
+ }
+ b.ResetTimer()
+ benchmarkParsePortMapping(b, ports)
+}
+
+func BenchmarkParsePortMappingReverse10k(b *testing.B) {
+ ports := make([]types.PortMapping, 0, 30000)
+ for i := uint16(18080); i > 8080; i-- {
+ ports = append(ports, types.PortMapping{
+ HostPort: i,
+ ContainerPort: i,
+ Protocol: "tcp",
+ })
+ }
+ b.ResetTimer()
+ benchmarkParsePortMapping(b, ports)
+}
+
+func BenchmarkParsePortMappingReverse1m(b *testing.B) {
+ ports := make([]types.PortMapping, 0, 1000000)
+ for j := 0; j < 20; j++ {
+ for i := uint16(50000); i > 0; i-- {
+ ports = append(ports, types.PortMapping{
+ HostPort: i,
+ ContainerPort: i,
+ Protocol: "tcp",
+ HostIP: fmt.Sprintf("192.168.1.%d", j),
+ })
+ }
+ }
+ b.ResetTimer()
+ benchmarkParsePortMapping(b, ports)
+}
+
+func BenchmarkParsePortMappingRange1(b *testing.B) {
+ benchmarkParsePortMapping(b, []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ Range: 1,
+ },
+ })
+}
+
+func BenchmarkParsePortMappingRange100(b *testing.B) {
+ benchmarkParsePortMapping(b, []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ Range: 100,
+ },
+ })
+}
+
+func BenchmarkParsePortMappingRange1k(b *testing.B) {
+ benchmarkParsePortMapping(b, []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ Range: 1000,
+ },
+ })
+}
+
+func BenchmarkParsePortMappingRange10k(b *testing.B) {
+ benchmarkParsePortMapping(b, []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ Range: 10000,
+ },
+ })
+}
+
+func BenchmarkParsePortMappingRange1m(b *testing.B) {
+ ports := make([]types.PortMapping, 0, 1000000)
+ for j := 0; j < 20; j++ {
+ ports = append(ports, types.PortMapping{
+ HostPort: 1,
+ ContainerPort: 1,
+ Protocol: "tcp",
+ Range: 50000,
+ HostIP: fmt.Sprintf("192.168.1.%d", j),
+ })
+ }
+ b.ResetTimer()
+ benchmarkParsePortMapping(b, ports)
+}
diff --git a/pkg/specgen/generate/ports_test.go b/pkg/specgen/generate/ports_test.go
new file mode 100644
index 000000000..20d5d0166
--- /dev/null
+++ b/pkg/specgen/generate/ports_test.go
@@ -0,0 +1,989 @@
+package generate
+
+import (
+ "testing"
+
+ "github.com/containers/podman/v3/libpod/network/types"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestParsePortMappingWithHostPort(t *testing.T) {
+ tests := []struct {
+ name string
+ arg []types.PortMapping
+ arg2 map[uint16][]string
+ want []types.PortMapping
+ }{
+ {
+ name: "no ports",
+ arg: nil,
+ want: nil,
+ },
+ {
+ name: "one tcp port",
+ arg: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ },
+ },
+ want: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ Range: 1,
+ },
+ },
+ },
+ {
+ name: "one tcp port no proto",
+ arg: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ },
+ },
+ want: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ Range: 1,
+ },
+ },
+ },
+ {
+ name: "one udp port",
+ arg: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "udp",
+ },
+ },
+ want: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "udp",
+ Range: 1,
+ },
+ },
+ },
+ {
+ name: "one sctp port",
+ arg: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "sctp",
+ },
+ },
+ want: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "sctp",
+ Range: 1,
+ },
+ },
+ },
+ {
+ name: "one port two protocols",
+ arg: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp,udp",
+ },
+ },
+ want: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ Range: 1,
+ },
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "udp",
+ Range: 1,
+ },
+ },
+ },
+ {
+ name: "one port three protocols",
+ arg: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp,udp,sctp",
+ },
+ },
+ want: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ Range: 1,
+ },
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "udp",
+ Range: 1,
+ },
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "sctp",
+ Range: 1,
+ },
+ },
+ },
+ {
+ name: "one port with range 1",
+ arg: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ Range: 1,
+ },
+ },
+ want: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ Range: 1,
+ },
+ },
+ },
+ {
+ name: "one port with range 5",
+ arg: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ Range: 5,
+ },
+ },
+ want: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ Range: 5,
+ },
+ },
+ },
+ {
+ name: "two ports joined",
+ arg: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ },
+ {
+ HostPort: 8081,
+ ContainerPort: 81,
+ Protocol: "tcp",
+ },
+ },
+ want: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ Range: 2,
+ },
+ },
+ },
+ {
+ name: "two ports joined with range",
+ arg: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ Range: 2,
+ },
+ {
+ HostPort: 8081,
+ ContainerPort: 81,
+ Protocol: "tcp",
+ },
+ },
+ want: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ Range: 2,
+ },
+ },
+ },
+ {
+ name: "two ports with no overlapping range",
+ arg: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ Range: 10,
+ },
+ {
+ HostPort: 9090,
+ ContainerPort: 9090,
+ Protocol: "tcp",
+ },
+ },
+ want: []types.PortMapping{
+ {
+ HostPort: 9090,
+ ContainerPort: 9090,
+ Protocol: "tcp",
+ Range: 1,
+ },
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ Range: 10,
+ },
+ },
+ },
+ {
+ name: "four ports with two overlapping ranges",
+ arg: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ Range: 10,
+ },
+ {
+ HostPort: 8085,
+ ContainerPort: 85,
+ Protocol: "tcp",
+ Range: 10,
+ },
+ {
+ HostPort: 100,
+ ContainerPort: 5,
+ Protocol: "tcp",
+ },
+ {
+ HostPort: 101,
+ ContainerPort: 6,
+ Protocol: "tcp",
+ },
+ },
+ want: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ Range: 15,
+ },
+ {
+ HostPort: 100,
+ ContainerPort: 5,
+ Protocol: "tcp",
+ Range: 2,
+ },
+ },
+ },
+ {
+ name: "two overlapping ranges",
+ arg: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ Range: 10,
+ },
+ {
+ HostPort: 8085,
+ ContainerPort: 85,
+ Protocol: "tcp",
+ Range: 2,
+ },
+ },
+ want: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ Range: 10,
+ },
+ },
+ },
+ {
+ name: "four overlapping ranges",
+ arg: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ Range: 10,
+ },
+ {
+ HostPort: 8085,
+ ContainerPort: 85,
+ Protocol: "tcp",
+ Range: 2,
+ },
+ {
+ HostPort: 8090,
+ ContainerPort: 90,
+ Protocol: "tcp",
+ Range: 7,
+ },
+ {
+ HostPort: 8095,
+ ContainerPort: 95,
+ Protocol: "tcp",
+ },
+ },
+ want: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ Range: 17,
+ },
+ },
+ },
+ {
+ name: "one port range overlaps 5 ports",
+ arg: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Range: 20,
+ },
+ {
+ HostPort: 8085,
+ ContainerPort: 85,
+ Range: 2,
+ },
+ {
+ HostPort: 8090,
+ ContainerPort: 90,
+ },
+ {
+ HostPort: 8095,
+ ContainerPort: 95,
+ },
+ {
+ HostPort: 8096,
+ ContainerPort: 96,
+ },
+ },
+ want: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ Range: 20,
+ },
+ },
+ },
+ {
+ name: "different host ip same port",
+ arg: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ HostIP: "192.168.1.1",
+ },
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ HostIP: "192.168.2.1",
+ },
+ },
+ want: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ HostIP: "192.168.1.1",
+ Range: 1,
+ },
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ HostIP: "192.168.2.1",
+ Range: 1,
+ },
+ },
+ },
+ }
+ for _, tt := range tests {
+ tt := tt
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := ParsePortMapping(tt.arg, tt.arg2)
+ assert.NoError(t, err, "error is not nil")
+ // use ElementsMatch instead of Equal because the order is not consistent
+ assert.ElementsMatch(t, tt.want, got, "got unexpected port mapping")
+ })
+ }
+}
+
+func TestParsePortMappingWithoutHostPort(t *testing.T) {
+ tests := []struct {
+ name string
+ arg []types.PortMapping
+ arg2 map[uint16][]string
+ want []types.PortMapping
+ }{
+ {
+ name: "one tcp port",
+ arg: []types.PortMapping{
+ {
+ HostPort: 0,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ },
+ },
+ want: []types.PortMapping{
+ {
+ HostPort: 0,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ Range: 1,
+ },
+ },
+ },
+ {
+ name: "one port with two protocols",
+ arg: []types.PortMapping{
+ {
+ HostPort: 0,
+ ContainerPort: 80,
+ Protocol: "tcp,udp",
+ },
+ },
+ want: []types.PortMapping{
+ {
+ HostPort: 0,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ Range: 1,
+ },
+ {
+ HostPort: 0,
+ ContainerPort: 80,
+ Protocol: "udp",
+ Range: 1,
+ },
+ },
+ },
+ {
+ name: "same port twice",
+ arg: []types.PortMapping{
+ {
+ HostPort: 0,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ },
+ {
+ HostPort: 0,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ },
+ },
+ want: []types.PortMapping{
+ {
+ HostPort: 0,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ Range: 1,
+ },
+ },
+ },
+ {
+ name: "neighbor ports are not joined",
+ arg: []types.PortMapping{
+ {
+ HostPort: 0,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ },
+ {
+ HostPort: 0,
+ ContainerPort: 81,
+ Protocol: "tcp",
+ },
+ },
+ want: []types.PortMapping{
+ {
+ HostPort: 0,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ Range: 1,
+ },
+ {
+ HostPort: 0,
+ ContainerPort: 81,
+ Protocol: "tcp",
+ Range: 1,
+ },
+ },
+ },
+ {
+ name: "overlapping range ports are joined",
+ arg: []types.PortMapping{
+ {
+ HostPort: 0,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ Range: 2,
+ },
+ {
+ HostPort: 0,
+ ContainerPort: 81,
+ Protocol: "tcp",
+ },
+ },
+ want: []types.PortMapping{
+ {
+ HostPort: 0,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ Range: 2,
+ },
+ },
+ },
+ {
+ name: "four overlapping range ports are joined",
+ arg: []types.PortMapping{
+ {
+ HostPort: 0,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ Range: 3,
+ },
+ {
+ HostPort: 0,
+ ContainerPort: 81,
+ Protocol: "tcp",
+ },
+ {
+ HostPort: 0,
+ ContainerPort: 82,
+ Protocol: "tcp",
+ Range: 10,
+ },
+ {
+ HostPort: 0,
+ ContainerPort: 90,
+ Protocol: "tcp",
+ Range: 5,
+ },
+ },
+ want: []types.PortMapping{
+ {
+ HostPort: 0,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ Range: 15,
+ },
+ },
+ },
+ {
+ name: "expose one tcp port",
+ arg2: map[uint16][]string{
+ 8080: {"tcp"},
+ },
+ want: []types.PortMapping{
+ {
+ HostPort: 0,
+ ContainerPort: 8080,
+ Protocol: "tcp",
+ Range: 1,
+ },
+ },
+ },
+ {
+ name: "expose already defined port",
+ arg: []types.PortMapping{
+ {
+ HostPort: 0,
+ ContainerPort: 8080,
+ Protocol: "tcp",
+ },
+ },
+ arg2: map[uint16][]string{
+ 8080: {"tcp"},
+ },
+ want: []types.PortMapping{
+ {
+ HostPort: 0,
+ ContainerPort: 8080,
+ Protocol: "tcp",
+ Range: 1,
+ },
+ },
+ },
+ {
+ name: "expose different proto",
+ arg: []types.PortMapping{
+ {
+ HostPort: 0,
+ ContainerPort: 8080,
+ Protocol: "tcp",
+ },
+ },
+ arg2: map[uint16][]string{
+ 8080: {"udp"},
+ },
+ want: []types.PortMapping{
+ {
+ HostPort: 0,
+ ContainerPort: 8080,
+ Protocol: "tcp",
+ Range: 1,
+ },
+ {
+ HostPort: 0,
+ ContainerPort: 8080,
+ Protocol: "udp",
+ Range: 1,
+ },
+ },
+ },
+ }
+ for _, tt := range tests {
+ tt := tt
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := ParsePortMapping(tt.arg, tt.arg2)
+ assert.NoError(t, err, "error is not nil")
+
+ // because we always get random host ports when it is set to 0 we cannot check that exactly
+ // check if it is not 0 and set to to 0 afterwards
+ for i := range got {
+ assert.Greater(t, got[i].HostPort, uint16(0), "host port is zero")
+ got[i].HostPort = 0
+ }
+
+ // use ElementsMatch instead of Equal because the order is not consistent
+ assert.ElementsMatch(t, tt.want, got, "got unexpected port mapping")
+ })
+ }
+}
+
+func TestParsePortMappingMixedHostPort(t *testing.T) {
+ tests := []struct {
+ name string
+ arg []types.PortMapping
+ want []types.PortMapping
+ resetHostPorts []int
+ }{
+ {
+ name: "two ports one without a hostport set",
+ arg: []types.PortMapping{
+ {
+ HostPort: 0,
+ ContainerPort: 80,
+ },
+ {
+ HostPort: 8080,
+ ContainerPort: 8080,
+ },
+ },
+ want: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 8080,
+ Protocol: "tcp",
+ Range: 1,
+ },
+ {
+ HostPort: 0,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ Range: 1,
+ },
+ },
+ resetHostPorts: []int{1},
+ },
+ {
+ name: "two ports one without a hostport set, inverted order",
+ arg: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 8080,
+ },
+ {
+ HostPort: 0,
+ ContainerPort: 80,
+ },
+ },
+ want: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 8080,
+ Protocol: "tcp",
+ Range: 1,
+ },
+ {
+ HostPort: 0,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ Range: 1,
+ },
+ },
+ resetHostPorts: []int{1},
+ },
+ {
+ name: "three ports without host ports, one with a hostport set, , inverted order",
+ arg: []types.PortMapping{
+ {
+ HostPort: 0,
+ ContainerPort: 80,
+ },
+ {
+ HostPort: 0,
+ ContainerPort: 85,
+ },
+ {
+ HostPort: 0,
+ ContainerPort: 90,
+ },
+ {
+ HostPort: 8080,
+ ContainerPort: 8080,
+ },
+ },
+ want: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 8080,
+ Protocol: "tcp",
+ Range: 1,
+ },
+ {
+ HostPort: 0,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ Range: 1,
+ },
+ {
+ HostPort: 0,
+ ContainerPort: 85,
+ Protocol: "tcp",
+ Range: 1,
+ },
+ {
+ HostPort: 0,
+ ContainerPort: 90,
+ Protocol: "tcp",
+ Range: 1,
+ },
+ },
+ resetHostPorts: []int{1, 2, 3},
+ },
+ {
+ name: "three ports without host ports, one with a hostport set",
+ arg: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 8080,
+ },
+ {
+ HostPort: 0,
+ ContainerPort: 90,
+ },
+ {
+ HostPort: 0,
+ ContainerPort: 85,
+ },
+ {
+ HostPort: 0,
+ ContainerPort: 80,
+ },
+ },
+ want: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 8080,
+ Protocol: "tcp",
+ Range: 1,
+ },
+ {
+ HostPort: 0,
+ ContainerPort: 80,
+ Protocol: "tcp",
+ Range: 1,
+ },
+ {
+ HostPort: 0,
+ ContainerPort: 85,
+ Protocol: "tcp",
+ Range: 1,
+ },
+ {
+ HostPort: 0,
+ ContainerPort: 90,
+ Protocol: "tcp",
+ Range: 1,
+ },
+ },
+ resetHostPorts: []int{1, 2, 3},
+ },
+ }
+ for _, tt := range tests {
+ tt := tt
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := ParsePortMapping(tt.arg, nil)
+ assert.NoError(t, err, "error is not nil")
+
+ // because we always get random host ports when it is set to 0 we cannot check that exactly
+ // use resetHostPorts to know which port element is 0
+ for _, num := range tt.resetHostPorts {
+ assert.Greater(t, got[num].HostPort, uint16(0), "host port is zero")
+ got[num].HostPort = 0
+ }
+
+ assert.Equal(t, tt.want, got, "got unexpected port mapping")
+ })
+ }
+}
+
+func TestParsePortMappingError(t *testing.T) {
+ tests := []struct {
+ name string
+ arg []types.PortMapping
+ err string
+ }{
+ {
+ name: "container port is 0",
+ arg: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 0,
+ Protocol: "tcp",
+ },
+ },
+ err: "container port number must be non-0",
+ },
+ {
+ name: "container port range exceeds max",
+ arg: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 65000,
+ Protocol: "tcp",
+ Range: 10000,
+ },
+ },
+ err: "container port range exceeds maximum allowable port number",
+ },
+ {
+ name: "host port range exceeds max",
+ arg: []types.PortMapping{
+ {
+ HostPort: 60000,
+ ContainerPort: 1,
+ Protocol: "tcp",
+ Range: 10000,
+ },
+ },
+ err: "host port range exceeds maximum allowable port number",
+ },
+ {
+ name: "invalid protocol",
+ arg: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "1",
+ },
+ },
+ err: "unrecognized protocol \"1\" in port mapping",
+ },
+ {
+ name: "invalid protocol 2",
+ arg: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Protocol: "udp,u",
+ },
+ },
+ err: "unrecognized protocol \"u\" in port mapping",
+ },
+ {
+ name: "invalid ip address",
+ arg: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ HostIP: "blah",
+ },
+ },
+ err: "invalid IP address \"blah\" in port mapping",
+ },
+ {
+ name: "invalid overalpping range",
+ arg: []types.PortMapping{
+ {
+ HostPort: 8080,
+ ContainerPort: 80,
+ Range: 5,
+ },
+ {
+ HostPort: 8081,
+ ContainerPort: 60,
+ },
+ },
+ err: "conflicting port mappings for host port 8081 (protocol tcp)",
+ },
+ {
+ name: "big port range with host port zero does not fit",
+ arg: []types.PortMapping{
+ {
+ HostPort: 0,
+ ContainerPort: 1,
+ Range: 65535,
+ },
+ },
+ err: "failed to find an open port to expose container port 1 with range 65535 on the host",
+ },
+ {
+ name: "big port range with host port zero does not fit",
+ arg: []types.PortMapping{
+ {
+ HostPort: 0,
+ ContainerPort: 80,
+ Range: 1,
+ },
+ {
+ HostPort: 0,
+ ContainerPort: 1000,
+ Range: 64535,
+ },
+ },
+ err: "failed to find an open port to expose container port 1000 with range 64535 on the host",
+ },
+ }
+ for _, tt := range tests {
+ tt := tt
+ t.Run(tt.name, func(t *testing.T) {
+ _, err := ParsePortMapping(tt.arg, nil)
+ assert.EqualError(t, err, tt.err, "error does not match")
+ })
+ }
+}
diff --git a/pkg/specgen/generate/storage.go b/pkg/specgen/generate/storage.go
index 3fde1a1b4..30248a886 100644
--- a/pkg/specgen/generate/storage.go
+++ b/pkg/specgen/generate/storage.go
@@ -214,9 +214,6 @@ func getImageVolumes(ctx context.Context, img *libimage.Image, s *specgen.SpecGe
}
for volume := range inspect.Config.Volumes {
logrus.Debugf("Image has volume at %q", volume)
- if err = parse.ValidateVolumeCtrDir(volume); err != nil {
- return nil, nil, err
- }
cleanDest := filepath.Clean(volume)
switch mode {
case "", "anonymous":
diff --git a/pkg/specgen/podspecgen.go b/pkg/specgen/podspecgen.go
index 7713ea26c..948fb990c 100644
--- a/pkg/specgen/podspecgen.go
+++ b/pkg/specgen/podspecgen.go
@@ -99,7 +99,8 @@ type PodNetworkConfig struct {
// Only available if NetNS is set to Bridge (the default for root).
// As such, conflicts with NoInfra=true by proxy.
// Optional.
- StaticMAC *net.HardwareAddr `json:"static_mac,omitempty"`
+ // swagger:strfmt string
+ StaticMAC *types.HardwareAddr `json:"static_mac,omitempty"`
// PortMappings is a set of ports to map into the infra container.
// As, by default, containers share their network with the infra
// container, this will forward the ports to the entire pod.
diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go
index dbb669291..8a4497130 100644
--- a/pkg/specgen/specgen.go
+++ b/pkg/specgen/specgen.go
@@ -272,6 +272,9 @@ type ContainerStorageConfig struct {
// If unset, the default, /, will be used.
// Optional.
WorkDir string `json:"work_dir,omitempty"`
+ // StorageOpts is the container's storage options
+ // Optional.
+ StorageOpts map[string]string `json:"storage_opts,omitempty"`
// RootfsPropagation is the rootfs propagation mode for the container.
// If not set, the default of rslave will be used.
// Optional.
@@ -398,7 +401,8 @@ type ContainerNetworkConfig struct {
// StaticMAC is a static MAC address to set in the container.
// Only available if NetNS is set to bridge.
// Optional.
- StaticMAC *net.HardwareAddr `json:"static_mac,omitempty"`
+ // swagger:strfmt string
+ StaticMAC *nettypes.HardwareAddr `json:"static_mac,omitempty"`
// PortBindings is a set of ports to map into the container.
// Only available if NetNS is set to bridge or slirp.
// Optional.
@@ -551,11 +555,11 @@ func NewSpecGenerator(arg string, rootfs bool) *SpecGenerator {
csc := ContainerStorageConfig{}
if rootfs {
csc.Rootfs = arg
- // check if rootfs is actually overlayed
- parts := strings.SplitN(csc.Rootfs, ":", 2)
- if len(parts) > 1 && parts[1] == "O" {
+ // check if rootfs should use overlay
+ lastColonIndex := strings.LastIndex(csc.Rootfs, ":")
+ if lastColonIndex != -1 && lastColonIndex+1 < len(csc.Rootfs) && csc.Rootfs[lastColonIndex+1:] == "O" {
csc.RootfsOverlay = true
- csc.Rootfs = parts[0]
+ csc.Rootfs = csc.Rootfs[:lastColonIndex]
}
} else {
csc.Image = arg
diff --git a/pkg/specgen/specgen_test.go b/pkg/specgen/specgen_test.go
new file mode 100644
index 000000000..b838d9d30
--- /dev/null
+++ b/pkg/specgen/specgen_test.go
@@ -0,0 +1,25 @@
+package specgen
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestNewSpecGeneratorWithRootfs(t *testing.T) {
+ tests := []struct {
+ rootfs string
+ expectedRootfsOverlay bool
+ expectedRootfs string
+ }{
+ {"/root/a:b:O", true, "/root/a:b"},
+ {"/root/a:b/c:O", true, "/root/a:b/c"},
+ {"/root/a:b/c:", false, "/root/a:b/c:"},
+ {"/root/a/b", false, "/root/a/b"},
+ }
+ for _, args := range tests {
+ val := NewSpecGenerator(args.rootfs, true)
+ assert.Equal(t, val.RootfsOverlay, args.expectedRootfsOverlay)
+ assert.Equal(t, val.Rootfs, args.expectedRootfs)
+ }
+}
diff --git a/pkg/specgenutil/specgen.go b/pkg/specgenutil/specgen.go
index 6a6397257..4e8f954fb 100644
--- a/pkg/specgenutil/specgen.go
+++ b/pkg/specgenutil/specgen.go
@@ -11,6 +11,7 @@ import (
"github.com/containers/image/v5/manifest"
"github.com/containers/podman/v3/cmd/podman/parse"
"github.com/containers/podman/v3/libpod/define"
+ "github.com/containers/podman/v3/libpod/network/types"
ann "github.com/containers/podman/v3/pkg/annotations"
"github.com/containers/podman/v3/pkg/domain/entities"
envLib "github.com/containers/podman/v3/pkg/env"
@@ -133,12 +134,14 @@ func getMemoryLimits(s *specgen.SpecGenerator, c *entities.ContainerCreateOption
if err != nil {
return nil, errors.Wrapf(err, "invalid value for memory")
}
- memory.Limit = &ml
- if c.MemorySwap == "" {
- limit := 2 * ml
- memory.Swap = &(limit)
+ if ml > 0 {
+ memory.Limit = &ml
+ if c.MemorySwap == "" {
+ limit := 2 * ml
+ memory.Swap = &(limit)
+ }
+ hasLimits = true
}
- hasLimits = true
}
if m := c.MemoryReservation; len(m) > 0 {
mr, err := units.RAMInBytes(m)
@@ -392,6 +395,17 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions
}
s.Annotations = annotations
+ if len(c.StorageOpts) > 0 {
+ opts := make(map[string]string, len(c.StorageOpts))
+ for _, opt := range c.StorageOpts {
+ split := strings.SplitN(opt, "=", 2)
+ if len(split) != 2 {
+ return errors.Errorf("storage-opt must be formatted KEY=VALUE")
+ }
+ opts[split[0]] = split[1]
+ }
+ s.StorageOpts = opts
+ }
s.WorkDir = c.Workdir
if c.Entrypoint != nil {
entrypoint := []string{}
@@ -444,7 +458,8 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions
s.DNSSearch = c.Net.DNSSearch
s.DNSOptions = c.Net.DNSOptions
s.StaticIP = c.Net.StaticIP
- s.StaticMAC = c.Net.StaticMAC
+ // type cast to types.HardwareAddr
+ s.StaticMAC = (*types.HardwareAddr)(c.Net.StaticMAC)
s.NetworkOptions = c.Net.NetworkOptions
s.UseImageHosts = c.Net.NoHosts
}
diff --git a/pkg/specgenutil/volumes.go b/pkg/specgenutil/volumes.go
index 3ce96164f..184bfadf8 100644
--- a/pkg/specgenutil/volumes.go
+++ b/pkg/specgenutil/volumes.go
@@ -360,7 +360,7 @@ func getBindMount(args []string) (spec.Mount, error) {
// Since Docker ignores this option so shall we.
continue
default:
- return newMount, errors.Wrapf(util.ErrBadMntOption, kv[0])
+ return newMount, errors.Wrapf(util.ErrBadMntOption, "%s", kv[0])
}
}
@@ -460,7 +460,7 @@ func getTmpfsMount(args []string) (spec.Mount, error) {
// Since Docker ignores this option so shall we.
continue
default:
- return newMount, errors.Wrapf(util.ErrBadMntOption, kv[0])
+ return newMount, errors.Wrapf(util.ErrBadMntOption, "%s", kv[0])
}
}
@@ -483,6 +483,8 @@ func getDevptsMount(args []string) (spec.Mount, error) {
for _, val := range args {
kv := strings.SplitN(val, "=", 2)
switch kv[0] {
+ case "uid", "gid", "mode", "ptxmode", "newinstance", "max":
+ newMount.Options = append(newMount.Options, val)
case "target", "dst", "destination":
if len(kv) == 1 {
return newMount, errors.Wrapf(optionArgError, kv[0])
@@ -493,7 +495,7 @@ func getDevptsMount(args []string) (spec.Mount, error) {
newMount.Destination = filepath.Clean(kv[1])
setDest = true
default:
- return newMount, errors.Wrapf(util.ErrBadMntOption, kv[0])
+ return newMount, errors.Wrapf(util.ErrBadMntOption, "%s", kv[0])
}
}
@@ -573,7 +575,7 @@ func getNamedVolume(args []string) (*specgen.NamedVolume, error) {
// Since Docker ignores this option so shall we.
continue
default:
- return nil, errors.Wrapf(util.ErrBadMntOption, kv[0])
+ return nil, errors.Wrapf(util.ErrBadMntOption, "%s", kv[0])
}
}
@@ -624,7 +626,7 @@ func getImageVolume(args []string) (*specgen.ImageVolume, error) {
// Since Docker ignores this option so shall we.
continue
default:
- return nil, errors.Wrapf(util.ErrBadMntOption, kv[0])
+ return nil, errors.Wrapf(util.ErrBadMntOption, "%s", kv[0])
}
}
diff --git a/pkg/systemd/dbus.go b/pkg/systemd/dbus.go
index c49f537b6..04aaa117a 100644
--- a/pkg/systemd/dbus.go
+++ b/pkg/systemd/dbus.go
@@ -84,7 +84,7 @@ func IsSystemdSessionValid(uid int) bool {
return true
}
-// GetDbusConnection returns an user connection to D-BUS
+// GetDbusConnection returns a user connection to D-BUS
func GetLogindConnection(uid int) (*godbus.Conn, error) {
return dbusAuthConnectionLogind(uid)
}