Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support multi doc yaml for generate/play kube #9759

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ require (
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
k8s.io/api v0.20.5
k8s.io/apimachinery v0.20.5
)
100 changes: 69 additions & 31 deletions pkg/domain/infra/abi/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,10 @@ 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
podYAML *k8sAPI.Pod
err error
ctrs []*libpod.Container
servicePorts []k8sAPI.ServicePort
serviceYAML k8sAPI.Service
kubePods []*k8sAPI.Pod
kubeServices []k8sAPI.Service
content []byte
)
for _, nameOrID := range nameOrIDs {
// Get the container in question
Expand All @@ -59,9 +58,6 @@ func (ic *ContainerEngine) GenerateKube(ctx context.Context, nameOrIDs []string,
return nil, err
}
pods = append(pods, pod)
if len(pods) > 1 {
return nil, errors.New("can only generate single pod at a time")
}
} else {
if len(ctr.Dependencies()) > 0 {
return nil, errors.Wrapf(define.ErrNotImplemented, "containers with dependencies")
Expand All @@ -79,45 +75,86 @@ func (ic *ContainerEngine) GenerateKube(ctx context.Context, nameOrIDs []string,
return nil, errors.New("cannot generate pods and containers at the same time")
}

if len(pods) == 1 {
podYAML, servicePorts, err = pods[0].GenerateForKube()
if len(pods) >= 1 {
pos, svcs, err := getKubePods(pods, options.Service)
if err != nil {
return nil, err
}

kubePods = append(kubePods, pos...)
if options.Service {
kubeServices = append(kubeServices, svcs...)
}
} else {
podYAML, err = libpod.GenerateForKube(ctrs)
}
if err != nil {
return nil, err
}
po, err := libpod.GenerateForKube(ctrs)
if err != nil {
return nil, err
}

if options.Service {
serviceYAML = libpod.GenerateKubeServiceFromV1Pod(podYAML, servicePorts)
kubePods = append(kubePods, po)
if options.Service {
kubeServices = append(kubeServices, libpod.GenerateKubeServiceFromV1Pod(po, []k8sAPI.ServicePort{}))
}
}

content, err := generateKubeOutput(podYAML, &serviceYAML, options.Service)
content, err := generateKubeOutput(kubePods, kubeServices, options.Service)
if err != nil {
return nil, err
}

return &entities.GenerateKubeReport{Reader: bytes.NewReader(content)}, nil
}

func generateKubeOutput(podYAML *k8sAPI.Pod, serviceYAML *k8sAPI.Service, hasService bool) ([]byte, error) {
var (
output []byte
marshalledPod []byte
marshalledService []byte
err error
)
func getKubePods(pods []*libpod.Pod, getService bool) ([]*k8sAPI.Pod, []k8sAPI.Service, error) {
kubePods := make([]*k8sAPI.Pod, 0)
kubeServices := make([]k8sAPI.Service, 0)

marshalledPod, err = yaml.Marshal(podYAML)
if err != nil {
return nil, err
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))
}
}

if hasService {
marshalledService, err = yaml.Marshal(serviceYAML)
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)
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)
if err != nil {
return nil, err
}

marshalledServices = append(marshalledServices, b...)
}
}

