aboutsummaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
authorOpenShift Merge Robot <openshift-merge-robot@users.noreply.github.com>2021-04-12 12:36:20 +0200
committerGitHub <noreply@github.com>2021-04-12 12:36:20 +0200
commit9d3e31071087908207a8f7fdc84939edf3dc47c0 (patch)
tree7993a84a9b70504b2a99c88085e5e7205bc0b94b /pkg
parent3b03ff7d1ea65c31ca8c9a28e70f7dd5a43afbf0 (diff)
parent61cb6d61dd420a000c843171b5917b5595874a67 (diff)
downloadpodman-9d3e31071087908207a8f7fdc84939edf3dc47c0.tar.gz
podman-9d3e31071087908207a8f7fdc84939edf3dc47c0.tar.bz2
podman-9d3e31071087908207a8f7fdc84939edf3dc47c0.zip
Merge pull request #9935 from EduardoVega/5788-kube-volume
Add support for play/generate kube PersistentVolumeClaims and Podman volumes
Diffstat (limited to 'pkg')
-rw-r--r--pkg/domain/entities/play.go8
-rw-r--r--pkg/domain/infra/abi/generate.go172
-rw-r--r--pkg/domain/infra/abi/play.go105
-rw-r--r--pkg/util/kube.go16
4 files changed, 245 insertions, 56 deletions
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/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/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"
+)