summaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
Diffstat (limited to 'pkg')
-rw-r--r--pkg/api/handlers/compat/containers_create.go3
-rw-r--r--pkg/api/handlers/libpod/containers_create.go7
-rw-r--r--pkg/api/handlers/libpod/pods.go65
-rw-r--r--pkg/api/handlers/types/types.go4
-rw-r--r--pkg/bindings/pods/pods.go12
-rw-r--r--pkg/bindings/test/pods_test.go7
-rw-r--r--pkg/domain/entities/engine_container.go2
-rw-r--r--pkg/domain/entities/pods.go183
-rw-r--r--pkg/domain/entities/types.go38
-rw-r--r--pkg/domain/infra/abi/containers.go12
-rw-r--r--pkg/domain/infra/abi/generate.go4
-rw-r--r--pkg/domain/infra/abi/play.go250
-rw-r--r--pkg/domain/infra/abi/pods.go9
-rw-r--r--pkg/domain/infra/tunnel/events.go1
-rw-r--r--pkg/domain/infra/tunnel/pods.go7
-rw-r--r--pkg/specgen/generate/container_create.go63
-rw-r--r--pkg/specgen/generate/kube/kube.go32
-rw-r--r--pkg/specgen/generate/namespaces.go2
-rw-r--r--pkg/specgen/generate/oci.go3
-rw-r--r--pkg/specgen/generate/pod_create.go214
-rw-r--r--pkg/specgen/podspecgen.go3
-rw-r--r--pkg/specgenutil/createparse.go34
-rw-r--r--pkg/specgenutil/ports.go22
-rw-r--r--pkg/specgenutil/specgen.go1004
-rw-r--r--pkg/specgenutil/util.go274
-rw-r--r--pkg/specgenutil/volumes.go630
26 files changed, 2563 insertions, 322 deletions
diff --git a/pkg/api/handlers/compat/containers_create.go b/pkg/api/handlers/compat/containers_create.go
index 0b5cbd343..9df35697a 100644
--- a/pkg/api/handlers/compat/containers_create.go
+++ b/pkg/api/handlers/compat/containers_create.go
@@ -11,6 +11,7 @@ import (
"github.com/containers/podman/v3/pkg/domain/entities"
"github.com/containers/podman/v3/pkg/domain/infra/abi"
"github.com/containers/podman/v3/pkg/specgen"
+ "github.com/containers/podman/v3/pkg/specgenutil"
"github.com/containers/storage"
"github.com/gorilla/schema"
"github.com/pkg/errors"
@@ -80,7 +81,7 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) {
}
sg := specgen.NewSpecGenerator(imgNameOrID, cliOpts.RootFS)
- if err := common.FillOutSpecGen(sg, cliOpts, args); err != nil {
+ if err := specgenutil.FillOutSpecGen(sg, cliOpts, args); err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "fill out specgen"))
return
}
diff --git a/pkg/api/handlers/libpod/containers_create.go b/pkg/api/handlers/libpod/containers_create.go
index 65951861b..0e2163d5c 100644
--- a/pkg/api/handlers/libpod/containers_create.go
+++ b/pkg/api/handlers/libpod/containers_create.go
@@ -28,7 +28,12 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) {
utils.InternalServerError(w, err)
return
}
- ctr, err := generate.MakeContainer(context.Background(), runtime, &sg)
+ rtSpec, spec, opts, err := generate.MakeContainer(context.Background(), runtime, &sg)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ ctr, err := generate.ExecuteCreate(context.Background(), runtime, rtSpec, spec, false, opts...)
if err != nil {
utils.InternalServerError(w, err)
return
diff --git a/pkg/api/handlers/libpod/pods.go b/pkg/api/handlers/libpod/pods.go
index ff105bc48..3d6cf093b 100644
--- a/pkg/api/handlers/libpod/pods.go
+++ b/pkg/api/handlers/libpod/pods.go
@@ -1,11 +1,15 @@
package libpod
import (
+ "context"
"encoding/json"
"fmt"
"net/http"
"strings"
+ "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"
@@ -14,6 +18,7 @@ import (
"github.com/containers/podman/v3/pkg/domain/infra/abi"
"github.com/containers/podman/v3/pkg/specgen"
"github.com/containers/podman/v3/pkg/specgen/generate"
+ "github.com/containers/podman/v3/pkg/specgenutil"
"github.com/containers/podman/v3/pkg/util"
"github.com/gorilla/schema"
"github.com/pkg/errors"
@@ -25,24 +30,70 @@ func PodCreate(w http.ResponseWriter, r *http.Request) {
runtime = r.Context().Value("runtime").(*libpod.Runtime)
err error
)
- var psg specgen.PodSpecGenerator
+ psg := specgen.PodSpecGenerator{InfraContainerSpec: &specgen.SpecGenerator{}}
if err := json.NewDecoder(r.Body).Decode(&psg); err != nil {
- utils.Error(w, "failed to decode specgen", http.StatusInternalServerError, errors.Wrap(err, "failed to decode specgen"))
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to decode specgen"))
return
}
- // parse userns so we get the valid default value of userns
- psg.Userns, err = specgen.ParseUserNamespace(psg.Userns.String())
if err != nil {
- utils.Error(w, "failed to parse userns", http.StatusInternalServerError, errors.Wrap(err, "failed to parse userns"))
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to decode specgen"))
return
}
- pod, err := generate.MakePod(&psg, runtime)
+ if !psg.NoInfra {
+ infraOptions := &entities.ContainerCreateOptions{ImageVolume: "bind", IsInfra: true, Net: &entities.NetOptions{}} // options for pulling the image and FillOutSpec
+ err = specgenutil.FillOutSpecGen(psg.InfraContainerSpec, infraOptions, []string{}) // necessary for default values in many cases (userns, idmappings)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error filling out specgen"))
+ return
+ }
+ out, err := json.Marshal(psg) // marshal our spec so the matching options can be unmarshaled into infra
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to decode specgen"))
+ return
+ }
+ tempSpec := &specgen.SpecGenerator{} // temporary spec since infra cannot be decoded into
+ err = json.Unmarshal(out, tempSpec) // unmarhal matching options
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to decode specgen"))
+ return
+ }
+ psg.InfraContainerSpec = tempSpec // set infra spec equal to temp
+ // a few extra that do not have the same json tags
+ psg.InfraContainerSpec.Name = psg.InfraName
+ psg.InfraContainerSpec.ConmonPidFile = psg.InfraConmonPidFile
+ psg.InfraContainerSpec.ContainerCreateCommand = psg.InfraCommand
+ imageName := psg.InfraImage
+ rawImageName := psg.InfraImage
+ if imageName == "" {
+ 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
+ }
+ podSpecComplete := entities.PodSpec{PodSpecGen: psg}
+ pod, err := generate.MakePod(&podSpecComplete, runtime)
if err != nil {
httpCode := http.StatusInternalServerError
if errors.Cause(err) == define.ErrPodExists {
httpCode = http.StatusConflict
}
- utils.Error(w, "Something went wrong.", httpCode, err)
+ utils.Error(w, "Something went wrong.", httpCode, errors.Wrap(err, "failed to make pod"))
return
}
utils.WriteResponse(w, http.StatusCreated, handlers.IDResponse{ID: pod.ID()})
diff --git a/pkg/api/handlers/types/types.go b/pkg/api/handlers/types/types.go
index 71165364f..e7920047e 100644
--- a/pkg/api/handlers/types/types.go
+++ b/pkg/api/handlers/types/types.go
@@ -1,8 +1,6 @@
package types
-import (
- "github.com/containers/podman/v3/pkg/domain/entities"
-)
+import "github.com/containers/podman/v3/pkg/domain/entities"
// LibpodImagesRemoveReport is the return type for image removal via the rest
// api.
diff --git a/pkg/bindings/pods/pods.go b/pkg/bindings/pods/pods.go
index 9d3ff322e..a1a431a3b 100644
--- a/pkg/bindings/pods/pods.go
+++ b/pkg/bindings/pods/pods.go
@@ -9,27 +9,25 @@ 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/specgen"
jsoniter "github.com/json-iterator/go"
)
-func CreatePodFromSpec(ctx context.Context, s *specgen.PodSpecGenerator, options *CreateOptions) (*entities.PodCreateReport, error) {
+func CreatePodFromSpec(ctx context.Context, spec *entities.PodSpec) (*entities.PodCreateReport, error) {
var (
pcr entities.PodCreateReport
)
- if options == nil {
- options = new(CreateOptions)
+ if spec == nil {
+ spec = new(entities.PodSpec)
}
- _ = options
conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
- specgenString, err := jsoniter.MarshalToString(s)
+ specString, err := jsoniter.MarshalToString(spec.PodSpecGen)
if err != nil {
return nil, err
}
- stringReader := strings.NewReader(specgenString)
+ stringReader := strings.NewReader(specString)
response, err := conn.DoRequest(stringReader, http.MethodPost, "/pods/create", nil, nil)
if err != nil {
return nil, err
diff --git a/pkg/bindings/test/pods_test.go b/pkg/bindings/test/pods_test.go
index b06ff31a2..5331cf439 100644
--- a/pkg/bindings/test/pods_test.go
+++ b/pkg/bindings/test/pods_test.go
@@ -8,6 +8,7 @@ import (
"github.com/containers/podman/v3/libpod/define"
"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/specgen"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
@@ -333,9 +334,9 @@ var _ = Describe("Podman pods", func() {
})
It("simple create pod", func() {
- ps := specgen.PodSpecGenerator{}
- ps.Name = "foobar"
- _, err := pods.CreatePodFromSpec(bt.conn, &ps, nil)
+ ps := entities.PodSpec{PodSpecGen: specgen.PodSpecGenerator{InfraContainerSpec: &specgen.SpecGenerator{}}}
+ ps.PodSpecGen.Name = "foobar"
+ _, err := pods.CreatePodFromSpec(bt.conn, &ps)
Expect(err).To(BeNil())
exists, err := pods.Exists(bt.conn, "foobar", nil)
diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go
index 5acf7211c..bd011d309 100644
--- a/pkg/domain/entities/engine_container.go
+++ b/pkg/domain/entities/engine_container.go
@@ -68,7 +68,7 @@ type ContainerEngine interface {
NetworkRm(ctx context.Context, namesOrIds []string, options NetworkRmOptions) ([]*NetworkRmReport, error)
PlayKube(ctx context.Context, path string, opts PlayKubeOptions) (*PlayKubeReport, error)
PlayKubeDown(ctx context.Context, path string, opts PlayKubeDownOptions) (*PlayKubeReport, error)
- PodCreate(ctx context.Context, opts PodCreateOptions) (*PodCreateReport, error)
+ PodCreate(ctx context.Context, specg PodSpec) (*PodCreateReport, error)
PodExists(ctx context.Context, nameOrID string) (*BoolReport, error)
PodInspect(ctx context.Context, options PodInspectOptions) (*PodInspectReport, error)
PodKill(ctx context.Context, namesOrIds []string, options PodKillOptions) ([]*PodKillReport, error)
diff --git a/pkg/domain/entities/pods.go b/pkg/domain/entities/pods.go
index c66bf96fc..10bd7e5ce 100644
--- a/pkg/domain/entities/pods.go
+++ b/pkg/domain/entities/pods.go
@@ -106,6 +106,14 @@ type PodRmReport struct {
Id string //nolint
}
+// PddSpec is an abstracted version of PodSpecGen designed to eventually accept options
+// not meant to be in a specgen
+type PodSpec struct {
+ PodSpecGen specgen.PodSpecGenerator
+}
+
+// PodCreateOptions provides all possible options for creating a pod and its infra container
+// swagger:model PodCreateOptions
type PodCreateOptions struct {
CGroupParent string
CreateCommand []string
@@ -125,6 +133,123 @@ type PodCreateOptions struct {
Userns specgen.Namespace
}
+type ContainerCreateOptions struct {
+ Annotation []string
+ Attach []string
+ Authfile string
+ BlkIOWeight string
+ BlkIOWeightDevice []string
+ CapAdd []string
+ CapDrop []string
+ CgroupNS string
+ CGroupsMode string
+ CGroupParent string
+ CIDFile string
+ ConmonPIDFile string
+ CPUPeriod uint64
+ CPUQuota int64
+ CPURTPeriod uint64
+ CPURTRuntime int64
+ CPUShares uint64
+ CPUS float64
+ CPUSetCPUs string
+ CPUSetMems string
+ Devices []string
+ DeviceCGroupRule []string
+ DeviceReadBPs []string
+ DeviceReadIOPs []string
+ DeviceWriteBPs []string
+ DeviceWriteIOPs []string
+ Entrypoint *string
+ Env []string
+ EnvHost bool
+ EnvFile []string
+ Expose []string
+ GIDMap []string
+ GroupAdd []string
+ HealthCmd string
+ HealthInterval string
+ HealthRetries uint
+ HealthStartPeriod string
+ HealthTimeout string
+ Hostname string
+ HTTPProxy bool
+ ImageVolume string
+ Init bool
+ InitContainerType string
+ InitPath string
+ Interactive bool
+ IPC string
+ KernelMemory string
+ Label []string
+ LabelFile []string
+ LogDriver string
+ LogOptions []string
+ Memory string
+ MemoryReservation string
+ MemorySwap string
+ MemorySwappiness int64
+ Name string
+ NoHealthCheck bool
+ OOMKillDisable bool
+ OOMScoreAdj int
+ Arch string
+ OS string
+ Variant string
+ PID string
+ PIDsLimit *int64
+ Platform string
+ Pod string
+ PodIDFile string
+ Personality string
+ PreserveFDs uint
+ Privileged bool
+ PublishAll bool
+ Pull string
+ Quiet bool
+ ReadOnly bool
+ ReadOnlyTmpFS bool
+ Restart string
+ Replace bool
+ Requires []string
+ Rm bool
+ RootFS bool
+ Secrets []string
+ SecurityOpt []string
+ SdNotifyMode string
+ ShmSize string
+ SignaturePolicy string
+ StopSignal string
+ StopTimeout uint
+ StorageOpt []string
+ SubUIDName string
+ SubGIDName string
+ Sysctl []string
+ Systemd string
+ Timeout uint
+ TLSVerify bool
+ TmpFS []string
+ TTY bool
+ Timezone string
+ Umask string
+ UIDMap []string
+ Ulimit []string
+ User string
+ UserNS string
+ UTS string
+ Mount []string
+ Volume []string
+ VolumesFrom []string
+ Workdir string
+ SeccompPolicy string
+ PidFile string
+ IsInfra bool
+
+ Net *NetOptions
+
+ CgroupConf []string
+}
+
type PodCreateReport struct {
Id string //nolint
}
@@ -149,21 +274,15 @@ func (p *PodCreateOptions) CPULimits() *specs.LinuxCPU {
return cpu
}
-func setNamespaces(p *PodCreateOptions) ([4]specgen.Namespace, error) {
- allNS := [4]specgen.Namespace{}
- if p.Pid != "" {
- pid, err := specgen.ParseNamespace(p.Pid)
- if err != nil {
- return [4]specgen.Namespace{}, err
- }
- allNS[0] = pid
- }
- return allNS, nil
-}
-
-func (p *PodCreateOptions) ToPodSpecGen(s *specgen.PodSpecGenerator) error {
+func ToPodSpecGen(s specgen.PodSpecGenerator, p *PodCreateOptions) (*specgen.PodSpecGenerator, error) {
// Basic Config
s.Name = p.Name
+ s.InfraName = p.InfraName
+ out, err := specgen.ParseNamespace(p.Pid)
+ if err != nil {
+ return nil, err
+ }
+ s.Pid = out
s.Hostname = p.Hostname
s.Labels = p.Labels
s.NoInfra = !p.Infra
@@ -174,32 +293,26 @@ func (p *PodCreateOptions) ToPodSpecGen(s *specgen.PodSpecGenerator) error {
s.InfraConmonPidFile = p.InfraConmonPidFile
}
s.InfraImage = p.InfraImage
- s.InfraName = p.InfraName
s.SharedNamespaces = p.Share
s.PodCreateCommand = p.CreateCommand
// Networking config
- s.NetNS = p.Net.Network
- s.StaticIP = p.Net.StaticIP
- s.StaticMAC = p.Net.StaticMAC
- s.PortMappings = p.Net.PublishPorts
- s.CNINetworks = p.Net.CNINetworks
- s.NetworkOptions = p.Net.NetworkOptions
- if p.Net.UseImageResolvConf {
- s.NoManageResolvConf = true
- }
- s.DNSServer = p.Net.DNSServers
- s.DNSSearch = p.Net.DNSSearch
- s.DNSOption = p.Net.DNSOptions
- s.NoManageHosts = p.Net.NoHosts
- s.HostAdd = p.Net.AddHosts
- namespaces, err := setNamespaces(p)
- if err != nil {
- return err
- }
- if !namespaces[0].IsDefault() {
- s.Pid = namespaces[0]
+ if p.Net != nil {
+ s.NetNS = p.Net.Network
+ s.StaticIP = p.Net.StaticIP
+ s.StaticMAC = p.Net.StaticMAC
+ s.PortMappings = p.Net.PublishPorts
+ s.CNINetworks = p.Net.CNINetworks
+ s.NetworkOptions = p.Net.NetworkOptions
+ if p.Net.UseImageResolvConf {
+ s.NoManageResolvConf = true
+ }
+ s.DNSServer = p.Net.DNSServers
+ s.DNSSearch = p.Net.DNSSearch
+ s.DNSOption = p.Net.DNSOptions
+ s.NoManageHosts = p.Net.NoHosts
+ s.HostAdd = p.Net.AddHosts
}
// Cgroup
@@ -219,7 +332,7 @@ func (p *PodCreateOptions) ToPodSpecGen(s *specgen.PodSpecGenerator) error {
}
}
s.Userns = p.Userns
- return nil
+ return &s, nil
}
type PodPruneOptions struct {
diff --git a/pkg/domain/entities/types.go b/pkg/domain/entities/types.go
index db4c6bb8a..ec4d4a902 100644
--- a/pkg/domain/entities/types.go
+++ b/pkg/domain/entities/types.go
@@ -31,21 +31,33 @@ type VolumeDeleteReport struct{ Report }
// NetOptions reflect the shared network options between
// pods and containers
+type NetFlags struct {
+ AddHosts []string `json:"add-host,omitempty"`
+ DNS []string `json:"dns,omitempty"`
+ DNSOpt []string `json:"dns-opt,omitempty"`
+ DNDSearch []string `json:"dns-search,omitempty"`
+ MacAddr string `json:"mac-address,omitempty"`
+ Publish []string `json:"publish,omitempty"`
+ IP string `json:"ip,omitempty"`
+ NoHosts bool `json:"no-hosts,omitempty"`
+ Network string `json:"network,omitempty"`
+ NetworkAlias []string `json:"network-alias,omitempty"`
+}
type NetOptions struct {
- AddHosts []string
- Aliases []string
- CNINetworks []string
- UseImageResolvConf bool
- DNSOptions []string
- DNSSearch []string
- DNSServers []net.IP
- Network specgen.Namespace
- NoHosts bool
- PublishPorts []types.PortMapping
- StaticIP *net.IP
- StaticMAC *net.HardwareAddr
+ AddHosts []string `json:"hostadd,omitempty"`
+ Aliases []string `json:"network_alias,omitempty"`
+ CNINetworks []string `json:"cni_networks,omitempty"`
+ UseImageResolvConf bool `json:"no_manage_resolv_conf,omitempty"`
+ DNSOptions []string `json:"dns_option,omitempty"`
+ DNSSearch []string `json:"dns_search,omitempty"`
+ DNSServers []net.IP `json:"dns_server,omitempty"`
+ Network specgen.Namespace `json:"netns,omitempty"`
+ NoHosts bool `json:"no_manage_hosts,omitempty"`
+ PublishPorts []types.PortMapping `json:"portmappings,omitempty"`
+ StaticIP *net.IP `json:"static_ip,omitempty"`
+ StaticMAC *net.HardwareAddr `json:"static_mac,omitempty"`
// NetworkOptions are additional options for each network
- NetworkOptions map[string][]string
+ NetworkOptions map[string][]string `json:"network_options,omitempty"`
}
// All CLI inspect commands and inspect sub-commands use the same options
diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go
index a74b65ab9..8b2a5bfae 100644
--- a/pkg/domain/infra/abi/containers.go
+++ b/pkg/domain/infra/abi/containers.go
@@ -583,7 +583,11 @@ func (ic *ContainerEngine) ContainerCreate(ctx context.Context, s *specgen.SpecG
for _, w := range warn {
fmt.Fprintf(os.Stderr, "%s\n", w)
}
- ctr, err := generate.MakeContainer(ctx, ic.Libpod, s)
+ rtSpec, spec, opts, err := generate.MakeContainer(context.Background(), ic.Libpod, s)
+ if err != nil {
+ return nil, err
+ }
+ ctr, err := generate.ExecuteCreate(ctx, ic.Libpod, rtSpec, spec, false, opts...)
if err != nil {
return nil, err
}
@@ -915,7 +919,11 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta
for _, w := range warn {
fmt.Fprintf(os.Stderr, "%s\n", w)
}
- ctr, err := generate.MakeContainer(ctx, ic.Libpod, opts.Spec)
+ rtSpec, spec, optsN, err := generate.MakeContainer(ctx, ic.Libpod, opts.Spec)
+ if err != nil {
+ return nil, err
+ }
+ ctr, err := generate.ExecuteCreate(ctx, ic.Libpod, rtSpec, spec, false, optsN...)
if err != nil {
return nil, err
}
diff --git a/pkg/domain/infra/abi/generate.go b/pkg/domain/infra/abi/generate.go
index b0853b554..2d7bc15f5 100644
--- a/pkg/domain/infra/abi/generate.go
+++ b/pkg/domain/infra/abi/generate.go
@@ -60,9 +60,7 @@ func (ic *ContainerEngine) GenerateKube(ctx context.Context, nameOrIDs []string,
return nil, err
}
} else {
- if len(ctr.Dependencies()) > 0 {
- return nil, errors.Wrapf(define.ErrNotImplemented, "containers with dependencies")
- }
+ // now that infra holds NS data, we need to support dependencies.
// we cannot deal with ctrs already in a pod.
if len(ctr.PodID()) > 0 {
return nil, errors.Errorf("container %s is associated with pod %s: use generate on the pod itself", ctr.ID(), ctr.PodID())
diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go
index f22b2dbbb..2799df794 100644
--- a/pkg/domain/infra/abi/play.go
+++ b/pkg/domain/infra/abi/play.go
@@ -6,6 +6,7 @@ import (
"fmt"
"io"
"io/ioutil"
+ "net"
"os"
"path/filepath"
"strconv"
@@ -22,6 +23,7 @@ import (
"github.com/containers/podman/v3/pkg/specgen"
"github.com/containers/podman/v3/pkg/specgen/generate"
"github.com/containers/podman/v3/pkg/specgen/generate/kube"
+ "github.com/containers/podman/v3/pkg/specgenutil"
"github.com/containers/podman/v3/pkg/util"
"github.com/ghodss/yaml"
"github.com/pkg/errors"
@@ -179,10 +181,12 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
}
}
- p, err := kube.ToPodGen(ctx, podName, podYAML)
+ podOpt := entities.PodCreateOptions{Infra: true, Net: &entities.NetOptions{StaticIP: &net.IP{}, StaticMAC: &net.HardwareAddr{}}}
+ podOpt, err = kube.ToPodOpt(ctx, podName, podOpt, podYAML)
if err != nil {
return nil, err
}
+
if options.Network != "" {
ns, cniNets, netOpts, err := specgen.ParseNetworkString(options.Network)
if err != nil {
@@ -193,42 +197,37 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
return nil, errors.Errorf("invalid value passed to --network: bridge or host networking must be configured in YAML")
}
logrus.Debugf("Pod %q joining CNI networks: %v", podName, cniNets)
- p.NetNS.NSMode = specgen.Bridge
- p.CNINetworks = append(p.CNINetworks, cniNets...)
+ podOpt.Net.Network.NSMode = specgen.Bridge
+ podOpt.Net.CNINetworks = append(podOpt.Net.CNINetworks, cniNets...)
if len(netOpts) > 0 {
- p.NetworkOptions = netOpts
+ podOpt.Net.NetworkOptions = netOpts
}
}
if len(options.StaticIPs) > *ipIndex {
- p.StaticIP = &options.StaticIPs[*ipIndex]
+ podOpt.Net.StaticIP = &options.StaticIPs[*ipIndex]
} else if len(options.StaticIPs) > 0 {
// only warn if the user has set at least one ip
logrus.Warn("No more static ips left using a random one")
}
if len(options.StaticMACs) > *ipIndex {
- p.StaticMAC = &options.StaticMACs[*ipIndex]
+ podOpt.Net.StaticMAC = &options.StaticMACs[*ipIndex]
} else if len(options.StaticIPs) > 0 {
// only warn if the user has set at least one mac
logrus.Warn("No more static macs left using a random one")
}
*ipIndex++
- // Create the Pod
- pod, err := generate.MakePod(p, ic.Libpod)
+ p := specgen.NewPodSpecGenerator()
if err != nil {
return nil, err
}
- podInfraID, err := pod.InfraContainerID()
+ p, err = entities.ToPodSpecGen(*p, &podOpt)
if err != nil {
return nil, err
}
-
- if !options.Quiet {
- writer = os.Stderr
- }
-
+ podSpec := entities.PodSpec{PodSpecGen: *p}
volumes, err := kube.InitializeVolumes(podYAML.Spec.Volumes)
if err != nil {
return nil, err
@@ -267,112 +266,146 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
configMaps = append(configMaps, cm)
}
- containers := make([]*libpod.Container, 0, len(podYAML.Spec.Containers))
- cwd, err := os.Getwd()
- if err != nil {
- return nil, err
- }
- for _, container := range podYAML.Spec.Containers {
- // Contains all labels obtained from kube
- labels := make(map[string]string)
- var pulledImage *libimage.Image
- buildFile, err := getBuildFile(container.Image, cwd)
+ if podOpt.Infra {
+ imagePull := config.DefaultInfraImage
+ if podOpt.InfraImage != config.DefaultInfraImage && podOpt.InfraImage != "" {
+ imagePull = podOpt.InfraImage
+ }
+
+ pulledImages, err := pullImage(ic, writer, imagePull, options, config.PullPolicyNewer)
if err != nil {
return nil, err
}
- existsLocally, err := ic.Libpod.LibimageRuntime().Exists(container.Image)
+ infraOptions := entities.ContainerCreateOptions{ImageVolume: "bind"}
+
+ podSpec.PodSpecGen.InfraImage = pulledImages[0].Names()[0]
+ podSpec.PodSpecGen.NoInfra = false
+ podSpec.PodSpecGen.InfraContainerSpec = specgen.NewSpecGenerator(pulledImages[0].Names()[0], false)
+ podSpec.PodSpecGen.InfraContainerSpec.NetworkOptions = p.NetworkOptions
+
+ err = specgenutil.FillOutSpecGen(podSpec.PodSpecGen.InfraContainerSpec, &infraOptions, []string{})
if err != nil {
return nil, err
}
- if (len(buildFile) > 0 && !existsLocally) || (len(buildFile) > 0 && options.Build) {
- buildOpts := new(buildahDefine.BuildOptions)
- commonOpts := new(buildahDefine.CommonBuildOptions)
- buildOpts.ConfigureNetwork = buildahDefine.NetworkDefault
- buildOpts.Isolation = buildahDefine.IsolationChroot
- buildOpts.CommonBuildOpts = commonOpts
- buildOpts.Output = container.Image
- if _, _, err := ic.Libpod.Build(ctx, *buildOpts, []string{buildFile}...); err != nil {
+ }
+
+ // Create the Pod
+ pod, err := generate.MakePod(&podSpec, ic.Libpod)
+ if err != nil {
+ return nil, err
+ }
+
+ podInfraID, err := pod.InfraContainerID()
+ if err != nil {
+ return nil, err
+ }
+
+ if !options.Quiet {
+ writer = os.Stderr
+ }
+
+ containers := make([]*libpod.Container, 0, len(podYAML.Spec.Containers))
+ cwd, err := os.Getwd()
+ if err != nil {
+ return nil, err
+ }
+ for _, container := range podYAML.Spec.Containers {
+ if !strings.Contains("infra", container.Name) {
+ // Contains all labels obtained from kube
+ labels := make(map[string]string)
+ var pulledImage *libimage.Image
+ buildFile, err := getBuildFile(container.Image, cwd)
+ if err != nil {
return nil, err
}
- i, _, err := ic.Libpod.LibimageRuntime().LookupImage(container.Image, new(libimage.LookupImageOptions))
+ existsLocally, err := ic.Libpod.LibimageRuntime().Exists(container.Image)
if err != nil {
return nil, err
}
- pulledImage = i
- } else {
- // NOTE: set the pull policy to "newer". This will cover cases
- // where the "latest" tag requires a pull and will also
- // transparently handle "localhost/" prefixed files which *may*
- // refer to a locally built image OR an image running a
- // registry on localhost.
- pullPolicy := config.PullPolicyNewer
- if len(container.ImagePullPolicy) > 0 {
- // Make sure to lower the strings since K8s pull policy
- // may be capitalized (see bugzilla.redhat.com/show_bug.cgi?id=1985905).
- rawPolicy := string(container.ImagePullPolicy)
- pullPolicy, err = config.ParsePullPolicy(strings.ToLower(rawPolicy))
+ if (len(buildFile) > 0 && !existsLocally) || (len(buildFile) > 0 && options.Build) {
+ buildOpts := new(buildahDefine.BuildOptions)
+ commonOpts := new(buildahDefine.CommonBuildOptions)
+ buildOpts.ConfigureNetwork = buildahDefine.NetworkDefault
+ buildOpts.Isolation = buildahDefine.IsolationChroot
+ buildOpts.CommonBuildOpts = commonOpts
+ buildOpts.Output = container.Image
+ if _, _, err := ic.Libpod.Build(ctx, *buildOpts, []string{buildFile}...); err != nil {
+ return nil, err
+ }
+ i, _, err := ic.Libpod.LibimageRuntime().LookupImage(container.Image, new(libimage.LookupImageOptions))
+ if err != nil {
+ return nil, err
+ }
+ pulledImage = i
+ } else {
+ // NOTE: set the pull policy to "newer". This will cover cases
+ // where the "latest" tag requires a pull and will also
+ // transparently handle "localhost/" prefixed files which *may*
+ // refer to a locally built image OR an image running a
+ // registry on localhost.
+ pullPolicy := config.PullPolicyNewer
+ if len(container.ImagePullPolicy) > 0 {
+ // Make sure to lower the strings since K8s pull policy
+ // may be capitalized (see bugzilla.redhat.com/show_bug.cgi?id=1985905).
+ rawPolicy := string(container.ImagePullPolicy)
+ pullPolicy, err = config.ParsePullPolicy(strings.ToLower(rawPolicy))
+ if err != nil {
+ return nil, err
+ }
+ }
+ pulledImages, err := pullImage(ic, writer, container.Image, options, pullPolicy)
if err != nil {
return nil, err
}
+ pulledImage = pulledImages[0]
+ }
+
+ // Handle kube annotations
+ for k, v := range annotations {
+ switch k {
+ // Auto update annotation without container name will apply to
+ // all containers within the pod
+ case autoupdate.Label, autoupdate.AuthfileLabel:
+ labels[k] = v
+ // Auto update annotation with container name will apply only
+ // to the specified container
+ case fmt.Sprintf("%s/%s", autoupdate.Label, container.Name),
+ fmt.Sprintf("%s/%s", autoupdate.AuthfileLabel, container.Name):
+ prefixAndCtr := strings.Split(k, "/")
+ labels[prefixAndCtr[0]] = v
+ }
}
- // This ensures the image is the image store
- pullOptions := &libimage.PullOptions{}
- pullOptions.AuthFilePath = options.Authfile
- pullOptions.CertDirPath = options.CertDir
- pullOptions.SignaturePolicyPath = options.SignaturePolicy
- pullOptions.Writer = writer
- pullOptions.Username = options.Username
- pullOptions.Password = options.Password
- pullOptions.InsecureSkipTLSVerify = options.SkipTLSVerify
-
- pulledImages, err := ic.Libpod.LibimageRuntime().Pull(ctx, container.Image, pullPolicy, pullOptions)
+
+ specgenOpts := kube.CtrSpecGenOptions{
+ Container: container,
+ Image: pulledImage,
+ Volumes: volumes,
+ PodID: pod.ID(),
+ PodName: podName,
+ PodInfraID: podInfraID,
+ ConfigMaps: configMaps,
+ SeccompPaths: seccompPaths,
+ RestartPolicy: ctrRestartPolicy,
+ NetNSIsHost: p.NetNS.IsHost(),
+ SecretsManager: secretsManager,
+ LogDriver: options.LogDriver,
+ Labels: labels,
+ }
+ specGen, err := kube.ToSpecGen(ctx, &specgenOpts)
if err != nil {
return nil, err
}
- pulledImage = pulledImages[0]
- }
- // Handle kube annotations
- for k, v := range annotations {
- switch k {
- // Auto update annotation without container name will apply to
- // all containers within the pod
- case autoupdate.Label, autoupdate.AuthfileLabel:
- labels[k] = v
- // Auto update annotation with container name will apply only
- // to the specified container
- case fmt.Sprintf("%s/%s", autoupdate.Label, container.Name),
- fmt.Sprintf("%s/%s", autoupdate.AuthfileLabel, container.Name):
- prefixAndCtr := strings.Split(k, "/")
- labels[prefixAndCtr[0]] = v
+ rtSpec, spec, opts, err := generate.MakeContainer(ctx, ic.Libpod, specGen)
+ if err != nil {
+ return nil, err
}
+ ctr, err := generate.ExecuteCreate(ctx, ic.Libpod, rtSpec, spec, false, opts...)
+ if err != nil {
+ return nil, err
+ }
+ containers = append(containers, ctr)
}
-
- specgenOpts := kube.CtrSpecGenOptions{
- Container: container,
- Image: pulledImage,
- Volumes: volumes,
- PodID: pod.ID(),
- PodName: podName,
- PodInfraID: podInfraID,
- ConfigMaps: configMaps,
- SeccompPaths: seccompPaths,
- RestartPolicy: ctrRestartPolicy,
- NetNSIsHost: p.NetNS.IsHost(),
- SecretsManager: secretsManager,
- LogDriver: options.LogDriver,
- Labels: labels,
- }
- specGen, err := kube.ToSpecGen(ctx, &specgenOpts)
- if err != nil {
- return nil, err
- }
-
- ctr, err := generate.MakeContainer(ctx, ic.Libpod, specGen)
- if err != nil {
- return nil, err
- }
- containers = append(containers, ctr)
}
if options.Start != types.OptionalBoolFalse {
@@ -383,6 +416,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
}
for id, err := range podStartErrors {
playKubePod.ContainerErrors = append(playKubePod.ContainerErrors, errors.Wrapf(err, "error starting container %s", id).Error())
+ fmt.Println(playKubePod.ContainerErrors)
}
}
@@ -656,3 +690,21 @@ func (ic *ContainerEngine) PlayKubeDown(ctx context.Context, path string, _ enti
}
return reports, nil
}
+
+// pullImage is a helper function to set up the proper pull options and pull the image for certain containers
+func pullImage(ic *ContainerEngine, writer io.Writer, imagePull string, options entities.PlayKubeOptions, pullPolicy config.PullPolicy) ([]*libimage.Image, error) {
+ // This ensures the image is the image store
+ pullOptions := &libimage.PullOptions{}
+ pullOptions.AuthFilePath = options.Authfile
+ pullOptions.CertDirPath = options.CertDir
+ pullOptions.SignaturePolicyPath = options.SignaturePolicy
+ pullOptions.Writer = writer
+ pullOptions.Username = options.Username
+ pullOptions.Password = options.Password
+ pullOptions.InsecureSkipTLSVerify = options.SkipTLSVerify
+ pulledImages, err := ic.Libpod.LibimageRuntime().Pull(context.Background(), imagePull, pullPolicy, pullOptions)
+ if err != nil {
+ return nil, err
+ }
+ return pulledImages, nil
+}
diff --git a/pkg/domain/infra/abi/pods.go b/pkg/domain/infra/abi/pods.go
index 055c495d5..98233f60d 100644
--- a/pkg/domain/infra/abi/pods.go
+++ b/pkg/domain/infra/abi/pods.go
@@ -8,7 +8,6 @@ import (
"github.com/containers/podman/v3/pkg/domain/entities"
dfilters "github.com/containers/podman/v3/pkg/domain/filters"
"github.com/containers/podman/v3/pkg/signal"
- "github.com/containers/podman/v3/pkg/specgen"
"github.com/containers/podman/v3/pkg/specgen/generate"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -248,12 +247,8 @@ func (ic *ContainerEngine) prunePodHelper(ctx context.Context) ([]*entities.PodP
return reports, nil
}
-func (ic *ContainerEngine) PodCreate(ctx context.Context, opts entities.PodCreateOptions) (*entities.PodCreateReport, error) {
- podSpec := specgen.NewPodSpecGenerator()
- if err := opts.ToPodSpecGen(podSpec); err != nil {
- return nil, err
- }
- pod, err := generate.MakePod(podSpec, ic.Libpod)
+func (ic *ContainerEngine) PodCreate(ctx context.Context, specg entities.PodSpec) (*entities.PodCreateReport, error) {
+ pod, err := generate.MakePod(&specg, ic.Libpod)
if err != nil {
return nil, err
}
diff --git a/pkg/domain/infra/tunnel/events.go b/pkg/domain/infra/tunnel/events.go
index 6e2c3f8ba..203550c5d 100644
--- a/pkg/domain/infra/tunnel/events.go
+++ b/pkg/domain/infra/tunnel/events.go
@@ -7,6 +7,7 @@ import (
"github.com/containers/podman/v3/libpod/events"
"github.com/containers/podman/v3/pkg/bindings/system"
"github.com/containers/podman/v3/pkg/domain/entities"
+
"github.com/pkg/errors"
)
diff --git a/pkg/domain/infra/tunnel/pods.go b/pkg/domain/infra/tunnel/pods.go
index 82f062b2c..480adb88a 100644
--- a/pkg/domain/infra/tunnel/pods.go
+++ b/pkg/domain/infra/tunnel/pods.go
@@ -6,7 +6,6 @@ import (
"github.com/containers/podman/v3/libpod/define"
"github.com/containers/podman/v3/pkg/bindings/pods"
"github.com/containers/podman/v3/pkg/domain/entities"
- "github.com/containers/podman/v3/pkg/specgen"
"github.com/containers/podman/v3/pkg/util"
"github.com/pkg/errors"
)
@@ -179,10 +178,8 @@ func (ic *ContainerEngine) PodPrune(ctx context.Context, opts entities.PodPruneO
return pods.Prune(ic.ClientCtx, nil)
}
-func (ic *ContainerEngine) PodCreate(ctx context.Context, opts entities.PodCreateOptions) (*entities.PodCreateReport, error) {
- podSpec := specgen.NewPodSpecGenerator()
- opts.ToPodSpecGen(podSpec)
- return pods.CreatePodFromSpec(ic.ClientCtx, podSpec, nil)
+func (ic *ContainerEngine) PodCreate(ctx context.Context, specg entities.PodSpec) (*entities.PodCreateReport, error) {
+ return pods.CreatePodFromSpec(ic.ClientCtx, &specg)
}
func (ic *ContainerEngine) PodTop(ctx context.Context, opts entities.PodTopOptions) (*entities.StringSliceReport, error) {
diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go
index 5101a6ccb..f82b2a3c6 100644
--- a/pkg/specgen/generate/container_create.go
+++ b/pkg/specgen/generate/container_create.go
@@ -22,10 +22,10 @@ import (
// MakeContainer creates a container based on the SpecGenerator.
// Returns the created, container and any warnings resulting from creating the
// container, or an error.
-func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGenerator) (*libpod.Container, error) {
+func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGenerator) (*spec.Spec, *specgen.SpecGenerator, []libpod.CtrCreateOption, error) {
rtc, err := rt.GetConfig()
if err != nil {
- return nil, err
+ return nil, nil, nil, err
}
// If joining a pod, retrieve the pod for use.
@@ -33,7 +33,7 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener
if s.Pod != "" {
pod, err = rt.LookupPod(s.Pod)
if err != nil {
- return nil, errors.Wrapf(err, "error retrieving pod %s", s.Pod)
+ return nil, nil, nil, errors.Wrapf(err, "error retrieving pod %s", s.Pod)
}
}
@@ -41,47 +41,48 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener
if s.PidNS.IsDefault() {
defaultNS, err := GetDefaultNamespaceMode("pid", rtc, pod)
if err != nil {
- return nil, err
+ return nil, nil, nil, err
}
s.PidNS = defaultNS
}
if s.IpcNS.IsDefault() {
defaultNS, err := GetDefaultNamespaceMode("ipc", rtc, pod)
if err != nil {
- return nil, err
+ return nil, nil, nil, err
}
s.IpcNS = defaultNS
}
if s.UtsNS.IsDefault() {
defaultNS, err := GetDefaultNamespaceMode("uts", rtc, pod)
if err != nil {
- return nil, err
+ return nil, nil, nil, err
}
s.UtsNS = defaultNS
}
if s.UserNS.IsDefault() {
defaultNS, err := GetDefaultNamespaceMode("user", rtc, pod)
if err != nil {
- return nil, err
+ return nil, nil, nil, err
}
s.UserNS = defaultNS
}
if s.NetNS.IsDefault() {
defaultNS, err := GetDefaultNamespaceMode("net", rtc, pod)
if err != nil {
- return nil, err
+ return nil, nil, nil, err
}
s.NetNS = defaultNS
}
if s.CgroupNS.IsDefault() {
defaultNS, err := GetDefaultNamespaceMode("cgroup", rtc, pod)
if err != nil {
- return nil, err
+ return nil, nil, nil, err
}
s.CgroupNS = defaultNS
}
options := []libpod.CtrCreateOption{}
+
if s.ContainerCreateCommand != nil {
options = append(options, libpod.WithCreateCommand(s.ContainerCreateCommand))
}
@@ -94,12 +95,11 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener
var resolvedImageName string
newImage, resolvedImageName, err = rt.LibimageRuntime().LookupImage(s.Image, nil)
if err != nil {
- return nil, err
+ return nil, nil, nil, err
}
-
imageData, err = newImage.Inspect(ctx, false)
if err != nil {
- return nil, err
+ return nil, nil, nil, err
}
// If the input name changed, we could properly resolve the
// image. Otherwise, it must have been an ID where we're
@@ -115,29 +115,32 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener
options = append(options, libpod.WithRootFSFromImage(newImage.ID(), resolvedImageName, s.RawImageName))
}
if err := s.Validate(); err != nil {
- return nil, errors.Wrap(err, "invalid config provided")
+ return nil, nil, nil, errors.Wrap(err, "invalid config provided")
}
finalMounts, finalVolumes, finalOverlays, err := finalizeMounts(ctx, s, rt, rtc, newImage)
if err != nil {
- return nil, err
+ return nil, nil, nil, err
}
command, err := makeCommand(ctx, s, imageData, rtc)
if err != nil {
- return nil, err
+ return nil, nil, nil, err
}
opts, err := createContainerOptions(ctx, rt, s, pod, finalVolumes, finalOverlays, imageData, command)
if err != nil {
- return nil, err
+ return nil, nil, nil, err
}
options = append(options, opts...)
- exitCommandArgs, err := CreateExitCommandArgs(rt.StorageConfig(), rtc, logrus.IsLevelEnabled(logrus.DebugLevel), s.Remove, false)
+ var exitCommandArgs []string
+
+ exitCommandArgs, err = CreateExitCommandArgs(rt.StorageConfig(), rtc, logrus.IsLevelEnabled(logrus.DebugLevel), s.Remove, false)
if err != nil {
- return nil, err
+ return nil, nil, nil, err
}
+
options = append(options, libpod.WithExitCommand(exitCommandArgs))
if len(s.Aliases) > 0 {
@@ -147,23 +150,26 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener
if containerType := s.InitContainerType; len(containerType) > 0 {
options = append(options, libpod.WithInitCtrType(containerType))
}
-
+ if len(s.Name) > 0 {
+ logrus.Debugf("setting container name %s", s.Name)
+ options = append(options, libpod.WithName(s.Name))
+ }
if len(s.Devices) > 0 {
opts = extractCDIDevices(s)
options = append(options, opts...)
}
runtimeSpec, err := SpecGenToOCI(ctx, s, rt, rtc, newImage, finalMounts, pod, command)
if err != nil {
- return nil, err
+ return nil, nil, nil, err
}
-
- ctr, err := rt.NewContainer(ctx, runtimeSpec, options...)
+ return runtimeSpec, s, options, err
+}
+func ExecuteCreate(ctx context.Context, rt *libpod.Runtime, runtimeSpec *spec.Spec, s *specgen.SpecGenerator, infra bool, options ...libpod.CtrCreateOption) (*libpod.Container, error) {
+ ctr, err := rt.NewContainer(ctx, runtimeSpec, s, infra, options...)
if err != nil {
return ctr, err
}
- // Copy the content from the underlying image into the newly created
- // volume if configured to do so.
return ctr, rt.PrepareVolumeOnCreateContainer(ctx, ctr)
}
@@ -256,11 +262,6 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen.
if len(s.SdNotifyMode) > 0 {
options = append(options, libpod.WithSdNotifyMode(s.SdNotifyMode))
}
-
- if len(s.Name) > 0 {
- logrus.Debugf("setting container name %s", s.Name)
- options = append(options, libpod.WithName(s.Name))
- }
if pod != nil {
logrus.Debugf("adding container to pod %s", pod.Name())
options = append(options, rt.WithPod(pod))
@@ -379,11 +380,11 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen.
options = append(options, libpod.WithPrivileged(s.Privileged))
// Get namespace related options
- namespaceOptions, err := namespaceOptions(ctx, s, rt, pod, imageData)
+ namespaceOpts, err := namespaceOptions(ctx, s, rt, pod, imageData)
if err != nil {
return nil, err
}
- options = append(options, namespaceOptions...)
+ options = append(options, namespaceOpts...)
if len(s.ConmonPidFile) > 0 {
options = append(options, libpod.WithConmonPidFile(s.ConmonPidFile))
diff --git a/pkg/specgen/generate/kube/kube.go b/pkg/specgen/generate/kube/kube.go
index 04b4e5ab3..5188abc3a 100644
--- a/pkg/specgen/generate/kube/kube.go
+++ b/pkg/specgen/generate/kube/kube.go
@@ -14,6 +14,7 @@ import (
"github.com/containers/image/v5/manifest"
"github.com/containers/podman/v3/libpod/network/types"
ann "github.com/containers/podman/v3/pkg/annotations"
+ "github.com/containers/podman/v3/pkg/domain/entities"
"github.com/containers/podman/v3/pkg/specgen"
"github.com/containers/podman/v3/pkg/specgen/generate"
"github.com/containers/podman/v3/pkg/util"
@@ -23,25 +24,26 @@ import (
"k8s.io/apimachinery/pkg/api/resource"
)
-func ToPodGen(ctx context.Context, podName string, podYAML *v1.PodTemplateSpec) (*specgen.PodSpecGenerator, error) {
- p := specgen.NewPodSpecGenerator()
+func ToPodOpt(ctx context.Context, podName string, p entities.PodCreateOptions, podYAML *v1.PodTemplateSpec) (entities.PodCreateOptions, error) {
+ // p := specgen.NewPodSpecGenerator()
+ p.Net = &entities.NetOptions{}
p.Name = podName
p.Labels = podYAML.ObjectMeta.Labels
// Kube pods must share {ipc, net, uts} by default
- p.SharedNamespaces = append(p.SharedNamespaces, "ipc")
- p.SharedNamespaces = append(p.SharedNamespaces, "net")
- p.SharedNamespaces = append(p.SharedNamespaces, "uts")
+ p.Share = append(p.Share, "ipc")
+ p.Share = append(p.Share, "net")
+ p.Share = append(p.Share, "uts")
// TODO we only configure Process namespace. We also need to account for Host{IPC,Network,PID}
// which is not currently possible with pod create
if podYAML.Spec.ShareProcessNamespace != nil && *podYAML.Spec.ShareProcessNamespace {
- p.SharedNamespaces = append(p.SharedNamespaces, "pid")
+ p.Share = append(p.Share, "pid")
}
p.Hostname = podYAML.Spec.Hostname
if p.Hostname == "" {
p.Hostname = podName
}
if podYAML.Spec.HostNetwork {
- p.NetNS.NSMode = specgen.Host
+ p.Net.Network = specgen.Namespace{NSMode: "host"}
}
if podYAML.Spec.HostAliases != nil {
hosts := make([]string, 0, len(podYAML.Spec.HostAliases))
@@ -50,10 +52,10 @@ func ToPodGen(ctx context.Context, podName string, podYAML *v1.PodTemplateSpec)
hosts = append(hosts, host+":"+hostAlias.IP)
}
}
- p.HostAdd = hosts
+ p.Net.AddHosts = hosts
}
podPorts := getPodPorts(podYAML.Spec.Containers)
- p.PortMappings = podPorts
+ p.Net.PublishPorts = podPorts
if dnsConfig := podYAML.Spec.DNSConfig; dnsConfig != nil {
// name servers
@@ -62,11 +64,11 @@ func ToPodGen(ctx context.Context, podName string, podYAML *v1.PodTemplateSpec)
for _, server := range dnsServers {
servers = append(servers, net.ParseIP(server))
}
- p.DNSServer = servers
+ p.Net.DNSServers = servers
}
// search domains
if domains := dnsConfig.Searches; len(domains) > 0 {
- p.DNSSearch = domains
+ p.Net.DNSSearch = domains
}
// dns options
if options := dnsConfig.Options; len(options) > 0 {
@@ -110,6 +112,8 @@ type CtrSpecGenOptions struct {
LogDriver string
// Labels define key-value pairs of metadata
Labels map[string]string
+ //
+ IsInfra bool
}
func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGenerator, error) {
@@ -216,19 +220,19 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener
}
}
// If only the yaml.Command is specified, set it as the entrypoint and drop the image Cmd
- if len(opts.Container.Command) != 0 {
+ if !opts.IsInfra && len(opts.Container.Command) != 0 {
s.Entrypoint = opts.Container.Command
s.Command = []string{}
}
// Only override the cmd field if yaml.Args is specified
// Keep the image entrypoint, or the yaml.command if specified
- if len(opts.Container.Args) != 0 {
+ if !opts.IsInfra && len(opts.Container.Args) != 0 {
s.Command = opts.Container.Args
}
// FIXME,
// we are currently ignoring imageData.Config.ExposedPorts
- if opts.Container.WorkingDir != "" {
+ if !opts.IsInfra && opts.Container.WorkingDir != "" {
s.WorkDir = opts.Container.WorkingDir
}
diff --git a/pkg/specgen/generate/namespaces.go b/pkg/specgen/generate/namespaces.go
index 80790dcc1..5349e224f 100644
--- a/pkg/specgen/generate/namespaces.go
+++ b/pkg/specgen/generate/namespaces.go
@@ -250,7 +250,7 @@ func namespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.
if s.NetNS.Value != "" {
val = fmt.Sprintf("slirp4netns:%s", s.NetNS.Value)
}
- toReturn = append(toReturn, libpod.WithNetNS(portMappings, expose, postConfigureNetNS, val, nil))
+ toReturn = append(toReturn, libpod.WithNetNS(portMappings, expose, postConfigureNetNS, val, s.CNINetworks))
case specgen.Private:
fallthrough
case specgen.Bridge:
diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go
index 1f3f9e832..80c7f112f 100644
--- a/pkg/specgen/generate/oci.go
+++ b/pkg/specgen/generate/oci.go
@@ -201,7 +201,8 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt
Options: []string{"rprivate", "nosuid", "noexec", "nodev", "rw"},
}
g.AddMount(sysMnt)
- } else if !canMountSys {
+ }
+ if !canMountSys {
addCgroup = false
g.RemoveMount("/sys")
r := "ro"
diff --git a/pkg/specgen/generate/pod_create.go b/pkg/specgen/generate/pod_create.go
index 426cf1b6d..e523aef42 100644
--- a/pkg/specgen/generate/pod_create.go
+++ b/pkg/specgen/generate/pod_create.go
@@ -2,53 +2,82 @@ package generate
import (
"context"
+ "net"
+ "github.com/containers/common/pkg/config"
"github.com/containers/podman/v3/libpod"
+ "github.com/containers/podman/v3/libpod/define"
+ "github.com/containers/podman/v3/pkg/domain/entities"
"github.com/containers/podman/v3/pkg/rootless"
"github.com/containers/podman/v3/pkg/specgen"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
-func MakePod(p *specgen.PodSpecGenerator, rt *libpod.Runtime) (*libpod.Pod, error) {
- if err := p.Validate(); err != nil {
+func MakePod(p *entities.PodSpec, rt *libpod.Runtime) (*libpod.Pod, error) {
+ if err := p.PodSpecGen.Validate(); err != nil {
return nil, err
}
- options, err := createPodOptions(p, rt)
+ if !p.PodSpecGen.NoInfra && p.PodSpecGen.InfraContainerSpec != nil {
+ var err error
+ p.PodSpecGen.InfraContainerSpec, err = MapSpec(&p.PodSpecGen)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ options, err := createPodOptions(&p.PodSpecGen, rt, p.PodSpecGen.InfraContainerSpec)
if err != nil {
return nil, err
}
- return rt.NewPod(context.Background(), options...)
+ pod, err := rt.NewPod(context.Background(), p.PodSpecGen, options...)
+ if err != nil {
+ 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"
+ }
+ _, err = CompleteSpec(context.Background(), rt, p.PodSpecGen.InfraContainerSpec)
+ if err != nil {
+ return nil, err
+ }
+ p.PodSpecGen.InfraContainerSpec.User = "" // infraSpec user will get incorrectly assigned via the container creation process, overwrite here
+ rtSpec, spec, opts, err := MakeContainer(context.Background(), rt, p.PodSpecGen.InfraContainerSpec)
+ if err != nil {
+ return nil, err
+ }
+ spec.Pod = pod.ID()
+ opts = append(opts, rt.WithPod(pod))
+ spec.CgroupParent = pod.CgroupParent()
+ infraCtr, err := ExecuteCreate(context.Background(), rt, rtSpec, spec, true, opts...)
+ if err != nil {
+ return nil, err
+ }
+ pod, err = rt.AddInfra(context.Background(), pod, infraCtr)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return pod, nil
}
-func createPodOptions(p *specgen.PodSpecGenerator, rt *libpod.Runtime) ([]libpod.PodCreateOption, error) {
+func createPodOptions(p *specgen.PodSpecGenerator, rt *libpod.Runtime, infraSpec *specgen.SpecGenerator) ([]libpod.PodCreateOption, error) {
var (
options []libpod.PodCreateOption
)
- if !p.NoInfra {
+ if !p.NoInfra { //&& infraSpec != nil {
options = append(options, libpod.WithInfraContainer())
- nsOptions, err := GetNamespaceOptions(p.SharedNamespaces, p.NetNS.IsHost())
+ nsOptions, err := GetNamespaceOptions(p.SharedNamespaces, p.InfraContainerSpec.NetNS.IsHost())
if err != nil {
return nil, err
}
options = append(options, nsOptions...)
// Use pod user and infra userns only when --userns is not set to host
- if !p.Userns.IsHost() {
+ if !p.InfraContainerSpec.UserNS.IsHost() && !p.InfraContainerSpec.UserNS.IsDefault() {
options = append(options, libpod.WithPodUser())
- options = append(options, libpod.WithPodUserns(p.Userns))
}
-
- // Make our exit command
- storageConfig := rt.StorageConfig()
- runtimeConfig, err := rt.GetConfig()
- if err != nil {
- return nil, err
- }
- exitCommand, err := CreateExitCommandArgs(storageConfig, runtimeConfig, logrus.IsLevelEnabled(logrus.DebugLevel), false, false)
- if err != nil {
- return nil, errors.Wrapf(err, "error creating infra container exit command")
- }
- options = append(options, libpod.WithPodInfraExitCommand(exitCommand))
}
if len(p.CgroupParent) > 0 {
options = append(options, libpod.WithPodCgroupParent(p.CgroupParent))
@@ -59,62 +88,27 @@ func createPodOptions(p *specgen.PodSpecGenerator, rt *libpod.Runtime) ([]libpod
if len(p.Name) > 0 {
options = append(options, libpod.WithPodName(p.Name))
}
- if p.ResourceLimits != nil && p.ResourceLimits.CPU != nil && p.ResourceLimits.CPU.Period != nil && p.ResourceLimits.CPU.Quota != nil {
- if *p.ResourceLimits.CPU.Period != 0 || *p.ResourceLimits.CPU.Quota != 0 {
- options = append(options, libpod.WithPodCPUPAQ((*p.ResourceLimits.CPU.Period), (*p.ResourceLimits.CPU.Quota)))
- }
- }
- if p.ResourceLimits != nil && p.ResourceLimits.CPU != nil && p.ResourceLimits.CPU.Cpus != "" {
- options = append(options, libpod.WithPodCPUSetCPUs(p.ResourceLimits.CPU.Cpus))
+ if p.PodCreateCommand != nil {
+ options = append(options, libpod.WithPodCreateCommand(p.PodCreateCommand))
}
+
if len(p.Hostname) > 0 {
options = append(options, libpod.WithPodHostname(p.Hostname))
}
- if len(p.HostAdd) > 0 {
- options = append(options, libpod.WithPodHosts(p.HostAdd))
- }
- if len(p.DNSServer) > 0 {
- var dnsServers []string
- for _, d := range p.DNSServer {
- dnsServers = append(dnsServers, d.String())
- }
- options = append(options, libpod.WithPodDNS(dnsServers))
- }
- if len(p.DNSOption) > 0 {
- options = append(options, libpod.WithPodDNSOption(p.DNSOption))
- }
- if len(p.DNSSearch) > 0 {
- options = append(options, libpod.WithPodDNSSearch(p.DNSSearch))
- }
- if p.StaticIP != nil {
- options = append(options, libpod.WithPodStaticIP(*p.StaticIP))
- }
- if p.StaticMAC != nil {
- options = append(options, libpod.WithPodStaticMAC(*p.StaticMAC))
- }
- if p.NoManageResolvConf {
- options = append(options, libpod.WithPodUseImageResolvConf())
- }
- if len(p.CNINetworks) > 0 {
- options = append(options, libpod.WithPodNetworks(p.CNINetworks))
- }
-
- if len(p.InfraImage) > 0 {
- options = append(options, libpod.WithInfraImage(p.InfraImage))
- }
- if len(p.InfraName) > 0 {
- options = append(options, libpod.WithInfraName(p.InfraName))
- }
-
- if len(p.InfraCommand) > 0 {
- options = append(options, libpod.WithInfraCommand(p.InfraCommand))
- }
+ return options, nil
+}
- if !p.Pid.IsDefault() {
- options = append(options, libpod.WithPodPidNS(p.Pid))
+// MapSpec modifies the already filled Infra specgenerator,
+// 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)
+ if err != nil {
+ return nil, err
+ }
+ p.InfraContainerSpec.PortMappings = libpod.WithInfraContainerPorts(ports, p.InfraContainerSpec)
}
-
switch p.NetNS.NSMode {
case specgen.Default, "":
if p.NoInfra {
@@ -123,42 +117,88 @@ func createPodOptions(p *specgen.PodSpecGenerator, rt *libpod.Runtime) ([]libpod
}
if rootless.IsRootless() {
logrus.Debugf("Pod will use slirp4netns")
- options = append(options, libpod.WithPodSlirp4netns(p.NetworkOptions))
+ if p.InfraContainerSpec.NetNS.NSMode != "host" {
+ p.InfraContainerSpec.NetworkOptions = p.NetworkOptions
+ p.InfraContainerSpec.NetNS.NSMode = specgen.NamespaceMode("slirp4netns")
+ }
} else {
logrus.Debugf("Pod using bridge network mode")
}
case specgen.Bridge:
+ p.InfraContainerSpec.NetNS.NSMode = specgen.Bridge
logrus.Debugf("Pod using bridge network mode")
case specgen.Host:
logrus.Debugf("Pod will use host networking")
- options = append(options, libpod.WithPodHostNetwork())
+ if len(p.InfraContainerSpec.PortMappings) > 0 ||
+ p.InfraContainerSpec.StaticIP != nil ||
+ p.InfraContainerSpec.StaticMAC != nil ||
+ len(p.InfraContainerSpec.CNINetworks) > 0 ||
+ p.InfraContainerSpec.NetNS.NSMode == specgen.NoNetwork {
+ return nil, errors.Wrapf(define.ErrInvalidArg, "cannot set host network if network-related configuration is specified")
+ }
+ p.InfraContainerSpec.NetNS.NSMode = specgen.Host
case specgen.Slirp:
logrus.Debugf("Pod will use slirp4netns")
- options = append(options, libpod.WithPodSlirp4netns(p.NetworkOptions))
+ if p.InfraContainerSpec.NetNS.NSMode != "host" {
+ p.InfraContainerSpec.NetworkOptions = p.NetworkOptions
+ p.InfraContainerSpec.NetNS.NSMode = specgen.NamespaceMode("slirp4netns")
+ }
case specgen.NoNetwork:
logrus.Debugf("Pod will not use networking")
- options = append(options, libpod.WithPodNoNetwork())
+ if len(p.InfraContainerSpec.PortMappings) > 0 ||
+ p.InfraContainerSpec.StaticIP != nil ||
+ p.InfraContainerSpec.StaticMAC != nil ||
+ len(p.InfraContainerSpec.CNINetworks) > 0 ||
+ p.InfraContainerSpec.NetNS.NSMode == "host" {
+ return nil, errors.Wrapf(define.ErrInvalidArg, "cannot disable pod network if network-related configuration is specified")
+ }
+ p.InfraContainerSpec.NetNS.NSMode = specgen.NoNetwork
default:
return nil, errors.Errorf("pods presently do not support network mode %s", p.NetNS.NSMode)
}
- if p.NoManageHosts {
- options = append(options, libpod.WithPodUseImageHosts())
+ libpod.WithPodCgroups()
+ if len(p.InfraCommand) > 0 {
+ p.InfraContainerSpec.Entrypoint = p.InfraCommand
}
- if len(p.PortMappings) > 0 {
- ports, _, _, err := ParsePortMapping(p.PortMappings)
- if err != nil {
- return nil, err
- }
- options = append(options, libpod.WithInfraContainerPorts(ports))
+
+ if len(p.HostAdd) > 0 {
+ p.InfraContainerSpec.HostAdd = p.HostAdd
}
- options = append(options, libpod.WithPodCgroups())
- if p.PodCreateCommand != nil {
- options = append(options, libpod.WithPodCreateCommand(p.PodCreateCommand))
+ if len(p.DNSServer) > 0 {
+ var dnsServers []net.IP
+ dnsServers = append(dnsServers, p.DNSServer...)
+
+ p.InfraContainerSpec.DNSServers = dnsServers
+ }
+ if len(p.DNSOption) > 0 {
+ p.InfraContainerSpec.DNSOptions = p.DNSOption
+ }
+ if len(p.DNSSearch) > 0 {
+ p.InfraContainerSpec.DNSSearch = p.DNSSearch
+ }
+ if p.StaticIP != nil {
+ p.InfraContainerSpec.StaticIP = p.StaticIP
+ }
+ if p.StaticMAC != nil {
+ p.InfraContainerSpec.StaticMAC = p.StaticMAC
+ }
+ if p.NoManageResolvConf {
+ p.InfraContainerSpec.UseImageResolvConf = true
+ }
+ if len(p.CNINetworks) > 0 {
+ p.InfraContainerSpec.CNINetworks = p.CNINetworks
+ }
+ if p.NoManageHosts {
+ p.InfraContainerSpec.UseImageHosts = p.NoManageHosts
}
+
if len(p.InfraConmonPidFile) > 0 {
- options = append(options, libpod.WithInfraConmonPidFile(p.InfraConmonPidFile))
+ p.InfraContainerSpec.ConmonPidFile = p.InfraConmonPidFile
}
- return options, nil
+ if p.InfraImage != config.DefaultInfraImage {
+ p.InfraContainerSpec.Image = p.InfraImage
+ }
+ return p.InfraContainerSpec, nil
}
diff --git a/pkg/specgen/podspecgen.go b/pkg/specgen/podspecgen.go
index 386571d11..8872a1321 100644
--- a/pkg/specgen/podspecgen.go
+++ b/pkg/specgen/podspecgen.go
@@ -67,7 +67,7 @@ type PodBasicConfig struct {
// Pid sets the process id namespace of the pod
// Optional (defaults to private if unset). This sets the PID namespace of the infra container
// This configuration will then be shared with the entire pod if PID namespace sharing is enabled via --share
- Pid Namespace `json:"pid,omitempty:"`
+ Pid Namespace `json:"pidns,omitempty"`
// Userns is used to indicate which kind of Usernamespace to enter.
// Any containers created within the pod will inherit the pod's userns settings.
// Optional
@@ -173,6 +173,7 @@ type PodSpecGenerator struct {
PodNetworkConfig
PodCgroupConfig
PodResourceConfig
+ InfraContainerSpec *SpecGenerator `json:"-"`
}
type PodResourceConfig struct {
diff --git a/pkg/specgenutil/createparse.go b/pkg/specgenutil/createparse.go
new file mode 100644
index 000000000..b46d8fbc6
--- /dev/null
+++ b/pkg/specgenutil/createparse.go
@@ -0,0 +1,34 @@
+package specgenutil
+
+import (
+ "github.com/containers/common/pkg/config"
+ "github.com/containers/podman/v3/pkg/domain/entities"
+ "github.com/pkg/errors"
+)
+
+// validate determines if the flags and values given by the user are valid. things checked
+// by validate must not need any state information on the flag (i.e. changed)
+func validate(c *entities.ContainerCreateOptions) error {
+ var ()
+ if c.Rm && (c.Restart != "" && c.Restart != "no" && c.Restart != "on-failure") {
+ return errors.Errorf(`the --rm option conflicts with --restart, when the restartPolicy is not "" and "no"`)
+ }
+
+ if _, err := config.ParsePullPolicy(c.Pull); err != nil {
+ return err
+ }
+
+ var imageVolType = map[string]string{
+ "bind": "",
+ "tmpfs": "",
+ "ignore": "",
+ }
+ if _, ok := imageVolType[c.ImageVolume]; !ok {
+ if c.IsInfra {
+ c.ImageVolume = "bind"
+ } else {
+ return errors.Errorf("invalid image-volume type %q. Pick one of bind, tmpfs, or ignore", c.ImageVolume)
+ }
+ }
+ return nil
+}
diff --git a/pkg/specgenutil/ports.go b/pkg/specgenutil/ports.go
new file mode 100644
index 000000000..6cc4de1ed
--- /dev/null
+++ b/pkg/specgenutil/ports.go
@@ -0,0 +1,22 @@
+package specgenutil
+
+import (
+ "github.com/docker/go-connections/nat"
+ "github.com/pkg/errors"
+)
+
+func verifyExpose(expose []string) error {
+ // add the expose ports from the user (--expose)
+ // can be single or a range
+ for _, expose := range expose {
+ // support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>]
+ _, port := nat.SplitProtoPort(expose)
+ // parse the start and end port and create a sequence of ports to expose
+ // if expose a port, the start and end port are the same
+ _, _, err := nat.ParsePortRange(port)
+ if err != nil {
+ return errors.Wrapf(err, "invalid range format for --expose: %s", expose)
+ }
+ }
+ return nil
+}
diff --git a/pkg/specgenutil/specgen.go b/pkg/specgenutil/specgen.go
new file mode 100644
index 000000000..9f676db1b
--- /dev/null
+++ b/pkg/specgenutil/specgen.go
@@ -0,0 +1,1004 @@
+package specgenutil
+
+import (
+ "encoding/json"
+ "fmt"
+ "os"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/containers/image/v5/manifest"
+ "github.com/containers/podman/v3/cmd/podman/parse"
+ "github.com/containers/podman/v3/libpod/define"
+ ann "github.com/containers/podman/v3/pkg/annotations"
+ "github.com/containers/podman/v3/pkg/domain/entities"
+ envLib "github.com/containers/podman/v3/pkg/env"
+ "github.com/containers/podman/v3/pkg/namespaces"
+ "github.com/containers/podman/v3/pkg/specgen"
+ systemdDefine "github.com/containers/podman/v3/pkg/systemd/define"
+ "github.com/containers/podman/v3/pkg/util"
+ "github.com/docker/go-units"
+ "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/pkg/errors"
+)
+
+func getCPULimits(c *entities.ContainerCreateOptions) *specs.LinuxCPU {
+ cpu := &specs.LinuxCPU{}
+ hasLimits := false
+
+ if c.CPUS > 0 {
+ period, quota := util.CoresToPeriodAndQuota(c.CPUS)
+
+ cpu.Period = &period
+ cpu.Quota = &quota
+ hasLimits = true
+ }
+ if c.CPUShares > 0 {
+ cpu.Shares = &c.CPUShares
+ hasLimits = true
+ }
+ if c.CPUPeriod > 0 {
+ cpu.Period = &c.CPUPeriod
+ hasLimits = true
+ }
+ if c.CPUSetCPUs != "" {
+ cpu.Cpus = c.CPUSetCPUs
+ hasLimits = true
+ }
+ if c.CPUSetMems != "" {
+ cpu.Mems = c.CPUSetMems
+ hasLimits = true
+ }
+ if c.CPUQuota > 0 {
+ cpu.Quota = &c.CPUQuota
+ hasLimits = true
+ }
+ if c.CPURTPeriod > 0 {
+ cpu.RealtimePeriod = &c.CPURTPeriod
+ hasLimits = true
+ }
+ if c.CPURTRuntime > 0 {
+ cpu.RealtimeRuntime = &c.CPURTRuntime
+ hasLimits = true
+ }
+
+ if !hasLimits {
+ return nil
+ }
+ return cpu
+}
+
+func getIOLimits(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions) (*specs.LinuxBlockIO, error) {
+ var err error
+ io := &specs.LinuxBlockIO{}
+ hasLimits := false
+ if b := c.BlkIOWeight; len(b) > 0 {
+ u, err := strconv.ParseUint(b, 10, 16)
+ if err != nil {
+ return nil, errors.Wrapf(err, "invalid value for blkio-weight")
+ }
+ nu := uint16(u)
+ io.Weight = &nu
+ hasLimits = true
+ }
+
+ if len(c.BlkIOWeightDevice) > 0 {
+ if err := parseWeightDevices(s, c.BlkIOWeightDevice); err != nil {
+ return nil, err
+ }
+ hasLimits = true
+ }
+
+ if bps := c.DeviceReadBPs; len(bps) > 0 {
+ if s.ThrottleReadBpsDevice, err = parseThrottleBPSDevices(bps); err != nil {
+ return nil, err
+ }
+ hasLimits = true
+ }
+
+ if bps := c.DeviceWriteBPs; len(bps) > 0 {
+ if s.ThrottleWriteBpsDevice, err = parseThrottleBPSDevices(bps); err != nil {
+ return nil, err
+ }
+ hasLimits = true
+ }
+
+ if iops := c.DeviceReadIOPs; len(iops) > 0 {
+ if s.ThrottleReadIOPSDevice, err = parseThrottleIOPsDevices(iops); err != nil {
+ return nil, err
+ }
+ hasLimits = true
+ }
+
+ if iops := c.DeviceWriteIOPs; len(iops) > 0 {
+ if s.ThrottleWriteIOPSDevice, err = parseThrottleIOPsDevices(iops); err != nil {
+ return nil, err
+ }
+ hasLimits = true
+ }
+
+ if !hasLimits {
+ return nil, nil
+ }
+ return io, nil
+}
+
+func getMemoryLimits(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions) (*specs.LinuxMemory, error) {
+ var err error
+ memory := &specs.LinuxMemory{}
+ hasLimits := false
+ if m := c.Memory; len(m) > 0 {
+ ml, err := units.RAMInBytes(m)
+ if err != nil {
+ return nil, errors.Wrapf(err, "invalid value for memory")
+ }
+ memory.Limit = &ml
+ if c.MemorySwap == "" {
+ limit := 2 * ml
+ memory.Swap = &(limit)
+ }
+ hasLimits = true
+ }
+ if m := c.MemoryReservation; len(m) > 0 {
+ mr, err := units.RAMInBytes(m)
+ if err != nil {
+ return nil, errors.Wrapf(err, "invalid value for memory")
+ }
+ memory.Reservation = &mr
+ hasLimits = true
+ }
+ if m := c.MemorySwap; len(m) > 0 {
+ var ms int64
+ // only set memory swap if it was set
+ // -1 indicates unlimited
+ if m != "-1" {
+ ms, err = units.RAMInBytes(m)
+ memory.Swap = &ms
+ if err != nil {
+ return nil, errors.Wrapf(err, "invalid value for memory")
+ }
+ hasLimits = true
+ }
+ }
+ if m := c.KernelMemory; len(m) > 0 {
+ mk, err := units.RAMInBytes(m)
+ if err != nil {
+ return nil, errors.Wrapf(err, "invalid value for kernel-memory")
+ }
+ memory.Kernel = &mk
+ hasLimits = true
+ }
+ if c.MemorySwappiness > 0 {
+ swappiness := uint64(c.MemorySwappiness)
+ memory.Swappiness = &swappiness
+ hasLimits = true
+ }
+ if c.OOMKillDisable {
+ memory.DisableOOMKiller = &c.OOMKillDisable
+ hasLimits = true
+ }
+ if !hasLimits {
+ return nil, nil
+ }
+ return memory, nil
+}
+
+func setNamespaces(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions) error {
+ var err error
+
+ if c.PID != "" {
+ s.PidNS, err = specgen.ParseNamespace(c.PID)
+ if err != nil {
+ return err
+ }
+ }
+ if c.IPC != "" {
+ s.IpcNS, err = specgen.ParseNamespace(c.IPC)
+ if err != nil {
+ return err
+ }
+ }
+ if c.UTS != "" {
+ s.UtsNS, err = specgen.ParseNamespace(c.UTS)
+ if err != nil {
+ return err
+ }
+ }
+ if c.CgroupNS != "" {
+ s.CgroupNS, err = specgen.ParseNamespace(c.CgroupNS)
+ if err != nil {
+ return err
+ }
+ }
+ // userns must be treated differently
+ if c.UserNS != "" {
+ s.UserNS, err = specgen.ParseUserNamespace(c.UserNS)
+ if err != nil {
+ return err
+ }
+ }
+ if c.Net != nil {
+ s.NetNS = c.Net.Network
+ }
+ return nil
+}
+
+func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions, args []string) error {
+ var (
+ err error
+ )
+ // validate flags as needed
+ if err := validate(c); err != nil {
+ return err
+ }
+ s.User = c.User
+ var inputCommand []string
+ if !c.IsInfra {
+ if len(args) > 1 {
+ inputCommand = args[1:]
+ }
+ }
+
+ if len(c.HealthCmd) > 0 {
+ if c.NoHealthCheck {
+ return errors.New("Cannot specify both --no-healthcheck and --health-cmd")
+ }
+ s.HealthConfig, err = makeHealthCheckFromCli(c.HealthCmd, c.HealthInterval, c.HealthRetries, c.HealthTimeout, c.HealthStartPeriod)
+ if err != nil {
+ return err
+ }
+ } else if c.NoHealthCheck {
+ s.HealthConfig = &manifest.Schema2HealthConfig{
+ Test: []string{"NONE"},
+ }
+ }
+ if err := setNamespaces(s, c); err != nil {
+ return err
+ }
+ userNS := namespaces.UsernsMode(s.UserNS.NSMode)
+ tempIDMap, err := util.ParseIDMapping(namespaces.UsernsMode(c.UserNS), []string{}, []string{}, "", "")
+ if err != nil {
+ return err
+ }
+ s.IDMappings, err = util.ParseIDMapping(userNS, c.UIDMap, c.GIDMap, c.SubUIDName, c.SubGIDName)
+ if err != nil {
+ return err
+ }
+ if len(s.IDMappings.GIDMap) == 0 {
+ s.IDMappings.AutoUserNsOpts.AdditionalGIDMappings = tempIDMap.AutoUserNsOpts.AdditionalGIDMappings
+ if s.UserNS.NSMode == specgen.NamespaceMode("auto") {
+ s.IDMappings.AutoUserNs = true
+ }
+ }
+ if len(s.IDMappings.UIDMap) == 0 {
+ s.IDMappings.AutoUserNsOpts.AdditionalUIDMappings = tempIDMap.AutoUserNsOpts.AdditionalUIDMappings
+ if s.UserNS.NSMode == specgen.NamespaceMode("auto") {
+ s.IDMappings.AutoUserNs = true
+ }
+ }
+ if tempIDMap.AutoUserNsOpts.Size != 0 {
+ s.IDMappings.AutoUserNsOpts.Size = tempIDMap.AutoUserNsOpts.Size
+ }
+ // If some mappings are specified, assume a private user namespace
+ if userNS.IsDefaultValue() && (!s.IDMappings.HostUIDMapping || !s.IDMappings.HostGIDMapping) {
+ s.UserNS.NSMode = specgen.Private
+ } else {
+ s.UserNS.NSMode = specgen.NamespaceMode(userNS)
+ }
+
+ s.Terminal = c.TTY
+
+ if err := verifyExpose(c.Expose); err != nil {
+ return err
+ }
+ // We are not handling the Expose flag yet.
+ // s.PortsExpose = c.Expose
+ if c.Net != nil {
+ s.PortMappings = c.Net.PublishPorts
+ }
+ s.PublishExposedPorts = c.PublishAll
+ s.Pod = c.Pod
+
+ if len(c.PodIDFile) > 0 {
+ if len(s.Pod) > 0 {
+ return errors.New("Cannot specify both --pod and --pod-id-file")
+ }
+ podID, err := ReadPodIDFile(c.PodIDFile)
+ if err != nil {
+ return err
+ }
+ s.Pod = podID
+ }
+
+ expose, err := createExpose(c.Expose)
+ if err != nil {
+ return err
+ }
+ s.Expose = expose
+
+ if sig := c.StopSignal; len(sig) > 0 {
+ stopSignal, err := util.ParseSignal(sig)
+ if err != nil {
+ return err
+ }
+ s.StopSignal = &stopSignal
+ }
+
+ // ENVIRONMENT VARIABLES
+ //
+ // Precedence order (higher index wins):
+ // 1) containers.conf (EnvHost, EnvHTTP, Env) 2) image data, 3 User EnvHost/EnvHTTP, 4) env-file, 5) env
+ // containers.conf handled and image data handled on the server side
+ // user specified EnvHost and EnvHTTP handled on Server Side relative to Server
+ // env-file and env handled on client side
+ var env map[string]string
+
+ // First transform the os env into a map. We need it for the labels later in
+ // any case.
+ osEnv, err := envLib.ParseSlice(os.Environ())
+ if err != nil {
+ return errors.Wrap(err, "error parsing host environment variables")
+ }
+
+ s.EnvHost = c.EnvHost
+ s.HTTPProxy = c.HTTPProxy
+
+ // env-file overrides any previous variables
+ for _, f := range c.EnvFile {
+ fileEnv, err := envLib.ParseFile(f)
+ if err != nil {
+ return err
+ }
+ // File env is overridden by env.
+ env = envLib.Join(env, fileEnv)
+ }
+
+ parsedEnv, err := envLib.ParseSlice(c.Env)
+ if err != nil {
+ return err
+ }
+
+ s.Env = envLib.Join(env, parsedEnv)
+
+ // LABEL VARIABLES
+ labels, err := parse.GetAllLabels(c.LabelFile, c.Label)
+ if err != nil {
+ return errors.Wrapf(err, "unable to process labels")
+ }
+
+ if systemdUnit, exists := osEnv[systemdDefine.EnvVariable]; exists {
+ labels[systemdDefine.EnvVariable] = systemdUnit
+ }
+
+ s.Labels = labels
+
+ // ANNOTATIONS
+ annotations := make(map[string]string)
+
+ // First, add our default annotations
+ annotations[ann.TTY] = "false"
+ if c.TTY {
+ annotations[ann.TTY] = "true"
+ }
+
+ // Last, add user annotations
+ for _, annotation := range c.Annotation {
+ splitAnnotation := strings.SplitN(annotation, "=", 2)
+ if len(splitAnnotation) < 2 {
+ return errors.Errorf("Annotations must be formatted KEY=VALUE")
+ }
+ annotations[splitAnnotation[0]] = splitAnnotation[1]
+ }
+ s.Annotations = annotations
+
+ s.WorkDir = c.Workdir
+ if c.Entrypoint != nil {
+ entrypoint := []string{}
+ if ep := *c.Entrypoint; len(ep) > 0 {
+ // Check if entrypoint specified is json
+ if err := json.Unmarshal([]byte(*c.Entrypoint), &entrypoint); err != nil {
+ entrypoint = append(entrypoint, ep)
+ }
+ }
+ s.Entrypoint = entrypoint
+ }
+
+ // Include the command used to create the container.
+
+ s.ContainerCreateCommand = os.Args
+
+ if len(inputCommand) > 0 {
+ s.Command = inputCommand
+ }
+
+ // SHM Size
+ if c.ShmSize != "" {
+ shmSize, err := units.FromHumanSize(c.ShmSize)
+ if err != nil {
+ return errors.Wrapf(err, "unable to translate --shm-size")
+ }
+ s.ShmSize = &shmSize
+ }
+
+ if c.Net != nil {
+ s.CNINetworks = c.Net.CNINetworks
+ }
+
+ // Network aliases
+ if c.Net != nil {
+ if len(c.Net.Aliases) > 0 {
+ // build a map of aliases where key=cniName
+ aliases := make(map[string][]string, len(s.CNINetworks))
+ for _, cniNetwork := range s.CNINetworks {
+ aliases[cniNetwork] = c.Net.Aliases
+ }
+ s.Aliases = aliases
+ }
+ }
+
+ if c.Net != nil {
+ s.HostAdd = c.Net.AddHosts
+ s.UseImageResolvConf = c.Net.UseImageResolvConf
+ s.DNSServers = c.Net.DNSServers
+ s.DNSSearch = c.Net.DNSSearch
+ s.DNSOptions = c.Net.DNSOptions
+ s.StaticIP = c.Net.StaticIP
+ s.StaticMAC = c.Net.StaticMAC
+ s.NetworkOptions = c.Net.NetworkOptions
+ s.UseImageHosts = c.Net.NoHosts
+ }
+ s.ImageVolumeMode = c.ImageVolume
+ if s.ImageVolumeMode == "bind" {
+ s.ImageVolumeMode = "anonymous"
+ }
+
+ s.Systemd = c.Systemd
+ s.SdNotifyMode = c.SdNotifyMode
+ if s.ResourceLimits == nil {
+ s.ResourceLimits = &specs.LinuxResources{}
+ }
+ s.ResourceLimits.Memory, err = getMemoryLimits(s, c)
+ if err != nil {
+ return err
+ }
+ s.ResourceLimits.BlockIO, err = getIOLimits(s, c)
+ if err != nil {
+ return err
+ }
+ if c.PIDsLimit != nil {
+ pids := specs.LinuxPids{
+ Limit: *c.PIDsLimit,
+ }
+
+ s.ResourceLimits.Pids = &pids
+ }
+ s.ResourceLimits.CPU = getCPULimits(c)
+
+ unifieds := make(map[string]string)
+ for _, unified := range c.CgroupConf {
+ splitUnified := strings.SplitN(unified, "=", 2)
+ if len(splitUnified) < 2 {
+ return errors.Errorf("--cgroup-conf must be formatted KEY=VALUE")
+ }
+ unifieds[splitUnified[0]] = splitUnified[1]
+ }
+ if len(unifieds) > 0 {
+ s.ResourceLimits.Unified = unifieds
+ }
+
+ if s.ResourceLimits.CPU == nil && s.ResourceLimits.Pids == nil && s.ResourceLimits.BlockIO == nil && s.ResourceLimits.Memory == nil && s.ResourceLimits.Unified == nil {
+ s.ResourceLimits = nil
+ }
+
+ if s.LogConfiguration == nil {
+ s.LogConfiguration = &specgen.LogConfig{}
+ }
+
+ if ld := c.LogDriver; len(ld) > 0 {
+ s.LogConfiguration.Driver = ld
+ }
+ s.CgroupParent = c.CGroupParent
+ s.CgroupsMode = c.CGroupsMode
+ s.Groups = c.GroupAdd
+
+ s.Hostname = c.Hostname
+ sysctl := map[string]string{}
+ if ctl := c.Sysctl; len(ctl) > 0 {
+ sysctl, err = util.ValidateSysctls(ctl)
+ if err != nil {
+ return err
+ }
+ }
+ s.Sysctl = sysctl
+
+ s.CapAdd = c.CapAdd
+ s.CapDrop = c.CapDrop
+ s.Privileged = c.Privileged
+ s.ReadOnlyFilesystem = c.ReadOnly
+ s.ConmonPidFile = c.ConmonPIDFile
+
+ s.DependencyContainers = c.Requires
+
+ // TODO
+ // outside of specgen and oci though
+ // defaults to true, check spec/storage
+ // s.readonly = c.ReadOnlyTmpFS
+ // TODO convert to map?
+ // check if key=value and convert
+ sysmap := make(map[string]string)
+ for _, ctl := range c.Sysctl {
+ splitCtl := strings.SplitN(ctl, "=", 2)
+ if len(splitCtl) < 2 {
+ return errors.Errorf("invalid sysctl value %q", ctl)
+ }
+ sysmap[splitCtl[0]] = splitCtl[1]
+ }
+ s.Sysctl = sysmap
+
+ if c.CIDFile != "" {
+ s.Annotations[define.InspectAnnotationCIDFile] = c.CIDFile
+ }
+
+ for _, opt := range c.SecurityOpt {
+ if opt == "no-new-privileges" {
+ s.ContainerSecurityConfig.NoNewPrivileges = true
+ } else {
+ con := strings.SplitN(opt, "=", 2)
+ if len(con) != 2 {
+ return fmt.Errorf("invalid --security-opt 1: %q", opt)
+ }
+ switch con[0] {
+ case "apparmor":
+ s.ContainerSecurityConfig.ApparmorProfile = con[1]
+ s.Annotations[define.InspectAnnotationApparmor] = con[1]
+ case "label":
+ // TODO selinux opts and label opts are the same thing
+ s.ContainerSecurityConfig.SelinuxOpts = append(s.ContainerSecurityConfig.SelinuxOpts, con[1])
+ s.Annotations[define.InspectAnnotationLabel] = strings.Join(s.ContainerSecurityConfig.SelinuxOpts, ",label=")
+ case "mask":
+ s.ContainerSecurityConfig.Mask = append(s.ContainerSecurityConfig.Mask, strings.Split(con[1], ":")...)
+ case "proc-opts":
+ s.ProcOpts = strings.Split(con[1], ",")
+ case "seccomp":
+ s.SeccompProfilePath = con[1]
+ s.Annotations[define.InspectAnnotationSeccomp] = con[1]
+ // this option is for docker compatibility, it is the same as unmask=ALL
+ case "systempaths":
+ if con[1] == "unconfined" {
+ s.ContainerSecurityConfig.Unmask = append(s.ContainerSecurityConfig.Unmask, []string{"ALL"}...)
+ } else {
+ return fmt.Errorf("invalid systempaths option %q, only `unconfined` is supported", con[1])
+ }
+ case "unmask":
+ s.ContainerSecurityConfig.Unmask = append(s.ContainerSecurityConfig.Unmask, con[1:]...)
+ default:
+ return fmt.Errorf("invalid --security-opt 2: %q", opt)
+ }
+ }
+ }
+
+ s.SeccompPolicy = c.SeccompPolicy
+
+ s.VolumesFrom = c.VolumesFrom
+
+ // Only add read-only tmpfs mounts in case that we are read-only and the
+ // read-only tmpfs flag has been set.
+ mounts, volumes, overlayVolumes, imageVolumes, err := parseVolumes(c.Volume, c.Mount, c.TmpFS, c.ReadOnlyTmpFS && c.ReadOnly)
+ if err != nil {
+ return err
+ }
+ s.Mounts = mounts
+ s.Volumes = volumes
+ s.OverlayVolumes = overlayVolumes
+ s.ImageVolumes = imageVolumes
+
+ for _, dev := range c.Devices {
+ s.Devices = append(s.Devices, specs.LinuxDevice{Path: dev})
+ }
+
+ for _, rule := range c.DeviceCGroupRule {
+ dev, err := parseLinuxResourcesDeviceAccess(rule)
+ if err != nil {
+ return err
+ }
+ s.DeviceCGroupRule = append(s.DeviceCGroupRule, dev)
+ }
+
+ s.Init = c.Init
+ s.InitPath = c.InitPath
+ s.Stdin = c.Interactive
+ // quiet
+ // DeviceCgroupRules: c.StringSlice("device-cgroup-rule"),
+
+ // Rlimits/Ulimits
+ for _, u := range c.Ulimit {
+ if u == "host" {
+ s.Rlimits = nil
+ break
+ }
+ ul, err := units.ParseUlimit(u)
+ if err != nil {
+ return errors.Wrapf(err, "ulimit option %q requires name=SOFT:HARD, failed to be parsed", u)
+ }
+ rl := specs.POSIXRlimit{
+ Type: ul.Name,
+ Hard: uint64(ul.Hard),
+ Soft: uint64(ul.Soft),
+ }
+ s.Rlimits = append(s.Rlimits, rl)
+ }
+
+ logOpts := make(map[string]string)
+ for _, o := range c.LogOptions {
+ split := strings.SplitN(o, "=", 2)
+ if len(split) < 2 {
+ return 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 err
+ }
+ s.LogConfiguration.Size = logSize
+ default:
+ logOpts[split[0]] = split[1]
+ }
+ }
+ s.LogConfiguration.Options = logOpts
+ s.Name = c.Name
+ s.PreserveFDs = c.PreserveFDs
+
+ s.OOMScoreAdj = &c.OOMScoreAdj
+ if c.Restart != "" {
+ splitRestart := strings.Split(c.Restart, ":")
+ switch len(splitRestart) {
+ case 1:
+ // No retries specified
+ case 2:
+ if strings.ToLower(splitRestart[0]) != "on-failure" {
+ return errors.Errorf("restart policy retries can only be specified with on-failure restart policy")
+ }
+ retries, err := strconv.Atoi(splitRestart[1])
+ if err != nil {
+ return errors.Wrapf(err, "error parsing restart policy retry count")
+ }
+ if retries < 0 {
+ return errors.Errorf("must specify restart policy retry count as a number greater than 0")
+ }
+ var retriesUint = uint(retries)
+ s.RestartRetries = &retriesUint
+ default:
+ return errors.Errorf("invalid restart policy: may specify retries at most once")
+ }
+ s.RestartPolicy = splitRestart[0]
+ }
+
+ s.Secrets, s.EnvSecrets, err = parseSecrets(c.Secrets)
+ if err != nil {
+ return err
+ }
+
+ if c.Personality != "" {
+ s.Personality = &specs.LinuxPersonality{}
+ s.Personality.Domain = specs.LinuxPersonalityDomain(c.Personality)
+ }
+
+ s.Remove = c.Rm
+ s.StopTimeout = &c.StopTimeout
+ s.Timeout = c.Timeout
+ s.Timezone = c.Timezone
+ s.Umask = c.Umask
+ s.PidFile = c.PidFile
+ s.Volatile = c.Rm
+
+ // Initcontainers
+ s.InitContainerType = c.InitContainerType
+ return nil
+}
+
+func makeHealthCheckFromCli(inCmd, interval string, retries uint, timeout, startPeriod string) (*manifest.Schema2HealthConfig, error) {
+ cmdArr := []string{}
+ isArr := true
+ err := json.Unmarshal([]byte(inCmd), &cmdArr) // array unmarshalling
+ if err != nil {
+ cmdArr = strings.SplitN(inCmd, " ", 2) // default for compat
+ isArr = false
+ }
+ // Every healthcheck requires a command
+ if len(cmdArr) == 0 {
+ return nil, errors.New("Must define a healthcheck command for all healthchecks")
+ }
+ concat := ""
+ if cmdArr[0] == "CMD" || cmdArr[0] == "none" { // this is for compat, we are already split properly for most compat cases
+ cmdArr = strings.Fields(inCmd)
+ } else if cmdArr[0] != "CMD-SHELL" { // this is for podman side of things, won't contain the keywords
+ if isArr && len(cmdArr) > 1 { // an array of consecutive commands
+ cmdArr = append([]string{"CMD"}, cmdArr...)
+ } else { // one singular command
+ if len(cmdArr) == 1 {
+ concat = cmdArr[0]
+ } else {
+ concat = strings.Join(cmdArr[0:], " ")
+ }
+ cmdArr = append([]string{"CMD-SHELL"}, concat)
+ }
+ }
+
+ if cmdArr[0] == "none" { // if specified to remove healtcheck
+ cmdArr = []string{"NONE"}
+ }
+
+ // healthcheck is by default an array, so we simply pass the user input
+ hc := manifest.Schema2HealthConfig{
+ Test: cmdArr,
+ }
+
+ if interval == "disable" {
+ interval = "0"
+ }
+ intervalDuration, err := time.ParseDuration(interval)
+ if err != nil {
+ return nil, errors.Wrapf(err, "invalid healthcheck-interval")
+ }
+
+ hc.Interval = intervalDuration
+
+ if retries < 1 {
+ return nil, errors.New("healthcheck-retries must be greater than 0")
+ }
+ hc.Retries = int(retries)
+ timeoutDuration, err := time.ParseDuration(timeout)
+ if err != nil {
+ return nil, errors.Wrapf(err, "invalid healthcheck-timeout")
+ }
+ if timeoutDuration < time.Duration(1) {
+ return nil, errors.New("healthcheck-timeout must be at least 1 second")
+ }
+ hc.Timeout = timeoutDuration
+
+ startPeriodDuration, err := time.ParseDuration(startPeriod)
+ if err != nil {
+ return nil, errors.Wrapf(err, "invalid healthcheck-start-period")
+ }
+ if startPeriodDuration < time.Duration(0) {
+ return nil, errors.New("healthcheck-start-period must be 0 seconds or greater")
+ }
+ hc.StartPeriod = startPeriodDuration
+
+ return &hc, nil
+}
+
+func parseWeightDevices(s *specgen.SpecGenerator, weightDevs []string) error {
+ for _, val := range weightDevs {
+ split := strings.SplitN(val, ":", 2)
+ if len(split) != 2 {
+ return fmt.Errorf("bad format: %s", val)
+ }
+ if !strings.HasPrefix(split[0], "/dev/") {
+ return fmt.Errorf("bad format for device path: %s", val)
+ }
+ weight, err := strconv.ParseUint(split[1], 10, 0)
+ if err != nil {
+ return fmt.Errorf("invalid weight for device: %s", val)
+ }
+ if weight > 0 && (weight < 10 || weight > 1000) {
+ return fmt.Errorf("invalid weight for device: %s", val)
+ }
+ w := uint16(weight)
+ s.WeightDevice[split[0]] = specs.LinuxWeightDevice{
+ Weight: &w,
+ LeafWeight: nil,
+ }
+ }
+ return nil
+}
+
+func parseThrottleBPSDevices(bpsDevices []string) (map[string]specs.LinuxThrottleDevice, error) {
+ td := make(map[string]specs.LinuxThrottleDevice)
+ for _, val := range bpsDevices {
+ split := strings.SplitN(val, ":", 2)
+ if len(split) != 2 {
+ return nil, fmt.Errorf("bad format: %s", val)
+ }
+ if !strings.HasPrefix(split[0], "/dev/") {
+ return nil, fmt.Errorf("bad format for device path: %s", val)
+ }
+ rate, err := units.RAMInBytes(split[1])
+ if err != nil {
+ return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>[<unit>]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val)
+ }
+ if rate < 0 {
+ return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>[<unit>]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val)
+ }
+ td[split[0]] = specs.LinuxThrottleDevice{Rate: uint64(rate)}
+ }
+ return td, nil
+}
+
+func parseThrottleIOPsDevices(iopsDevices []string) (map[string]specs.LinuxThrottleDevice, error) {
+ td := make(map[string]specs.LinuxThrottleDevice)
+ for _, val := range iopsDevices {
+ split := strings.SplitN(val, ":", 2)
+ if len(split) != 2 {
+ return nil, fmt.Errorf("bad format: %s", val)
+ }
+ if !strings.HasPrefix(split[0], "/dev/") {
+ return nil, fmt.Errorf("bad format for device path: %s", val)
+ }
+ rate, err := strconv.ParseUint(split[1], 10, 64)
+ if err != nil {
+ return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>. Number must be a positive integer", val)
+ }
+ td[split[0]] = specs.LinuxThrottleDevice{Rate: rate}
+ }
+ return td, nil
+}
+
+func parseSecrets(secrets []string) ([]specgen.Secret, map[string]string, error) {
+ secretParseError := errors.New("error parsing secret")
+ var mount []specgen.Secret
+ envs := make(map[string]string)
+ for _, val := range secrets {
+ // mount only tells if user has set an option that can only be used with mount secret type
+ mountOnly := false
+ source := ""
+ secretType := ""
+ target := ""
+ var uid, gid uint32
+ // default mode 444 octal = 292 decimal
+ var mode uint32 = 292
+ split := strings.Split(val, ",")
+
+ // --secret mysecret
+ if len(split) == 1 {
+ mountSecret := specgen.Secret{
+ Source: val,
+ UID: uid,
+ GID: gid,
+ Mode: mode,
+ }
+ mount = append(mount, mountSecret)
+ continue
+ }
+ // --secret mysecret,opt=opt
+ if !strings.Contains(split[0], "=") {
+ source = split[0]
+ split = split[1:]
+ }
+
+ for _, val := range split {
+ kv := strings.SplitN(val, "=", 2)
+ if len(kv) < 2 {
+ return nil, nil, errors.Wrapf(secretParseError, "option %s must be in form option=value", val)
+ }
+ switch kv[0] {
+ case "source":
+ source = kv[1]
+ case "type":
+ if secretType != "" {
+ return nil, nil, errors.Wrap(secretParseError, "cannot set more tha one secret type")
+ }
+ if kv[1] != "mount" && kv[1] != "env" {
+ return nil, nil, errors.Wrapf(secretParseError, "type %s is invalid", kv[1])
+ }
+ secretType = kv[1]
+ case "target":
+ target = kv[1]
+ case "mode":
+ mountOnly = true
+ mode64, err := strconv.ParseUint(kv[1], 8, 32)
+ if err != nil {
+ return nil, nil, errors.Wrapf(secretParseError, "mode %s invalid", kv[1])
+ }
+ mode = uint32(mode64)
+ case "uid", "UID":
+ mountOnly = true
+ uid64, err := strconv.ParseUint(kv[1], 10, 32)
+ if err != nil {
+ return nil, nil, errors.Wrapf(secretParseError, "UID %s invalid", kv[1])
+ }
+ uid = uint32(uid64)
+ case "gid", "GID":
+ mountOnly = true
+ gid64, err := strconv.ParseUint(kv[1], 10, 32)
+ if err != nil {
+ return nil, nil, errors.Wrapf(secretParseError, "GID %s invalid", kv[1])
+ }
+ gid = uint32(gid64)
+
+ default:
+ return nil, nil, errors.Wrapf(secretParseError, "option %s invalid", val)
+ }
+ }
+
+ if secretType == "" {
+ secretType = "mount"
+ }
+ if source == "" {
+ return nil, nil, errors.Wrapf(secretParseError, "no source found %s", val)
+ }
+ if secretType == "mount" {
+ if target != "" {
+ return nil, nil, errors.Wrapf(secretParseError, "target option is invalid for mounted secrets")
+ }
+ mountSecret := specgen.Secret{
+ Source: source,
+ UID: uid,
+ GID: gid,
+ Mode: mode,
+ }
+ mount = append(mount, mountSecret)
+ }
+ if secretType == "env" {
+ if mountOnly {
+ return nil, nil, errors.Wrap(secretParseError, "UID, GID, Mode options cannot be set with secret type env")
+ }
+ if target == "" {
+ target = source
+ }
+ envs[target] = source
+ }
+ }
+ return mount, envs, nil
+}
+
+var cgroupDeviceType = map[string]bool{
+ "a": true, // all
+ "b": true, // block device
+ "c": true, // character device
+}
+
+var cgroupDeviceAccess = map[string]bool{
+ "r": true, //read
+ "w": true, //write
+ "m": true, //mknod
+}
+
+// parseLinuxResourcesDeviceAccess parses the raw string passed with the --device-access-add flag
+func parseLinuxResourcesDeviceAccess(device string) (specs.LinuxDeviceCgroup, error) {
+ var devType, access string
+ var major, minor *int64
+
+ value := strings.Split(device, " ")
+ if len(value) != 3 {
+ return specs.LinuxDeviceCgroup{}, fmt.Errorf("invalid device cgroup rule requires type, major:Minor, and access rules: %q", device)
+ }
+
+ devType = value[0]
+ if !cgroupDeviceType[devType] {
+ return specs.LinuxDeviceCgroup{}, fmt.Errorf("invalid device type in device-access-add: %s", devType)
+ }
+
+ number := strings.SplitN(value[1], ":", 2)
+ i, err := strconv.ParseInt(number[0], 10, 64)
+ if err != nil {
+ return specs.LinuxDeviceCgroup{}, err
+ }
+ major = &i
+ if len(number) == 2 && number[1] != "*" {
+ i, err := strconv.ParseInt(number[1], 10, 64)
+ if err != nil {
+ return specs.LinuxDeviceCgroup{}, err
+ }
+ minor = &i
+ }
+ access = value[2]
+ for _, c := range strings.Split(access, "") {
+ if !cgroupDeviceAccess[c] {
+ return specs.LinuxDeviceCgroup{}, fmt.Errorf("invalid device access in device-access-add: %s", c)
+ }
+ }
+ return specs.LinuxDeviceCgroup{
+ Allow: true,
+ Type: devType,
+ Major: major,
+ Minor: minor,
+ Access: access,
+ }, nil
+}
diff --git a/pkg/specgenutil/util.go b/pkg/specgenutil/util.go
new file mode 100644
index 000000000..15676d086
--- /dev/null
+++ b/pkg/specgenutil/util.go
@@ -0,0 +1,274 @@
+package specgenutil
+
+import (
+ "io/ioutil"
+ "net"
+ "strconv"
+ "strings"
+
+ "github.com/containers/podman/v3/libpod/network/types"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+// ReadPodIDFile reads the specified file and returns its content (i.e., first
+// line).
+func ReadPodIDFile(path string) (string, error) {
+ content, err := ioutil.ReadFile(path)
+ if err != nil {
+ return "", errors.Wrap(err, "error reading pod ID file")
+ }
+ return strings.Split(string(content), "\n")[0], nil
+}
+
+// ReadPodIDFiles reads the specified files and returns their content (i.e.,
+// first line).
+func ReadPodIDFiles(files []string) ([]string, error) {
+ ids := []string{}
+ for _, file := range files {
+ id, err := ReadPodIDFile(file)
+ if err != nil {
+ return nil, err
+ }
+ ids = append(ids, id)
+ }
+ return ids, nil
+}
+
+// ParseFilters transforms one filter format to another and validates input
+func ParseFilters(filter []string) (map[string][]string, error) {
+ // TODO Remove once filter refactor is finished and url.Values done.
+ filters := map[string][]string{}
+ for _, f := range filter {
+ t := strings.SplitN(f, "=", 2)
+ filters = make(map[string][]string)
+ if len(t) < 2 {
+ return map[string][]string{}, errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f)
+ }
+ filters[t[0]] = append(filters[t[0]], t[1])
+ }
+ return filters, nil
+}
+
+// createExpose parses user-provided exposed port definitions and converts them
+// into SpecGen format.
+// TODO: The SpecGen format should really handle ranges more sanely - we could
+// be massively inflating what is sent over the wire with a large range.
+func createExpose(expose []string) (map[uint16]string, error) {
+ toReturn := make(map[uint16]string)
+
+ for _, e := range expose {
+ // Check for protocol
+ proto := "tcp"
+ splitProto := strings.Split(e, "/")
+ if len(splitProto) > 2 {
+ return nil, errors.Errorf("invalid expose format - protocol can only be specified once")
+ } else if len(splitProto) == 2 {
+ proto = splitProto[1]
+ }
+
+ // Check for a range
+ start, len, err := parseAndValidateRange(splitProto[0])
+ if err != nil {
+ return nil, err
+ }
+
+ var index uint16
+ for index = 0; index < len; index++ {
+ portNum := start + index
+ protocols, ok := toReturn[portNum]
+ if !ok {
+ toReturn[portNum] = proto
+ } else {
+ newProto := strings.Join(append(strings.Split(protocols, ","), strings.Split(proto, ",")...), ",")
+ toReturn[portNum] = newProto
+ }
+ }
+ }
+
+ return toReturn, nil
+}
+
+// CreatePortBindings iterates ports mappings into SpecGen format.
+func CreatePortBindings(ports []string) ([]types.PortMapping, error) {
+ // --publish is formatted as follows:
+ // [[hostip:]hostport[-endPort]:]containerport[-endPort][/protocol]
+ toReturn := make([]types.PortMapping, 0, len(ports))
+
+ for _, p := range ports {
+ var (
+ ctrPort string
+ proto, hostIP, hostPort *string
+ )
+
+ splitProto := strings.Split(p, "/")
+ switch len(splitProto) {
+ case 1:
+ // No protocol was provided
+ case 2:
+ proto = &(splitProto[1])
+ default:
+ return nil, errors.Errorf("invalid port format - protocol can only be specified once")
+ }
+
+ remainder := splitProto[0]
+ haveV6 := false
+
+ // Check for an IPv6 address in brackets
+ splitV6 := strings.Split(remainder, "]")
+ switch len(splitV6) {
+ case 1:
+ // Do nothing, proceed as before
+ case 2:
+ // We potentially have an IPv6 address
+ haveV6 = true
+ if !strings.HasPrefix(splitV6[0], "[") {
+ return nil, errors.Errorf("invalid port format - IPv6 addresses must be enclosed by []")
+ }
+ if !strings.HasPrefix(splitV6[1], ":") {
+ return nil, errors.Errorf("invalid port format - IPv6 address must be followed by a colon (':')")
+ }
+ ipNoPrefix := strings.TrimPrefix(splitV6[0], "[")
+ hostIP = &ipNoPrefix
+ remainder = strings.TrimPrefix(splitV6[1], ":")
+ default:
+ return nil, errors.Errorf("invalid port format - at most one IPv6 address can be specified in a --publish")
+ }
+
+ splitPort := strings.Split(remainder, ":")
+ switch len(splitPort) {
+ case 1:
+ if haveV6 {
+ return nil, errors.Errorf("invalid port format - must provide host and destination port if specifying an IP")
+ }
+ ctrPort = splitPort[0]
+ case 2:
+ hostPort = &(splitPort[0])
+ ctrPort = splitPort[1]
+ case 3:
+ if haveV6 {
+ return nil, errors.Errorf("invalid port format - when v6 address specified, must be [ipv6]:hostPort:ctrPort")
+ }
+ hostIP = &(splitPort[0])
+ hostPort = &(splitPort[1])
+ ctrPort = splitPort[2]
+ default:
+ return nil, errors.Errorf("invalid port format - format is [[hostIP:]hostPort:]containerPort")
+ }
+
+ newPort, err := parseSplitPort(hostIP, hostPort, ctrPort, proto)
+ if err != nil {
+ return nil, err
+ }
+
+ toReturn = append(toReturn, newPort)
+ }
+
+ return toReturn, nil
+}
+
+// parseSplitPort parses individual components of the --publish flag to produce
+// a single port mapping in SpecGen format.
+func parseSplitPort(hostIP, hostPort *string, ctrPort string, protocol *string) (types.PortMapping, error) {
+ newPort := types.PortMapping{}
+ if ctrPort == "" {
+ return newPort, errors.Errorf("must provide a non-empty container port to publish")
+ }
+ ctrStart, ctrLen, err := parseAndValidateRange(ctrPort)
+ if err != nil {
+ return newPort, errors.Wrapf(err, "error parsing container port")
+ }
+ newPort.ContainerPort = ctrStart
+ newPort.Range = ctrLen
+
+ if protocol != nil {
+ if *protocol == "" {
+ return newPort, errors.Errorf("must provide a non-empty protocol to publish")
+ }
+ newPort.Protocol = *protocol
+ }
+ if hostIP != nil {
+ if *hostIP == "" {
+ return newPort, errors.Errorf("must provide a non-empty container host IP to publish")
+ } else if *hostIP != "0.0.0.0" {
+ // If hostIP is 0.0.0.0, leave it unset - CNI treats
+ // 0.0.0.0 and empty differently, Docker does not.
+ testIP := net.ParseIP(*hostIP)
+ if testIP == nil {
+ return newPort, errors.Errorf("cannot parse %q as an IP address", *hostIP)
+ }
+ newPort.HostIP = testIP.String()
+ }
+ }
+ if hostPort != nil {
+ if *hostPort == "" {
+ // Set 0 as a placeholder. The server side of Specgen
+ // will find a random, open, unused port to use.
+ newPort.HostPort = 0
+ } else {
+ hostStart, hostLen, err := parseAndValidateRange(*hostPort)
+ if err != nil {
+ return newPort, errors.Wrapf(err, "error parsing host port")
+ }
+ if hostLen != ctrLen {
+ return newPort, errors.Errorf("host and container port ranges have different lengths: %d vs %d", hostLen, ctrLen)
+ }
+ newPort.HostPort = hostStart
+ }
+ }
+
+ hport := newPort.HostPort
+ logrus.Debugf("Adding port mapping from %d to %d length %d protocol %q", hport, newPort.ContainerPort, newPort.Range, newPort.Protocol)
+
+ return newPort, nil
+}
+
+// Parse and validate a port range.
+// Returns start port, length of range, error.
+func parseAndValidateRange(portRange string) (uint16, uint16, error) {
+ splitRange := strings.Split(portRange, "-")
+ if len(splitRange) > 2 {
+ return 0, 0, errors.Errorf("invalid port format - port ranges are formatted as startPort-stopPort")
+ }
+
+ if splitRange[0] == "" {
+ return 0, 0, errors.Errorf("port numbers cannot be negative")
+ }
+
+ startPort, err := parseAndValidatePort(splitRange[0])
+ if err != nil {
+ return 0, 0, err
+ }
+
+ var rangeLen uint16 = 1
+ if len(splitRange) == 2 {
+ if splitRange[1] == "" {
+ return 0, 0, errors.Errorf("must provide ending number for port range")
+ }
+ endPort, err := parseAndValidatePort(splitRange[1])
+ if err != nil {
+ return 0, 0, err
+ }
+ if endPort <= startPort {
+ return 0, 0, errors.Errorf("the end port of a range must be higher than the start port - %d is not higher than %d", endPort, startPort)
+ }
+ // Our range is the total number of ports
+ // involved, so we need to add 1 (8080:8081 is
+ // 2 ports, for example, not 1)
+ rangeLen = endPort - startPort + 1
+ }
+
+ return startPort, rangeLen, nil
+}
+
+// Turn a single string into a valid U16 port.
+func parseAndValidatePort(port string) (uint16, error) {
+ num, err := strconv.Atoi(port)
+ if err != nil {
+ return 0, errors.Wrapf(err, "invalid port number")
+ }
+ if num < 1 || num > 65535 {
+ return 0, errors.Errorf("port numbers must be between 1 and 65535 (inclusive), got %d", num)
+ }
+ return uint16(num), nil
+}
diff --git a/pkg/specgenutil/volumes.go b/pkg/specgenutil/volumes.go
new file mode 100644
index 000000000..e9f70fc9d
--- /dev/null
+++ b/pkg/specgenutil/volumes.go
@@ -0,0 +1,630 @@
+package specgenutil
+
+import (
+ "fmt"
+ "path/filepath"
+ "strings"
+
+ "github.com/containers/common/pkg/parse"
+ "github.com/containers/podman/v3/libpod/define"
+ "github.com/containers/podman/v3/pkg/specgen"
+ "github.com/containers/podman/v3/pkg/util"
+ spec "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/pkg/errors"
+)
+
+var (
+ errDuplicateDest = errors.Errorf("duplicate mount destination")
+ optionArgError = errors.Errorf("must provide an argument for option")
+ noDestError = errors.Errorf("must set volume destination")
+ errInvalidSyntax = errors.Errorf("incorrect mount format: should be --mount type=<bind|tmpfs|volume>,[src=<host-dir|volume-name>,]target=<ctr-dir>[,options]")
+)
+
+// Parse all volume-related options in the create config into a set of mounts
+// and named volumes to add to the container.
+// Handles --volumes, --mount, and --tmpfs flags.
+// Does not handle image volumes, init, and --volumes-from flags.
+// Can also add tmpfs mounts from read-only tmpfs.
+// TODO: handle options parsing/processing via containers/storage/pkg/mount
+func parseVolumes(volumeFlag, mountFlag, tmpfsFlag []string, addReadOnlyTmpfs bool) ([]spec.Mount, []*specgen.NamedVolume, []*specgen.OverlayVolume, []*specgen.ImageVolume, error) {
+ // Get mounts from the --mounts flag.
+ unifiedMounts, unifiedVolumes, unifiedImageVolumes, err := getMounts(mountFlag)
+ if err != nil {
+ return nil, nil, nil, nil, err
+ }
+
+ // Next --volumes flag.
+ volumeMounts, volumeVolumes, overlayVolumes, err := specgen.GenVolumeMounts(volumeFlag)
+ if err != nil {
+ return nil, nil, nil, nil, err
+ }
+
+ // Next --tmpfs flag.
+ tmpfsMounts, err := getTmpfsMounts(tmpfsFlag)
+ if err != nil {
+ return nil, nil, nil, nil, err
+ }
+
+ // Unify mounts from --mount, --volume, --tmpfs.
+ // Start with --volume.
+ for dest, mount := range volumeMounts {
+ if _, ok := unifiedMounts[dest]; ok {
+ return nil, nil, nil, nil, errors.Wrapf(errDuplicateDest, dest)
+ }
+ unifiedMounts[dest] = mount
+ }
+ for dest, volume := range volumeVolumes {
+ if _, ok := unifiedVolumes[dest]; ok {
+ return nil, nil, nil, nil, errors.Wrapf(errDuplicateDest, dest)
+ }
+ unifiedVolumes[dest] = volume
+ }
+ // Now --tmpfs
+ for dest, tmpfs := range tmpfsMounts {
+ if _, ok := unifiedMounts[dest]; ok {
+ return nil, nil, nil, nil, errors.Wrapf(errDuplicateDest, dest)
+ }
+ unifiedMounts[dest] = tmpfs
+ }
+
+ // If requested, add tmpfs filesystems for read-only containers.
+ if addReadOnlyTmpfs {
+ readonlyTmpfs := []string{"/tmp", "/var/tmp", "/run"}
+ options := []string{"rw", "rprivate", "nosuid", "nodev", "tmpcopyup"}
+ for _, dest := range readonlyTmpfs {
+ if _, ok := unifiedMounts[dest]; ok {
+ continue
+ }
+ if _, ok := unifiedVolumes[dest]; ok {
+ continue
+ }
+ unifiedMounts[dest] = spec.Mount{
+ Destination: dest,
+ Type: define.TypeTmpfs,
+ Source: "tmpfs",
+ Options: options,
+ }
+ }
+ }
+
+ // Check for conflicts between named volumes, overlay & image volumes,
+ // and mounts
+ allMounts := make(map[string]bool)
+ testAndSet := func(dest string) error {
+ if _, ok := allMounts[dest]; ok {
+ return errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest)
+ }
+ allMounts[dest] = true
+ return nil
+ }
+ for dest := range unifiedMounts {
+ if err := testAndSet(dest); err != nil {
+ return nil, nil, nil, nil, err
+ }
+ }
+ for dest := range unifiedVolumes {
+ if err := testAndSet(dest); err != nil {
+ return nil, nil, nil, nil, err
+ }
+ }
+ for dest := range overlayVolumes {
+ if err := testAndSet(dest); err != nil {
+ return nil, nil, nil, nil, err
+ }
+ }
+ for dest := range unifiedImageVolumes {
+ if err := testAndSet(dest); err != nil {
+ return nil, nil, nil, nil, err
+ }
+ }
+
+ // Final step: maps to arrays
+ finalMounts := make([]spec.Mount, 0, len(unifiedMounts))
+ for _, mount := range unifiedMounts {
+ if mount.Type == define.TypeBind {
+ absSrc, err := filepath.Abs(mount.Source)
+ if err != nil {
+ return nil, nil, nil, nil, errors.Wrapf(err, "error getting absolute path of %s", mount.Source)
+ }
+ mount.Source = absSrc
+ }
+ finalMounts = append(finalMounts, mount)
+ }
+ finalVolumes := make([]*specgen.NamedVolume, 0, len(unifiedVolumes))
+ for _, volume := range unifiedVolumes {
+ finalVolumes = append(finalVolumes, volume)
+ }
+ finalOverlayVolume := make([]*specgen.OverlayVolume, 0)
+ for _, volume := range overlayVolumes {
+ finalOverlayVolume = append(finalOverlayVolume, volume)
+ }
+ finalImageVolumes := make([]*specgen.ImageVolume, 0, len(unifiedImageVolumes))
+ for _, volume := range unifiedImageVolumes {
+ finalImageVolumes = append(finalImageVolumes, volume)
+ }
+
+ return finalMounts, finalVolumes, finalOverlayVolume, finalImageVolumes, nil
+}
+
+// findMountType parses the input and extracts the type of the mount type and
+// the remaining non-type tokens.
+func findMountType(input string) (mountType string, tokens []string, err error) {
+ // Split by comma, iterate over the slice and look for
+ // "type=$mountType". Everything else is appended to tokens.
+ found := false
+ for _, s := range strings.Split(input, ",") {
+ kv := strings.Split(s, "=")
+ if found || !(len(kv) == 2 && kv[0] == "type") {
+ tokens = append(tokens, s)
+ continue
+ }
+ mountType = kv[1]
+ found = true
+ }
+ if !found {
+ err = errInvalidSyntax
+ }
+ return
+}
+
+// getMounts takes user-provided input from the --mount flag and creates OCI
+// spec mounts and Libpod named volumes.
+// podman run --mount type=bind,src=/etc/resolv.conf,target=/etc/resolv.conf ...
+// podman run --mount type=tmpfs,target=/dev/shm ...
+// podman run --mount type=volume,source=test-volume, ...
+func getMounts(mountFlag []string) (map[string]spec.Mount, map[string]*specgen.NamedVolume, map[string]*specgen.ImageVolume, error) {
+ finalMounts := make(map[string]spec.Mount)
+ finalNamedVolumes := make(map[string]*specgen.NamedVolume)
+ finalImageVolumes := make(map[string]*specgen.ImageVolume)
+
+ for _, mount := range mountFlag {
+ // TODO: Docker defaults to "volume" if no mount type is specified.
+ mountType, tokens, err := findMountType(mount)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ switch mountType {
+ case define.TypeBind:
+ mount, err := getBindMount(tokens)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ if _, ok := finalMounts[mount.Destination]; ok {
+ return nil, nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination)
+ }
+ finalMounts[mount.Destination] = mount
+ case define.TypeTmpfs:
+ mount, err := getTmpfsMount(tokens)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ if _, ok := finalMounts[mount.Destination]; ok {
+ return nil, nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination)
+ }
+ finalMounts[mount.Destination] = mount
+ case define.TypeDevpts:
+ mount, err := getDevptsMount(tokens)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ if _, ok := finalMounts[mount.Destination]; ok {
+ return nil, nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination)
+ }
+ finalMounts[mount.Destination] = mount
+ case "image":
+ volume, err := getImageVolume(tokens)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ if _, ok := finalImageVolumes[volume.Destination]; ok {
+ return nil, nil, nil, errors.Wrapf(errDuplicateDest, volume.Destination)
+ }
+ finalImageVolumes[volume.Destination] = volume
+ case "volume":
+ volume, err := getNamedVolume(tokens)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ if _, ok := finalNamedVolumes[volume.Dest]; ok {
+ return nil, nil, nil, errors.Wrapf(errDuplicateDest, volume.Dest)
+ }
+ finalNamedVolumes[volume.Dest] = volume
+ default:
+ return nil, nil, nil, errors.Errorf("invalid filesystem type %q", mountType)
+ }
+ }
+
+ return finalMounts, finalNamedVolumes, finalImageVolumes, nil
+}
+
+// Parse a single bind mount entry from the --mount flag.
+func getBindMount(args []string) (spec.Mount, error) {
+ newMount := spec.Mount{
+ Type: define.TypeBind,
+ }
+
+ var setSource, setDest, setRORW, setSuid, setDev, setExec, setRelabel bool
+
+ for _, val := range args {
+ kv := strings.SplitN(val, "=", 2)
+ switch kv[0] {
+ case "bind-nonrecursive":
+ newMount.Options = append(newMount.Options, "bind")
+ case "readonly", "ro", "rw":
+ if setRORW {
+ return newMount, errors.Wrapf(optionArgError, "cannot pass 'readonly', 'ro', or 'rw' options more than once")
+ }
+ setRORW = true
+ // Can be formatted as one of:
+ // readonly
+ // readonly=[true|false]
+ // ro
+ // ro=[true|false]
+ // rw
+ // rw=[true|false]
+ if kv[0] == "readonly" {
+ kv[0] = "ro"
+ }
+ switch len(kv) {
+ case 1:
+ newMount.Options = append(newMount.Options, kv[0])
+ case 2:
+ switch strings.ToLower(kv[1]) {
+ case "true":
+ newMount.Options = append(newMount.Options, kv[0])
+ case "false":
+ // Set the opposite only for rw
+ // ro's opposite is the default
+ if kv[0] == "rw" {
+ newMount.Options = append(newMount.Options, "ro")
+ }
+ default:
+ return newMount, errors.Wrapf(optionArgError, "'readonly', 'ro', or 'rw' must be set to true or false, instead received %q", kv[1])
+ }
+ default:
+ return newMount, errors.Wrapf(optionArgError, "badly formatted option %q", val)
+ }
+ case "nosuid", "suid":
+ if setSuid {
+ return newMount, errors.Wrapf(optionArgError, "cannot pass 'nosuid' and 'suid' options more than once")
+ }
+ setSuid = true
+ newMount.Options = append(newMount.Options, kv[0])
+ case "nodev", "dev":
+ if setDev {
+ return newMount, errors.Wrapf(optionArgError, "cannot pass 'nodev' and 'dev' options more than once")
+ }
+ setDev = true
+ newMount.Options = append(newMount.Options, kv[0])
+ case "noexec", "exec":
+ if setExec {
+ return newMount, errors.Wrapf(optionArgError, "cannot pass 'noexec' and 'exec' options more than once")
+ }
+ setExec = true
+ newMount.Options = append(newMount.Options, kv[0])
+ case "shared", "rshared", "private", "rprivate", "slave", "rslave", "unbindable", "runbindable", "Z", "z":
+ newMount.Options = append(newMount.Options, kv[0])
+ case "bind-propagation":
+ if len(kv) == 1 {
+ return newMount, errors.Wrapf(optionArgError, kv[0])
+ }
+ newMount.Options = append(newMount.Options, kv[1])
+ case "src", "source":
+ if len(kv) == 1 {
+ return newMount, errors.Wrapf(optionArgError, kv[0])
+ }
+ if len(kv[1]) == 0 {
+ return newMount, errors.Wrapf(optionArgError, "host directory cannot be empty")
+ }
+ newMount.Source = kv[1]
+ setSource = true
+ case "target", "dst", "destination":
+ if len(kv) == 1 {
+ return newMount, errors.Wrapf(optionArgError, kv[0])
+ }
+ if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil {
+ return newMount, err
+ }
+ newMount.Destination = filepath.Clean(kv[1])
+ setDest = true
+ case "relabel":
+ if setRelabel {
+ return newMount, errors.Wrapf(optionArgError, "cannot pass 'relabel' option more than once")
+ }
+ setRelabel = true
+ if len(kv) != 2 {
+ return newMount, errors.Wrapf(util.ErrBadMntOption, "%s mount option must be 'private' or 'shared'", kv[0])
+ }
+ switch kv[1] {
+ case "private":
+ newMount.Options = append(newMount.Options, "Z")
+ case "shared":
+ newMount.Options = append(newMount.Options, "z")
+ default:
+ return newMount, errors.Wrapf(util.ErrBadMntOption, "%s mount option must be 'private' or 'shared'", kv[0])
+ }
+ case "consistency":
+ // Often used on MACs and mistakenly on Linux platforms.
+ // Since Docker ignores this option so shall we.
+ continue
+ default:
+ return newMount, errors.Wrapf(util.ErrBadMntOption, kv[0])
+ }
+ }
+
+ if !setDest {
+ return newMount, noDestError
+ }
+
+ if !setSource {
+ newMount.Source = newMount.Destination
+ }
+
+ options, err := parse.ValidateVolumeOpts(newMount.Options)
+ if err != nil {
+ return newMount, err
+ }
+ newMount.Options = options
+ return newMount, nil
+}
+
+// Parse a single tmpfs mount entry from the --mount flag
+func getTmpfsMount(args []string) (spec.Mount, error) {
+ newMount := spec.Mount{
+ Type: define.TypeTmpfs,
+ Source: define.TypeTmpfs,
+ }
+
+ var setDest, setRORW, setSuid, setDev, setExec, setTmpcopyup bool
+
+ for _, val := range args {
+ kv := strings.SplitN(val, "=", 2)
+ switch kv[0] {
+ case "tmpcopyup", "notmpcopyup":
+ if setTmpcopyup {
+ return newMount, errors.Wrapf(optionArgError, "cannot pass 'tmpcopyup' and 'notmpcopyup' options more than once")
+ }
+ setTmpcopyup = true
+ newMount.Options = append(newMount.Options, kv[0])
+ case "ro", "rw":
+ if setRORW {
+ return newMount, errors.Wrapf(optionArgError, "cannot pass 'ro' and 'rw' options more than once")
+ }
+ setRORW = true
+ newMount.Options = append(newMount.Options, kv[0])
+ case "nosuid", "suid":
+ if setSuid {
+ return newMount, errors.Wrapf(optionArgError, "cannot pass 'nosuid' and 'suid' options more than once")
+ }
+ setSuid = true
+ newMount.Options = append(newMount.Options, kv[0])
+ case "nodev", "dev":
+ if setDev {
+ return newMount, errors.Wrapf(optionArgError, "cannot pass 'nodev' and 'dev' options more than once")
+ }
+ setDev = true
+ newMount.Options = append(newMount.Options, kv[0])
+ case "noexec", "exec":
+ if setExec {
+ return newMount, errors.Wrapf(optionArgError, "cannot pass 'noexec' and 'exec' options more than once")
+ }
+ setExec = true
+ newMount.Options = append(newMount.Options, kv[0])
+ case "tmpfs-mode":
+ if len(kv) == 1 {
+ return newMount, errors.Wrapf(optionArgError, kv[0])
+ }
+ newMount.Options = append(newMount.Options, fmt.Sprintf("mode=%s", kv[1]))
+ case "tmpfs-size":
+ if len(kv) == 1 {
+ return newMount, errors.Wrapf(optionArgError, kv[0])
+ }
+ newMount.Options = append(newMount.Options, fmt.Sprintf("size=%s", kv[1]))
+ case "src", "source":
+ return newMount, errors.Errorf("source is not supported with tmpfs mounts")
+ case "target", "dst", "destination":
+ if len(kv) == 1 {
+ return newMount, errors.Wrapf(optionArgError, kv[0])
+ }
+ if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil {
+ return newMount, err
+ }
+ newMount.Destination = filepath.Clean(kv[1])
+ setDest = true
+ case "consistency":
+ // Often used on MACs and mistakenly on Linux platforms.
+ // Since Docker ignores this option so shall we.
+ continue
+ default:
+ return newMount, errors.Wrapf(util.ErrBadMntOption, kv[0])
+ }
+ }
+
+ if !setDest {
+ return newMount, noDestError
+ }
+
+ return newMount, nil
+}
+
+// Parse a single devpts mount entry from the --mount flag
+func getDevptsMount(args []string) (spec.Mount, error) {
+ newMount := spec.Mount{
+ Type: define.TypeDevpts,
+ Source: define.TypeDevpts,
+ }
+
+ var setDest bool
+
+ for _, val := range args {
+ kv := strings.SplitN(val, "=", 2)
+ switch kv[0] {
+ case "target", "dst", "destination":
+ if len(kv) == 1 {
+ return newMount, errors.Wrapf(optionArgError, kv[0])
+ }
+ if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil {
+ return newMount, err
+ }
+ newMount.Destination = filepath.Clean(kv[1])
+ setDest = true
+ default:
+ return newMount, errors.Wrapf(util.ErrBadMntOption, kv[0])
+ }
+ }
+
+ if !setDest {
+ return newMount, noDestError
+ }
+
+ return newMount, nil
+}
+
+// Parse a single volume mount entry from the --mount flag.
+// Note that the volume-label option for named volumes is currently NOT supported.
+// TODO: add support for --volume-label
+func getNamedVolume(args []string) (*specgen.NamedVolume, error) {
+ newVolume := new(specgen.NamedVolume)
+
+ var setSource, setDest, setRORW, setSuid, setDev, setExec bool
+
+ for _, val := range args {
+ kv := strings.SplitN(val, "=", 2)
+ switch kv[0] {
+ case "ro", "rw":
+ if setRORW {
+ return nil, errors.Wrapf(optionArgError, "cannot pass 'ro' and 'rw' options more than once")
+ }
+ setRORW = true
+ newVolume.Options = append(newVolume.Options, kv[0])
+ case "nosuid", "suid":
+ if setSuid {
+ return nil, errors.Wrapf(optionArgError, "cannot pass 'nosuid' and 'suid' options more than once")
+ }
+ setSuid = true
+ newVolume.Options = append(newVolume.Options, kv[0])
+ case "nodev", "dev":
+ if setDev {
+ return nil, errors.Wrapf(optionArgError, "cannot pass 'nodev' and 'dev' options more than once")
+ }
+ setDev = true
+ newVolume.Options = append(newVolume.Options, kv[0])
+ case "noexec", "exec":
+ if setExec {
+ return nil, errors.Wrapf(optionArgError, "cannot pass 'noexec' and 'exec' options more than once")
+ }
+ setExec = true
+ newVolume.Options = append(newVolume.Options, kv[0])
+ case "volume-label":
+ return nil, errors.Errorf("the --volume-label option is not presently implemented")
+ case "src", "source":
+ if len(kv) == 1 {
+ return nil, errors.Wrapf(optionArgError, kv[0])
+ }
+ newVolume.Name = kv[1]
+ setSource = true
+ case "target", "dst", "destination":
+ if len(kv) == 1 {
+ return nil, errors.Wrapf(optionArgError, kv[0])
+ }
+ if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil {
+ return nil, err
+ }
+ newVolume.Dest = filepath.Clean(kv[1])
+ setDest = true
+ case "consistency":
+ // Often used on MACs and mistakenly on Linux platforms.
+ // Since Docker ignores this option so shall we.
+ continue
+ default:
+ return nil, errors.Wrapf(util.ErrBadMntOption, kv[0])
+ }
+ }
+
+ if !setSource {
+ return nil, errors.Errorf("must set source volume")
+ }
+ if !setDest {
+ return nil, noDestError
+ }
+
+ return newVolume, nil
+}
+
+// Parse the arguments into an image volume. An image volume is a volume based
+// on a container image. The container image is first mounted on the host and
+// is then bind-mounted into the container. An ImageVolume is always mounted
+// read only.
+func getImageVolume(args []string) (*specgen.ImageVolume, error) {
+ newVolume := new(specgen.ImageVolume)
+
+ for _, val := range args {
+ kv := strings.SplitN(val, "=", 2)
+ switch kv[0] {
+ case "src", "source":
+ if len(kv) == 1 {
+ return nil, errors.Wrapf(optionArgError, kv[0])
+ }
+ newVolume.Source = kv[1]
+ case "target", "dst", "destination":
+ if len(kv) == 1 {
+ return nil, errors.Wrapf(optionArgError, kv[0])
+ }
+ if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil {
+ return nil, err
+ }
+ newVolume.Destination = filepath.Clean(kv[1])
+ case "rw", "readwrite":
+ switch kv[1] {
+ case "true":
+ newVolume.ReadWrite = true
+ case "false":
+ // Nothing to do. RO is default.
+ default:
+ return nil, errors.Wrapf(util.ErrBadMntOption, "invalid rw value %q", kv[1])
+ }
+ case "consistency":
+ // Often used on MACs and mistakenly on Linux platforms.
+ // Since Docker ignores this option so shall we.
+ continue
+ default:
+ return nil, errors.Wrapf(util.ErrBadMntOption, kv[0])
+ }
+ }
+
+ if len(newVolume.Source)*len(newVolume.Destination) == 0 {
+ return nil, errors.Errorf("must set source and destination for image volume")
+ }
+
+ return newVolume, nil
+}
+
+// GetTmpfsMounts creates spec.Mount structs for user-requested tmpfs mounts
+func getTmpfsMounts(tmpfsFlag []string) (map[string]spec.Mount, error) {
+ m := make(map[string]spec.Mount)
+ for _, i := range tmpfsFlag {
+ // Default options if nothing passed
+ var options []string
+ spliti := strings.Split(i, ":")
+ destPath := spliti[0]
+ if err := parse.ValidateVolumeCtrDir(spliti[0]); err != nil {
+ return nil, err
+ }
+ if len(spliti) > 1 {
+ options = strings.Split(spliti[1], ",")
+ }
+
+ if _, ok := m[destPath]; ok {
+ return nil, errors.Wrapf(errDuplicateDest, destPath)
+ }
+
+ mount := spec.Mount{
+ Destination: filepath.Clean(destPath),
+ Type: string(define.TypeTmpfs),
+ Options: options,
+ Source: string(define.TypeTmpfs),
+ }
+ m[destPath] = mount
+ }
+ return m, nil
+}