Skip to content

Commit

Permalink
Support multi doc yaml for generate/play kube
Browse files Browse the repository at this point in the history
Signed-off-by: Eduardo Vega <[email protected]>

<MH: Fixed cherry-pick conflicts>

Signed-off-by: Matthew Heon <[email protected]>
  • Loading branch information
EduardoVega authored and mheon committed Mar 29, 2021
1 parent d560f16 commit 6b69892
Show file tree
Hide file tree
Showing 6 changed files with 411 additions and 77 deletions.
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-20201207232520-09787c993a3a
golang.org/x/sys v0.0.0-20210324051608-47abb6519492
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.1
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

0 comments on commit 6b69892

Please sign in to comment.