summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/podman/common/completion.go14
-rw-r--r--cmd/podman/generate/generate.go4
-rw-r--r--cmd/podman/generate/kube.go14
-rw-r--r--cmd/podman/play/kube.go13
-rw-r--r--cmd/podman/play/play.go4
-rw-r--r--cmd/podman/system/unshare.go6
-rw-r--r--docs/source/markdown/podman-generate-kube.1.md12
-rw-r--r--docs/source/markdown/podman-generate.1.md6
-rw-r--r--docs/source/markdown/podman-play-kube.1.md22
-rw-r--r--docs/source/markdown/podman-play.1.md6
-rw-r--r--docs/source/markdown/podman-unshare.1.md37
-rw-r--r--docs/source/markdown/podman.1.md4
-rw-r--r--libpod/kube.go45
-rw-r--r--libpod/networking_linux.go27
-rw-r--r--pkg/api/handlers/libpod/manifests.go16
-rw-r--r--pkg/bindings/network/network.go10
-rw-r--r--pkg/bindings/network/types.go3
-rw-r--r--pkg/bindings/network/types_prune_options.go16
-rw-r--r--pkg/bindings/test/networks_test.go47
-rw-r--r--pkg/domain/entities/engine_container.go2
-rw-r--r--pkg/domain/entities/play.go8
-rw-r--r--pkg/domain/entities/system.go5
-rw-r--r--pkg/domain/infra/abi/generate.go172
-rw-r--r--pkg/domain/infra/abi/play.go105
-rw-r--r--pkg/domain/infra/abi/system.go26
-rw-r--r--pkg/domain/infra/tunnel/network.go3
-rw-r--r--pkg/domain/infra/tunnel/system.go2
-rw-r--r--pkg/util/kube.go16
-rw-r--r--test/apiv2/rest_api/test_rest_v2_0_0.py5
-rw-r--r--test/e2e/generate_kube_test.go58
-rw-r--r--test/e2e/play_kube_test.go182
-rw-r--r--test/e2e/unshare_test.go7
32 files changed, 782 insertions, 115 deletions
diff --git a/cmd/podman/common/completion.go b/cmd/podman/common/completion.go
index bc106263c..d110fb1b5 100644
--- a/cmd/podman/common/completion.go
+++ b/cmd/podman/common/completion.go
@@ -405,6 +405,20 @@ func AutocompletePodsRunning(cmd *cobra.Command, args []string, toComplete strin
return getPods(cmd, toComplete, completeDefault, "running", "degraded")
}
+// AutocompleteForKube - Autocomplete all Podman objects supported by kube generate.
+func AutocompleteForKube(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+ if !validCurrentCmdLine(cmd, args, toComplete) {
+ return nil, cobra.ShellCompDirectiveNoFileComp
+ }
+ containers, _ := getContainers(cmd, toComplete, completeDefault)
+ pods, _ := getPods(cmd, toComplete, completeDefault)
+ volumes, _ := getVolumes(cmd, toComplete)
+ objs := containers
+ objs = append(objs, pods...)
+ objs = append(objs, volumes...)
+ return objs, cobra.ShellCompDirectiveNoFileComp
+}
+
// AutocompleteContainersAndPods - Autocomplete container names and pod names.
func AutocompleteContainersAndPods(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if !validCurrentCmdLine(cmd, args, toComplete) {
diff --git a/cmd/podman/generate/generate.go b/cmd/podman/generate/generate.go
index c559b9e1b..a6d39bbbe 100644
--- a/cmd/podman/generate/generate.go
+++ b/cmd/podman/generate/generate.go
@@ -12,8 +12,8 @@ var (
// Command: podman _generate_
generateCmd = &cobra.Command{
Use: "generate",
- Short: "Generate structured data based on containers and pods.",
- Long: "Generate structured data (e.g., Kubernetes yaml or systemd units) based on containers and pods.",
+ Short: "Generate structured data based on containers, pods or volumes.",
+ Long: "Generate structured data (e.g., Kubernetes YAML or systemd units) based on containers, pods or volumes.",
RunE: validate.SubCommandExists,
}
containerConfig = util.DefaultContainerConfig()
diff --git a/cmd/podman/generate/kube.go b/cmd/podman/generate/kube.go
index 90e58271f..9767b0e06 100644
--- a/cmd/podman/generate/kube.go
+++ b/cmd/podman/generate/kube.go
@@ -17,20 +17,22 @@ import (
var (
kubeOptions = entities.GenerateKubeOptions{}
kubeFile = ""
- kubeDescription = `Command generates Kubernetes pod and service YAML (v1 specification) from Podman containers or a pod.
+ kubeDescription = `Command generates Kubernetes Pod, Service or PersistenVolumeClaim YAML (v1 specification) from Podman containers, pods or volumes.
-Whether the input is for a container or pod, Podman will always generate the specification as a pod.`
+ Whether the input is for a container or pod, Podman will always generate the specification as a pod.`
kubeCmd = &cobra.Command{
- Use: "kube [options] {CONTAINER...|POD}",
- Short: "Generate Kubernetes YAML from a container or pod.",
+ Use: "kube [options] {CONTAINER...|POD...|VOLUME...}",
+ Short: "Generate Kubernetes YAML from containers, pods or volumes.",
Long: kubeDescription,
RunE: kube,
Args: cobra.MinimumNArgs(1),
- ValidArgsFunction: common.AutocompleteContainersAndPods,
+ ValidArgsFunction: common.AutocompleteForKube,
Example: `podman generate kube ctrID
podman generate kube podID
- podman generate kube --service podID`,
+ podman generate kube --service podID
+ podman generate kube volumeName
+ podman generate kube ctrID podID volumeName --service`,
}
)
diff --git a/cmd/podman/play/kube.go b/cmd/podman/play/kube.go
index 4c0f7f39e..ddba5dc0f 100644
--- a/cmd/podman/play/kube.go
+++ b/cmd/podman/play/kube.go
@@ -32,11 +32,11 @@ var (
kubeOptions = playKubeOptionsWrapper{}
kubeDescription = `Command reads in a structured file of Kubernetes YAML.
- It creates the pod and containers described in the YAML. The containers within the pod are then started and the ID of the new Pod is output.`
+ It creates pods or volumes based on the Kubernetes kind described in the YAML. Supported kinds are Pods, Deployments and PersistentVolumeClaims.`
kubeCmd = &cobra.Command{
Use: "kube [options] KUBEFILE|-",
- Short: "Play a pod based on Kubernetes YAML.",
+ Short: "Play a pod or volume based on Kubernetes YAML.",
Long: kubeDescription,
RunE: kube,
Args: cobra.ExactArgs(1),
@@ -129,6 +129,15 @@ func kube(cmd *cobra.Command, args []string) error {
return err
}
+ // Print volumes report
+ for i, volume := range report.Volumes {
+ if i == 0 {
+ fmt.Println("Volumes:")
+ }
+ fmt.Println(volume.Name)
+ }
+
+ // Print pods report
for _, pod := range report.Pods {
for _, l := range pod.Logs {
fmt.Fprint(os.Stderr, l)
diff --git a/cmd/podman/play/play.go b/cmd/podman/play/play.go
index 92f87ad80..89c7e0139 100644
--- a/cmd/podman/play/play.go
+++ b/cmd/podman/play/play.go
@@ -11,8 +11,8 @@ var (
// Command: podman _play_
playCmd = &cobra.Command{
Use: "play",
- Short: "Play a pod and its containers from a structured file.",
- Long: "Play structured data (e.g., Kubernetes pod or service yaml) based on containers and pods.",
+ Short: "Play containers, pods or volumes from a structured file.",
+ Long: "Play structured data (e.g., Kubernetes YAML) based on containers, pods or volumes.",
RunE: validate.SubCommandExists,
}
)
diff --git a/cmd/podman/system/unshare.go b/cmd/podman/system/unshare.go
index 5e6ff569b..c07751532 100644
--- a/cmd/podman/system/unshare.go
+++ b/cmd/podman/system/unshare.go
@@ -12,9 +12,10 @@ import (
)
var (
+ unshareOptions = entities.SystemUnshareOptions{}
unshareDescription = "Runs a command in a modified user namespace."
unshareCommand = &cobra.Command{
- Use: "unshare [COMMAND [ARG...]]",
+ Use: "unshare [options] [COMMAND [ARG...]]",
DisableFlagsInUseLine: true,
Short: "Run a command in a modified user namespace",
Long: unshareDescription,
@@ -33,6 +34,7 @@ func init() {
})
flags := unshareCommand.Flags()
flags.SetInterspersed(false)
+ flags.BoolVar(&unshareOptions.RootlessCNI, "rootless-cni", false, "Join the rootless network namespace used for CNI networking")
}
func unshare(cmd *cobra.Command, args []string) error {
@@ -49,5 +51,5 @@ func unshare(cmd *cobra.Command, args []string) error {
args = []string{shell}
}
- return registry.ContainerEngine().Unshare(registry.Context(), args)
+ return registry.ContainerEngine().Unshare(registry.Context(), args, unshareOptions)
}
diff --git a/docs/source/markdown/podman-generate-kube.1.md b/docs/source/markdown/podman-generate-kube.1.md
index 90b4c59fb..a426d2c3c 100644
--- a/docs/source/markdown/podman-generate-kube.1.md
+++ b/docs/source/markdown/podman-generate-kube.1.md
@@ -1,14 +1,16 @@
% podman-generate-kube(1)
## NAME
-podman-generate-kube - Generate Kubernetes YAML based on a pod or container
+podman-generate-kube - Generate Kubernetes YAML based on containers, pods or volumes
## SYNOPSIS
-**podman generate kube** [*options*] *container...* | *pod*
+**podman generate kube** [*options*] *container...* | *pod...* | *volume...*
## DESCRIPTION
-**podman generate kube** will generate Kubernetes Pod YAML (v1 specification) from Podman from one or more containers or a single pod. Whether
-the input is for containers or a pod, Podman will always generate the specification as a Pod. The input may be in the form
-of a pod or one or more container names or IDs.
+**podman generate kube** will generate Kubernetes YAML (v1 specification) from Podman containers, pods or volumes. Whether
+the input is for containers or pods, Podman will always generate the specification as a Pod. The input may be in the form
+of one or more containers, pods or volumes names or IDs.
+
+`Podman Containers or Pods`
Volumes appear in the generated YAML according to two different volume types. Bind-mounted volumes become *hostPath* volume types and named volumes become *persistentVolumeClaim* volume types. Generated *hostPath* volume types will be one of three subtypes depending on the state of the host path: *DirectoryOrCreate* when no file or directory exists at the host, *Directory* when host path is a directory, or *File* when host path is a file. The value for *claimName* for a *persistentVolumeClaim* is the name of the named volume registered in Podman.
diff --git a/docs/source/markdown/podman-generate.1.md b/docs/source/markdown/podman-generate.1.md
index 82c67fdb1..c2060d1a4 100644
--- a/docs/source/markdown/podman-generate.1.md
+++ b/docs/source/markdown/podman-generate.1.md
@@ -1,19 +1,19 @@
% podman-generate(1)
## NAME
-podman\-generate - Generate structured data based for a containers and pods
+podman\-generate - Generate structured data based on containers, pods or volumes
## SYNOPSIS
**podman generate** *subcommand*
## DESCRIPTION
-The generate command will create structured output (like YAML) based on a container or pod.
+The generate command will create structured output (like YAML) based on a container, pod or volume.
## COMMANDS
| Command | Man Page | Description |
|---------|------------------------------------------------------------|-------------------------------------------------------------------------------------|
-| kube | [podman-generate-kube(1)](podman-generate-kube.1.md) | Generate Kubernetes YAML based on a pod or container. |
+| kube | [podman-generate-kube(1)](podman-generate-kube.1.md) | Generate Kubernetes YAML based on containers, pods or volumes. |
| systemd | [podman-generate-systemd(1)](podman-generate-systemd.1.md) | Generate systemd unit file(s) for a container or pod. |
diff --git a/docs/source/markdown/podman-play-kube.1.md b/docs/source/markdown/podman-play-kube.1.md
index 6206a2ea9..91899a8bd 100644
--- a/docs/source/markdown/podman-play-kube.1.md
+++ b/docs/source/markdown/podman-play-kube.1.md
@@ -1,22 +1,40 @@
% podman-play-kube(1)
## NAME
-podman-play-kube - Create pods and containers based on Kubernetes YAML
+podman-play-kube - Create containers, pods or volumes based on Kubernetes YAML
## SYNOPSIS
**podman play kube** [*options*] *file.yml|-*
## DESCRIPTION
-**podman play kube** will read in a structured file of Kubernetes YAML. It will then recreate the pod and containers described in the YAML. The containers within the pod are then started and the ID of the new Pod is output. If the yaml file is specified as "-" then `podman play kube` with read the yaml file from stdin.
+**podman play kube** will read in a structured file of Kubernetes YAML. It will then recreate the containers, pods or volumes described in the YAML. Containers within a pod are then started and the ID of the new Pod or the name of the new Volume is output. If the yaml file is specified as "-" then `podman play kube` will read the YAML file from stdin.
Ideally the input file would be one created by Podman (see podman-generate-kube(1)). This would guarantee a smooth import and expected results.
+Currently, the supported Kubernetes kinds are:
+- Pod
+- Deployment
+- PersistentVolumeClaim
+
+`Kubernetes Pods or Deployments`
+
Only two volume types are supported by play kube, the *hostPath* and *persistentVolumeClaim* volume types. For the *hostPath* volume type, only the *default (empty)*, *DirectoryOrCreate*, *Directory*, *FileOrCreate*, *File*, and *Socket* subtypes are supported. The *CharDevice* and *BlockDevice* subtypes are not supported. Podman interprets the value of *hostPath* *path* as a file path when it contains at least one forward slash, otherwise Podman treats the value as the name of a named volume. When using a *persistentVolumeClaim*, the value for *claimName* is the name for the Podman named volume.
Note: *hostPath* volume types created by play kube will be given an SELinux private label (Z)
Note: If the `:latest` tag is used, Podman will attempt to pull the image from a registry. If the image was built locally with Podman or Buildah, it will have `localhost` as the domain, in that case, Podman will use the image from the local store even if it has the `:latest` tag.
+`Kubernetes PersistentVolumeClaims`
+
+A Kubernetes PersistentVolumeClaim represents a Podman named volume. Only the PersistentVolumeClaim name is required by Podman to create a volume. Kubernetes annotations can be used to make use of the available options for Podman volumes.
+
+- volume.podman.io/driver
+- volume.podman.io/device
+- volume.podman.io/type
+- volume.podman.io/uid
+- volume.podman.io/gid
+- volume.podman.io/mount-options
+
## OPTIONS
#### **\-\-authfile**=*path*
diff --git a/docs/source/markdown/podman-play.1.md b/docs/source/markdown/podman-play.1.md
index 364baad60..39101ad31 100644
--- a/docs/source/markdown/podman-play.1.md
+++ b/docs/source/markdown/podman-play.1.md
@@ -1,20 +1,20 @@
% podman-play(1)
## NAME
-podman\-play - Play pods and containers based on a structured input file
+podman\-play - Play containers, pods or volumes based on a structured input file
## SYNOPSIS
**podman play** *subcommand*
## DESCRIPTION
-The play command will recreate pods and containers based on the input from a structured (like YAML)
+The play command will recreate containers, pods or volumes based on the input from a structured (like YAML)
file input. Containers will be automatically started.
## COMMANDS
| Command | Man Page | Description |
| ------- | --------------------------------------------------- | ---------------------------------------------------------------------------- |
-| kube | [podman-play-kube(1)](podman-play-kube.1.md) | Create pods and containers based on Kubernetes YAML. |
+| kube | [podman-play-kube(1)](podman-play-kube.1.md) | Create containers, pods or volumes based on Kubernetes YAML. |
## SEE ALSO
podman, podman-pod(1), podman-container(1), podman-generate(1), podman-play(1), podman-play-kube(1)
diff --git a/docs/source/markdown/podman-unshare.1.md b/docs/source/markdown/podman-unshare.1.md
index 239213981..4451ad79c 100644
--- a/docs/source/markdown/podman-unshare.1.md
+++ b/docs/source/markdown/podman-unshare.1.md
@@ -24,6 +24,19 @@ The unshare session defines two environment variables:
- **CONTAINERS_GRAPHROOT**: the path to the persistent container's data.
- **CONTAINERS_RUNROOT**: the path to the volatile container's data.
+## OPTIONS
+
+#### **\-\-help**, **-h**
+
+Print usage statement
+
+#### **\-\-rootless-cni**
+
+Join the rootless network namespace used for CNI networking. It can be used to
+connect to a rootless container via IP address (CNI networking). This is otherwise
+not possible from the host network namespace.
+_Note: Using this option with more than one unshare session can have unexpected results._
+
## EXAMPLE
```
@@ -35,6 +48,30 @@ $ podman unshare cat /proc/self/uid_map /proc/self/gid_map
1 10000 65536
0 1000 1
1 10000 65536
+
+$ podman unshare --rootless-cni ip addr
+1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
+ link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
+ inet 127.0.0.1/8 scope host lo
+ valid_lft forever preferred_lft forever
+ inet6 ::1/128 scope host
+ valid_lft forever preferred_lft forever
+2: tap0: <BROADCAST,UP,LOWER_UP> mtu 65520 qdisc fq_codel state UNKNOWN group default qlen 1000
+ link/ether 36:0e:4a:c7:45:7e brd ff:ff:ff:ff:ff:ff
+ inet 10.0.2.100/24 brd 10.0.2.255 scope global tap0
+ valid_lft forever preferred_lft forever
+ inet6 fe80::340e:4aff:fec7:457e/64 scope link
+ valid_lft forever preferred_lft forever
+3: cni-podman2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
+ link/ether 5e:3a:71:d2:b4:3a brd ff:ff:ff:ff:ff:ff
+ inet 10.89.1.1/24 brd 10.89.1.255 scope global cni-podman2
+ valid_lft forever preferred_lft forever
+ inet6 fe80::5c3a:71ff:fed2:b43a/64 scope link
+ valid_lft forever preferred_lft forever
+4: vethd4ba3a2f@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master cni-podman2 state UP group default
+ link/ether 8a:c9:56:32:17:0c brd ff:ff:ff:ff:ff:ff link-netnsid 0
+ inet6 fe80::88c9:56ff:fe32:170c/64 scope link
+ valid_lft forever preferred_lft forever
```
diff --git a/docs/source/markdown/podman.1.md b/docs/source/markdown/podman.1.md
index 5755b45ac..87bcd8802 100644
--- a/docs/source/markdown/podman.1.md
+++ b/docs/source/markdown/podman.1.md
@@ -223,7 +223,7 @@ the exit codes follow the `chroot` standard, see below:
| [podman-events(1)](podman-events.1.md) | Monitor Podman events |
| [podman-exec(1)](podman-exec.1.md) | Execute a command in a running container. |
| [podman-export(1)](podman-export.1.md) | Export a container's filesystem contents as a tar archive. |
-| [podman-generate(1)](podman-generate.1.md) | Generate structured data based for a containers and pods. |
+| [podman-generate(1)](podman-generate.1.md) | Generate structured data based on containers, pods or volumes. |
| [podman-healthcheck(1)](podman-healthcheck.1.md) | Manage healthchecks for containers |
| [podman-history(1)](podman-history.1.md) | Show the history of an image. |
| [podman-image(1)](podman-image.1.md) | Manage images. |
@@ -242,7 +242,7 @@ the exit codes follow the `chroot` standard, see below:
| [podman-mount(1)](podman-mount.1.md) | Mount a working container's root filesystem. |
| [podman-network(1)](podman-network.1.md) | Manage Podman CNI networks. |
| [podman-pause(1)](podman-pause.1.md) | Pause one or more containers. |
-| [podman-play(1)](podman-play.1.md) | Play pods and containers based on a structured input file. |
+| [podman-play(1)](podman-play.1.md) | Play containers, pods or volumes based on a structured input file. |
| [podman-pod(1)](podman-pod.1.md) | Management tool for groups of containers, called pods. |
| [podman-port(1)](podman-port.1.md) | List port mappings for a container. |
| [podman-ps(1)](podman-ps.1.md) | Prints out information about containers. |
diff --git a/libpod/kube.go b/libpod/kube.go
index b4dd4f10a..11ccaeadc 100644
--- a/libpod/kube.go
+++ b/libpod/kube.go
@@ -16,6 +16,7 @@ import (
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
v1 "k8s.io/api/core/v1"
+ "k8s.io/apimachinery/pkg/api/resource"
v12 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@@ -113,6 +114,50 @@ func (p *Pod) getInfraContainer() (*Container, error) {
return p.runtime.GetContainer(infraID)
}
+// GenerateForKube generates a v1.PersistentVolumeClaim from a libpod volume.
+func (v *Volume) GenerateForKube() *v1.PersistentVolumeClaim {
+ annotations := make(map[string]string)
+ annotations[util.VolumeDriverAnnotation] = v.Driver()
+
+ for k, v := range v.Options() {
+ switch k {
+ case "o":
+ annotations[util.VolumeMountOptsAnnotation] = v
+ case "device":
+ annotations[util.VolumeDeviceAnnotation] = v
+ case "type":
+ annotations[util.VolumeTypeAnnotation] = v
+ case "UID":
+ annotations[util.VolumeUIDAnnotation] = v
+ case "GID":
+ annotations[util.VolumeGIDAnnotation] = v
+ }
+ }
+
+ return &v1.PersistentVolumeClaim{
+ TypeMeta: v12.TypeMeta{
+ Kind: "PersistentVolumeClaim",
+ APIVersion: "v1",
+ },
+ ObjectMeta: v12.ObjectMeta{
+ Name: v.Name(),
+ Labels: v.Labels(),
+ Annotations: annotations,
+ CreationTimestamp: v12.Now(),
+ },
+ Spec: v1.PersistentVolumeClaimSpec{
+ Resources: v1.ResourceRequirements{
+ Requests: map[v1.ResourceName]resource.Quantity{
+ v1.ResourceStorage: resource.MustParse("1Gi"),
+ },
+ },
+ AccessModes: []v1.PersistentVolumeAccessMode{
+ v1.ReadWriteOnce,
+ },
+ },
+ }
+}
+
// GenerateKubeServiceFromV1Pod creates a v1 service object from a v1 pod object
func GenerateKubeServiceFromV1Pod(pod *v1.Pod, servicePorts []v1.ServicePort) v1.Service {
service := v1.Service{}
diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go
index 3c4014c73..6e2c2880f 100644
--- a/libpod/networking_linux.go
+++ b/libpod/networking_linux.go
@@ -105,13 +105,13 @@ func (r *Runtime) getPodNetwork(id, name, nsPath string, networks []string, port
return ctrNetwork
}
-type rootlessCNI struct {
+type RootlessCNI struct {
ns ns.NetNS
dir string
lock lockfile.Locker
}
-func (r *rootlessCNI) Do(toRun func() error) error {
+func (r *RootlessCNI) Do(toRun func() error) error {
err := r.ns.Do(func(_ ns.NetNS) error {
// before we can run the given function
// we have to setup all mounts correctly
@@ -174,9 +174,14 @@ func (r *rootlessCNI) Do(toRun func() error) error {
return err
}
-// cleanup the rootless cni namespace if needed
+// Cleanup the rootless cni namespace if needed
// check if we have running containers with the bridge network mode
-func (r *rootlessCNI) cleanup(runtime *Runtime) error {
+func (r *RootlessCNI) Cleanup(runtime *Runtime) error {
+ _, err := os.Stat(r.dir)
+ if os.IsNotExist(err) {
+ // the directory does not exists no need for cleanup
+ return nil
+ }
r.lock.Lock()
defer r.lock.Unlock()
running := func(c *Container) bool {
@@ -234,10 +239,10 @@ func (r *rootlessCNI) cleanup(runtime *Runtime) error {
return nil
}
-// getRootlessCNINetNs returns the rootless cni object. If create is set to true
+// GetRootlessCNINetNs returns the rootless cni object. If create is set to true
// the rootless cni namespace will be created if it does not exists already.
-func (r *Runtime) getRootlessCNINetNs(new bool) (*rootlessCNI, error) {
- var rootlessCNINS *rootlessCNI
+func (r *Runtime) GetRootlessCNINetNs(new bool) (*RootlessCNI, error) {
+ var rootlessCNINS *RootlessCNI
if rootless.IsRootless() {
runDir, err := util.GetRuntimeDir()
if err != nil {
@@ -421,7 +426,7 @@ func (r *Runtime) getRootlessCNINetNs(new bool) (*rootlessCNI, error) {
os.Setenv("PATH", path)
}
- rootlessCNINS = &rootlessCNI{
+ rootlessCNINS = &RootlessCNI{
ns: ns,
dir: cniDir,
lock: lock,
@@ -433,7 +438,7 @@ func (r *Runtime) getRootlessCNINetNs(new bool) (*rootlessCNI, error) {
// setUpOCICNIPod will set up the cni networks, on error it will also tear down the cni
// networks. If rootless it will join/create the rootless cni namespace.
func (r *Runtime) setUpOCICNIPod(podNetwork ocicni.PodNetwork) ([]ocicni.NetResult, error) {
- rootlessCNINS, err := r.getRootlessCNINetNs(true)
+ rootlessCNINS, err := r.GetRootlessCNINetNs(true)
if err != nil {
return nil, err
}
@@ -651,7 +656,7 @@ func (r *Runtime) closeNetNS(ctr *Container) error {
// Tear down a container's CNI network configuration and joins the
// rootless net ns as rootless user
func (r *Runtime) teardownOCICNIPod(podNetwork ocicni.PodNetwork) error {
- rootlessCNINS, err := r.getRootlessCNINetNs(false)
+ rootlessCNINS, err := r.GetRootlessCNINetNs(false)
if err != nil {
return err
}
@@ -665,7 +670,7 @@ func (r *Runtime) teardownOCICNIPod(podNetwork ocicni.PodNetwork) error {
// execute the cni setup in the rootless net ns
err = rootlessCNINS.Do(tearDownPod)
if err == nil {
- err = rootlessCNINS.cleanup(r)
+ err = rootlessCNINS.Cleanup(r)
}
} else {
err = tearDownPod()
diff --git a/pkg/api/handlers/libpod/manifests.go b/pkg/api/handlers/libpod/manifests.go
index 5ababc36b..6a491ae48 100644
--- a/pkg/api/handlers/libpod/manifests.go
+++ b/pkg/api/handlers/libpod/manifests.go
@@ -5,6 +5,7 @@ import (
"encoding/json"
"net/http"
+ "github.com/containers/image/v5/docker/reference"
"github.com/containers/image/v5/manifest"
"github.com/containers/image/v5/types"
"github.com/containers/podman/v3/libpod"
@@ -34,6 +35,16 @@ func ManifestCreate(w http.ResponseWriter, r *http.Request) {
errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
return
}
+
+ // TODO: (jhonce) When c/image is refactored the roadmap calls for this check to be pushed into that library.
+ for _, n := range query.Name {
+ if _, err := reference.ParseNormalizedNamed(n); err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "invalid image name %s", n))
+ return
+ }
+ }
+
rtc, err := runtime.GetConfig()
if err != nil {
utils.InternalServerError(w, err)
@@ -75,11 +86,16 @@ func ManifestInspect(w http.ResponseWriter, r *http.Request) {
utils.Error(w, "Something went wrong.", http.StatusNotFound, inspectError)
return
}
+
var list manifest.Schema2List
if err := json.Unmarshal(inspectReport, &list); err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Unmarshal()"))
return
}
+ if list.Manifests == nil {
+ list.Manifests = make([]manifest.Schema2ManifestDescriptor, 0)
+ }
+
utils.WriteResponse(w, http.StatusOK, &list)
}
diff --git a/pkg/bindings/network/network.go b/pkg/bindings/network/network.go
index 6f3aa8594..17451c273 100644
--- a/pkg/bindings/network/network.go
+++ b/pkg/bindings/network/network.go
@@ -184,7 +184,13 @@ func Exists(ctx context.Context, nameOrID string, options *ExistsOptions) (bool,
// Prune removes unused CNI networks
func Prune(ctx context.Context, options *PruneOptions) ([]*entities.NetworkPruneReport, error) {
- // TODO Filters is not implemented
+ if options == nil {
+ options = new(PruneOptions)
+ }
+ params, err := options.ToParams()
+ if err != nil {
+ return nil, err
+ }
var (
prunedNetworks []*entities.NetworkPruneReport
)
@@ -193,7 +199,7 @@ func Prune(ctx context.Context, options *PruneOptions) ([]*entities.NetworkPrune
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/networks/prune", nil, nil)
+ response, err := conn.DoRequest(nil, http.MethodPost, "/networks/prune", params, nil)
if err != nil {
return nil, err
}
diff --git a/pkg/bindings/network/types.go b/pkg/bindings/network/types.go
index 47dce67c7..e62ae8f52 100644
--- a/pkg/bindings/network/types.go
+++ b/pkg/bindings/network/types.go
@@ -79,4 +79,7 @@ type ExistsOptions struct {
// PruneOptions are optional options for removing unused
// CNI networks
type PruneOptions struct {
+ // Filters are applied to the prune of networks to be more
+ // specific on choosing
+ Filters map[string][]string
}
diff --git a/pkg/bindings/network/types_prune_options.go b/pkg/bindings/network/types_prune_options.go
index 84e1a8b32..f17e09d69 100644
--- a/pkg/bindings/network/types_prune_options.go
+++ b/pkg/bindings/network/types_prune_options.go
@@ -19,3 +19,19 @@ func (o *PruneOptions) Changed(fieldName string) bool {
func (o *PruneOptions) ToParams() (url.Values, error) {
return util.ToParams(o)
}
+
+// WithFilters
+func (o *PruneOptions) WithFilters(value map[string][]string) *PruneOptions {
+ v := value
+ o.Filters = v
+ return o
+}
+
+// GetFilters
+func (o *PruneOptions) GetFilters() map[string][]string {
+ var filters map[string][]string
+ if o.Filters == nil {
+ return filters
+ }
+ return o.Filters
+}
diff --git a/pkg/bindings/test/networks_test.go b/pkg/bindings/test/networks_test.go
index ef20235ae..df7d7cd1c 100644
--- a/pkg/bindings/test/networks_test.go
+++ b/pkg/bindings/test/networks_test.go
@@ -37,6 +37,53 @@ var _ = Describe("Podman networks", func() {
bt.cleanup()
})
+ It("podman prune unused networks with filters", func() {
+ name := "foobar"
+ opts := network.CreateOptions{
+ Name: &name,
+ }
+ _, err = network.Create(connText, &opts)
+ Expect(err).To(BeNil())
+
+ // Invalid filters should return error
+ filtersIncorrect := map[string][]string{
+ "status": {"dummy"},
+ }
+ _, err = network.Prune(connText, new(network.PruneOptions).WithFilters(filtersIncorrect))
+ Expect(err).ToNot(BeNil())
+
+ // List filter params should not work with prune.
+ filtersIncorrect = map[string][]string{
+ "name": {name},
+ }
+ _, err = network.Prune(connText, new(network.PruneOptions).WithFilters(filtersIncorrect))
+ Expect(err).ToNot(BeNil())
+
+ // Mismatched label, correct filter params => no network should be pruned.
+ filtersIncorrect = map[string][]string{
+ "label": {"xyz"},
+ }
+ pruneResponse, err := network.Prune(connText, new(network.PruneOptions).WithFilters(filtersIncorrect))
+ Expect(err).To(BeNil())
+ Expect(len(pruneResponse)).To(Equal(0))
+
+ // Mismatched until, correct filter params => no network should be pruned.
+ filters := map[string][]string{
+ "until": {"50"}, // January 1, 1970
+ }
+ pruneResponse, err = network.Prune(connText, new(network.PruneOptions).WithFilters(filters))
+ Expect(err).To(BeNil())
+ Expect(len(pruneResponse)).To(Equal(0))
+
+ // Valid filter params => network should be pruned now.
+ filters = map[string][]string{
+ "until": {"5000000000"}, //June 11, 2128
+ }
+ pruneResponse, err = network.Prune(connText, new(network.PruneOptions).WithFilters(filters))
+ Expect(err).To(BeNil())
+ Expect(len(pruneResponse)).To(Equal(1))
+ })
+
It("create network", func() {
// create a network with blank config should work
_, err = network.Create(connText, &network.CreateOptions{})
diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go
index bcab617af..f695d32fd 100644
--- a/pkg/domain/entities/engine_container.go
+++ b/pkg/domain/entities/engine_container.go
@@ -88,7 +88,7 @@ type ContainerEngine interface {
SecretRm(ctx context.Context, nameOrID []string, opts SecretRmOptions) ([]*SecretRmReport, error)
Shutdown(ctx context.Context)
SystemDf(ctx context.Context, options SystemDfOptions) (*SystemDfReport, error)
- Unshare(ctx context.Context, args []string) error
+ Unshare(ctx context.Context, args []string, options SystemUnshareOptions) error
Version(ctx context.Context) (*SystemVersionReport, error)
VolumeCreate(ctx context.Context, opts VolumeCreateOptions) (*IDOrNameResponse, error)
VolumeExists(ctx context.Context, namesOrID string) (*BoolReport, error)
diff --git a/pkg/domain/entities/play.go b/pkg/domain/entities/play.go
index 6883fe6c5..cd8bb9506 100644
--- a/pkg/domain/entities/play.go
+++ b/pkg/domain/entities/play.go
@@ -45,8 +45,16 @@ type PlayKubePod struct {
ContainerErrors []string
}
+// PlayKubeVolume represents a single volume created by play kube.
+type PlayKubeVolume struct {
+ // Name - Name of the volume created by play kube.
+ Name string
+}
+
// PlayKubeReport contains the results of running play kube.
type PlayKubeReport struct {
// Pods - pods created by play kube.
Pods []PlayKubePod
+ // Volumes - volumes created by play kube.
+ Volumes []PlayKubeVolume
}
diff --git a/pkg/domain/entities/system.go b/pkg/domain/entities/system.go
index 1a671d59e..31a6185dc 100644
--- a/pkg/domain/entities/system.go
+++ b/pkg/domain/entities/system.go
@@ -98,6 +98,11 @@ type SystemVersionReport struct {
Server *define.Version `json:",omitempty"`
}
+// SystemUnshareOptions describes the options for the unshare command
+type SystemUnshareOptions struct {
+ RootlessCNI bool
+}
+
type ComponentVersion struct {
types.Version
}
diff --git a/pkg/domain/infra/abi/generate.go b/pkg/domain/infra/abi/generate.go
index 94f649e15..b0853b554 100644
--- a/pkg/domain/infra/abi/generate.go
+++ b/pkg/domain/infra/abi/generate.go
@@ -4,6 +4,7 @@ import (
"bytes"
"context"
"fmt"
+ "strings"
"github.com/containers/podman/v3/libpod"
"github.com/containers/podman/v3/libpod/define"
@@ -43,120 +44,174 @@ func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string,
func (ic *ContainerEngine) GenerateKube(ctx context.Context, nameOrIDs []string, options entities.GenerateKubeOptions) (*entities.GenerateKubeReport, error) {
var (
- pods []*libpod.Pod
- ctrs []*libpod.Container
- kubePods []*k8sAPI.Pod
- kubeServices []k8sAPI.Service
- content []byte
+ pods []*libpod.Pod
+ ctrs []*libpod.Container
+ vols []*libpod.Volume
+ podContent [][]byte
+ content [][]byte
)
+
+ // Lookup for podman objects.
for _, nameOrID := range nameOrIDs {
- // Get the container in question
+ // Let's assume it's a container, so get the container.
ctr, err := ic.Libpod.LookupContainer(nameOrID)
if err != nil {
- pod, err := ic.Libpod.LookupPod(nameOrID)
- if err != nil {
+ if !strings.Contains(err.Error(), "no such container") {
return nil, err
}
- pods = append(pods, pod)
} else {
if len(ctr.Dependencies()) > 0 {
return nil, errors.Wrapf(define.ErrNotImplemented, "containers with dependencies")
}
- // we cannot deal with ctrs already in a pod
+ // we cannot deal with ctrs already in a pod.
if len(ctr.PodID()) > 0 {
return nil, errors.Errorf("container %s is associated with pod %s: use generate on the pod itself", ctr.ID(), ctr.PodID())
}
ctrs = append(ctrs, ctr)
+ continue
+ }
+
+ // Maybe it's a pod.
+ pod, err := ic.Libpod.LookupPod(nameOrID)
+ if err != nil {
+ if !strings.Contains(err.Error(), "no such pod") {
+ return nil, err
+ }
+ } else {
+ pods = append(pods, pod)
+ continue
+ }
+
+ // Or volume.
+ vol, err := ic.Libpod.LookupVolume(nameOrID)
+ if err != nil {
+ if !strings.Contains(err.Error(), "no such volume") {
+ return nil, err
+ }
+ } else {
+ vols = append(vols, vol)
+ continue
}
+
+ // If it reaches here is because the name or id did not exist.
+ return nil, errors.Errorf("Name or ID %q not found", nameOrID)
}
- // check our inputs
- if len(pods) > 0 && len(ctrs) > 0 {
- return nil, errors.New("cannot generate pods and containers at the same time")
+ // Generate kube persistent volume claims from volumes.
+ if len(vols) >= 1 {
+ pvs, err := getKubePVCs(vols)
+ if err != nil {
+ return nil, err
+ }
+
+ content = append(content, pvs...)
}
+ // Generate kube pods and services from pods.
if len(pods) >= 1 {
pos, svcs, err := getKubePods(pods, options.Service)
if err != nil {
return nil, err
}
- kubePods = append(kubePods, pos...)
+ podContent = append(podContent, pos...)
if options.Service {
- kubeServices = append(kubeServices, svcs...)
+ content = append(content, svcs...)
}
- } else {
+ }
+
+ // Generate the kube pods from containers.
+ if len(ctrs) >= 1 {
po, err := libpod.GenerateForKube(ctrs)
if err != nil {
return nil, err
}
- kubePods = append(kubePods, po)
+ b, err := generateKubeYAML(po)
+ if err != nil {
+ return nil, err
+ }
+
+ podContent = append(podContent, b)
if options.Service {
- kubeServices = append(kubeServices, libpod.GenerateKubeServiceFromV1Pod(po, []k8sAPI.ServicePort{}))
+ b, err := generateKubeYAML(libpod.GenerateKubeServiceFromV1Pod(po, []k8sAPI.ServicePort{}))
+ if err != nil {
+ return nil, err
+ }
+ content = append(content, b)
}
}
- content, err := generateKubeOutput(kubePods, kubeServices, options.Service)
+ // Content order is based on helm install order (secret, persistentVolumeClaim, service, pod).
+ content = append(content, podContent...)
+
+ // Generate kube YAML file from all kube kinds.
+ k, err := generateKubeOutput(content)
if err != nil {
return nil, err
}
- return &entities.GenerateKubeReport{Reader: bytes.NewReader(content)}, nil
+ return &entities.GenerateKubeReport{Reader: bytes.NewReader(k)}, nil
}
-func getKubePods(pods []*libpod.Pod, getService bool) ([]*k8sAPI.Pod, []k8sAPI.Service, error) {
- kubePods := make([]*k8sAPI.Pod, 0)
- kubeServices := make([]k8sAPI.Service, 0)
+// getKubePods returns kube pod and service YAML files from podman pods.
+func getKubePods(pods []*libpod.Pod, getService bool) ([][]byte, [][]byte, error) {
+ pos := [][]byte{}
+ svcs := [][]byte{}
for _, p := range pods {
- po, svc, err := p.GenerateForKube()
+ po, sp, err := p.GenerateForKube()
+ if err != nil {
+ return nil, nil, err
+ }
+
+ b, err := generateKubeYAML(po)
if err != nil {
return nil, nil, err
}
+ pos = append(pos, b)
- kubePods = append(kubePods, po)
if getService {
- kubeServices = append(kubeServices, libpod.GenerateKubeServiceFromV1Pod(po, svc))
+ b, err := generateKubeYAML(libpod.GenerateKubeServiceFromV1Pod(po, sp))
+ if err != nil {
+ return nil, nil, err
+ }
+ svcs = append(svcs, b)
}
}
- return kubePods, kubeServices, nil
+ return pos, svcs, nil
}
-func generateKubeOutput(kubePods []*k8sAPI.Pod, kubeServices []k8sAPI.Service, hasService bool) ([]byte, error) {
- output := make([]byte, 0)
- marshalledPods := make([]byte, 0)
- marshalledServices := make([]byte, 0)
+// getKubePVCs returns kube persistent volume claim YAML files from podman volumes.
+func getKubePVCs(volumes []*libpod.Volume) ([][]byte, error) {
+ pvs := [][]byte{}
- for i, p := range kubePods {
- if i != 0 {
- marshalledPods = append(marshalledPods, []byte("---\n")...)
- }
-
- b, err := yaml.Marshal(p)
+ for _, v := range volumes {
+ b, err := generateKubeYAML(v.GenerateForKube())
if err != nil {
return nil, err
}
-
- marshalledPods = append(marshalledPods, b...)
+ pvs = append(pvs, b)
}
- if hasService {
- for i, s := range kubeServices {
- if i != 0 {
- marshalledServices = append(marshalledServices, []byte("---\n")...)
- }
-
- b, err := yaml.Marshal(s)
- if err != nil {
- return nil, err
- }
+ return pvs, nil
+}
- marshalledServices = append(marshalledServices, b...)
- }
+// generateKubeYAML marshalls a kube kind into a YAML file.
+func generateKubeYAML(kubeKind interface{}) ([]byte, error) {
+ b, err := yaml.Marshal(kubeKind)
+ if err != nil {
+ return nil, err
}
+ return b, nil
+}
+
+// generateKubeOutput generates kube YAML file containing multiple kube kinds.
+func generateKubeOutput(content [][]byte) ([]byte, error) {
+ output := make([]byte, 0)
+
header := `# Generation of Kubernetes YAML is still under development!
#
# Save the output of this file and use kubectl create -f to import
@@ -169,13 +224,18 @@ func generateKubeOutput(kubePods []*k8sAPI.Pod, kubeServices []k8sAPI.Service, h
return nil, err
}
+ // Add header to kube YAML file.
output = append(output, []byte(fmt.Sprintf(header, podmanVersion.Version))...)
- // kube generate order is based on helm install order (service, pod...)
- if hasService {
- output = append(output, marshalledServices...)
- output = append(output, []byte("---\n")...)
+
+ // kube generate order is based on helm install order (secret, persistentVolume, service, pod...).
+ // Add kube kinds.
+ for i, b := range content {
+ if i != 0 {
+ output = append(output, []byte("---\n")...)
+ }
+
+ output = append(output, b...)
}
- output = append(output, marshalledPods...)
return output, nil
}
diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go
index 3b5c141d7..52f759f13 100644
--- a/pkg/domain/infra/abi/play.go
+++ b/pkg/domain/infra/abi/play.go
@@ -7,6 +7,7 @@ import (
"io"
"io/ioutil"
"os"
+ "strconv"
"strings"
"github.com/containers/common/pkg/secrets"
@@ -43,6 +44,12 @@ func (ic *ContainerEngine) PlayKube(ctx context.Context, path string, options en
return nil, err
}
+ // sort kube kinds
+ documentList, err = sortKubeKinds(documentList)
+ if err != nil {
+ return nil, errors.Wrapf(err, "unable to sort kube kinds in %q", path)
+ }
+
// create pod on each document if it is a pod or deployment
// any other kube kind will be skipped
for _, document := range documentList {
@@ -84,6 +91,20 @@ func (ic *ContainerEngine) PlayKube(ctx context.Context, path string, options en
report.Pods = append(report.Pods, r.Pods...)
validKinds++
+ case "PersistentVolumeClaim":
+ var pvcYAML v1.PersistentVolumeClaim
+
+ if err := yaml.Unmarshal(document, &pvcYAML); err != nil {
+ return nil, errors.Wrapf(err, "unable to read YAML %q as Kube PersistentVolumeClaim", path)
+ }
+
+ r, err := ic.playKubePVC(ctx, &pvcYAML, options)
+ if err != nil {
+ return nil, err
+ }
+
+ report.Volumes = append(report.Volumes, r.Volumes...)
+ validKinds++
default:
logrus.Infof("kube kind %s not supported", kind)
continue
@@ -313,6 +334,68 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
return &report, nil
}
+// playKubePVC creates a podman volume from a kube persistent volume claim.
+func (ic *ContainerEngine) playKubePVC(ctx context.Context, pvcYAML *v1.PersistentVolumeClaim, options entities.PlayKubeOptions) (*entities.PlayKubeReport, error) {
+ var report entities.PlayKubeReport
+ opts := make(map[string]string)
+
+ // Get pvc name.
+ // This is the only required pvc attribute to create a podman volume.
+ name := pvcYAML.GetName()
+ if strings.TrimSpace(name) == "" {
+ return nil, fmt.Errorf("persistent volume claim name can not be empty")
+ }
+
+ // Create podman volume options.
+ volOptions := []libpod.VolumeCreateOption{
+ libpod.WithVolumeName(name),
+ libpod.WithVolumeLabels(pvcYAML.GetLabels()),
+ }
+
+ // Get pvc annotations and create remaining podman volume options if available.
+ // These are podman volume options that do not match any of the persistent volume claim
+ // attributes, so they can be configured using annotations since they will not affect k8s.
+ for k, v := range pvcYAML.GetAnnotations() {
+ switch k {
+ case util.VolumeDriverAnnotation:
+ volOptions = append(volOptions, libpod.WithVolumeDriver(v))
+ case util.VolumeDeviceAnnotation:
+ opts["device"] = v
+ case util.VolumeTypeAnnotation:
+ opts["type"] = v
+ case util.VolumeUIDAnnotation:
+ uid, err := strconv.Atoi(v)
+ if err != nil {
+ return nil, errors.Wrapf(err, "cannot convert uid %s to integer", v)
+ }
+ volOptions = append(volOptions, libpod.WithVolumeUID(uid))
+ opts["UID"] = v
+ case util.VolumeGIDAnnotation:
+ gid, err := strconv.Atoi(v)
+ if err != nil {
+ return nil, errors.Wrapf(err, "cannot convert gid %s to integer", v)
+ }
+ volOptions = append(volOptions, libpod.WithVolumeGID(gid))
+ opts["GID"] = v
+ case util.VolumeMountOptsAnnotation:
+ opts["o"] = v
+ }
+ }
+ volOptions = append(volOptions, libpod.WithVolumeOptions(opts))
+
+ // Create volume.
+ vol, err := ic.Libpod.NewVolume(ctx, volOptions...)
+ if err != nil {
+ return nil, err
+ }
+
+ report.Volumes = append(report.Volumes, entities.PlayKubeVolume{
+ Name: vol.Name(),
+ })
+
+ return &report, nil
+}
+
// readConfigMapFromFile returns a kubernetes configMap obtained from --configmap flag
func readConfigMapFromFile(r io.Reader) (v1.ConfigMap, error) {
var cm v1.ConfigMap
@@ -374,3 +457,25 @@ func getKubeKind(obj []byte) (string, error) {
return kubeObject.Kind, nil
}
+
+// sortKubeKinds adds the correct creation order for the kube kinds.
+// Any pod dependecy will be created first like volumes, secrets, etc.
+func sortKubeKinds(documentList [][]byte) ([][]byte, error) {
+ var sortedDocumentList [][]byte
+
+ for _, document := range documentList {
+ kind, err := getKubeKind(document)
+ if err != nil {
+ return nil, err
+ }
+
+ switch kind {
+ case "Pod", "Deployment":
+ sortedDocumentList = append(sortedDocumentList, document)
+ default:
+ sortedDocumentList = append([][]byte{document}, sortedDocumentList...)
+ }
+ }
+
+ return sortedDocumentList, nil
+}
diff --git a/pkg/domain/infra/abi/system.go b/pkg/domain/infra/abi/system.go
index a3e753384..f87f9e370 100644
--- a/pkg/domain/infra/abi/system.go
+++ b/pkg/domain/infra/abi/system.go
@@ -390,13 +390,25 @@ func unshareEnv(graphroot, runroot string) []string {
fmt.Sprintf("CONTAINERS_RUNROOT=%s", runroot))
}
-func (ic *ContainerEngine) Unshare(ctx context.Context, args []string) error {
- cmd := exec.Command(args[0], args[1:]...)
- cmd.Env = unshareEnv(ic.Libpod.StorageConfig().GraphRoot, ic.Libpod.StorageConfig().RunRoot)
- cmd.Stdin = os.Stdin
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
- return cmd.Run()
+func (ic *ContainerEngine) Unshare(ctx context.Context, args []string, options entities.SystemUnshareOptions) error {
+ unshare := func() error {
+ cmd := exec.Command(args[0], args[1:]...)
+ cmd.Env = unshareEnv(ic.Libpod.StorageConfig().GraphRoot, ic.Libpod.StorageConfig().RunRoot)
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ return cmd.Run()
+ }
+
+ if options.RootlessCNI {
+ rootlesscni, err := ic.Libpod.GetRootlessCNINetNs(true)
+ if err != nil {
+ return err
+ }
+ defer rootlesscni.Cleanup(ic.Libpod)
+ return rootlesscni.Do(unshare)
+ }
+ return unshare()
}
func (ic ContainerEngine) Version(ctx context.Context) (*entities.SystemVersionReport, error) {
diff --git a/pkg/domain/infra/tunnel/network.go b/pkg/domain/infra/tunnel/network.go
index adf34460c..7e59e44c2 100644
--- a/pkg/domain/infra/tunnel/network.go
+++ b/pkg/domain/infra/tunnel/network.go
@@ -92,5 +92,6 @@ func (ic *ContainerEngine) NetworkExists(ctx context.Context, networkname string
// Network prune removes unused cni networks
func (ic *ContainerEngine) NetworkPrune(ctx context.Context, options entities.NetworkPruneOptions) ([]*entities.NetworkPruneReport, error) {
- return network.Prune(ic.ClientCtx, nil)
+ opts := new(network.PruneOptions).WithFilters(options.Filters)
+ return network.Prune(ic.ClientCtx, opts)
}
diff --git a/pkg/domain/infra/tunnel/system.go b/pkg/domain/infra/tunnel/system.go
index d2c5063c9..7400d3771 100644
--- a/pkg/domain/infra/tunnel/system.go
+++ b/pkg/domain/infra/tunnel/system.go
@@ -28,7 +28,7 @@ func (ic *ContainerEngine) SystemDf(ctx context.Context, options entities.System
return system.DiskUsage(ic.ClientCtx, nil)
}
-func (ic *ContainerEngine) Unshare(ctx context.Context, args []string) error {
+func (ic *ContainerEngine) Unshare(ctx context.Context, args []string, options entities.SystemUnshareOptions) error {
return errors.New("unshare is not supported on remote clients")
}
diff --git a/pkg/util/kube.go b/pkg/util/kube.go
new file mode 100644
index 000000000..1255cdfc5
--- /dev/null
+++ b/pkg/util/kube.go
@@ -0,0 +1,16 @@
+package util
+
+const (
+ // Kube annotation for podman volume driver.
+ VolumeDriverAnnotation = "volume.podman.io/driver"
+ // Kube annotation for podman volume type.
+ VolumeTypeAnnotation = "volume.podman.io/type"
+ // Kube annotation for podman volume device.
+ VolumeDeviceAnnotation = "volume.podman.io/device"
+ // Kube annotation for podman volume UID.
+ VolumeUIDAnnotation = "volume.podman.io/uid"
+ // Kube annotation for podman volume GID.
+ VolumeGIDAnnotation = "volume.podman.io/gid"
+ // Kube annotation for podman volume mount options.
+ VolumeMountOptsAnnotation = "volume.podman.io/mount-options"
+)
diff --git a/test/apiv2/rest_api/test_rest_v2_0_0.py b/test/apiv2/rest_api/test_rest_v2_0_0.py
index e3874c182..97336cb71 100644
--- a/test/apiv2/rest_api/test_rest_v2_0_0.py
+++ b/test/apiv2/rest_api/test_rest_v2_0_0.py
@@ -727,10 +727,13 @@ class TestApi(unittest.TestCase):
start = json.loads(r.text)
self.assertGreater(len(start["Errs"]), 0, r.text)
+ def test_manifest_409(self):
+ r = requests.post(_url("/manifests/create"), params={"name": "ThisIsAnInvalidImage"})
+ self.assertEqual(r.status_code, 400, r.text)
+
def test_df(self):
r = requests.get(_url("/system/df"))
self.assertEqual(r.status_code, 200, r.text)
-
if __name__ == "__main__":
unittest.main()
diff --git a/test/e2e/generate_kube_test.go b/test/e2e/generate_kube_test.go
index 1c53307bd..c3586d9b6 100644
--- a/test/e2e/generate_kube_test.go
+++ b/test/e2e/generate_kube_test.go
@@ -6,6 +6,7 @@ import (
"path/filepath"
"strconv"
+ "github.com/containers/podman/v3/pkg/util"
. "github.com/containers/podman/v3/test/utils"
"github.com/ghodss/yaml"
. "github.com/onsi/ginkgo"
@@ -554,7 +555,7 @@ var _ = Describe("Podman generate kube", func() {
Expect(inspect.OutputToString()).To(ContainSubstring(`"pid"`))
})
- It("podman generate kube with pods and containers should fail", func() {
+ It("podman generate kube with pods and containers", func() {
pod1 := podmanTest.Podman([]string{"run", "-dt", "--pod", "new:pod1", ALPINE, "top"})
pod1.WaitWithDefaultTimeout()
Expect(pod1.ExitCode()).To(Equal(0))
@@ -565,7 +566,7 @@ var _ = Describe("Podman generate kube", func() {
kube := podmanTest.Podman([]string{"generate", "kube", "pod1", "top"})
kube.WaitWithDefaultTimeout()
- Expect(kube.ExitCode()).ToNot(Equal(0))
+ Expect(kube.ExitCode()).To(Equal(0))
})
It("podman generate kube with containers in a pod should fail", func() {
@@ -630,7 +631,7 @@ var _ = Describe("Podman generate kube", func() {
Expect(*pod.Spec.DNSConfig.Options[0].Value).To(Equal("blue"))
})
- It("podman generate kube multiple contianer dns servers and options are cumulative", func() {
+ It("podman generate kube multiple container dns servers and options are cumulative", func() {
top1 := podmanTest.Podman([]string{"run", "-dt", "--name", "top1", "--dns", "8.8.8.8", "--dns-search", "foobar.com", ALPINE, "top"})
top1.WaitWithDefaultTimeout()
Expect(top1.ExitCode()).To(BeZero())
@@ -798,4 +799,55 @@ USER test1`
Expect(*pod.Spec.Containers[0].SecurityContext.RunAsUser).To(Equal(int64(10001)))
})
+ It("podman generate kube on named volume", func() {
+ vol := "simple-named-volume"
+
+ session := podmanTest.Podman([]string{"volume", "create", vol})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ kube := podmanTest.Podman([]string{"generate", "kube", vol})
+ kube.WaitWithDefaultTimeout()
+ Expect(kube.ExitCode()).To(Equal(0))
+
+ pvc := new(v1.PersistentVolumeClaim)
+ err := yaml.Unmarshal(kube.Out.Contents(), pvc)
+ Expect(err).To(BeNil())
+ Expect(pvc.GetName()).To(Equal(vol))
+ Expect(pvc.Spec.AccessModes[0]).To(Equal(v1.ReadWriteOnce))
+ Expect(pvc.Spec.Resources.Requests.Storage().String()).To(Equal("1Gi"))
+ })
+
+ It("podman generate kube on named volume with options", func() {
+ vol := "complex-named-volume"
+ volDevice := "tmpfs"
+ volType := "tmpfs"
+ volOpts := "nodev,noexec"
+
+ session := podmanTest.Podman([]string{"volume", "create", "--opt", "device=" + volDevice, "--opt", "type=" + volType, "--opt", "o=" + volOpts, vol})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ kube := podmanTest.Podman([]string{"generate", "kube", vol})
+ kube.WaitWithDefaultTimeout()
+ Expect(kube.ExitCode()).To(Equal(0))
+
+ pvc := new(v1.PersistentVolumeClaim)
+ err := yaml.Unmarshal(kube.Out.Contents(), pvc)
+ Expect(err).To(BeNil())
+ Expect(pvc.GetName()).To(Equal(vol))
+ Expect(pvc.Spec.AccessModes[0]).To(Equal(v1.ReadWriteOnce))
+ Expect(pvc.Spec.Resources.Requests.Storage().String()).To(Equal("1Gi"))
+
+ for k, v := range pvc.GetAnnotations() {
+ switch k {
+ case util.VolumeDeviceAnnotation:
+ Expect(v).To(Equal(volDevice))
+ case util.VolumeTypeAnnotation:
+ Expect(v).To(Equal(volType))
+ case util.VolumeMountOptsAnnotation:
+ Expect(v).To(Equal(volOpts))
+ }
+ }
+ })
})
diff --git a/test/e2e/play_kube_test.go b/test/e2e/play_kube_test.go
index 93c8426a7..41afd9f75 100644
--- a/test/e2e/play_kube_test.go
+++ b/test/e2e/play_kube_test.go
@@ -10,6 +10,7 @@ import (
"strings"
"text/template"
+ "github.com/containers/podman/v3/pkg/util"
. "github.com/containers/podman/v3/test/utils"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
@@ -83,6 +84,26 @@ data:
{{ end }}
`
+var persistentVolumeClaimYamlTemplate = `
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+ name: {{ .Name }}
+{{ with .Annotations }}
+ annotations:
+ {{ range $key, $value := . }}
+ {{ $key }}: {{ $value }}
+ {{ end }}
+{{ end }}
+spec:
+ accessModes:
+ - "ReadWriteOnce"
+ resources:
+ requests:
+ storage: "1Gi"
+ storageClassName: default
+`
+
var podYamlTemplate = `
apiVersion: v1
kind: Pod
@@ -337,10 +358,31 @@ spec:
privileged: false
readOnlyRootFilesystem: false
workingDir: /
+ volumeMounts:
+ {{ if .VolumeMount }}
+ - name: {{.VolumeName}}
+ mountPath: {{ .VolumeMountPath }}
+ readonly: {{.VolumeReadOnly}}
+ {{ end }}
{{ end }}
{{ end }}
{{ end }}
- {{ end }}
+ {{ with .Volumes }}
+ volumes:
+ {{ range . }}
+ - name: {{ .Name }}
+ {{- if (eq .VolumeType "HostPath") }}
+ hostPath:
+ path: {{ .HostPath.Path }}
+ type: {{ .HostPath.Type }}
+ {{- end }}
+ {{- if (eq .VolumeType "PersistentVolumeClaim") }}
+ persistentVolumeClaim:
+ claimName: {{ .PersistentVolumeClaim.ClaimName }}
+ {{- end }}
+ {{ end }}
+ {{ end }}
+{{ end }}
`
var (
@@ -352,6 +394,7 @@ var (
defaultVolName = "testVol"
defaultDeploymentName = "testDeployment"
defaultConfigMapName = "testConfigMap"
+ defaultPVCName = "testPVC"
seccompPwdEPERM = []byte(`{"defaultAction":"SCMP_ACT_ALLOW","syscalls":[{"name":"getcwd","action":"SCMP_ACT_ERRNO"}]}`)
// CPU Period in ms
defaultCPUPeriod = 100
@@ -386,6 +429,8 @@ func getKubeYaml(kind string, object interface{}) (string, error) {
yamlTemplate = podYamlTemplate
case "deployment":
yamlTemplate = deploymentYamlTemplate
+ case "persistentVolumeClaim":
+ yamlTemplate = persistentVolumeClaimYamlTemplate
default:
return "", fmt.Errorf("unsupported kubernetes kind")
}
@@ -467,6 +512,39 @@ func withConfigMapData(k, v string) configMapOption {
}
}
+// PVC describes the options a kube yaml can be configured at persistent volume claim level
+type PVC struct {
+ Name string
+ Annotations map[string]string
+}
+
+func getPVC(options ...pvcOption) *PVC {
+ pvc := PVC{
+ Name: defaultPVCName,
+ Annotations: map[string]string{},
+ }
+
+ for _, option := range options {
+ option(&pvc)
+ }
+
+ return &pvc
+}
+
+type pvcOption func(*PVC)
+
+func withPVCName(name string) pvcOption {
+ return func(pvc *PVC) {
+ pvc.Name = name
+ }
+}
+
+func withPVCAnnotations(k, v string) pvcOption {
+ return func(pvc *PVC) {
+ pvc.Annotations[k] = v
+ }
+}
+
// Pod describes the options a kube yaml can be configured at pod level
type Pod struct {
Name string
@@ -1941,8 +2019,106 @@ MemoryReservation: {{ .HostConfig.MemoryReservation }}`})
Expect(inspect.OutputToString()).To(Equal("true"))
})
+ It("podman play kube persistentVolumeClaim", func() {
+ volName := "myvol"
+ volDevice := "tmpfs"
+ volType := "tmpfs"
+ volOpts := "nodev,noexec"
+
+ pvc := getPVC(withPVCName(volName),
+ withPVCAnnotations(util.VolumeDeviceAnnotation, volDevice),
+ withPVCAnnotations(util.VolumeTypeAnnotation, volType),
+ withPVCAnnotations(util.VolumeMountOptsAnnotation, volOpts))
+ err = generateKubeYaml("persistentVolumeClaim", pvc, kubeYaml)
+ Expect(err).To(BeNil())
+
+ kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
+ kube.WaitWithDefaultTimeout()
+ Expect(kube.ExitCode()).To(Equal(0))
+
+ inspect := podmanTest.Podman([]string{"inspect", volName, "--format", `
+Name: {{ .Name }}
+Device: {{ .Options.device }}
+Type: {{ .Options.type }}
+o: {{ .Options.o }}`})
+ inspect.WaitWithDefaultTimeout()
+ Expect(inspect.ExitCode()).To(Equal(0))
+ Expect(inspect.OutputToString()).To(ContainSubstring("Name: " + volName))
+ Expect(inspect.OutputToString()).To(ContainSubstring("Device: " + volDevice))
+ Expect(inspect.OutputToString()).To(ContainSubstring("Type: " + volType))
+ Expect(inspect.OutputToString()).To(ContainSubstring("o: " + volOpts))
+ })
+
// Multi doc related tests
- It("podman play kube multi doc yaml", func() {
+ It("podman play kube multi doc yaml with persistentVolumeClaim, service and deployment", func() {
+ yamlDocs := []string{}
+
+ serviceTemplate := `apiVersion: v1
+kind: Service
+metadata:
+ name: %s
+spec:
+ ports:
+ - port: 80
+ protocol: TCP
+ targetPort: 9376
+ selector:
+ app: %s
+`
+ // generate persistentVolumeClaim
+ volName := "multiFoo"
+ pvc := getPVC(withPVCName(volName))
+
+ // generate deployment
+ deploymentName := "multiFoo"
+ podName := "multiFoo"
+ ctrName := "ctr-01"
+ ctr := getCtr(withVolumeMount("/test", false))
+ ctr.Name = ctrName
+ pod := getPod(withPodName(podName), withVolume(getPersistentVolumeClaimVolume(volName)), withCtr(ctr))
+ deployment := getDeployment(withPod(pod))
+ deployment.Name = deploymentName
+
+ // add pvc
+ k, err := getKubeYaml("persistentVolumeClaim", pvc)
+ Expect(err).To(BeNil())
+ yamlDocs = append(yamlDocs, k)
+
+ // add service
+ yamlDocs = append(yamlDocs, fmt.Sprintf(serviceTemplate, deploymentName, deploymentName))
+
+ // add deployment
+ k, err = getKubeYaml("deployment", deployment)
+ Expect(err).To(BeNil())
+ yamlDocs = append(yamlDocs, k)
+
+ // generate multi doc yaml
+ err = generateMultiDocKubeYaml(yamlDocs, kubeYaml)
+ Expect(err).To(BeNil())
+
+ kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
+ kube.WaitWithDefaultTimeout()
+ Expect(kube.ExitCode()).To(Equal(0))
+
+ inspectVolume := podmanTest.Podman([]string{"inspect", volName, "--format", "'{{ .Name }}'"})
+ inspectVolume.WaitWithDefaultTimeout()
+ Expect(inspectVolume.ExitCode()).To(Equal(0))
+ Expect(inspectVolume.OutputToString()).To(ContainSubstring(volName))
+
+ inspectPod := podmanTest.Podman([]string{"inspect", podName + "-pod-0", "--format", "'{{ .State }}'"})
+ inspectPod.WaitWithDefaultTimeout()
+ Expect(inspectPod.ExitCode()).To(Equal(0))
+ Expect(inspectPod.OutputToString()).To(ContainSubstring(`Running`))
+
+ inspectMounts := podmanTest.Podman([]string{"inspect", podName + "-pod-0-" + ctrName, "--format", "{{ (index .Mounts 0).Type }}:{{ (index .Mounts 0).Name }}"})
+ inspectMounts.WaitWithDefaultTimeout()
+ Expect(inspectMounts.ExitCode()).To(Equal(0))
+
+ correct := fmt.Sprintf("volume:%s", volName)
+ Expect(inspectMounts.OutputToString()).To(Equal(correct))
+ })
+
+ It("podman play kube multi doc yaml with multiple services, pods and deployments", func() {
yamlDocs := []string{}
podNames := []string{}
@@ -1958,7 +2134,7 @@ spec:
selector:
app: %s
`
- // generate servies, pods and deployments
+ // generate services, pods and deployments
for i := 0; i < 2; i++ {
podName := fmt.Sprintf("testPod%d", i)
deploymentName := fmt.Sprintf("testDeploy%d", i)
diff --git a/test/e2e/unshare_test.go b/test/e2e/unshare_test.go
index 515b3a42e..24ab98916 100644
--- a/test/e2e/unshare_test.go
+++ b/test/e2e/unshare_test.go
@@ -49,4 +49,11 @@ var _ = Describe("Podman unshare", func() {
ok, _ := session.GrepString(userNS)
Expect(ok).To(BeFalse())
})
+
+ It("podman unshare --rootles-cni", func() {
+ session := podmanTest.Podman([]string{"unshare", "--rootless-cni", "ip", "addr"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.OutputToString()).To(ContainSubstring("tap0"))
+ })
})