diff options
author | OpenShift Merge Robot <openshift-merge-robot@users.noreply.github.com> | 2021-04-12 12:36:20 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-04-12 12:36:20 +0200 |
commit | 9d3e31071087908207a8f7fdc84939edf3dc47c0 (patch) | |
tree | 7993a84a9b70504b2a99c88085e5e7205bc0b94b /pkg | |
parent | 3b03ff7d1ea65c31ca8c9a28e70f7dd5a43afbf0 (diff) | |
parent | 61cb6d61dd420a000c843171b5917b5595874a67 (diff) | |
download | podman-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.go | 8 | ||||
-rw-r--r-- | pkg/domain/infra/abi/generate.go | 172 | ||||
-rw-r--r-- | pkg/domain/infra/abi/play.go | 105 | ||||
-rw-r--r-- | pkg/util/kube.go | 16 |
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" +) |