Skip to content

Commit

Permalink
Test the kyma alpha add command (#2201)
Browse files Browse the repository at this point in the history
Co-authored-by: pPrecel <[email protected]>
  • Loading branch information
Cortey and pPrecel authored Aug 8, 2024
1 parent 71450a6 commit df10007
Show file tree
Hide file tree
Showing 7 changed files with 620 additions and 176 deletions.
148 changes: 7 additions & 141 deletions internal/cmd/alpha/add/add.go
Original file line number Diff line number Diff line change
@@ -1,29 +1,19 @@
package add

import (
"bytes"
"fmt"
"io"
"net/http"

"github.com/kyma-project/cli.v3/internal/clierror"
"github.com/kyma-project/cli.v3/internal/cmd/alpha/remove/managed"
"github.com/kyma-project/cli.v3/internal/cmdcommon"
"github.com/kyma-project/cli.v3/internal/communitymodules"
"github.com/kyma-project/cli.v3/internal/communitymodules/cluster"
"github.com/spf13/cobra"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
yaml "sigs.k8s.io/yaml/goyaml.v3"
)

type addConfig struct {
*cmdcommon.KymaConfig
cmdcommon.KubeClientConfig

wantedModules []string
//custom string
modules []string
crs []string
}

func NewAddCMD(kymaConfig *cmdcommon.KymaConfig) *cobra.Command {
Expand All @@ -47,141 +37,17 @@ func NewAddCMD(kymaConfig *cmdcommon.KymaConfig) *cobra.Command {
cmd.AddCommand(managed.NewManagedCMD(kymaConfig))

cfg.KubeClientConfig.AddFlag(cmd)
cmd.Flags().StringSliceVar(&cfg.wantedModules, "module", []string{}, "Name and version of the modules to add. Example: --module serverless,keda:1.1.1,etc...")
//cmd.Flags().StringVar(&cfg.custom, "custom", "", "Path to the custom file")
cmd.Flags().StringSliceVar(&cfg.modules, "module", []string{}, "Name and version of the modules to add. Example: --module serverless,keda:1.1.1,etc...")
cmd.Flags().StringSliceVar(&cfg.crs, "cr", []string{}, "Path to the custom CR file")

return cmd
}

func runAdd(cfg *addConfig) clierror.Error {
err := assureNamespace("kyma-system", cfg)
err := cluster.AssureNamespace(cfg.Ctx, cfg.KubeClient.Static(), "kyma-system")
if err != nil {
return err
}

return applySpecifiedModules(cfg)
}

func applySpecifiedModules(cfg *addConfig) clierror.Error {
modules, err := communitymodules.GetAvailableModules()
if err != nil {
return err
}
for _, rec := range modules {
if !containsModule(rec.Name, cfg.wantedModules) {
continue
}

fmt.Printf("Found matching module for %s\n", rec.Name)
latestVersion := communitymodules.GetLatestVersion(rec.Versions)

err = applyGivenObjects(cfg, latestVersion.DeploymentYaml)
if err != nil {
return err
}
err = applyGivenObjects(cfg, latestVersion.CrYaml)
if err != nil {
return err
}
}

return nil
}

func applyGivenObjects(cfg *addConfig, url string) clierror.Error {
givenYaml, err := http.Get(url)
if err != nil {
return clierror.Wrap(err, clierror.New("failed to get YAML from URL"))
}
defer givenYaml.Body.Close()

yamlContent, err := io.ReadAll(givenYaml.Body)
if err != nil {
return clierror.Wrap(err, clierror.New("failed to read YAML"))
}

objects, err := decodeYaml(bytes.NewReader(yamlContent))
if err != nil {
return clierror.Wrap(err, clierror.New("failed to decode YAML"))
}

err = cfg.KubeClient.RootlessDynamic().ApplyMany(cfg.Ctx, objects)
if err != nil {
return clierror.Wrap(err, clierror.New("failed to apply module resources"))

}
return nil
}

//func applyCustomConfiguration(cfg *addConfig) clierror.Error {
// fmt.Println("Applying custom configuration from " + cfg.custom)
//
// customYaml, err := os.ReadFile(cfg.custom)
// if err != nil {
// return clierror.Wrap(err, clierror.New("failed to read custom file"))
// }
//
// objects, err := decodeYaml(bytes.NewReader(customYaml))
// if err != nil {
// return clierror.Wrap(err, clierror.New("failed to decode YAML"))
// }
//
// err = cfg.KubeClient.RootlessDynamic().ApplyMany(cfg.Ctx, objects)
// if err != nil {
// return clierror.Wrap(err, clierror.New("failed to apply module resources"))
// }
//
// return nil
//}

func assureNamespace(namespace string, cfg *addConfig) clierror.Error {
_, err := cfg.KubeClientConfig.KubeClient.Static().CoreV1().Namespaces().Get(cfg.Ctx, namespace, metav1.GetOptions{})
if !errors.IsNotFound(err) {
return nil
}
_, err = cfg.KubeClientConfig.KubeClient.Static().CoreV1().Namespaces().Create(cfg.Ctx, &v1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: namespace,
},
}, metav1.CreateOptions{})
if err != nil {
return clierror.New("failed to create namespace")
}
return nil
}