header := `# Generation of Kubernetes YAML is still under development!
Expand All @@ -133,11 +170,12 @@ func generateKubeOutput(podYAML *k8sAPI.Pod, serviceYAML *k8sAPI.Service, hasSer
}

output = append(output, []byte(fmt.Sprintf(header, podmanVersion.Version))...)
output = append(output, marshalledPod...)
// kube generate order is based on helm install order (service, pod...)
if hasService {
output = append(output, marshalledServices...)
output = append(output, []byte("---\n")...)
output = append(output, marshalledService...)
}
output = append(output, marshalledPods...)

return output, nil
}
124 changes: 100 additions & 24 deletions pkg/domain/infra/abi/play.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package abi

import (
"bytes"
"context"
"fmt"
"io"
Expand All @@ -20,46 +21,79 @@ import (
"github.com/ghodss/yaml"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
yamlv3 "gopkg.in/yaml.v3"
v1apps "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
)

func (ic *ContainerEngine) PlayKube(ctx context.Context, path string, options entities.PlayKubeOptions) (*entities.PlayKubeReport, error) {
var (
kubeObject v1.ObjectReference
)
report := &entities.PlayKubeReport{}
validKinds := 0

// read yaml document
content, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}

if err := yaml.Unmarshal(content, &kubeObject); err != nil {
return nil, errors.Wrapf(err, "unable to read %q as YAML", path)
// split yaml document
documentList, err := splitMultiDocYAML(content)
if err != nil {
return nil, err
}

// NOTE: pkg/bindings/play is also parsing the file.
// A pkg/kube would be nice to refactor and abstract
// parts of the K8s-related code.
switch kubeObject.Kind {
case "Pod":
var podYAML v1.Pod
var podTemplateSpec v1.PodTemplateSpec
if err := yaml.Unmarshal(content, &podYAML); err != nil {
return nil, errors.Wrapf(err, "unable to read YAML %q as Kube Pod", path)
// create pod on each document if it is a pod or deployment
// any other kube kind will be skipped
for _, document := range documentList {
kind, err := getKubeKind(document)
if err != nil {
return nil, errors.Wrapf(err, "unable to read %q as kube YAML", path)
}
podTemplateSpec.ObjectMeta = podYAML.ObjectMeta
podTemplateSpec.Spec = podYAML.Spec
return ic.playKubePod(ctx, podTemplateSpec.ObjectMeta.Name, &podTemplateSpec, options)
case "Deployment":
var deploymentYAML v1apps.Deployment
if err := yaml.Unmarshal(content, &deploymentYAML); err != nil {
return nil, errors.Wrapf(err, "unable to read YAML %q as Kube Deployment", path)

switch kind {
case "Pod":
var podYAML v1.Pod
var podTemplateSpec v1.PodTemplateSpec

if err := yaml.Unmarshal(document, &podYAML); err != nil {
return nil, errors.Wrapf(err, "unable to read YAML %q as Kube Pod", path)
}

podTemplateSpec.ObjectMeta = podYAML.ObjectMeta
podTemplateSpec.Spec = podYAML.Spec

r, err := ic.playKubePod(ctx, podTemplateSpec.ObjectMeta.Name, &podTemplateSpec, options)
if err != nil {
return nil, err
}

report.Pods = append(report.Pods, r.Pods...)
validKinds++
case "Deployment":
var deploymentYAML v1apps.Deployment

if err := yaml.Unmarshal(document, &deploymentYAML); err != nil {
return nil, errors.Wrapf(err, "unable to read YAML %q as Kube Deployment", path)
}

r, err := ic.playKubeDeployment(ctx, &deploymentYAML, options)
if err != nil {
return nil, err
}

report.Pods = append(report.Pods, r.Pods...)
validKinds++
default:
logrus.Infof("kube kind %s not supported", kind)
continue
}
return ic.playKubeDeployment(ctx, &deploymentYAML, options)
default:
return nil, errors.Errorf("invalid YAML kind: %q. [Pod|Deployment] are the only supported Kubernetes Kinds", kubeObject.Kind)
}

if validKinds == 0 {
return nil, fmt.Errorf("YAML document does not contain any supported kube kind")
}

return report, nil
}

func (ic *ContainerEngine) playKubeDeployment(ctx context.Context, deploymentYAML *v1apps.Deployment, options entities.PlayKubeOptions) (*entities.PlayKubeReport, error) {
Expand Down Expand Up @@ -290,3 +324,45 @@ func readConfigMapFromFile(r io.Reader) (v1.ConfigMap, error) {

return cm, nil
}

// splitMultiDocYAML reads mutiple documents in a YAML file and
// returns them as a list.
func splitMultiDocYAML(yamlContent []byte) ([][]byte, error) {
var documentList [][]byte

d := yamlv3.NewDecoder(bytes.NewReader(yamlContent))
for {
var o interface{}
// read individual document
err := d.Decode(&o)
if err == io.EOF {
break
}
if err != nil {
return nil, errors.Wrapf(err, "multi doc yaml could not be split")
}

if o != nil {
// back to bytes
document, err := yamlv3.Marshal(o)
if err != nil {
return nil, errors.Wrapf(err, "individual doc yaml could not be marshalled")
}

documentList = append(documentList, document)
}
}

return documentList, nil
}

// getKubeKind unmarshals a kube YAML document and returns its kind.
func getKubeKind(obj []byte) (string, error) {
var kubeObject v1.ObjectReference

if err := yaml.Unmarshal(obj, &kubeObject); err != nil {
return "", err
}

return kubeObject.Kind, nil
}
Loading