Merge pull request #9935 from EduardoVega/5788-kube-volume

Add support for play/generate kube PersistentVolumeClaims and Podman volumes
This commit is contained in:
OpenShift Merge Robot 2021-04-12 12:36:20 +02:00 committed by GitHub
commit 9d3e310710
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 617 additions and 110 deletions

View file

@ -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) {

View file

@ -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()

View file

@ -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`,
}
)

View file

@ -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)

View file

@ -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,
}
)

View file

@ -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.

View file

@ -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. |

View file

@ -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*

View file

@ -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)

View file

@ -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. |

View file

@ -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{}

View file

@ -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
}

View file

@ -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)
if options.Service {
kubeServices = append(kubeServices, libpod.GenerateKubeServiceFromV1Pod(po, []k8sAPI.ServicePort{}))
}
}
content, err := generateKubeOutput(kubePods, kubeServices, options.Service)
if err != nil {
return nil, err
}
return &entities.GenerateKubeReport{Reader: bytes.NewReader(content)}, nil
}
func getKubePods(pods []*libpod.Pod, getService bool) ([]*k8sAPI.Pod, []k8sAPI.Service, error) {
kubePods := make([]*k8sAPI.Pod, 0)
kubeServices := make([]k8sAPI.Service, 0)
for _, p := range pods {
po, svc, err := p.GenerateForKube()
if err != nil {
return nil, nil, err
}
kubePods = append(kubePods, po)
if getService {
kubeServices = append(kubeServices, libpod.GenerateKubeServiceFromV1Pod(po, svc))
}
}
return kubePods, kubeServices, 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)
for i, p := range kubePods {
if i != 0 {
marshalledPods = append(marshalledPods, []byte("---\n")...)
}
b, err := yaml.Marshal(p)
b, err := generateKubeYAML(po)
if err != nil {
return nil, err
}
marshalledPods = append(marshalledPods, b...)
}
if hasService {
for i, s := range kubeServices {
if i != 0 {
marshalledServices = append(marshalledServices, []byte("---\n")...)
}
b, err := yaml.Marshal(s)
podContent = append(podContent, b)
if options.Service {
b, err := generateKubeYAML(libpod.GenerateKubeServiceFromV1Pod(po, []k8sAPI.ServicePort{}))
if err != nil {
return nil, err
}
marshalledServices = append(marshalledServices, b...)
content = append(content, b)
}
}
// 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(k)}, nil
}
// 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, 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)
if getService {
b, err := generateKubeYAML(libpod.GenerateKubeServiceFromV1Pod(po, sp))
if err != nil {
return nil, nil, err
}
svcs = append(svcs, b)
}
}
return pos, svcs, nil
}
// getKubePVCs returns kube persistent volume claim YAML files from podman volumes.
func getKubePVCs(volumes []*libpod.Volume) ([][]byte, error) {
pvs := [][]byte{}
for _, v := range volumes {
b, err := generateKubeYAML(v.GenerateForKube())
if err != nil {
return nil, err
}
pvs = append(pvs, b)
}
return pvs, nil
}
// 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
}

View file

@ -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
}

16
pkg/util/kube.go Normal file
View file

@ -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"
)

View file

@ -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))
}
}
})
})

View file

@ -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)