summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/podman/pod_create.go17
-rw-r--r--completions/bash/podman8
-rw-r--r--docs/source/markdown/podman-pod-create.1.md54
-rw-r--r--libpod/container_internal_linux.go23
-rw-r--r--libpod/options.go96
-rw-r--r--libpod/pod.go1
-rw-r--r--libpod/runtime_pod_infra_linux.go74
-rw-r--r--pkg/adapter/pods.go91
-rw-r--r--pkg/adapter/pods_remote.go2
-rw-r--r--pkg/api/handlers/generic/containers_create.go (renamed from pkg/api/handlers/containers_create.go)34
-rw-r--r--pkg/api/handlers/generic/swagger.go6
-rw-r--r--pkg/api/handlers/libpod/containers_create.go29
-rw-r--r--pkg/api/handlers/libpod/pods.go4
-rw-r--r--pkg/api/handlers/types.go9
-rw-r--r--pkg/api/handlers/utils/containers.go25
-rw-r--r--pkg/api/server/register_containers.go28
-rw-r--r--pkg/api/server/register_images.go4
-rw-r--r--pkg/api/server/register_pods.go19
-rw-r--r--pkg/bindings/containers/create.go30
-rw-r--r--pkg/seccomp/seccomp.go2
-rw-r--r--pkg/spec/config_linux.go18
-rw-r--r--pkg/spec/config_unsupported.go4
-rw-r--r--pkg/spec/createconfig.go5
-rw-r--r--pkg/spec/parse.go14
-rw-r--r--pkg/spec/spec.go26
-rw-r--r--pkg/spec/storage.go4
-rw-r--r--pkg/specgen/config_linux_cgo.go62
-rw-r--r--pkg/specgen/config_linux_nocgo.go11
-rw-r--r--pkg/specgen/config_unsupported.go12
-rw-r--r--pkg/specgen/create.go187
-rw-r--r--pkg/specgen/namespaces.go467
-rw-r--r--pkg/specgen/oci.go260
-rw-r--r--pkg/specgen/specgen.go99
-rw-r--r--pkg/specgen/validate.go159
-rw-r--r--pkg/varlinkapi/pods.go16
-rw-r--r--test/e2e/pod_create_test.go165
-rw-r--r--test/system/150-login.bats339
37 files changed, 2182 insertions, 222 deletions
diff --git a/cmd/podman/pod_create.go b/cmd/podman/pod_create.go
index 0f72780f9..810f62f02 100644
--- a/cmd/podman/pod_create.go
+++ b/cmd/podman/pod_create.go
@@ -45,19 +45,7 @@ func init() {
podCreateCommand.SetUsageTemplate(UsageTemplate())
flags := podCreateCommand.Flags()
flags.SetInterspersed(false)
- // When we are ready to add the network options to the create commmand, we need to uncomment
- // the following
-
- //flags.AddFlagSet(getNetFlags())
-
- // Once this is uncommented, then the publish option below needs to be removed because it
- // conflicts with the publish in getNetFlags. Upon removal, the c.Publish will not work
- // anymore and needs to be cleaned up. I suggest starting with removing the Publish attribute
- // from PodCreateValues structure. Running make should then expose all areas that need to be
- // addressed. To get the value of publish (and other flags in getNetFlags, use the syntax:
- // c.<type>("<flag_name") or c.Bool("publish")
- // Remember to do this safely by checking len, etc.
-
+ flags.AddFlagSet(getNetFlags())
flags.StringVar(&podCreateCommand.CgroupParent, "cgroup-parent", "", "Set parent cgroup for the pod")
flags.BoolVar(&podCreateCommand.Infra, "infra", true, "Create an infra container associated with the pod to share namespaces with")
flags.StringVar(&podCreateCommand.InfraImage, "infra-image", define.DefaultInfraImage, "The image of the infra container to associate with the pod")
@@ -67,7 +55,6 @@ func init() {
flags.StringVarP(&podCreateCommand.Name, "name", "n", "", "Assign a name to the pod")
flags.StringVarP(&podCreateCommand.Hostname, "hostname", "", "", "Set a hostname to the pod")
flags.StringVar(&podCreateCommand.PodIDFile, "pod-id-file", "", "Write the pod ID to the file")
- flags.StringSliceVarP(&podCreateCommand.Publish, "publish", "p", []string{}, "Publish a container's port, or a range of ports, to the host (default [])")
flags.StringVar(&podCreateCommand.Share, "share", shared.DefaultKernelNamespaces, "A comma delimited list of kernel namespaces the pod will share")
}
@@ -83,7 +70,7 @@ func podCreateCmd(c *cliconfig.PodCreateValues) error {
}
defer runtime.DeferredShutdown(false)
- if len(c.Publish) > 0 {
+ if len(c.StringSlice("publish")) > 0 {
if !c.Infra {
return errors.Errorf("you must have an infra container to publish port bindings to the host")
}
diff --git a/completions/bash/podman b/completions/bash/podman
index 10d40a0c9..958633bf0 100644
--- a/completions/bash/podman
+++ b/completions/bash/podman
@@ -2897,13 +2897,21 @@ _podman_image_exists() {
_podman_pod_create() {
local options_with_args="
+ --add-host
--cgroup-parent
+ --dns
+ --dns-opt
+ --dns-search
--infra-command
--infra-image
+ --ip
--label-file
--label
-l
+ --mac-address
--name
+ --network
+ --no-hosts
--podidfile
--publish
-p
diff --git a/docs/source/markdown/podman-pod-create.1.md b/docs/source/markdown/podman-pod-create.1.md
index cd1de6401..dba31f681 100644
--- a/docs/source/markdown/podman-pod-create.1.md
+++ b/docs/source/markdown/podman-pod-create.1.md
@@ -15,50 +15,82 @@ containers added to it. The pod id is printed to STDOUT. You can then use
## OPTIONS
+**--add-host**=_host_:_ip_
+
+Add a host to the /etc/hosts file shared between all containers in the pod.
+
**--cgroup-parent**=*path*
Path to cgroups under which the cgroup for the pod will be created. If the path is not absolute, the path is considered to be relative to the cgroups path of the init process. Cgroups will be created if they do not already exist.
+**--dns**=*ipaddr*
+
+Set custom DNS servers in the /etc/resolv.conf file that will be shared between all containers in the pod. A special option, "none" is allowed which disables creation of /etc/resolv.conf for the pod.
+
+**--dns-opt**=*option*
+
+Set custom DNS options in the /etc/resolv.conf file that will be shared between all containers in the pod.
+
+**--dns-search**=*domain*
+
+Set custom DNS search domains in the /etc/resolv.conf file that will be shared between all containers in the pod.
+
**--help**
-Print usage statement
+Print usage statement.
-**--infra**
+**--infra**=**true**|**false**
-Create an infra container and associate it with the pod. An infra container is a lightweight container used to coordinate the shared kernel namespace of a pod. Default: true
+Create an infra container and associate it with the pod. An infra container is a lightweight container used to coordinate the shared kernel namespace of a pod. Default: true.
**--infra-command**=*command*
-The command that will be run to start the infra container. Default: "/pause"
+The command that will be run to start the infra container. Default: "/pause".
**--infra-image**=*image*
-The image that will be created for the infra container. Default: "k8s.gcr.io/pause:3.1"
+The image that will be created for the infra container. Default: "k8s.gcr.io/pause:3.1".
+
+**--ip**=*ipaddr*
+
+Set a static IP for the pod's shared network.
**-l**, **--label**=*label*
-Add metadata to a pod (e.g., --label com.example.key=value)
+Add metadata to a pod (e.g., --label com.example.key=value).
**--label-file**=*label*
-Read in a line delimited file of labels
+Read in a line delimited file of labels.
+
+**--mac-address**=*address*
+
+Set a static MAC address for the pod's shared network.
**-n**, **--name**=*name*
-Assign a name to the pod
+Assign a name to the pod.
+
+**--network**=*mode*
+
+Set network mode for the pod. Supported values are *bridge* (the default), *host* (do not create a network namespace, all containers in the pod will use the host's network), or a comma-separated list of the names of CNI networks the pod should join.
+
+**--no-hosts**=**true**|**false**
+
+Disable creation of /etc/hosts for the pod.
**--podidfile**=*podid*
-Write the pod ID to the file
+Write the pod ID to the file.
**-p**, **--publish**=*port*
-Publish a port or range of ports from the pod to the host
+Publish a port or range of ports from the pod to the host.
Format: `ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort | containerPort`
Both hostPort and containerPort can be specified as a range of ports.
When specifying ranges for both, the number of container ports in the range must match the number of host ports in the range.
-Use `podman port` to see the actual mapping: `podman port CONTAINER $CONTAINERPORT`
+Use `podman port` to see the actual mapping: `podman port CONTAINER $CONTAINERPORT`.
NOTE: This cannot be modified once the pod is created.
diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go
index 561dbdc1c..739026264 100644
--- a/libpod/container_internal_linux.go
+++ b/libpod/container_internal_linux.go
@@ -1114,22 +1114,17 @@ func (c *Container) makeBindMounts() error {
return errors.Wrapf(err, "error fetching bind mounts from dependency %s of container %s", depCtr.ID(), c.ID())
}
- if !c.config.UseImageResolvConf {
- // The other container may not have a resolv.conf or /etc/hosts
- // If it doesn't, don't copy them
- resolvPath, exists := bindMounts["/etc/resolv.conf"]
- if exists {
- c.state.BindMounts["/etc/resolv.conf"] = resolvPath
- }
+ // The other container may not have a resolv.conf or /etc/hosts
+ // If it doesn't, don't copy them
+ resolvPath, exists := bindMounts["/etc/resolv.conf"]
+ if !c.config.UseImageResolvConf && exists {
+ c.state.BindMounts["/etc/resolv.conf"] = resolvPath
}
- if !c.config.UseImageHosts {
- // check if dependency container has an /etc/hosts file
- hostsPath, exists := bindMounts["/etc/hosts"]
- if !exists {
- return errors.Errorf("error finding hosts file of dependency container %s for container %s", depCtr.ID(), c.ID())
- }
-
+ // check if dependency container has an /etc/hosts file.
+ // It may not have one, so only use it if it does.
+ hostsPath, exists := bindMounts["/etc/hosts"]
+ if !c.config.UseImageHosts && exists {
depCtr.lock.Lock()
// generate a hosts file for the dependency container,
// based on either its old hosts file, or the default,
diff --git a/libpod/options.go b/libpod/options.go
index 4957f822d..1fd588867 100644
--- a/libpod/options.go
+++ b/libpod/options.go
@@ -953,6 +953,16 @@ func WithNetNS(portMappings []ocicni.PortMapping, postConfigureNetNS bool, netmo
return define.ErrCtrFinalized
}
+ if rootless.IsRootless() {
+ if len(networks) > 0 {
+ return errors.Wrapf(define.ErrInvalidArg, "cannot use CNI networks with rootless containers")
+ }
+ }
+
+ if len(networks) > 1 && (ctr.config.StaticIP != nil || ctr.config.StaticMAC != nil) {
+ return errors.Wrapf(define.ErrInvalidArg, "cannot join more than one CNI network if configuring a static IP or MAC address")
+ }
+
if ctr.config.NetNsCtr != "" {
return errors.Wrapf(define.ErrInvalidArg, "container is already set to join another container's net ns, cannot create a new net ns")
}
@@ -962,12 +972,6 @@ func WithNetNS(portMappings []ocicni.PortMapping, postConfigureNetNS bool, netmo
ctr.config.CreateNetNS = true
ctr.config.PortMappings = portMappings
- if rootless.IsRootless() {
- if len(networks) > 0 {
- return errors.New("cannot use CNI networks with rootless containers")
- }
- }
-
ctr.config.Networks = networks
return nil
@@ -1780,6 +1784,9 @@ func WithInfraContainerPorts(bindings []ocicni.PortMapping) PodCreateOption {
if pod.valid {
return define.ErrPodFinalized
}
+ if !pod.config.InfraContainer.HasInfraContainer {
+ return errors.Wrapf(define.ErrInvalidArg, "cannot set pod ports as no infra container is being created")
+ }
pod.config.InfraContainer.PortBindings = bindings
return nil
}
@@ -1792,6 +1799,14 @@ func WithPodStaticIP(ip net.IP) PodCreateOption {
return define.ErrPodFinalized
}
+ if !pod.config.InfraContainer.HasInfraContainer {
+ return errors.Wrapf(define.ErrInvalidArg, "cannot set pod static IP as no infra container is being created")
+ }
+
+ if pod.config.InfraContainer.HostNetwork {
+ return errors.Wrapf(define.ErrInvalidArg, "cannot set static IP if host network is specified")
+ }
+
if len(pod.config.InfraContainer.Networks) > 1 {
return errors.Wrapf(define.ErrInvalidArg, "cannot set a static IP if joining more than 1 CNI network")
}
@@ -1809,6 +1824,14 @@ func WithPodStaticMAC(mac net.HardwareAddr) PodCreateOption {
return define.ErrPodFinalized
}
+ if !pod.config.InfraContainer.HasInfraContainer {
+ return errors.Wrapf(define.ErrInvalidArg, "cannot set pod static MAC as no infra container is being created")
+ }
+
+ if pod.config.InfraContainer.HostNetwork {
+ return errors.Wrapf(define.ErrInvalidArg, "cannot set static MAC if host network is specified")
+ }
+
if len(pod.config.InfraContainer.Networks) > 1 {
return errors.Wrapf(define.ErrInvalidArg, "cannot set a static MAC if joining more than 1 CNI network")
}
@@ -1827,6 +1850,10 @@ func WithPodUseImageResolvConf() PodCreateOption {
return define.ErrPodFinalized
}
+ if !pod.config.InfraContainer.HasInfraContainer {
+ return errors.Wrapf(define.ErrInvalidArg, "cannot configure pod DNS as no infra container is being created")
+ }
+
if len(pod.config.InfraContainer.DNSServer) != 0 ||
len(pod.config.InfraContainer.DNSSearch) != 0 ||
len(pod.config.InfraContainer.DNSOption) != 0 {
@@ -1846,6 +1873,10 @@ func WithPodDNS(dnsServer []string) PodCreateOption {
return define.ErrPodFinalized
}
+ if !pod.config.InfraContainer.HasInfraContainer {
+ return errors.Wrapf(define.ErrInvalidArg, "cannot configure pod DNS as no infra container is being created")
+ }
+
if pod.config.InfraContainer.UseImageResolvConf {
return errors.Wrapf(define.ErrInvalidArg, "cannot add DNS servers if pod will not create /etc/resolv.conf")
}
@@ -1863,6 +1894,10 @@ func WithPodDNSSearch(dnsSearch []string) PodCreateOption {
return define.ErrPodFinalized
}
+ if !pod.config.InfraContainer.HasInfraContainer {
+ return errors.Wrapf(define.ErrInvalidArg, "cannot configure pod DNS as no infra container is being created")
+ }
+
if pod.config.InfraContainer.UseImageResolvConf {
return errors.Wrapf(define.ErrInvalidArg, "cannot add DNS search domains if pod will not create /etc/resolv.conf")
}
@@ -1880,6 +1915,10 @@ func WithPodDNSOption(dnsOption []string) PodCreateOption {
return define.ErrPodFinalized
}
+ if !pod.config.InfraContainer.HasInfraContainer {
+ return errors.Wrapf(define.ErrInvalidArg, "cannot configure pod DNS as no infra container is being created")
+ }
+
if pod.config.InfraContainer.UseImageResolvConf {
return errors.Wrapf(define.ErrInvalidArg, "cannot add DNS options if pod will not create /etc/resolv.conf")
}
@@ -1898,6 +1937,10 @@ func WithPodUseImageHosts() PodCreateOption {
return define.ErrPodFinalized
}
+ if !pod.config.InfraContainer.HasInfraContainer {
+ return errors.Wrapf(define.ErrInvalidArg, "cannot configure pod hosts as no infra container is being created")
+ }
+
if len(pod.config.InfraContainer.HostAdd) != 0 {
return errors.Wrapf(define.ErrInvalidArg, "not creating /etc/hosts conflicts with adding to the hosts file")
}
@@ -1915,6 +1958,10 @@ func WithPodHosts(hosts []string) PodCreateOption {
return define.ErrPodFinalized
}
+ if !pod.config.InfraContainer.HasInfraContainer {
+ return errors.Wrapf(define.ErrInvalidArg, "cannot configure pod hosts as no infra container is being created")
+ }
+
if pod.config.InfraContainer.UseImageHosts {
return errors.Wrapf(define.ErrInvalidArg, "cannot add to /etc/hosts if container is using image hosts")
}
@@ -1932,8 +1979,45 @@ func WithPodNetworks(networks []string) PodCreateOption {
return define.ErrPodFinalized
}
+ if !pod.config.InfraContainer.HasInfraContainer {
+ return errors.Wrapf(define.ErrInvalidArg, "cannot configure pod CNI networks as no infra container is being created")
+ }
+
+ if (pod.config.InfraContainer.StaticIP != nil || pod.config.InfraContainer.StaticMAC != nil) &&
+ len(networks) > 1 {
+ return errors.Wrapf(define.ErrInvalidArg, "cannot join more than one CNI network if setting a static IP or MAC address")
+ }
+
+ if pod.config.InfraContainer.HostNetwork {
+ return errors.Wrapf(define.ErrInvalidArg, "cannot join pod to CNI networks if host network is specified")
+ }
+
pod.config.InfraContainer.Networks = networks
return nil
}
}
+
+// WithPodHostNetwork tells the pod to use the host's network namespace.
+func WithPodHostNetwork() PodCreateOption {
+ return func(pod *Pod) error {
+ if pod.valid {
+ return define.ErrPodFinalized
+ }
+
+ if !pod.config.InfraContainer.HasInfraContainer {
+ return errors.Wrapf(define.ErrInvalidArg, "cannot configure pod host networking as no infra container is being created")
+ }
+
+ if len(pod.config.InfraContainer.PortBindings) > 0 ||
+ pod.config.InfraContainer.StaticIP != nil ||
+ pod.config.InfraContainer.StaticMAC != nil ||
+ len(pod.config.InfraContainer.Networks) > 0 {
+ return errors.Wrapf(define.ErrInvalidArg, "cannot set host network if network-related configuration is specified")
+ }
+
+ pod.config.InfraContainer.HostNetwork = true
+
+ return nil
+ }
+}
diff --git a/libpod/pod.go b/libpod/pod.go
index 4f85caf08..1b4c06c9d 100644
--- a/libpod/pod.go
+++ b/libpod/pod.go
@@ -99,6 +99,7 @@ type PodContainerInfo struct {
// InfraContainerConfig is the configuration for the pod's infra container
type InfraContainerConfig struct {
HasInfraContainer bool `json:"makeInfraContainer"`
+ HostNetwork bool `json:"infraHostNetwork,omitempty"`
PortBindings []ocicni.PortMapping `json:"infraPortBindings"`
StaticIP net.IP `json:"staticIP,omitempty"`
StaticMAC net.HardwareAddr `json:"staticMAC,omitempty"`
diff --git a/libpod/runtime_pod_infra_linux.go b/libpod/runtime_pod_infra_linux.go
index 1b1421ca8..a6cac2b72 100644
--- a/libpod/runtime_pod_infra_linux.go
+++ b/libpod/runtime_pod_infra_linux.go
@@ -37,6 +37,7 @@ func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, imgID
isRootless := rootless.IsRootless()
entryCmd := []string{r.config.InfraCommand}
+ var options []CtrCreateOption
// I've seen circumstances where config is being passed as nil.
// Let's err on the side of safety and make sure it's safe to use.
if config != nil {
@@ -68,6 +69,44 @@ func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, imgID
g.AddProcessEnv(nameValSlice[0], nameValSlice[1])
}
}
+
+ // Since user namespace sharing is not implemented, we only need to check if it's rootless
+ if !p.config.InfraContainer.HostNetwork {
+ netmode := "bridge"
+ if isRootless {
+ netmode = "slirp4netns"
+ }
+ // PostConfigureNetNS should not be set since user namespace sharing is not implemented
+ // and rootless networking no longer supports post configuration setup
+ options = append(options, WithNetNS(p.config.InfraContainer.PortBindings, false, netmode, p.config.InfraContainer.Networks))
+ } else if err := g.RemoveLinuxNamespace(string(spec.NetworkNamespace)); err != nil {
+ return nil, errors.Wrapf(err, "error removing network namespace from pod %s infra container", p.ID())
+ }
+
+ if p.config.InfraContainer.StaticIP != nil {
+ options = append(options, WithStaticIP(p.config.InfraContainer.StaticIP))
+ }
+ if p.config.InfraContainer.StaticMAC != nil {
+ options = append(options, WithStaticMAC(p.config.InfraContainer.StaticMAC))
+ }
+ if p.config.InfraContainer.UseImageResolvConf {
+ options = append(options, WithUseImageResolvConf())
+ }
+ if len(p.config.InfraContainer.DNSServer) > 0 {
+ options = append(options, WithDNS(p.config.InfraContainer.DNSServer))
+ }
+ if len(p.config.InfraContainer.DNSSearch) > 0 {
+ options = append(options, WithDNSSearch(p.config.InfraContainer.DNSSearch))
+ }
+ if len(p.config.InfraContainer.DNSOption) > 0 {
+ options = append(options, WithDNSOption(p.config.InfraContainer.DNSOption))
+ }
+ if p.config.InfraContainer.UseImageHosts {
+ options = append(options, WithUseImageHosts())
+ }
+ if len(p.config.InfraContainer.HostAdd) > 0 {
+ options = append(options, WithHosts(p.config.InfraContainer.HostAdd))
+ }
}
g.SetRootReadonly(true)
@@ -87,46 +126,11 @@ func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, imgID
}
containerName := p.ID()[:IDTruncLength] + "-infra"
- var options []CtrCreateOption
options = append(options, r.WithPod(p))
options = append(options, WithRootFSFromImage(imgID, imgName, false))
options = append(options, WithName(containerName))
options = append(options, withIsInfra())
- // Since user namespace sharing is not implemented, we only need to check if it's rootless
- netmode := "bridge"
- if isRootless {
- netmode = "slirp4netns"
- }
- // PostConfigureNetNS should not be set since user namespace sharing is not implemented
- // and rootless networking no longer supports post configuration setup
- options = append(options, WithNetNS(p.config.InfraContainer.PortBindings, false, netmode, p.config.InfraContainer.Networks))
-
- if p.config.InfraContainer.StaticIP != nil {
- options = append(options, WithStaticIP(p.config.InfraContainer.StaticIP))
- }
- if p.config.InfraContainer.StaticMAC != nil {
- options = append(options, WithStaticMAC(p.config.InfraContainer.StaticMAC))
- }
- if p.config.InfraContainer.UseImageResolvConf {
- options = append(options, WithUseImageResolvConf())
- }
- if len(p.config.InfraContainer.DNSServer) > 0 {
- options = append(options, WithDNS(p.config.InfraContainer.DNSServer))
- }
- if len(p.config.InfraContainer.DNSSearch) > 0 {
- options = append(options, WithDNSSearch(p.config.InfraContainer.DNSSearch))
- }
- if len(p.config.InfraContainer.DNSOption) > 0 {
- options = append(options, WithDNSOption(p.config.InfraContainer.DNSOption))
- }
- if p.config.InfraContainer.UseImageHosts {
- options = append(options, WithUseImageHosts())
- }
- if len(p.config.InfraContainer.HostAdd) > 0 {
- options = append(options, WithHosts(p.config.InfraContainer.HostAdd))
- }
-
return r.newContainer(ctx, g.Config, options...)
}
diff --git a/pkg/adapter/pods.go b/pkg/adapter/pods.go
index 49f086ef3..0d9fa7210 100644
--- a/pkg/adapter/pods.go
+++ b/pkg/adapter/pods.go
@@ -7,6 +7,7 @@ import (
"fmt"
"io"
"io/ioutil"
+ "net"
"os"
"path/filepath"
"strings"
@@ -248,6 +249,17 @@ func (r *LocalRuntime) CreatePod(ctx context.Context, cli *cliconfig.PodCreateVa
err error
)
+ // This needs to be first, as a lot of options depend on
+ // WithInfraContainer()
+ if cli.Infra {
+ options = append(options, libpod.WithInfraContainer())
+ nsOptions, err := shared.GetNamespaceOptions(strings.Split(cli.Share, ","))
+ if err != nil {
+ return "", err
+ }
+ options = append(options, nsOptions...)
+ }
+
if cli.Flag("cgroup-parent").Changed {
options = append(options, libpod.WithPodCgroupParent(cli.CgroupParent))
}
@@ -264,17 +276,78 @@ func (r *LocalRuntime) CreatePod(ctx context.Context, cli *cliconfig.PodCreateVa
options = append(options, libpod.WithPodHostname(cli.Hostname))
}
- if cli.Infra {
- options = append(options, libpod.WithInfraContainer())
- nsOptions, err := shared.GetNamespaceOptions(strings.Split(cli.Share, ","))
+ if cli.Flag("add-host").Changed {
+ options = append(options, libpod.WithPodHosts(cli.StringSlice("add-host")))
+ }
+ if cli.Flag("dns").Changed {
+ dns := cli.StringSlice("dns")
+ foundHost := false
+ for _, entry := range dns {
+ if entry == "host" {
+ foundHost = true
+ }
+ }
+ if foundHost && len(dns) > 1 {
+ return "", errors.Errorf("cannot set dns=host and still provide other DNS servers")
+ }
+ if foundHost {
+ options = append(options, libpod.WithPodUseImageResolvConf())
+ } else {
+ options = append(options, libpod.WithPodDNS(cli.StringSlice("dns")))
+ }
+ }
+ if cli.Flag("dns-opt").Changed {
+ options = append(options, libpod.WithPodDNSOption(cli.StringSlice("dns-opt")))
+ }
+ if cli.Flag("dns-search").Changed {
+ options = append(options, libpod.WithPodDNSSearch(cli.StringSlice("dns-search")))
+ }
+ if cli.Flag("ip").Changed {
+ ip := net.ParseIP(cli.String("ip"))
+ if ip == nil {
+ return "", errors.Errorf("invalid IP address %q passed to --ip", cli.String("ip"))
+ }
+
+ options = append(options, libpod.WithPodStaticIP(ip))
+ }
+ if cli.Flag("mac-address").Changed {
+ mac, err := net.ParseMAC(cli.String("mac-address"))
if err != nil {
- return "", err
+ return "", errors.Wrapf(err, "invalid MAC address %q passed to --mac-address", cli.String("mac-address"))
+ }
+
+ options = append(options, libpod.WithPodStaticMAC(mac))
+ }
+ if cli.Flag("network").Changed {
+ netValue := cli.String("network")
+ switch strings.ToLower(netValue) {
+ case "bridge":
+ // Do nothing.
+ // TODO: Maybe this should be split between slirp and
+ // bridge? Better to wait until someone asks...
+ logrus.Debugf("Pod using default network mode")
+ case "host":
+ logrus.Debugf("Pod will use host networking")
+ options = append(options, libpod.WithPodHostNetwork())
+ case "":
+ return "", errors.Errorf("invalid value passed to --net: must provide a comma-separated list of CNI networks or host")
+ default:
+ // We'll assume this is a comma-separated list of CNI
+ // networks.
+ networks := strings.Split(netValue, ",")
+ logrus.Debugf("Pod joining CNI networks: %v", networks)
+ options = append(options, libpod.WithPodNetworks(networks))
+ }
+ }
+ if cli.Flag("no-hosts").Changed {
+ if cli.Bool("no-hosts") {
+ options = append(options, libpod.WithPodUseImageHosts())
}
- options = append(options, nsOptions...)
}
- if len(cli.Publish) > 0 {
- portBindings, err := shared.CreatePortBindings(cli.Publish)
+ publish := cli.StringSlice("publish")
+ if len(publish) > 0 {
+ portBindings, err := shared.CreatePortBindings(publish)
if err != nil {
return "", err
}
@@ -497,6 +570,10 @@ func (r *LocalRuntime) PlayKubeYAML(ctx context.Context, c *cliconfig.KubePlayVa
}
podOptions = append(podOptions, libpod.WithPodHostname(hostname))
+ if podYAML.Spec.HostNetwork {
+ podOptions = append(podOptions, libpod.WithPodHostNetwork())
+ }
+
nsOptions, err := shared.GetNamespaceOptions(strings.Split(shared.DefaultKernelNamespaces, ","))
if err != nil {
return nil, err
diff --git a/pkg/adapter/pods_remote.go b/pkg/adapter/pods_remote.go
index 5ef1a9216..20f089628 100644
--- a/pkg/adapter/pods_remote.go
+++ b/pkg/adapter/pods_remote.go
@@ -185,7 +185,7 @@ func (r *LocalRuntime) CreatePod(ctx context.Context, cli *cliconfig.PodCreateVa
Infra: cli.Infra,
InfraCommand: cli.InfraCommand,
InfraImage: cli.InfraCommand,
- Publish: cli.Publish,
+ Publish: cli.StringSlice("publish"),
}
return iopodman.CreatePod().Call(r.Conn, pc)
diff --git a/pkg/api/handlers/containers_create.go b/pkg/api/handlers/generic/containers_create.go
index 48f0de94d..7e542752f 100644
--- a/pkg/api/handlers/containers_create.go
+++ b/pkg/api/handlers/generic/containers_create.go
@@ -1,4 +1,4 @@
-package handlers
+package generic
import (
"encoding/json"
@@ -6,10 +6,10 @@ import (
"net/http"
"strings"
- "github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
image2 "github.com/containers/libpod/libpod/image"
+ "github.com/containers/libpod/pkg/api/handlers"
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/containers/libpod/pkg/namespaces"
"github.com/containers/libpod/pkg/signal"
@@ -17,14 +17,13 @@ import (
"github.com/containers/storage"
"github.com/gorilla/schema"
"github.com/pkg/errors"
- log "github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
)
func CreateContainer(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder)
- input := CreateContainerConfig{}
+ input := handlers.CreateContainerConfig{}
query := struct {
Name string `schema:"name"`
}{
@@ -52,34 +51,11 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "makeCreatConfig()"))
return
}
-
cc.Name = query.Name
- var pod *libpod.Pod
- ctr, err := shared.CreateContainerFromCreateConfig(runtime, &cc, r.Context(), pod)
- if err != nil {
- if strings.Contains(err.Error(), "invalid log driver") {
- // this does not quite work yet and needs a little more massaging
- w.Header().Set("Content-Type", "text/plain; charset=us-ascii")
- w.WriteHeader(http.StatusInternalServerError)
- msg := fmt.Sprintf("logger: no log driver named '%s' is registered", input.HostConfig.LogConfig.Type)
- if _, err := fmt.Fprintln(w, msg); err != nil {
- log.Errorf("%s: %q", msg, err)
- }
- //s.WriteResponse(w, http.StatusInternalServerError, fmt.Sprintf("logger: no log driver named '%s' is registered", input.HostConfig.LogConfig.Type))
- return
- }
- utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "CreateContainerFromCreateConfig()"))
- return
- }
-
- response := ContainerCreateResponse{
- ID: ctr.ID(),
- Warnings: []string{}}
-
- utils.WriteResponse(w, http.StatusCreated, response)
+ utils.CreateContainer(r.Context(), w, runtime, &cc)
}
-func makeCreateConfig(input CreateContainerConfig, newImage *image2.Image) (createconfig.CreateConfig, error) {
+func makeCreateConfig(input handlers.CreateContainerConfig, newImage *image2.Image) (createconfig.CreateConfig, error) {
var (
err error
init bool
diff --git a/pkg/api/handlers/generic/swagger.go b/pkg/api/handlers/generic/swagger.go
index bfe527c41..c9c9610bb 100644
--- a/pkg/api/handlers/generic/swagger.go
+++ b/pkg/api/handlers/generic/swagger.go
@@ -1,13 +1,15 @@
package generic
-import "github.com/containers/libpod/pkg/api/handlers"
+import (
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+)
// Create container
// swagger:response ContainerCreateResponse
type swagCtrCreateResponse struct {
// in:body
Body struct {
- handlers.ContainerCreateResponse
+ utils.ContainerCreateResponse
}
}
diff --git a/pkg/api/handlers/libpod/containers_create.go b/pkg/api/handlers/libpod/containers_create.go
new file mode 100644
index 000000000..ebca41151
--- /dev/null
+++ b/pkg/api/handlers/libpod/containers_create.go
@@ -0,0 +1,29 @@
+package libpod
+
+import (
+ "encoding/json"
+ "net/http"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/containers/libpod/pkg/specgen"
+ "github.com/pkg/errors"
+)
+
+// CreateContainer takes a specgenerator and makes a container. It returns
+// the new container ID on success along with any warnings.
+func CreateContainer(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ var sg specgen.SpecGenerator
+ if err := json.NewDecoder(r.Body).Decode(&sg); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
+ return
+ }
+ ctr, err := sg.MakeContainer(runtime)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ response := utils.ContainerCreateResponse{ID: ctr.ID()}
+ utils.WriteJSON(w, http.StatusCreated, response)
+}
diff --git a/pkg/api/handlers/libpod/pods.go b/pkg/api/handlers/libpod/pods.go
index d043b1204..008b9b14b 100644
--- a/pkg/api/handlers/libpod/pods.go
+++ b/pkg/api/handlers/libpod/pods.go
@@ -99,12 +99,10 @@ func PodCreate(w http.ResponseWriter, r *http.Request) {
utils.Error(w, "Something went wrong.", http_code, err)
return
}
- utils.WriteResponse(w, http.StatusCreated, handlers.IDResponse{ID: pod.CgroupParent()})
+ utils.WriteResponse(w, http.StatusCreated, handlers.IDResponse{ID: pod.ID()})
}
func Pods(w http.ResponseWriter, r *http.Request) {
- // 200 ok
- // 500 internal
var (
runtime = r.Context().Value("runtime").(*libpod.Runtime)
podInspectData []*libpod.PodInspect
diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go
index a50f183f7..6268028f5 100644
--- a/pkg/api/handlers/types.go
+++ b/pkg/api/handlers/types.go
@@ -151,6 +151,7 @@ type ContainerTopOKBody struct {
dockerContainer.ContainerTopOKBody
}
+// swagger:model PodCreateConfig
type PodCreateConfig struct {
Name string `json:"name"`
CGroupParent string `json:"cgroup-parent"`
@@ -548,11 +549,3 @@ func portsToPortSet(input map[string]struct{}) (nat.PortSet, error) {
}
return ports, nil
}
-
-// ContainerCreateResponse is the response struct for creating a container
-type ContainerCreateResponse struct {
- // ID of the container created
- ID string `json:"id"`
- // Warnings during container creation
- Warnings []string `json:"Warnings"`
-}
diff --git a/pkg/api/handlers/utils/containers.go b/pkg/api/handlers/utils/containers.go
index c9bb9cf09..402005581 100644
--- a/pkg/api/handlers/utils/containers.go
+++ b/pkg/api/handlers/utils/containers.go
@@ -1,6 +1,7 @@
package utils
import (
+ "context"
"fmt"
"net/http"
"syscall"
@@ -9,10 +10,19 @@ import (
"github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
+ createconfig "github.com/containers/libpod/pkg/spec"
"github.com/gorilla/schema"
"github.com/pkg/errors"
)
+// ContainerCreateResponse is the response struct for creating a container
+type ContainerCreateResponse struct {
+ // ID of the container created
+ ID string `json:"id"`
+ // Warnings during container creation
+ Warnings []string `json:"Warnings"`
+}
+
func KillContainer(w http.ResponseWriter, r *http.Request) (*libpod.Container, error) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder)
@@ -119,3 +129,18 @@ func GenerateFilterFuncsFromMap(r *libpod.Runtime, filters map[string][]string)
}
return filterFuncs, nil
}
+
+func CreateContainer(ctx context.Context, w http.ResponseWriter, runtime *libpod.Runtime, cc *createconfig.CreateConfig) {
+ var pod *libpod.Pod
+ ctr, err := shared.CreateContainerFromCreateConfig(runtime, cc, ctx, pod)
+ if err != nil {
+ Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "CreateContainerFromCreateConfig()"))
+ return
+ }
+
+ response := ContainerCreateResponse{
+ ID: ctr.ID(),
+ Warnings: []string{}}
+
+ WriteResponse(w, http.StatusCreated, response)
+}
diff --git a/pkg/api/server/register_containers.go b/pkg/api/server/register_containers.go
index 06877863c..6007a2d00 100644
--- a/pkg/api/server/register_containers.go
+++ b/pkg/api/server/register_containers.go
@@ -33,7 +33,7 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
// $ref: "#/responses/ConflictError"
// 500:
// $ref: "#/responses/InternalError"
- r.HandleFunc(VersionedPath("/containers/create"), s.APIHandler(handlers.CreateContainer)).Methods(http.MethodPost)
+ r.HandleFunc(VersionedPath("/containers/create"), s.APIHandler(generic.CreateContainer)).Methods(http.MethodPost)
// swagger:operation GET /containers/json compat listContainers
// ---
// tags:
@@ -550,7 +550,31 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
libpod endpoints
*/
- r.HandleFunc(VersionedPath("/libpod/containers/create"), s.APIHandler(handlers.CreateContainer)).Methods(http.MethodPost)
+ // swagger:operation POST /containers/create libpod libpodContainerCreate
+ // ---
+ // summary: Create a container
+ // tags:
+ // - containers
+ // produces:
+ // - application/json
+ // parameters:
+ // - in: body
+ // name: create
+ // description: attributes for creating a container
+ // schema:
+ // $ref: "#/definitions/SpecGenerator"
+ // responses:
+ // 201:
+ // $ref: "#/responses/ContainerCreateResponse"
+ // 400:
+ // $ref: "#/responses/BadParamError"
+ // 404:
+ // $ref: "#/responses/NoSuchContainer"
+ // 409:
+ // $ref: "#/responses/ConflictError"
+ // 500:
+ // $ref: "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/libpod/containers/create"), s.APIHandler(libpod.CreateContainer)).Methods(http.MethodPost)
// swagger:operation GET /libpod/containers/json libpod libpodListContainers
// ---
// tags:
diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go
index 5b9ac7952..4c8f05385 100644
--- a/pkg/api/server/register_images.go
+++ b/pkg/api/server/register_images.go
@@ -822,7 +822,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// 500:
// $ref: '#/responses/InternalError'
r.Handle(VersionedPath("/libpod/images/search"), s.APIHandler(handlers.SearchImages)).Methods(http.MethodGet)
- // swagger:operation DELETE /libpod/images/{name} libpod libpodRemoveImage
+ // swagger:operation DELETE /libpod/images/{name:.*} libpod libpodRemoveImage
// ---
// tags:
// - images
@@ -830,7 +830,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// description: Delete an image from local store
// parameters:
// - in: path
- // name: name
+ // name: name:.*
// type: string
// required: true
// description: name or ID of image to delete
diff --git a/pkg/api/server/register_pods.go b/pkg/api/server/register_pods.go
index 1c7486711..af2330665 100644
--- a/pkg/api/server/register_pods.go
+++ b/pkg/api/server/register_pods.go
@@ -26,6 +26,25 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error {
// 500:
// $ref: "#/responses/InternalError"
r.Handle(VersionedPath("/libpod/pods/json"), s.APIHandler(libpod.Pods)).Methods(http.MethodGet)
+ // swagger:operation POST /libpod/pods/create pods CreatePod
+ // ---
+ // summary: Create a pod
+ // produces:
+ // - application/json
+ // parameters:
+ // - in: body
+ // name: create
+ // description: attributes for creating a pod
+ // schema:
+ // type: object
+ // $ref: "#/definitions/PodCreateConfig"
+ // responses:
+ // 200:
+ // $ref: "#/definitions/IdResponse"
+ // 400:
+ // $ref: "#/responses/BadParamError"
+ // 500:
+ // $ref: "#/responses/InternalError"
r.Handle(VersionedPath("/libpod/pods/create"), s.APIHandler(libpod.PodCreate)).Methods(http.MethodPost)
// swagger:operation POST /libpod/pods/prune pods PrunePods
// ---
diff --git a/pkg/bindings/containers/create.go b/pkg/bindings/containers/create.go
new file mode 100644
index 000000000..18b32335b
--- /dev/null
+++ b/pkg/bindings/containers/create.go
@@ -0,0 +1,30 @@
+package containers
+
+import (
+ "context"
+ "net/http"
+ "strings"
+
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/containers/libpod/pkg/bindings"
+ "github.com/containers/libpod/pkg/specgen"
+ jsoniter "github.com/json-iterator/go"
+)
+
+func CreateWithSpec(ctx context.Context, s specgen.SpecGenerator) (utils.ContainerCreateResponse, error) {
+ var ccr utils.ContainerCreateResponse
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return ccr, err
+ }
+ specgenString, err := jsoniter.MarshalToString(s)
+ if err != nil {
+ return ccr, nil
+ }
+ stringReader := strings.NewReader(specgenString)
+ response, err := conn.DoRequest(stringReader, http.MethodPost, "/containers/create", nil)
+ if err != nil {
+ return ccr, err
+ }
+ return ccr, response.Process(&ccr)
+}
diff --git a/pkg/seccomp/seccomp.go b/pkg/seccomp/seccomp.go
index dcf255378..eeba46a72 100644
--- a/pkg/seccomp/seccomp.go
+++ b/pkg/seccomp/seccomp.go
@@ -6,7 +6,7 @@ import (
"github.com/pkg/errors"
)
-// ContianerImageLabel is the key of the image annotation embedding a seccomp
+// ContainerImageLabel is the key of the image annotation embedding a seccomp
// profile.
const ContainerImageLabel = "io.containers.seccomp.profile"
diff --git a/pkg/spec/config_linux.go b/pkg/spec/config_linux.go
index 5f39b6d0d..544c0020d 100644
--- a/pkg/spec/config_linux.go
+++ b/pkg/spec/config_linux.go
@@ -32,8 +32,8 @@ func Device(d *configs.Device) spec.LinuxDevice {
}
}
-// devicesFromPath computes a list of devices
-func devicesFromPath(g *generate.Generator, devicePath string) error {
+// DevicesFromPath computes a list of devices
+func DevicesFromPath(g *generate.Generator, devicePath string) error {
devs := strings.Split(devicePath, ":")
resolvedDevicePath := devs[0]
// check if it is a symbolic link
@@ -216,7 +216,7 @@ func getDevices(path string) ([]*configs.Device, error) {
return out, nil
}
-func (c *CreateConfig) addPrivilegedDevices(g *generate.Generator) error {
+func addPrivilegedDevices(g *generate.Generator) error {
hostDevices, err := getDevices("/dev")
if err != nil {
return err
@@ -280,16 +280,16 @@ func (c *CreateConfig) createBlockIO() (*spec.LinuxBlockIO, error) {
var lwds []spec.LinuxWeightDevice
ret = bio
for _, i := range c.Resources.BlkioWeightDevice {
- wd, err := validateweightDevice(i)
+ wd, err := ValidateweightDevice(i)
if err != nil {
return ret, errors.Wrapf(err, "invalid values for blkio-weight-device")
}
- wdStat, err := getStatFromPath(wd.path)
+ wdStat, err := GetStatFromPath(wd.Path)
if err != nil {
- return ret, errors.Wrapf(err, "error getting stat from path %q", wd.path)
+ return ret, errors.Wrapf(err, "error getting stat from path %q", wd.Path)
}
lwd := spec.LinuxWeightDevice{
- Weight: &wd.weight,
+ Weight: &wd.Weight,
}
lwd.Major = int64(unix.Major(wdStat.Rdev))
lwd.Minor = int64(unix.Minor(wdStat.Rdev))
@@ -347,7 +347,7 @@ func makeThrottleArray(throttleInput []string, rateType int) ([]spec.LinuxThrott
if err != nil {
return []spec.LinuxThrottleDevice{}, err
}
- ltdStat, err := getStatFromPath(t.path)
+ ltdStat, err := GetStatFromPath(t.path)
if err != nil {
return ltds, errors.Wrapf(err, "error getting stat from path %q", t.path)
}
@@ -361,7 +361,7 @@ func makeThrottleArray(throttleInput []string, rateType int) ([]spec.LinuxThrott
return ltds, nil
}
-func getStatFromPath(path string) (unix.Stat_t, error) {
+func GetStatFromPath(path string) (unix.Stat_t, error) {
s := unix.Stat_t{}
err := unix.Stat(path, &s)
return s, err
diff --git a/pkg/spec/config_unsupported.go b/pkg/spec/config_unsupported.go
index be3e7046d..568afde55 100644
--- a/pkg/spec/config_unsupported.go
+++ b/pkg/spec/config_unsupported.go
@@ -15,7 +15,7 @@ func addDevice(g *generate.Generator, device string) error {
return errors.New("function not implemented")
}
-func (c *CreateConfig) addPrivilegedDevices(g *generate.Generator) error {
+func addPrivilegedDevices(g *generate.Generator) error {
return errors.New("function not implemented")
}
@@ -27,7 +27,7 @@ func makeThrottleArray(throttleInput []string, rateType int) ([]spec.LinuxThrott
return nil, errors.New("function not implemented")
}
-func devicesFromPath(g *generate.Generator, devicePath string) error {
+func DevicesFromPath(g *generate.Generator, devicePath string) error {
return errors.New("function not implemented")
}
diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go
index 8010be0d4..5011df496 100644
--- a/pkg/spec/createconfig.go
+++ b/pkg/spec/createconfig.go
@@ -126,6 +126,7 @@ type SecurityConfig struct {
}
// CreateConfig is a pre OCI spec structure. It represents user input from varlink or the CLI
+// swagger:model CreateConfig
type CreateConfig struct {
Annotations map[string]string
Args []string
@@ -386,6 +387,6 @@ func (c *CreateConfig) getContainerCreateOptions(runtime *libpod.Runtime, pod *l
// AddPrivilegedDevices iterates through host devices and adds all
// host devices to the spec
-func (c *CreateConfig) AddPrivilegedDevices(g *generate.Generator) error {
- return c.addPrivilegedDevices(g)
+func AddPrivilegedDevices(g *generate.Generator) error {
+ return addPrivilegedDevices(g)
}
diff --git a/pkg/spec/parse.go b/pkg/spec/parse.go
index a5dfccdb9..38d93b87f 100644
--- a/pkg/spec/parse.go
+++ b/pkg/spec/parse.go
@@ -19,12 +19,12 @@ const Pod = "pod"
// weightDevice is a structure that holds device:weight pair
type weightDevice struct {
- path string
- weight uint16
+ Path string
+ Weight uint16
}
func (w *weightDevice) String() string {
- return fmt.Sprintf("%s:%d", w.path, w.weight)
+ return fmt.Sprintf("%s:%d", w.Path, w.Weight)
}
// LinuxNS is a struct that contains namespace information
@@ -59,9 +59,9 @@ func NS(s string) string {
return ""
}
-// validateweightDevice validates that the specified string has a valid device-weight format
+// ValidateweightDevice validates that the specified string has a valid device-weight format
// for blkio-weight-device flag
-func validateweightDevice(val string) (*weightDevice, error) {
+func ValidateweightDevice(val string) (*weightDevice, error) {
split := strings.SplitN(val, ":", 2)
if len(split) != 2 {
return nil, fmt.Errorf("bad format: %s", val)
@@ -78,8 +78,8 @@ func validateweightDevice(val string) (*weightDevice, error) {
}
return &weightDevice{
- path: split[0],
- weight: uint16(weight),
+ Path: split[0],
+ Weight: uint16(weight),
}, nil
}
diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go
index 21b6bc3b3..a4ae22efd 100644
--- a/pkg/spec/spec.go
+++ b/pkg/spec/spec.go
@@ -16,9 +16,9 @@ import (
"github.com/pkg/errors"
)
-const cpuPeriod = 100000
+const CpuPeriod = 100000
-func getAvailableGids() (int64, error) {
+func GetAvailableGids() (int64, error) {
idMap, err := user.ParseIDMapFile("/proc/self/gid_map")
if err != nil {
return 0, err
@@ -80,7 +80,7 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM
}
gid5Available := true
if isRootless {
- nGids, err := getAvailableGids()
+ nGids, err := GetAvailableGids()
if err != nil {
return nil, err
}
@@ -197,8 +197,8 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM
addedResources = true
}
if config.Resources.CPUs != 0 {
- g.SetLinuxResourcesCPUPeriod(cpuPeriod)
- g.SetLinuxResourcesCPUQuota(int64(config.Resources.CPUs * cpuPeriod))
+ g.SetLinuxResourcesCPUPeriod(CpuPeriod)
+ g.SetLinuxResourcesCPUQuota(int64(config.Resources.CPUs * CpuPeriod))
addedResources = true
}
if config.Resources.CPURtRuntime != 0 {
@@ -223,12 +223,12 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM
// If privileged, we need to add all the host devices to the
// spec. We do not add the user provided ones because we are
// already adding them all.
- if err := config.AddPrivilegedDevices(&g); err != nil {
+ if err := AddPrivilegedDevices(&g); err != nil {
return nil, err
}
} else {
for _, devicePath := range config.Devices {
- if err := devicesFromPath(&g, devicePath); err != nil {
+ if err := DevicesFromPath(&g, devicePath); err != nil {
return nil, err
}
}
@@ -268,7 +268,7 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM
}
}
- blockAccessToKernelFilesystems(config, &g)
+ BlockAccessToKernelFilesystems(config.Security.Privileged, config.Pid.PidMode.IsHost(), &g)
// RESOURCES - PIDS
if config.Resources.PidsLimit > 0 {
@@ -332,9 +332,9 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM
}
// BIND MOUNTS
- configSpec.Mounts = supercedeUserMounts(userMounts, configSpec.Mounts)
+ configSpec.Mounts = SupercedeUserMounts(userMounts, configSpec.Mounts)
// Process mounts to ensure correct options
- finalMounts, err := initFSMounts(configSpec.Mounts)
+ finalMounts, err := InitFSMounts(configSpec.Mounts)
if err != nil {
return nil, err
}
@@ -416,8 +416,8 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM
return configSpec, nil
}
-func blockAccessToKernelFilesystems(config *CreateConfig, g *generate.Generator) {
- if !config.Security.Privileged {
+func BlockAccessToKernelFilesystems(privileged, pidModeIsHost bool, g *generate.Generator) {
+ if !privileged {
for _, mp := range []string{
"/proc/acpi",
"/proc/kcore",
@@ -433,7 +433,7 @@ func blockAccessToKernelFilesystems(config *CreateConfig, g *generate.Generator)
g.AddLinuxMaskedPaths(mp)
}
- if config.Pid.PidMode.IsHost() && rootless.IsRootless() {
+ if pidModeIsHost && rootless.IsRootless() {
return
}
diff --git a/pkg/spec/storage.go b/pkg/spec/storage.go
index 0e2098c1d..e37fa2451 100644
--- a/pkg/spec/storage.go
+++ b/pkg/spec/storage.go
@@ -825,7 +825,7 @@ func (config *CreateConfig) addContainerInitBinary(path string) (spec.Mount, err
// TODO: Should we unmount subtree mounts? E.g., if /tmp/ is mounted by
// one mount, and we already have /tmp/a and /tmp/b, should we remove
// the /tmp/a and /tmp/b mounts in favor of the more general /tmp?
-func supercedeUserMounts(mounts []spec.Mount, configMount []spec.Mount) []spec.Mount {
+func SupercedeUserMounts(mounts []spec.Mount, configMount []spec.Mount) []spec.Mount {
if len(mounts) > 0 {
// If we have overlappings mounts, remove them from the spec in favor of
// the user-added volume mounts
@@ -854,7 +854,7 @@ func supercedeUserMounts(mounts []spec.Mount, configMount []spec.Mount) []spec.M
}
// Ensure mount options on all mounts are correct
-func initFSMounts(inputMounts []spec.Mount) ([]spec.Mount, error) {
+func InitFSMounts(inputMounts []spec.Mount) ([]spec.Mount, error) {
// We need to look up mounts so we can figure out the proper mount flags
// to apply.
systemMounts, err := pmount.GetMounts()
diff --git a/pkg/specgen/config_linux_cgo.go b/pkg/specgen/config_linux_cgo.go
new file mode 100644
index 000000000..6f547a40d
--- /dev/null
+++ b/pkg/specgen/config_linux_cgo.go
@@ -0,0 +1,62 @@
+// +build linux,cgo
+
+package specgen
+
+import (
+ "context"
+ "io/ioutil"
+
+ "github.com/containers/libpod/libpod/image"
+ "github.com/containers/libpod/pkg/seccomp"
+ spec "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/pkg/errors"
+ goSeccomp "github.com/seccomp/containers-golang"
+ "github.com/sirupsen/logrus"
+)
+
+func (s *SpecGenerator) getSeccompConfig(configSpec *spec.Spec, img *image.Image) (*spec.LinuxSeccomp, error) {
+ var seccompConfig *spec.LinuxSeccomp
+ var err error
+
+ scp, err := seccomp.LookupPolicy(s.SeccompPolicy)
+ if err != nil {
+ return nil, err
+ }
+
+ if scp == seccomp.PolicyImage {
+ labels, err := img.Labels(context.Background())
+ if err != nil {
+ return nil, err
+ }
+ imagePolicy := labels[seccomp.ContainerImageLabel]
+ if len(imagePolicy) < 1 {
+ return nil, errors.New("no seccomp policy defined by image")
+ }
+ logrus.Debug("Loading seccomp profile from the security config")
+ seccompConfig, err = goSeccomp.LoadProfile(imagePolicy, configSpec)
+ if err != nil {
+ return nil, errors.Wrap(err, "loading seccomp profile failed")
+ }
+ return seccompConfig, nil
+ }
+
+ if s.SeccompProfilePath != "" {
+ logrus.Debugf("Loading seccomp profile from %q", s.SeccompProfilePath)
+ seccompProfile, err := ioutil.ReadFile(s.SeccompProfilePath)
+ if err != nil {
+ return nil, errors.Wrapf(err, "opening seccomp profile (%s) failed", s.SeccompProfilePath)
+ }
+ seccompConfig, err = goSeccomp.LoadProfile(string(seccompProfile), configSpec)
+ if err != nil {
+ return nil, errors.Wrapf(err, "loading seccomp profile (%s) failed", s.SeccompProfilePath)
+ }
+ } else {
+ logrus.Debug("Loading default seccomp profile")
+ seccompConfig, err = goSeccomp.GetDefaultProfile(configSpec)
+ if err != nil {
+ return nil, errors.Wrapf(err, "loading seccomp profile (%s) failed", s.SeccompProfilePath)
+ }
+ }
+
+ return seccompConfig, nil
+}
diff --git a/pkg/specgen/config_linux_nocgo.go b/pkg/specgen/config_linux_nocgo.go
new file mode 100644
index 000000000..fc0c58c37
--- /dev/null
+++ b/pkg/specgen/config_linux_nocgo.go
@@ -0,0 +1,11 @@
+// +build linux,!cgo
+
+package specgen
+
+import (
+ spec "github.com/opencontainers/runtime-spec/specs-go"
+)
+
+func (s *SpecGenerator) getSeccompConfig(configSpec *spec.Spec) (*spec.LinuxSeccomp, error) {
+ return nil, nil
+}
diff --git a/pkg/specgen/config_unsupported.go b/pkg/specgen/config_unsupported.go
new file mode 100644
index 000000000..5d24ac39c
--- /dev/null
+++ b/pkg/specgen/config_unsupported.go
@@ -0,0 +1,12 @@
+// +build !linux
+
+package specgen
+
+import (
+ spec "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/pkg/errors"
+)
+
+func (s *SpecGenerator) getSeccompConfig(configSpec *spec.Spec) (*spec.LinuxSeccomp, error) {
+ return nil, errors.New("function not supported on non-linux OS's")
+}
diff --git a/pkg/specgen/create.go b/pkg/specgen/create.go
new file mode 100644
index 000000000..c8fee5f05
--- /dev/null
+++ b/pkg/specgen/create.go
@@ -0,0 +1,187 @@
+package specgen
+
+import (
+ "context"
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/config"
+ "github.com/containers/libpod/libpod/define"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "os"
+)
+
+// MakeContainer creates a container based on the SpecGenerator
+func (s *SpecGenerator) MakeContainer(rt *libpod.Runtime) (*libpod.Container, error) {
+ var pod *libpod.Pod
+ if err := s.validate(rt); err != nil {
+ return nil, errors.Wrap(err, "invalid config provided")
+ }
+ rtc, err := rt.GetConfig()
+ if err != nil {
+ return nil, err
+ }
+
+ options, err := s.createContainerOptions(rt, pod)
+ if err != nil {
+ return nil, err
+ }
+
+ podmanPath, err := os.Executable()
+ if err != nil {
+ return nil, err
+ }
+ options = append(options, s.createExitCommandOption(rtc, podmanPath))
+ newImage, err := rt.ImageRuntime().NewFromLocal(s.Image)
+ if err != nil {
+ return nil, err
+ }
+
+ // TODO mheon wants to talk with Dan about this
+ useImageVolumes := s.ImageVolumeMode == "bind"
+ options = append(options, libpod.WithRootFSFromImage(newImage.ID(), s.Image, useImageVolumes))
+
+ runtimeSpec, err := s.toOCISpec(rt, newImage)
+ if err != nil {
+ return nil, err
+ }
+ return rt.NewContainer(context.Background(), runtimeSpec, options...)
+}
+
+func (s *SpecGenerator) createContainerOptions(rt *libpod.Runtime, pod *libpod.Pod) ([]libpod.CtrCreateOption, error) {
+ var options []libpod.CtrCreateOption
+ var err error
+
+ if s.Stdin {
+ options = append(options, libpod.WithStdin())
+ }
+ if len(s.Systemd) > 0 {
+ options = append(options, libpod.WithSystemd())
+ }
+ if len(s.Name) > 0 {
+ logrus.Debugf("setting container name %s", s.Name)
+ options = append(options, libpod.WithName(s.Name))
+ }
+ if s.Pod != "" {
+ logrus.Debugf("adding container to pod %s", s.Pod)
+ options = append(options, rt.WithPod(pod))
+ }
+ destinations := []string{}
+ // // Take all mount and named volume destinations.
+ for _, mount := range s.Mounts {
+ destinations = append(destinations, mount.Destination)
+ }
+ for _, volume := range s.Volumes {
+ destinations = append(destinations, volume.Dest)
+ }
+ options = append(options, libpod.WithUserVolumes(destinations))
+
+ if len(s.Volumes) != 0 {
+ options = append(options, libpod.WithNamedVolumes(s.Volumes))
+ }
+
+ if len(s.Command) != 0 {
+ options = append(options, libpod.WithCommand(s.Command))
+ }
+
+ options = append(options, libpod.WithEntrypoint(s.Entrypoint))
+ if s.StopSignal != nil {
+ options = append(options, libpod.WithStopSignal(*s.StopSignal))
+ }
+ if s.StopTimeout != nil {
+ options = append(options, libpod.WithStopTimeout(*s.StopTimeout))
+ }
+ if s.LogConfiguration != nil {
+ if len(s.LogConfiguration.Path) > 0 {
+ options = append(options, libpod.WithLogPath(s.LogConfiguration.Path))
+ }
+ if len(s.LogConfiguration.Options) > 0 && s.LogConfiguration.Options["tag"] != "" {
+ // Note: I'm really guessing here.
+ options = append(options, libpod.WithLogTag(s.LogConfiguration.Options["tag"]))
+ }
+
+ if len(s.LogConfiguration.Driver) > 0 {
+ options = append(options, libpod.WithLogDriver(s.LogConfiguration.Driver))
+ }
+ }
+
+ // Security options
+ if len(s.SelinuxOpts) > 0 {
+ options = append(options, libpod.WithSecLabels(s.SelinuxOpts))
+ }
+ options = append(options, libpod.WithPrivileged(s.Privileged))
+
+ // Get namespace related options
+ namespaceOptions, err := s.generateNamespaceContainerOpts(rt)
+ if err != nil {
+ return nil, err
+ }
+ options = append(options, namespaceOptions...)
+
+ // TODO NetworkNS still needs to be done!
+ if len(s.ConmonPidFile) > 0 {
+ options = append(options, libpod.WithConmonPidFile(s.ConmonPidFile))
+ }
+ options = append(options, libpod.WithLabels(s.Labels))
+ if s.ShmSize != nil {
+ options = append(options, libpod.WithShmSize(*s.ShmSize))
+ }
+ if s.Rootfs != "" {
+ options = append(options, libpod.WithRootFS(s.Rootfs))
+ }
+ // Default used if not overridden on command line
+
+ if s.RestartPolicy != "" {
+ if s.RestartPolicy == "unless-stopped" {
+ return nil, errors.Wrapf(define.ErrInvalidArg, "the unless-stopped restart policy is not supported")
+ }
+ if s.RestartRetries != nil {
+ options = append(options, libpod.WithRestartRetries(*s.RestartRetries))
+ }
+ options = append(options, libpod.WithRestartPolicy(s.RestartPolicy))
+ }
+
+ if s.ContainerHealthCheckConfig.HealthConfig != nil {
+ options = append(options, libpod.WithHealthCheck(s.ContainerHealthCheckConfig.HealthConfig))
+ logrus.Debugf("New container has a health check")
+ }
+ return options, nil
+}
+
+func (s *SpecGenerator) createExitCommandOption(config *config.Config, podmanPath string) libpod.CtrCreateOption {
+ // We need a cleanup process for containers in the current model.
+ // But we can't assume that the caller is Podman - it could be another
+ // user of the API.
+ // As such, provide a way to specify a path to Podman, so we can
+ // still invoke a cleanup process.
+
+ command := []string{podmanPath,
+ "--root", config.StorageConfig.GraphRoot,
+ "--runroot", config.StorageConfig.RunRoot,
+ "--log-level", logrus.GetLevel().String(),
+ "--cgroup-manager", config.CgroupManager,
+ "--tmpdir", config.TmpDir,
+ }
+ if config.OCIRuntime != "" {
+ command = append(command, []string{"--runtime", config.OCIRuntime}...)
+ }
+ if config.StorageConfig.GraphDriverName != "" {
+ command = append(command, []string{"--storage-driver", config.StorageConfig.GraphDriverName}...)
+ }
+ for _, opt := range config.StorageConfig.GraphDriverOptions {
+ command = append(command, []string{"--storage-opt", opt}...)
+ }
+ if config.EventsLogger != "" {
+ command = append(command, []string{"--events-backend", config.EventsLogger}...)
+ }
+
+ // TODO Mheon wants to leave this for now
+ //if s.sys {
+ // command = append(command, "--syslog", "true")
+ //}
+ command = append(command, []string{"container", "cleanup"}...)
+
+ if s.Remove {
+ command = append(command, "--rm")
+ }
+ return libpod.WithExitCommand(command)
+}
diff --git a/pkg/specgen/namespaces.go b/pkg/specgen/namespaces.go
new file mode 100644
index 000000000..025cb31e0
--- /dev/null
+++ b/pkg/specgen/namespaces.go
@@ -0,0 +1,467 @@
+package specgen
+
+import (
+ "os"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/image"
+ "github.com/containers/libpod/pkg/capabilities"
+ "github.com/cri-o/ocicni/pkg/ocicni"
+ spec "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/opencontainers/runtime-tools/generate"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+type NamespaceMode string
+
+const (
+ // Host means the the namespace is derived from
+ // the host
+ Host NamespaceMode = "host"
+ // Path is the path to a namespace
+ Path NamespaceMode = "path"
+ // FromContainer means namespace is derived from a
+ // different container
+ FromContainer NamespaceMode = "container"
+ // FromPod indicates the namespace is derived from a pod
+ FromPod NamespaceMode = "pod"
+ // Private indicates the namespace is private
+ Private NamespaceMode = "private"
+ // NoNetwork indicates no network namespace should
+ // be joined. loopback should still exists
+ NoNetwork NamespaceMode = "none"
+ // Bridge indicates that a CNI network stack
+ // should be used
+ Bridge NamespaceMode = "bridge"
+ // Slirp indicates that a slirp4ns network stack should
+ // be used
+ Slirp NamespaceMode = "slirp4ns"
+)
+
+// Namespace describes the namespace
+type Namespace struct {
+ NSMode NamespaceMode `json:"nsmode,omitempty"`
+ Value string `json:"string,omitempty"`
+}
+
+// IsHost returns a bool if the namespace is host based
+func (n *Namespace) IsHost() bool {
+ return n.NSMode == Host
+}
+
+// IsPath indicates via bool if the namespace is based on a path
+func (n *Namespace) IsPath() bool {
+ return n.NSMode == Path
+}
+
+// IsContainer indicates via bool if the namespace is based on a container
+func (n *Namespace) IsContainer() bool {
+ return n.NSMode == FromContainer
+}
+
+// IsPod indicates via bool if the namespace is based on a pod
+func (n *Namespace) IsPod() bool {
+ return n.NSMode == FromPod
+}
+
+// IsPrivate indicates the namespace is private
+func (n *Namespace) IsPrivate() bool {
+ return n.NSMode == Private
+}
+
+// validate perform simple validation on the namespace to make sure it is not
+// invalid from the get-go
+func (n *Namespace) validate() error {
+ if n == nil {
+ return nil
+ }
+ switch n.NSMode {
+ case Host, Path, FromContainer, FromPod, Private, NoNetwork, Bridge, Slirp:
+ break
+ default:
+ return errors.Errorf("invalid network %q", n.NSMode)
+ }
+ // Path and From Container MUST have a string value set
+ if n.NSMode == Path || n.NSMode == FromContainer {
+ if len(n.Value) < 1 {
+ return errors.Errorf("namespace mode %s requires a value", n.NSMode)
+ }
+ } else {
+ // All others must NOT set a string value
+ if len(n.Value) > 0 {
+ return errors.Errorf("namespace value %s cannot be provided with namespace mode %s", n.Value, n.NSMode)
+ }
+ }
+ return nil
+}
+
+func (s *SpecGenerator) generateNamespaceContainerOpts(rt *libpod.Runtime) ([]libpod.CtrCreateOption, error) {
+ var portBindings []ocicni.PortMapping
+ options := make([]libpod.CtrCreateOption, 0)
+
+ // Cgroups
+ switch {
+ case s.CgroupNS.IsPrivate():
+ ns := s.CgroupNS.Value
+ if _, err := os.Stat(ns); err != nil {
+ return nil, err
+ }
+ case s.CgroupNS.IsContainer():
+ connectedCtr, err := rt.LookupContainer(s.CgroupNS.Value)
+ if err != nil {
+ return nil, errors.Wrapf(err, "container %q not found", s.CgroupNS.Value)
+ }
+ options = append(options, libpod.WithCgroupNSFrom(connectedCtr))
+ // TODO
+ //default:
+ // return nil, errors.New("cgroup name only supports private and container")
+ }
+
+ if s.CgroupParent != "" {
+ options = append(options, libpod.WithCgroupParent(s.CgroupParent))
+ }
+
+ if s.CgroupsMode != "" {
+ options = append(options, libpod.WithCgroupsMode(s.CgroupsMode))
+ }
+
+ // ipc
+ switch {
+ case s.IpcNS.IsHost():
+ options = append(options, libpod.WithShmDir("/dev/shm"))
+ case s.IpcNS.IsContainer():
+ connectedCtr, err := rt.LookupContainer(s.IpcNS.Value)
+ if err != nil {
+ return nil, errors.Wrapf(err, "container %q not found", s.IpcNS.Value)
+ }
+ options = append(options, libpod.WithIPCNSFrom(connectedCtr))
+ options = append(options, libpod.WithShmDir(connectedCtr.ShmDir()))
+ }
+
+ // pid
+ if s.PidNS.IsContainer() {
+ connectedCtr, err := rt.LookupContainer(s.PidNS.Value)
+ if err != nil {
+ return nil, errors.Wrapf(err, "container %q not found", s.PidNS.Value)
+ }
+ options = append(options, libpod.WithPIDNSFrom(connectedCtr))
+ }
+
+ // uts
+ switch {
+ case s.UtsNS.IsPod():
+ connectedPod, err := rt.LookupPod(s.UtsNS.Value)
+ if err != nil {
+ return nil, errors.Wrapf(err, "pod %q not found", s.UtsNS.Value)
+ }
+ options = append(options, libpod.WithUTSNSFromPod(connectedPod))
+ case s.UtsNS.IsContainer():
+ connectedCtr, err := rt.LookupContainer(s.UtsNS.Value)
+ if err != nil {
+ return nil, errors.Wrapf(err, "container %q not found", s.UtsNS.Value)
+ }
+
+ options = append(options, libpod.WithUTSNSFrom(connectedCtr))
+ }
+
+ if s.UseImageHosts {
+ options = append(options, libpod.WithUseImageHosts())
+ } else if len(s.HostAdd) > 0 {
+ options = append(options, libpod.WithHosts(s.HostAdd))
+ }
+
+ // User
+
+ switch {
+ case s.UserNS.IsPath():
+ ns := s.UserNS.Value
+ if ns == "" {
+ return nil, errors.Errorf("invalid empty user-defined user namespace")
+ }
+ _, err := os.Stat(ns)
+ if err != nil {
+ return nil, err
+ }
+ if s.IDMappings != nil {
+ options = append(options, libpod.WithIDMappings(*s.IDMappings))
+ }
+ case s.UserNS.IsContainer():
+ connectedCtr, err := rt.LookupContainer(s.UserNS.Value)
+ if err != nil {
+ return nil, errors.Wrapf(err, "container %q not found", s.UserNS.Value)
+ }
+ options = append(options, libpod.WithUserNSFrom(connectedCtr))
+ default:
+ if s.IDMappings != nil {
+ options = append(options, libpod.WithIDMappings(*s.IDMappings))
+ }
+ }
+
+ options = append(options, libpod.WithUser(s.User))
+ options = append(options, libpod.WithGroups(s.Groups))
+
+ if len(s.PortMappings) > 0 {
+ portBindings = s.PortMappings
+ }
+
+ switch {
+ case s.NetNS.IsPath():
+ ns := s.NetNS.Value
+ if ns == "" {
+ return nil, errors.Errorf("invalid empty user-defined network namespace")
+ }
+ _, err := os.Stat(ns)
+ if err != nil {
+ return nil, err
+ }
+ case s.NetNS.IsContainer():
+ connectedCtr, err := rt.LookupContainer(s.NetNS.Value)
+ if err != nil {
+ return nil, errors.Wrapf(err, "container %q not found", s.NetNS.Value)
+ }
+ options = append(options, libpod.WithNetNSFrom(connectedCtr))
+ case !s.NetNS.IsHost() && s.NetNS.NSMode != NoNetwork:
+ postConfigureNetNS := !s.UserNS.IsHost()
+ options = append(options, libpod.WithNetNS(portBindings, postConfigureNetNS, string(s.NetNS.NSMode), s.CNINetworks))
+ }
+
+ if len(s.DNSSearch) > 0 {
+ options = append(options, libpod.WithDNSSearch(s.DNSSearch))
+ }
+ if len(s.DNSServer) > 0 {
+ // TODO I'm not sure how we are going to handle this given the input
+ if len(s.DNSServer) == 1 { //&& strings.ToLower(s.DNSServer[0].) == "none" {
+ options = append(options, libpod.WithUseImageResolvConf())
+ } else {
+ var dnsServers []string
+ for _, d := range s.DNSServer {
+ dnsServers = append(dnsServers, d.String())
+ }
+ options = append(options, libpod.WithDNS(dnsServers))
+ }
+ }
+ if len(s.DNSOption) > 0 {
+ options = append(options, libpod.WithDNSOption(s.DNSOption))
+ }
+ if s.StaticIP != nil {
+ options = append(options, libpod.WithStaticIP(*s.StaticIP))
+ }
+
+ if s.StaticMAC != nil {
+ options = append(options, libpod.WithStaticMAC(*s.StaticMAC))
+ }
+ return options, nil
+}
+
+func (s *SpecGenerator) pidConfigureGenerator(g *generate.Generator) error {
+ if s.PidNS.IsPath() {
+ return g.AddOrReplaceLinuxNamespace(string(spec.PIDNamespace), s.PidNS.Value)
+ }
+ if s.PidNS.IsHost() {
+ return g.RemoveLinuxNamespace(string(spec.PIDNamespace))
+ }
+ if s.PidNS.IsContainer() {
+ logrus.Debugf("using container %s pidmode", s.PidNS.Value)
+ }
+ if s.PidNS.IsPod() {
+ logrus.Debug("using pod pidmode")
+ }
+ return nil
+}
+
+func (s *SpecGenerator) utsConfigureGenerator(g *generate.Generator, runtime *libpod.Runtime) error {
+ hostname := s.Hostname
+ var err error
+ if hostname == "" {
+ switch {
+ case s.UtsNS.IsContainer():
+ utsCtr, err := runtime.GetContainer(s.UtsNS.Value)
+ if err != nil {
+ return errors.Wrapf(err, "unable to retrieve hostname from dependency container %s", s.UtsNS.Value)
+ }
+ hostname = utsCtr.Hostname()
+ case s.NetNS.IsHost() || s.UtsNS.IsHost():
+ hostname, err = os.Hostname()
+ if err != nil {
+ return errors.Wrap(err, "unable to retrieve hostname of the host")
+ }
+ default:
+ logrus.Debug("No hostname set; container's hostname will default to runtime default")
+ }
+ }
+ g.RemoveHostname()
+ if s.Hostname != "" || !s.UtsNS.IsHost() {
+ // Set the hostname in the OCI configuration only
+ // if specified by the user or if we are creating
+ // a new UTS namespace.
+ g.SetHostname(hostname)
+ }
+ g.AddProcessEnv("HOSTNAME", hostname)
+
+ if s.UtsNS.IsPath() {
+ return g.AddOrReplaceLinuxNamespace(string(spec.UTSNamespace), s.UtsNS.Value)
+ }
+ if s.UtsNS.IsHost() {
+ return g.RemoveLinuxNamespace(string(spec.UTSNamespace))
+ }
+ if s.UtsNS.IsContainer() {
+ logrus.Debugf("using container %s utsmode", s.UtsNS.Value)
+ }
+ return nil
+}
+
+func (s *SpecGenerator) ipcConfigureGenerator(g *generate.Generator) error {
+ if s.IpcNS.IsPath() {
+ return g.AddOrReplaceLinuxNamespace(string(spec.IPCNamespace), s.IpcNS.Value)
+ }
+ if s.IpcNS.IsHost() {
+ return g.RemoveLinuxNamespace(s.IpcNS.Value)
+ }
+ if s.IpcNS.IsContainer() {
+ logrus.Debugf("Using container %s ipcmode", s.IpcNS.Value)
+ }
+ return nil
+}
+
+func (s *SpecGenerator) cgroupConfigureGenerator(g *generate.Generator) error {
+ if s.CgroupNS.IsPath() {
+ return g.AddOrReplaceLinuxNamespace(string(spec.CgroupNamespace), s.CgroupNS.Value)
+ }
+ if s.CgroupNS.IsHost() {
+ return g.RemoveLinuxNamespace(s.CgroupNS.Value)
+ }
+ if s.CgroupNS.IsPrivate() {
+ return g.AddOrReplaceLinuxNamespace(string(spec.CgroupNamespace), "")
+ }
+ if s.CgroupNS.IsContainer() {
+ logrus.Debugf("Using container %s cgroup mode", s.CgroupNS.Value)
+ }
+ return nil
+}
+
+func (s *SpecGenerator) networkConfigureGenerator(g *generate.Generator) error {
+ switch {
+ case s.NetNS.IsHost():
+ logrus.Debug("Using host netmode")
+ if err := g.RemoveLinuxNamespace(string(spec.NetworkNamespace)); err != nil {
+ return err
+ }
+
+ case s.NetNS.NSMode == NoNetwork:
+ logrus.Debug("Using none netmode")
+ case s.NetNS.NSMode == Bridge:
+ logrus.Debug("Using bridge netmode")
+ case s.NetNS.IsContainer():
+ logrus.Debugf("using container %s netmode", s.NetNS.Value)
+ case s.NetNS.IsPath():
+ logrus.Debug("Using ns netmode")
+ if err := g.AddOrReplaceLinuxNamespace(string(spec.NetworkNamespace), s.NetNS.Value); err != nil {
+ return err
+ }
+ case s.NetNS.IsPod():
+ logrus.Debug("Using pod netmode, unless pod is not sharing")
+ case s.NetNS.NSMode == Slirp:
+ logrus.Debug("Using slirp4netns netmode")
+ default:
+ return errors.Errorf("unknown network mode")
+ }
+
+ if g.Config.Annotations == nil {
+ g.Config.Annotations = make(map[string]string)
+ }
+
+ if s.PublishImagePorts {
+ g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseTrue
+ } else {
+ g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseFalse
+ }
+
+ return nil
+}
+
+func (s *SpecGenerator) userConfigureGenerator(g *generate.Generator) error {
+ if s.UserNS.IsPath() {
+ if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), s.UserNS.Value); err != nil {
+ return err
+ }
+ // runc complains if no mapping is specified, even if we join another ns. So provide a dummy mapping
+ g.AddLinuxUIDMapping(uint32(0), uint32(0), uint32(1))
+ g.AddLinuxGIDMapping(uint32(0), uint32(0), uint32(1))
+ }
+
+ if s.IDMappings != nil {
+ if (len(s.IDMappings.UIDMap) > 0 || len(s.IDMappings.GIDMap) > 0) && !s.UserNS.IsHost() {
+ if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), ""); err != nil {
+ return err
+ }
+ }
+ for _, uidmap := range s.IDMappings.UIDMap {
+ g.AddLinuxUIDMapping(uint32(uidmap.HostID), uint32(uidmap.ContainerID), uint32(uidmap.Size))
+ }
+ for _, gidmap := range s.IDMappings.GIDMap {
+ g.AddLinuxGIDMapping(uint32(gidmap.HostID), uint32(gidmap.ContainerID), uint32(gidmap.Size))
+ }
+ }
+ return nil
+}
+
+func (s *SpecGenerator) securityConfigureGenerator(g *generate.Generator, newImage *image.Image) error {
+ // HANDLE CAPABILITIES
+ // NOTE: Must happen before SECCOMP
+ if s.Privileged {
+ g.SetupPrivileged(true)
+ }
+
+ useNotRoot := func(user string) bool {
+ if user == "" || user == "root" || user == "0" {
+ return false
+ }
+ return true
+ }
+ configSpec := g.Config
+ var err error
+ var caplist []string
+ bounding := configSpec.Process.Capabilities.Bounding
+ if useNotRoot(s.User) {
+ configSpec.Process.Capabilities.Bounding = caplist
+ }
+ caplist, err = capabilities.MergeCapabilities(configSpec.Process.Capabilities.Bounding, s.CapAdd, s.CapDrop)
+ if err != nil {
+ return err
+ }
+
+ configSpec.Process.Capabilities.Bounding = caplist
+ configSpec.Process.Capabilities.Permitted = caplist
+ configSpec.Process.Capabilities.Inheritable = caplist
+ configSpec.Process.Capabilities.Effective = caplist
+ configSpec.Process.Capabilities.Ambient = caplist
+ if useNotRoot(s.User) {
+ caplist, err = capabilities.MergeCapabilities(bounding, s.CapAdd, s.CapDrop)
+ if err != nil {
+ return err
+ }
+ }
+ configSpec.Process.Capabilities.Bounding = caplist
+
+ // HANDLE SECCOMP
+ if s.SeccompProfilePath != "unconfined" {
+ seccompConfig, err := s.getSeccompConfig(configSpec, newImage)
+ if err != nil {
+ return err
+ }
+ configSpec.Linux.Seccomp = seccompConfig
+ }
+
+ // Clear default Seccomp profile from Generator for privileged containers
+ if s.SeccompProfilePath == "unconfined" || s.Privileged {
+ configSpec.Linux.Seccomp = nil
+ }
+
+ g.SetRootReadonly(s.ReadOnlyFilesystem)
+ for sysctlKey, sysctlVal := range s.Sysctl {
+ g.AddLinuxSysctl(sysctlKey, sysctlVal)
+ }
+
+ return nil
+}
diff --git a/pkg/specgen/oci.go b/pkg/specgen/oci.go
new file mode 100644
index 000000000..2523f21b3
--- /dev/null
+++ b/pkg/specgen/oci.go
@@ -0,0 +1,260 @@
+package specgen
+
+import (
+ "strings"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/image"
+ "github.com/containers/libpod/pkg/rootless"
+ createconfig "github.com/containers/libpod/pkg/spec"
+ spec "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/opencontainers/runtime-tools/generate"
+)
+
+func (s *SpecGenerator) toOCISpec(rt *libpod.Runtime, newImage *image.Image) (*spec.Spec, error) {
+ var (
+ inUserNS bool
+ )
+ cgroupPerm := "ro"
+ g, err := generate.New("linux")
+ if err != nil {
+ return nil, err
+ }
+ // Remove the default /dev/shm mount to ensure we overwrite it
+ g.RemoveMount("/dev/shm")
+ g.HostSpecific = true
+ addCgroup := true
+ canMountSys := true
+
+ isRootless := rootless.IsRootless()
+ if isRootless {
+ inUserNS = true
+ }
+ if !s.UserNS.IsHost() {
+ if s.UserNS.IsContainer() || s.UserNS.IsPath() {
+ inUserNS = true
+ }
+ if s.UserNS.IsPrivate() {
+ inUserNS = true
+ }
+ }
+ if inUserNS && s.NetNS.IsHost() {
+ canMountSys = false
+ }
+
+ if s.Privileged && canMountSys {
+ cgroupPerm = "rw"
+ g.RemoveMount("/sys")
+ sysMnt := spec.Mount{
+ Destination: "/sys",
+ Type: "sysfs",
+ Source: "sysfs",
+ Options: []string{"rprivate", "nosuid", "noexec", "nodev", "rw"},
+ }
+ g.AddMount(sysMnt)
+ } else if !canMountSys {
+ addCgroup = false
+ g.RemoveMount("/sys")
+ r := "ro"
+ if s.Privileged {
+ r = "rw"
+ }
+ sysMnt := spec.Mount{
+ Destination: "/sys",
+ Type: "bind", // should we use a constant for this, like createconfig?
+ Source: "/sys",
+ Options: []string{"rprivate", "nosuid", "noexec", "nodev", r, "rbind"},
+ }
+ g.AddMount(sysMnt)
+ if !s.Privileged && isRootless {
+ g.AddLinuxMaskedPaths("/sys/kernel")
+ }
+ }
+ gid5Available := true
+ if isRootless {
+ nGids, err := createconfig.GetAvailableGids()
+ if err != nil {
+ return nil, err
+ }
+ gid5Available = nGids >= 5
+ }
+ // When using a different user namespace, check that the GID 5 is mapped inside
+ // the container.
+ if gid5Available && (s.IDMappings != nil && len(s.IDMappings.GIDMap) > 0) {
+ mappingFound := false
+ for _, r := range s.IDMappings.GIDMap {
+ if r.ContainerID <= 5 && 5 < r.ContainerID+r.Size {
+ mappingFound = true
+ break
+ }
+ }
+ if !mappingFound {
+ gid5Available = false
+ }
+
+ }
+ if !gid5Available {
+ // If we have no GID mappings, the gid=5 default option would fail, so drop it.
+ g.RemoveMount("/dev/pts")
+ devPts := spec.Mount{
+ Destination: "/dev/pts",
+ Type: "devpts",
+ Source: "devpts",
+ Options: []string{"rprivate", "nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620"},
+ }
+ g.AddMount(devPts)
+ }
+
+ if inUserNS && s.IpcNS.IsHost() {
+ g.RemoveMount("/dev/mqueue")
+ devMqueue := spec.Mount{
+ Destination: "/dev/mqueue",
+ Type: "bind", // constant ?
+ Source: "/dev/mqueue",
+ Options: []string{"bind", "nosuid", "noexec", "nodev"},
+ }
+ g.AddMount(devMqueue)
+ }
+ if inUserNS && s.PidNS.IsHost() {
+ g.RemoveMount("/proc")
+ procMount := spec.Mount{
+ Destination: "/proc",
+ Type: createconfig.TypeBind,
+ Source: "/proc",
+ Options: []string{"rbind", "nosuid", "noexec", "nodev"},
+ }
+ g.AddMount(procMount)
+ }
+
+ if addCgroup {
+ cgroupMnt := spec.Mount{
+ Destination: "/sys/fs/cgroup",
+ Type: "cgroup",
+ Source: "cgroup",
+ Options: []string{"rprivate", "nosuid", "noexec", "nodev", "relatime", cgroupPerm},
+ }
+ g.AddMount(cgroupMnt)
+ }
+ g.SetProcessCwd(s.WorkDir)
+ g.SetProcessArgs(s.Command)
+ g.SetProcessTerminal(s.Terminal)
+
+ for key, val := range s.Annotations {
+ g.AddAnnotation(key, val)
+ }
+ g.AddProcessEnv("container", "podman")
+
+ g.Config.Linux.Resources = s.ResourceLimits
+
+ // Devices
+ if s.Privileged {
+ // If privileged, we need to add all the host devices to the
+ // spec. We do not add the user provided ones because we are
+ // already adding them all.
+ if err := createconfig.AddPrivilegedDevices(&g); err != nil {
+ return nil, err
+ }
+ } else {
+ for _, device := range s.Devices {
+ if err := createconfig.DevicesFromPath(&g, device.Path); err != nil {
+ return nil, err
+ }
+ }
+ }
+
+ // SECURITY OPTS
+ g.SetProcessNoNewPrivileges(s.NoNewPrivileges)
+
+ if !s.Privileged {
+ g.SetProcessApparmorProfile(s.ApparmorProfile)
+ }
+
+ createconfig.BlockAccessToKernelFilesystems(s.Privileged, s.PidNS.IsHost(), &g)
+
+ for name, val := range s.Env {
+ g.AddProcessEnv(name, val)
+ }
+
+ // TODO rlimits and ulimits needs further refinement by someone more
+ // familiar with the code.
+ //if err := addRlimits(config, &g); err != nil {
+ // return nil, err
+ //}
+
+ // NAMESPACES
+
+ if err := s.pidConfigureGenerator(&g); err != nil {
+ return nil, err
+ }
+
+ if err := s.userConfigureGenerator(&g); err != nil {
+ return nil, err
+ }
+
+ if err := s.networkConfigureGenerator(&g); err != nil {
+ return nil, err
+ }
+
+ if err := s.utsConfigureGenerator(&g, rt); err != nil {
+ return nil, err
+ }
+
+ if err := s.ipcConfigureGenerator(&g); err != nil {
+ return nil, err
+ }
+
+ if err := s.cgroupConfigureGenerator(&g); err != nil {
+ return nil, err
+ }
+ configSpec := g.Config
+
+ if err := s.securityConfigureGenerator(&g, newImage); err != nil {
+ return nil, err
+ }
+
+ // BIND MOUNTS
+ configSpec.Mounts = createconfig.SupercedeUserMounts(s.Mounts, configSpec.Mounts)
+ // Process mounts to ensure correct options
+ finalMounts, err := createconfig.InitFSMounts(configSpec.Mounts)
+ if err != nil {
+ return nil, err
+ }
+ configSpec.Mounts = finalMounts
+
+ // Add annotations
+ if configSpec.Annotations == nil {
+ configSpec.Annotations = make(map[string]string)
+ }
+
+ // TODO cidfile is not in specgen; when wiring up cli, we will need to move this out of here
+ // leaving as a reminder
+ //if config.CidFile != "" {
+ // configSpec.Annotations[libpod.InspectAnnotationCIDFile] = config.CidFile
+ //}
+
+ if s.Remove {
+ configSpec.Annotations[libpod.InspectAnnotationAutoremove] = libpod.InspectResponseTrue
+ } else {
+ configSpec.Annotations[libpod.InspectAnnotationAutoremove] = libpod.InspectResponseFalse
+ }
+
+ if len(s.VolumesFrom) > 0 {
+ configSpec.Annotations[libpod.InspectAnnotationVolumesFrom] = strings.Join(s.VolumesFrom, ",")
+ }
+
+ if s.Privileged {
+ configSpec.Annotations[libpod.InspectAnnotationPrivileged] = libpod.InspectResponseTrue
+ } else {
+ configSpec.Annotations[libpod.InspectAnnotationPrivileged] = libpod.InspectResponseFalse
+ }
+
+ // TODO Init might not make it into the specgen and therefore is not available here. We should deal
+ // with this when we wire up the CLI; leaving as a reminder
+ //if s.Init {
+ // configSpec.Annotations[libpod.InspectAnnotationInit] = libpod.InspectResponseTrue
+ //} else {
+ // configSpec.Annotations[libpod.InspectAnnotationInit] = libpod.InspectResponseFalse
+ //}
+
+ return configSpec, nil
+}
diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go
index e22ee598f..e1dfe4dc5 100644
--- a/pkg/specgen/specgen.go
+++ b/pkg/specgen/specgen.go
@@ -2,24 +2,28 @@ package specgen
import (
"net"
+ "syscall"
"github.com/containers/image/v5/manifest"
"github.com/containers/libpod/libpod"
- "github.com/containers/libpod/libpod/define"
+ "github.com/containers/libpod/pkg/rootless"
"github.com/containers/storage"
"github.com/cri-o/ocicni/pkg/ocicni"
spec "github.com/opencontainers/runtime-spec/specs-go"
)
-// TODO
-// mheon provided this an off the cuff suggestion. Adding it here to retain
-// for history as we implement it. When this struct is implemented, we need
-// to remove the nolints.
-type Namespace struct {
- isHost bool //nolint
- isPath string //nolint
- isContainer string //nolint
- isPod bool //nolint
+// LogConfig describes the logging characteristics for a container
+type LogConfig struct {
+ // LogDriver is the container's log driver.
+ // Optional.
+ Driver string `json:"driver,omitempty"`
+ // LogPath is the path the container's logs will be stored at.
+ // Only available if LogDriver is set to "json-file" or "k8s-file".
+ // Optional.
+ Path string `json:"path,omitempty"`
+ // A set of options to accompany the log driver.
+ // Optional.
+ Options map[string]string `json:"options,omitempty"`
}
// ContainerBasicConfig contains the basic parts of a container.
@@ -62,7 +66,7 @@ type ContainerBasicConfig struct {
// If not provided, the default, SIGTERM, will be used.
// Will conflict with Systemd if Systemd is set to "true" or "always".
// Optional.
- StopSignal *uint `json:"stop_signal,omitempty"`
+ StopSignal *syscall.Signal `json:"stop_signal,omitempty"`
// StopTimeout is a timeout between the container's stop signal being
// sent and SIGKILL being sent.
// If not provided, the default will be used.
@@ -70,13 +74,10 @@ type ContainerBasicConfig struct {
// instead.
// Optional.
StopTimeout *uint `json:"stop_timeout,omitempty"`
- // LogDriver is the container's log driver.
- // Optional.
- LogDriver string `json:"log_driver,omitempty"`
- // LogPath is the path the container's logs will be stored at.
- // Only available if LogDriver is set to "json-file" or "k8s-file".
- // Optional.
- LogPath string `json:"log_path,omitempty"`
+ // LogConfiguration describes the logging for a container including
+ // driver, path, and options.
+ // Optional
+ LogConfiguration *LogConfig `json:"log_configuration,omitempty"`
// ConmonPidFile is a path at which a PID file for Conmon will be
// placed.
// If not given, a default location will be used.
@@ -111,12 +112,10 @@ type ContainerBasicConfig struct {
// Namespace is the libpod namespace the container will be placed in.
// Optional.
Namespace string `json:"namespace,omitempty"`
-
// PidNS is the container's PID namespace.
// It defaults to private.
// Mandatory.
PidNS Namespace `json:"pidns,omitempty"`
-
// UtsNS is the container's UTS namespace.
// It defaults to private.
// Must be set to Private to set Hostname.
@@ -128,6 +127,11 @@ type ContainerBasicConfig struct {
// Conflicts with UtsNS if UtsNS is not set to private.
// Optional.
Hostname string `json:"hostname,omitempty"`
+ // Sysctl sets kernel parameters for the container
+ Sysctl map[string]string `json:"sysctl,omitempty"`
+ // Remove indicates if the container should be removed once it has been started
+ // and exits
+ Remove bool `json:"remove"`
}
// ContainerStorageConfig contains information on the storage configuration of a
@@ -175,7 +179,7 @@ type ContainerStorageConfig struct {
// Mandatory.
IpcNS Namespace `json:"ipcns,omitempty"`
// ShmSize is the size of the tmpfs to mount in at /dev/shm, in bytes.
- // Conflicts with ShmSize if ShmSize is not private.
+ // Conflicts with ShmSize if IpcNS is not private.
// Optional.
ShmSize *int64 `json:"shm_size,omitempty"`
// WorkDir is the container's working directory.
@@ -234,6 +238,9 @@ type ContainerSecurityConfig struct {
// will use.
// Optional.
ApparmorProfile string `json:"apparmor_profile,omitempty"`
+ // SeccompPolicy determines which seccomp profile gets applied
+ // the container. valid values: empty,default,image
+ SeccompPolicy string `json:"seccomp_policy,omitempty"`
// SeccompProfilePath is the path to a JSON file containing the
// container's Seccomp profile.
// If not specified, no Seccomp profile will be used.
@@ -252,7 +259,10 @@ type ContainerSecurityConfig struct {
// IDMappings are UID and GID mappings that will be used by user
// namespaces.
// Required if UserNS is private.
- IDMappings storage.IDMappingOptions `json:"idmappings,omitempty"`
+ IDMappings *storage.IDMappingOptions `json:"idmappings,omitempty"`
+ // ReadOnlyFilesystem indicates that everything will be mounted
+ // as read-only
+ ReadOnlyFilesystem bool `json:"read_only_filesystem,omittempty"`
}
// ContainerCgroupConfig contains configuration information about a container's
@@ -260,16 +270,13 @@ type ContainerSecurityConfig struct {
type ContainerCgroupConfig struct {
// CgroupNS is the container's cgroup namespace.
// It defaults to private.
- // Conflicts with NoCgroups if not set to host.
// Mandatory.
CgroupNS Namespace `json:"cgroupns,omitempty"`
- // NoCgroups indicates that the container should not create CGroups.
- // Conflicts with CgroupParent and CgroupNS if CgroupNS is not set to
- // host.
- NoCgroups bool `json:"no_cgroups,omitempty"`
+ // CgroupsMode sets a policy for how cgroups will be created in the
+ // container, including the ability to disable creation entirely.
+ CgroupsMode string `json:"cgroups_mode,omitempty"`
// CgroupParent is the container's CGroup parent.
// If not set, the default for the current cgroup driver will be used.
- // Conflicts with NoCgroups.
// Optional.
CgroupParent string `json:"cgroup_parent,omitempty"`
}
@@ -348,7 +355,7 @@ type ContainerNetworkConfig struct {
// ContainerResourceConfig contains information on container resource limits.
type ContainerResourceConfig struct {
- // ResourceLimits are resource limits to apply to the container.
+ // ResourceLimits are resource limits to apply to the container.,
// Can only be set as root on cgroups v1 systems, but can be set as
// rootless as well for cgroups v2.
// Optional.
@@ -365,11 +372,12 @@ type ContainerResourceConfig struct {
// ContainerHealthCheckConfig describes a container healthcheck with attributes
// like command, retries, interval, start period, and timeout.
type ContainerHealthCheckConfig struct {
- HealthConfig manifest.Schema2HealthConfig `json:"healthconfig,omitempty"`
+ HealthConfig *manifest.Schema2HealthConfig `json:"healthconfig,omitempty"`
}
// SpecGenerator creates an OCI spec and Libpod configuration options to create
// a container based on the given configuration.
+// swagger:model SpecGenerator
type SpecGenerator struct {
ContainerBasicConfig
ContainerStorageConfig
@@ -381,19 +389,24 @@ type SpecGenerator struct {
}
// NewSpecGenerator returns a SpecGenerator struct given one of two mandatory inputs
-func NewSpecGenerator(image, rootfs *string) (*SpecGenerator, error) {
- _ = image
- _ = rootfs
- return &SpecGenerator{}, define.ErrNotImplemented
-}
-
-// Validate verifies that the given SpecGenerator is valid and satisfies required
-// input for creating a container.
-func (s *SpecGenerator) Validate() error {
- return define.ErrNotImplemented
+func NewSpecGenerator(image string) *SpecGenerator {
+ net := ContainerNetworkConfig{
+ NetNS: Namespace{
+ NSMode: Bridge,
+ },
+ }
+ csc := ContainerStorageConfig{Image: image}
+ if rootless.IsRootless() {
+ net.NetNS.NSMode = Slirp
+ }
+ return &SpecGenerator{
+ ContainerStorageConfig: csc,
+ ContainerNetworkConfig: net,
+ }
}
-// MakeContainer creates a container based on the SpecGenerator
-func (s *SpecGenerator) MakeContainer() (*libpod.Container, error) {
- return nil, define.ErrNotImplemented
+// NewSpecGenerator returns a SpecGenerator struct given one of two mandatory inputs
+func NewSpecGeneratorWithRootfs(rootfs string) *SpecGenerator {
+ csc := ContainerStorageConfig{Rootfs: rootfs}
+ return &SpecGenerator{ContainerStorageConfig: csc}
}
diff --git a/pkg/specgen/validate.go b/pkg/specgen/validate.go
new file mode 100644
index 000000000..78e4d8ad5
--- /dev/null
+++ b/pkg/specgen/validate.go
@@ -0,0 +1,159 @@
+package specgen
+
+import (
+ "strings"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/util"
+ "github.com/pkg/errors"
+)
+
+var (
+ // ErrInvalidSpecConfig describes an error that the given SpecGenerator is invalid
+ ErrInvalidSpecConfig error = errors.New("invalid configuration")
+ // SystemDValues describes the only values that SystemD can be
+ SystemDValues = []string{"true", "false", "always"}
+ // ImageVolumeModeValues describes the only values that ImageVolumeMode can be
+ ImageVolumeModeValues = []string{"ignore", "tmpfs", "anonymous"}
+)
+
+func exclusiveOptions(opt1, opt2 string) error {
+ return errors.Errorf("%s and %s are mutually exclusive options", opt1, opt2)
+}
+
+// Validate verifies that the given SpecGenerator is valid and satisfies required
+// input for creating a container.
+func (s *SpecGenerator) validate(rt *libpod.Runtime) error {
+
+ //
+ // ContainerBasicConfig
+ //
+ // Rootfs and Image cannot both populated
+ if len(s.ContainerStorageConfig.Image) > 0 && len(s.ContainerStorageConfig.Rootfs) > 0 {
+ return errors.Wrap(ErrInvalidSpecConfig, "both image and rootfs cannot be simultaneously")
+ }
+ // Cannot set hostname and utsns
+ if len(s.ContainerBasicConfig.Hostname) > 0 && !s.ContainerBasicConfig.UtsNS.IsPrivate() {
+ return errors.Wrap(ErrInvalidSpecConfig, "cannot set hostname when creating an UTS namespace")
+ }
+ // systemd values must be true, false, or always
+ if len(s.ContainerBasicConfig.Systemd) > 0 && !util.StringInSlice(strings.ToLower(s.ContainerBasicConfig.Systemd), SystemDValues) {
+ return errors.Wrapf(ErrInvalidSpecConfig, "SystemD values must be one of %s", strings.Join(SystemDValues, ","))
+ }
+
+ //
+ // ContainerStorageConfig
+ //
+ // rootfs and image cannot both be set
+ if len(s.ContainerStorageConfig.Image) > 0 && len(s.ContainerStorageConfig.Rootfs) > 0 {
+ return exclusiveOptions("rootfs", "image")
+ }
+ // imagevolumemode must be one of ignore, tmpfs, or anonymous if given
+ if len(s.ContainerStorageConfig.ImageVolumeMode) > 0 && !util.StringInSlice(strings.ToLower(s.ContainerStorageConfig.ImageVolumeMode), ImageVolumeModeValues) {
+ return errors.Errorf("ImageVolumeMode values must be one of %s", strings.Join(ImageVolumeModeValues, ","))
+ }
+ // shmsize conflicts with IPC namespace
+ if s.ContainerStorageConfig.ShmSize != nil && !s.ContainerStorageConfig.IpcNS.IsPrivate() {
+ return errors.New("cannot set shmsize when creating an IPC namespace")
+ }
+
+ //
+ // ContainerSecurityConfig
+ //
+ // groups and privileged are exclusive
+ if len(s.Groups) > 0 && s.Privileged {
+ return exclusiveOptions("Groups", "privileged")
+ }
+ // capadd and privileged are exclusive
+ if len(s.CapAdd) > 0 && s.Privileged {
+ return exclusiveOptions("CapAdd", "privileged")
+ }
+ // selinuxprocesslabel and privileged are exclusive
+ if len(s.SelinuxProcessLabel) > 0 && s.Privileged {
+ return exclusiveOptions("SelinuxProcessLabel", "privileged")
+ }
+ // selinuxmounmtlabel and privileged are exclusive
+ if len(s.SelinuxMountLabel) > 0 && s.Privileged {
+ return exclusiveOptions("SelinuxMountLabel", "privileged")
+ }
+ // selinuxopts and privileged are exclusive
+ if len(s.SelinuxOpts) > 0 && s.Privileged {
+ return exclusiveOptions("SelinuxOpts", "privileged")
+ }
+ // apparmor and privileged are exclusive
+ if len(s.ApparmorProfile) > 0 && s.Privileged {
+ return exclusiveOptions("AppArmorProfile", "privileged")
+ }
+ // userns and idmappings conflict
+ if s.UserNS.IsPrivate() && s.IDMappings == nil {
+ return errors.Wrap(ErrInvalidSpecConfig, "IDMappings are required when not creating a User namespace")
+ }
+
+ //
+ // ContainerCgroupConfig
+ //
+ //
+ // None for now
+
+ //
+ // ContainerNetworkConfig
+ //
+ if !s.NetNS.IsPrivate() && s.ConfigureNetNS {
+ return errors.New("can only configure network namespace when creating a network a network namespace")
+ }
+ // useimageresolveconf conflicts with dnsserver, dnssearch, dnsoption
+ if s.UseImageResolvConf {
+ if len(s.DNSServer) > 0 {
+ return exclusiveOptions("UseImageResolvConf", "DNSServer")
+ }
+ if len(s.DNSSearch) > 0 {
+ return exclusiveOptions("UseImageResolvConf", "DNSSearch")
+ }
+ if len(s.DNSOption) > 0 {
+ return exclusiveOptions("UseImageResolvConf", "DNSOption")
+ }
+ }
+ // UseImageHosts and HostAdd are exclusive
+ if s.UseImageHosts && len(s.HostAdd) > 0 {
+ return exclusiveOptions("UseImageHosts", "HostAdd")
+ }
+
+ // TODO the specgen does not appear to handle this? Should it
+ //switch config.Cgroup.Cgroups {
+ //case "disabled":
+ // if addedResources {
+ // return errors.New("cannot specify resource limits when cgroups are disabled is specified")
+ // }
+ // configSpec.Linux.Resources = &spec.LinuxResources{}
+ //case "enabled", "no-conmon", "":
+ // // Do nothing
+ //default:
+ // return errors.New("unrecognized option for cgroups; supported are 'default', 'disabled', 'no-conmon'")
+ //}
+
+ // Namespaces
+ if err := s.UtsNS.validate(); err != nil {
+ return err
+ }
+ if err := s.IpcNS.validate(); err != nil {
+ return err
+ }
+ if err := s.NetNS.validate(); err != nil {
+ return err
+ }
+ if err := s.PidNS.validate(); err != nil {
+ return err
+ }
+ if err := s.CgroupNS.validate(); err != nil {
+ return err
+ }
+ if err := s.UserNS.validate(); err != nil {
+ return err
+ }
+
+ // The following are defaults as needed by container creation
+ if len(s.WorkDir) < 1 {
+ s.WorkDir = "/"
+ }
+ return nil
+}
diff --git a/pkg/varlinkapi/pods.go b/pkg/varlinkapi/pods.go
index 1ebe5d424..2ec45f7a1 100644
--- a/pkg/varlinkapi/pods.go
+++ b/pkg/varlinkapi/pods.go
@@ -16,6 +16,14 @@ import (
// CreatePod ...
func (i *LibpodAPI) CreatePod(call iopodman.VarlinkCall, create iopodman.PodCreate) error {
var options []libpod.PodCreateOption
+ if create.Infra {
+ options = append(options, libpod.WithInfraContainer())
+ nsOptions, err := shared.GetNamespaceOptions(create.Share)
+ if err != nil {
+ return err
+ }
+ options = append(options, nsOptions...)
+ }
if create.CgroupParent != "" {
options = append(options, libpod.WithPodCgroupParent(create.CgroupParent))
}
@@ -43,14 +51,6 @@ func (i *LibpodAPI) CreatePod(call iopodman.VarlinkCall, create iopodman.PodCrea
options = append(options, libpod.WithInfraContainerPorts(portBindings))
}
- if create.Infra {
- options = append(options, libpod.WithInfraContainer())
- nsOptions, err := shared.GetNamespaceOptions(create.Share)
- if err != nil {
- return err
- }
- options = append(options, nsOptions...)
- }
options = append(options, libpod.WithPodCgroups())
pod, err := i.Runtime.NewPod(getContext(), options...)
diff --git a/test/e2e/pod_create_test.go b/test/e2e/pod_create_test.go
index 2efa36141..e0a10c202 100644
--- a/test/e2e/pod_create_test.go
+++ b/test/e2e/pod_create_test.go
@@ -1,7 +1,9 @@
package integration
import (
+ "fmt"
"os"
+ "strings"
. "github.com/containers/libpod/test/utils"
. "github.com/onsi/ginkgo"
@@ -117,4 +119,167 @@ var _ = Describe("Podman pod create", func() {
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(125))
})
+
+ It("podman create pod with --no-hosts", func() {
+ SkipIfRemote()
+ name := "test"
+ podCreate := podmanTest.Podman([]string{"pod", "create", "--no-hosts", "--name", name})
+ podCreate.WaitWithDefaultTimeout()
+ Expect(podCreate.ExitCode()).To(Equal(0))
+
+ alpineResolvConf := podmanTest.Podman([]string{"run", "-ti", "--rm", "--no-hosts", ALPINE, "cat", "/etc/hosts"})
+ alpineResolvConf.WaitWithDefaultTimeout()
+ Expect(alpineResolvConf.ExitCode()).To(Equal(0))
+
+ podResolvConf := podmanTest.Podman([]string{"run", "--pod", name, "-ti", "--rm", ALPINE, "cat", "/etc/hosts"})
+ podResolvConf.WaitWithDefaultTimeout()
+ Expect(podResolvConf.ExitCode()).To(Equal(0))
+ Expect(podResolvConf.OutputToString()).To(Equal(alpineResolvConf.OutputToString()))
+ })
+
+ It("podman create pod with --no-hosts and no infra should fail", func() {
+ SkipIfRemote()
+ name := "test"
+ podCreate := podmanTest.Podman([]string{"pod", "create", "--no-hosts", "--name", name, "--infra=false"})
+ podCreate.WaitWithDefaultTimeout()
+ Expect(podCreate.ExitCode()).To(Equal(125))
+ })
+
+ It("podman create pod with --add-host", func() {
+ SkipIfRemote()
+ name := "test"
+ podCreate := podmanTest.Podman([]string{"pod", "create", "--add-host", "test.example.com:12.34.56.78", "--name", name})
+ podCreate.WaitWithDefaultTimeout()
+ Expect(podCreate.ExitCode()).To(Equal(0))
+
+ podResolvConf := podmanTest.Podman([]string{"run", "--pod", name, "-ti", "--rm", ALPINE, "cat", "/etc/hosts"})
+ podResolvConf.WaitWithDefaultTimeout()
+ Expect(podResolvConf.ExitCode()).To(Equal(0))
+ Expect(strings.Contains(podResolvConf.OutputToString(), "12.34.56.78 test.example.com")).To(BeTrue())
+ })
+
+ It("podman create pod with --add-host and no infra should fail", func() {
+ SkipIfRemote()
+ name := "test"
+ podCreate := podmanTest.Podman([]string{"pod", "create", "--add-host", "test.example.com:12.34.56.78", "--name", name, "--infra=false"})
+ podCreate.WaitWithDefaultTimeout()
+ Expect(podCreate.ExitCode()).To(Equal(125))
+ })
+
+ It("podman create pod with DNS server set", func() {
+ SkipIfRemote()
+ name := "test"
+ server := "12.34.56.78"
+ podCreate := podmanTest.Podman([]string{"pod", "create", "--dns", server, "--name", name})
+ podCreate.WaitWithDefaultTimeout()
+ Expect(podCreate.ExitCode()).To(Equal(0))
+
+ podResolvConf := podmanTest.Podman([]string{"run", "--pod", name, "-ti", "--rm", ALPINE, "cat", "/etc/resolv.conf"})
+ podResolvConf.WaitWithDefaultTimeout()
+ Expect(podResolvConf.ExitCode()).To(Equal(0))
+ Expect(strings.Contains(podResolvConf.OutputToString(), fmt.Sprintf("nameserver %s", server))).To(BeTrue())
+ })
+
+ It("podman create pod with DNS server set and no infra should fail", func() {
+ SkipIfRemote()
+ name := "test"
+ server := "12.34.56.78"
+ podCreate := podmanTest.Podman([]string{"pod", "create", "--dns", server, "--name", name, "--infra=false"})
+ podCreate.WaitWithDefaultTimeout()
+ Expect(podCreate.ExitCode()).To(Equal(125))
+ })
+
+ It("podman create pod with DNS option set", func() {
+ SkipIfRemote()
+ name := "test"
+ option := "attempts:5"
+ podCreate := podmanTest.Podman([]string{"pod", "create", "--dns-opt", option, "--name", name})
+ podCreate.WaitWithDefaultTimeout()
+ Expect(podCreate.ExitCode()).To(Equal(0))
+
+ podResolvConf := podmanTest.Podman([]string{"run", "--pod", name, "-ti", "--rm", ALPINE, "cat", "/etc/resolv.conf"})
+ podResolvConf.WaitWithDefaultTimeout()
+ Expect(podResolvConf.ExitCode()).To(Equal(0))
+ Expect(strings.Contains(podResolvConf.OutputToString(), fmt.Sprintf("options %s", option))).To(BeTrue())
+ })
+
+ It("podman create pod with DNS option set and no infra should fail", func() {
+ SkipIfRemote()
+ name := "test"
+ option := "attempts:5"
+ podCreate := podmanTest.Podman([]string{"pod", "create", "--dns-opt", option, "--name", name, "--infra=false"})
+ podCreate.WaitWithDefaultTimeout()
+ Expect(podCreate.ExitCode()).To(Equal(125))
+ })
+
+ It("podman create pod with DNS search domain set", func() {
+ SkipIfRemote()
+ name := "test"
+ search := "example.com"
+ podCreate := podmanTest.Podman([]string{"pod", "create", "--dns-search", search, "--name", name})
+ podCreate.WaitWithDefaultTimeout()
+ Expect(podCreate.ExitCode()).To(Equal(0))
+
+ podResolvConf := podmanTest.Podman([]string{"run", "--pod", name, "-ti", "--rm", ALPINE, "cat", "/etc/resolv.conf"})
+ podResolvConf.WaitWithDefaultTimeout()
+ Expect(podResolvConf.ExitCode()).To(Equal(0))
+ Expect(strings.Contains(podResolvConf.OutputToString(), fmt.Sprintf("search %s", search))).To(BeTrue())
+ })
+
+ It("podman create pod with DNS search domain set and no infra should fail", func() {
+ SkipIfRemote()
+ name := "test"
+ search := "example.com"
+ podCreate := podmanTest.Podman([]string{"pod", "create", "--dns-search", search, "--name", name, "--infra=false"})
+ podCreate.WaitWithDefaultTimeout()
+ Expect(podCreate.ExitCode()).To(Equal(125))
+ })
+
+ It("podman create pod with IP address", func() {
+ SkipIfRemote()
+ SkipIfRootless()
+ name := "test"
+ ip := GetRandomIPAddress()
+ podCreate := podmanTest.Podman([]string{"pod", "create", "--ip", ip, "--name", name})
+ podCreate.WaitWithDefaultTimeout()
+ Expect(podCreate.ExitCode()).To(Equal(0))
+
+ podResolvConf := podmanTest.Podman([]string{"run", "--pod", name, "-ti", "--rm", ALPINE, "ip", "addr"})
+ podResolvConf.WaitWithDefaultTimeout()
+ Expect(podResolvConf.ExitCode()).To(Equal(0))
+ Expect(strings.Contains(podResolvConf.OutputToString(), ip)).To(BeTrue())
+ })
+
+ It("podman create pod with IP address and no infra should fail", func() {
+ SkipIfRemote()
+ name := "test"
+ ip := GetRandomIPAddress()
+ podCreate := podmanTest.Podman([]string{"pod", "create", "--ip", ip, "--name", name, "--infra=false"})
+ podCreate.WaitWithDefaultTimeout()
+ Expect(podCreate.ExitCode()).To(Equal(125))
+ })
+
+ It("podman create pod with MAC address", func() {
+ SkipIfRemote()
+ SkipIfRootless()
+ name := "test"
+ mac := "92:d0:c6:0a:29:35"
+ podCreate := podmanTest.Podman([]string{"pod", "create", "--mac-address", mac, "--name", name})
+ podCreate.WaitWithDefaultTimeout()
+ Expect(podCreate.ExitCode()).To(Equal(0))
+
+ podResolvConf := podmanTest.Podman([]string{"run", "--pod", name, "-ti", "--rm", ALPINE, "ip", "addr"})
+ podResolvConf.WaitWithDefaultTimeout()
+ Expect(podResolvConf.ExitCode()).To(Equal(0))
+ Expect(strings.Contains(podResolvConf.OutputToString(), mac)).To(BeTrue())
+ })
+
+ It("podman create pod with MAC address and no infra should fail", func() {
+ SkipIfRemote()
+ name := "test"
+ mac := "92:d0:c6:0a:29:35"
+ podCreate := podmanTest.Podman([]string{"pod", "create", "--mac-address", mac, "--name", name, "--infra=false"})
+ podCreate.WaitWithDefaultTimeout()
+ Expect(podCreate.ExitCode()).To(Equal(125))
+ })
})
diff --git a/test/system/150-login.bats b/test/system/150-login.bats
new file mode 100644
index 000000000..9c9593311
--- /dev/null
+++ b/test/system/150-login.bats
@@ -0,0 +1,339 @@
+#!/usr/bin/env bats -*- bats -*-
+#
+# tests for podman login
+#
+
+load helpers
+
+###############################################################################
+# BEGIN one-time envariable setup
+
+# Create a scratch directory; our podman registry will run from here. We
+# also use it for other temporary files like authfiles.
+if [ -z "${PODMAN_LOGIN_WORKDIR}" ]; then
+ export PODMAN_LOGIN_WORKDIR=$(mktemp -d --tmpdir=${BATS_TMPDIR:-${TMPDIR:-/tmp}} podman_bats_login.XXXXXX)
+fi
+
+# Randomly-generated username and password
+if [ -z "${PODMAN_LOGIN_USER}" ]; then
+ export PODMAN_LOGIN_USER="user$(random_string 4)"
+ export PODMAN_LOGIN_PASS=$(random_string 15)
+fi
+
+# Randomly-assigned port in the 5xxx range
+if [ -z "${PODMAN_LOGIN_REGISTRY_PORT}" ]; then
+ for port in $(shuf -i 5000-5999);do
+ if ! { exec 3<> /dev/tcp/127.0.0.1/$port; } &>/dev/null; then
+ export PODMAN_LOGIN_REGISTRY_PORT=$port
+ break
+ fi
+ done
+fi
+
+# Override any user-set path to an auth file
+unset REGISTRY_AUTH_FILE
+
+# END one-time envariable setup
+###############################################################################
+# BEGIN filtering - none of these tests will work with podman-remote
+
+function setup() {
+ skip_if_remote "none of these tests work with podman-remote"
+
+ basic_setup
+}
+
+# END filtering - none of these tests will work with podman-remote
+###############################################################################
+# BEGIN first "test" - start a registry for use by other tests
+#
+# This isn't really a test: it's a helper that starts a local registry.
+# Note that we're careful to use a root/runroot separate from our tests,
+# so setup/teardown don't clobber our registry image.
+#
+
+@test "podman login [start registry]" {
+ AUTHDIR=${PODMAN_LOGIN_WORKDIR}/auth
+ mkdir -p $AUTHDIR
+
+ # Pull registry image, but into a separate container storage
+ mkdir -p ${PODMAN_LOGIN_WORKDIR}/root
+ mkdir -p ${PODMAN_LOGIN_WORKDIR}/runroot
+ PODMAN_LOGIN_ARGS="--root ${PODMAN_LOGIN_WORKDIR}/root --runroot ${PODMAN_LOGIN_WORKDIR}/runroot"
+ # Give it three tries, to compensate for flakes
+ run_podman ${PODMAN_LOGIN_ARGS} pull registry:2 ||
+ run_podman ${PODMAN_LOGIN_ARGS} pull registry:2 ||
+ run_podman ${PODMAN_LOGIN_ARGS} pull registry:2
+
+ # Registry image needs a cert. Self-signed is good enough.
+ CERT=$AUTHDIR/domain.crt
+ if [ ! -e $CERT ]; then
+ openssl req -newkey rsa:4096 -nodes -sha256 \
+ -keyout $AUTHDIR/domain.key -x509 -days 2 \
+ -out $AUTHDIR/domain.crt \
+ -subj "/C=US/ST=Foo/L=Bar/O=Red Hat, Inc./CN=localhost"
+ fi
+
+ # Store credentials where container will see them
+ if [ ! -e $AUTHDIR/htpasswd ]; then
+ run_podman ${PODMAN_LOGIN_ARGS} run --rm \
+ --entrypoint htpasswd registry:2 \
+ -Bbn ${PODMAN_LOGIN_USER} ${PODMAN_LOGIN_PASS} \
+ > $AUTHDIR/htpasswd
+
+ # In case $PODMAN_TEST_KEEP_LOGIN_REGISTRY is set, for testing later
+ echo "${PODMAN_LOGIN_USER}:${PODMAN_LOGIN_PASS}" \
+ > $AUTHDIR/htpasswd-plaintext
+ fi
+
+ # Run the registry container.
+ run_podman '?' ${PODMAN_LOGIN_ARGS} rm -f registry
+ run_podman ${PODMAN_LOGIN_ARGS} run -d \
+ -p ${PODMAN_LOGIN_REGISTRY_PORT}:5000 \
+ --name registry \
+ -v $AUTHDIR:/auth:Z \
+ -e "REGISTRY_AUTH=htpasswd" \
+ -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
+ -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
+ -e REGISTRY_HTTP_TLS_CERTIFICATE=/auth/domain.crt \
+ -e REGISTRY_HTTP_TLS_KEY=/auth/domain.key \
+ registry:2
+}
+
+# END first "test" - start a registry for use by other tests
+###############################################################################
+# BEGIN actual tests
+# BEGIN primary podman login/push/pull tests
+
+@test "podman login - basic test" {
+ run_podman login --tls-verify=false \
+ --username ${PODMAN_LOGIN_USER} \
+ --password ${PODMAN_LOGIN_PASS} \
+ localhost:${PODMAN_LOGIN_REGISTRY_PORT}
+ is "$output" "Login Succeeded!" "output from podman login"
+
+ # Now log out
+ run_podman logout localhost:${PODMAN_LOGIN_REGISTRY_PORT}
+ is "$output" "Removed login credentials for localhost:${PODMAN_LOGIN_REGISTRY_PORT}" \
+ "output from podman logout"
+}
+
+@test "podman login - with wrong credentials" {
+ registry=localhost:${PODMAN_LOGIN_REGISTRY_PORT}
+
+ run_podman 125 login --tls-verify=false \
+ --username ${PODMAN_LOGIN_USER} \
+ --password "x${PODMAN_LOGIN_PASS}" \
+ $registry
+ is "$output" \
+ "Error: error logging into \"$registry\": invalid username/password" \
+ 'output from podman login'
+}
+
+@test "podman login - check generated authfile" {
+ authfile=${PODMAN_LOGIN_WORKDIR}/auth-$(random_string 10).json
+ rm -f $authfile
+
+ registry=localhost:${PODMAN_LOGIN_REGISTRY_PORT}
+
+ run_podman login --authfile=$authfile \
+ --tls-verify=false \
+ --username ${PODMAN_LOGIN_USER} \
+ --password ${PODMAN_LOGIN_PASS} \
+ $registry
+
+ # Confirm that authfile now exists
+ test -e $authfile || \
+ die "podman login did not create authfile $authfile"
+
+ # Special bracket form needed because of colon in host:port
+ run jq -r ".[\"auths\"][\"$registry\"][\"auth\"]" <$authfile
+ is "$status" "0" "jq from $authfile"
+
+ expect_userpass="${PODMAN_LOGIN_USER}:${PODMAN_LOGIN_PASS}"
+ actual_userpass=$(base64 -d <<<"$output")
+ is "$actual_userpass" "$expect_userpass" "credentials stored in $authfile"
+
+
+ # Now log out and make sure credentials are removed
+ run_podman logout --authfile=$authfile $registry
+
+ run jq -r '.auths' <$authfile
+ is "$status" "0" "jq from $authfile"
+ is "$output" "{}" "credentials removed from $authfile"
+}
+
+# Some push tests
+@test "podman push fail" {
+ # Create an invalid authfile
+ authfile=${PODMAN_LOGIN_WORKDIR}/auth-$(random_string 10).json
+ rm -f $authfile
+
+ wrong_auth=$(base64 <<<"baduser:wrongpassword")
+ cat >$authfile <<EOF
+{
+ "auths": {
+ "localhost:${PODMAN_LOGIN_REGISTRY_PORT}": {
+ "auth": "$wrong_auth"
+ }
+ }
+}
+EOF
+
+ run_podman 125 push --authfile=$authfile \
+ --tls-verify=false $IMAGE \
+ localhost:${PODMAN_LOGIN_REGISTRY_PORT}/badpush:1
+ is "$output" ".*: unauthorized: authentication required" \
+ "auth error on push"
+}
+
+@test "podman push ok" {
+ # ARGH! We can't push $IMAGE (alpine_labels) to this registry; error is:
+ #
+ # Writing manifest to image destination
+ # Error: Error copying image to the remote destination: Error writing manifest: Error uploading manifest latest to localhost:${PODMAN_LOGIN_REGISTRY_PORT}/okpush: received unexpected HTTP status: 500 Internal Server Error
+ #
+ # Root cause: something to do with v1/v2 s1/s2:
+ #
+ # https://github.com/containers/skopeo/issues/651
+ #
+ run_podman pull busybox
+
+ # Preserve its ID for later comparison against push/pulled image
+ run_podman inspect --format '{{.Id}}' busybox
+ id_busybox=$output
+
+ destname=ok-$(random_string 10 | tr A-Z a-z)-ok
+ # Use command-line credentials
+ run_podman push --tls-verify=false \
+ --creds ${PODMAN_LOGIN_USER}:${PODMAN_LOGIN_PASS} \
+ busybox localhost:${PODMAN_LOGIN_REGISTRY_PORT}/$destname
+
+ # Yay! Pull it back
+ run_podman pull --tls-verify=false \
+ --creds ${PODMAN_LOGIN_USER}:${PODMAN_LOGIN_PASS} \
+ localhost:${PODMAN_LOGIN_REGISTRY_PORT}/$destname
+
+ # Compare to original busybox
+ run_podman inspect --format '{{.Id}}' $destname
+ is "$output" "$id_busybox" "Image ID of pulled image == busybox"
+
+ run_podman rmi busybox $destname
+}
+
+# END primary podman login/push/pull tests
+###############################################################################
+# BEGIN cooperation with skopeo
+
+# Skopeo helper - keep this separate, so we can test with different
+# envariable settings
+function _test_skopeo_credential_sharing() {
+ if ! type -p skopeo; then
+ skip "skopeo not available"
+ fi
+
+ registry=localhost:${PODMAN_LOGIN_REGISTRY_PORT}
+
+ run_podman login "$@" --tls-verify=false \
+ --username ${PODMAN_LOGIN_USER} \
+ --password ${PODMAN_LOGIN_PASS} \
+ $registry
+
+ destname=skopeo-ok-$(random_string 10 | tr A-Z a-z)-ok
+ echo "# skopeo copy ..."
+ run skopeo copy "$@" \
+ --format=v2s2 \
+ --dest-tls-verify=false \
+ containers-storage:$IMAGE \
+ docker://$registry/$destname
+ echo "$output"
+ is "$status" "0" "skopeo copy - exit status"
+ is "$output" ".*Copying blob .*" "output of skopeo copy"
+ is "$output" ".*Copying config .*" "output of skopeo copy"
+ is "$output" ".*Writing manifest .*" "output of skopeo copy"
+
+ echo "# skopeo inspect ..."
+ run skopeo inspect "$@" --tls-verify=false docker://$registry/$destname
+ echo "$output"
+ is "$status" "0" "skopeo inspect - exit status"
+
+ got_name=$(jq -r .Name <<<"$output")
+ is "$got_name" "$registry/$dest_name" "skopeo inspect -> Name"
+
+ # Now try without a valid login; it should fail
+ run_podman logout "$@" $registry
+ echo "# skopeo inspect [with no credentials] ..."
+ run skopeo inspect "$@" --tls-verify=false docker://$registry/$destname
+ echo "$output"
+ is "$status" "1" "skopeo inspect - exit status"
+ is "$output" ".*: unauthorized: authentication required" \
+ "auth error on skopeo inspect"
+}
+
+@test "podman login - shares credentials with skopeo - default auth file" {
+ if is_rootless; then
+ if [ -z "${XDG_RUNTIME_DIR}" ]; then
+ skip "skopeo does not match podman when XDG_RUNTIME_DIR unset; #823"
+ fi
+ fi
+ _test_skopeo_credential_sharing
+}
+
+@test "podman login - shares credentials with skopeo - via envariable" {
+ skip "skopeo does not yet support REGISTRY_AUTH_FILE; #822"
+ authfile=${PODMAN_LOGIN_WORKDIR}/auth-$(random_string 10).json
+ rm -f $authfile
+
+ REGISTRY_AUTH_FILE=$authfile _test_skopeo_credential_sharing
+ rm -f $authfile
+}
+
+@test "podman login - shares credentials with skopeo - via --authfile" {
+ # Also test that command-line --authfile overrides envariable
+ authfile=${PODMAN_LOGIN_WORKDIR}/auth-$(random_string 10).json
+ rm -f $authfile
+
+ fake_authfile=${PODMAN_LOGIN_WORKDIR}/auth-$(random_string 10).json
+ rm -f $fake_authfile
+
+ REGISTRY_AUTH_FILE=$authfile _test_skopeo_credential_sharing --authfile=$authfile
+
+ if [ -e $fake_authfile ]; then
+ die "REGISTRY_AUTH_FILE overrode command-line --authfile!"
+ fi
+ rm -f $authfile
+}
+
+# END cooperation with skopeo
+# END actual tests
+###############################################################################
+# BEGIN teardown (remove the registry container)
+
+@test "podman login [stop registry, clean up]" {
+ # For manual debugging; user may request keeping the registry running
+ if [ -n "${PODMAN_TEST_KEEP_LOGIN_REGISTRY}" ]; then
+ skip "[leaving registry running by request]"
+ fi
+
+ run_podman --root ${PODMAN_LOGIN_WORKDIR}/root \
+ --runroot ${PODMAN_LOGIN_WORKDIR}/runroot \
+ rm -f registry
+ run_podman --root ${PODMAN_LOGIN_WORKDIR}/root \
+ --runroot ${PODMAN_LOGIN_WORKDIR}/runroot \
+ rmi -a
+
+ # By default, clean up
+ if [ -z "${PODMAN_TEST_KEEP_LOGIN_WORKDIR}" ]; then
+ rm -rf ${PODMAN_LOGIN_WORKDIR}
+ fi
+
+ # Make sure socket is closed
+ if { exec 3<> /dev/tcp/127.0.0.1/${PODMAN_LOGIN_REGISTRY_PORT}; } &>/dev/null; then
+ die "Socket still seems open"
+ fi
+}
+
+# END teardown (remove the registry container)
+###############################################################################
+
+# vim: filetype=sh