func containsModule(have string, want []string) bool {
for _, rec := range want {
if rec == have {
return true
}
}
return false
}

func decodeYaml(r io.Reader) ([]unstructured.Unstructured, error) {
results := make([]unstructured.Unstructured, 0)
decoder := yaml.NewDecoder(r)

for {
var obj map[string]interface{}
err := decoder.Decode(&obj)

if err == io.EOF {
break
}

if err != nil {
return nil, err
}

u := unstructured.Unstructured{Object: obj}
if u.GetObjectKind().GroupVersionKind().Kind == "CustomResourceDefinition" {
results = append([]unstructured.Unstructured{u}, results...)
continue
}
results = append(results, u)
}

return results, nil
return cluster.ApplySpecifiedModules(cfg.Ctx, cfg.KubeClient.RootlessDynamic(), cfg.modules, cfg.crs)
}
162 changes: 162 additions & 0 deletions internal/communitymodules/cluster/modules.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
package cluster

import (
"bytes"
"context"
"fmt"
"io"
"net/http"
"os"
"strings"

"github.com/kyma-project/cli.v3/internal/clierror"
"github.com/kyma-project/cli.v3/internal/communitymodules"
"github.com/kyma-project/cli.v3/internal/kube/rootlessdynamic"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
yaml "sigs.k8s.io/yaml/goyaml.v3"
)

func ApplySpecifiedModules(ctx context.Context, client rootlessdynamic.Interface, modules, crs []string) clierror.Error {
available, err := communitymodules.GetAvailableModules()
if err != nil {
return err
}

customConfig, err := readCustomConfig(crs)
if err != nil {
return err
}

for _, rec := range available {
versionedName := containsModule(rec.Name, modules) //TODO move splitting to earlier
if versionedName == nil {
continue
}

wantedVersion := verifyVersion(versionedName, rec)
fmt.Printf("Applying %s module manifest\n", rec.Name)
err = applyGivenObjects(ctx, client, wantedVersion.DeploymentYaml)
if err != nil {
return err
}

if applyGivenCustomCR(ctx, client, rec, customConfig) {
fmt.Println("Applying custom CR")
continue
}

fmt.Println("Applying CR")
err = applyGivenObjects(ctx, client, wantedVersion.CrYaml)
if err != nil {
return err
}
}
return nil
}

func readCustomConfig(cr []string) ([]unstructured.Unstructured, clierror.Error) {
if len(cr) == 0 {
return nil, nil
}
var objects []unstructured.Unstructured
for _, rec := range cr {
yaml, err := os.ReadFile(rec)
if err != nil {
return nil, clierror.Wrap(err, clierror.New("failed to read custom file"))
}
currentObjects, err := decodeYaml(bytes.NewReader(yaml))
if err != nil {
return nil, clierror.Wrap(err, clierror.New("failed to decode custom YAML"))
}
objects = append(objects, currentObjects...)
}
return objects, nil
}

func containsModule(have string, want []string) []string {
for _, rec := range want {
name := strings.Split(rec, ":")
if name[0] == have {
return name
}
}
return nil
}

func verifyVersion(name []string, rec communitymodules.Module) communitymodules.Version {
if len(name) != 1 {
for _, version := range rec.Versions {
if version.Version == name[1] {
fmt.Printf("Version %s found for %s\n", version.Version, rec.Name)
return version
}
}
}

fmt.Printf("Using latest version for %s\n", rec.Name)
return communitymodules.GetLatestVersion(rec.Versions)
}

// applyGivenCustomCR applies custom CR if it exists
func applyGivenCustomCR(ctx context.Context, client rootlessdynamic.Interface, rec communitymodules.Module, config []unstructured.Unstructured) bool {
for _, obj := range config {
if strings.EqualFold(obj.GetKind(), strings.ToLower(rec.Name)) {
client.Apply(ctx, &obj)
return true
}
}
return false

}

func applyGivenObjects(ctx context.Context, client rootlessdynamic.Interface, url string) clierror.Error {
givenYaml, err := http.Get(url)
if err != nil {
return clierror.Wrap(err, clierror.New("failed to get YAML from URL"))
}
defer givenYaml.Body.Close()

yamlContent, err := io.ReadAll(givenYaml.Body)
if err != nil {
return clierror.Wrap(err, clierror.New("failed to read YAML"))
}

objects, err := decodeYaml(bytes.NewReader(yamlContent))
if err != nil {
return clierror.Wrap(err, clierror.New("failed to decode YAML"))
}

cliErr := client.ApplyMany(ctx, objects)
if cliErr != nil {
return clierror.WrapE(cliErr, clierror.New("failed to apply module resources"))

}
return nil
}

func decodeYaml(r io.Reader) ([]unstructured.Unstructured, error) {
results := make([]unstructured.Unstructured, 0)
decoder := yaml.NewDecoder(r)

for {
var obj map[string]interface{}
err := decoder.Decode(&obj)

if err == io.EOF {
break
}

if err != nil {
return nil, err
}

u := unstructured.Unstructured{Object: obj}
if u.GetObjectKind().GroupVersionKind().Kind == "CustomResourceDefinition" {
results = append([]unstructured.Unstructured{u}, results...)
continue
}
results = append(results, u)
}

return results, nil
}
Loading

0 comments on commit df10007

Please sign in to comment.