Skip to content

Commit

Permalink
Alpha Add command for Kyma CLI (kyma-project#2197)
Browse files Browse the repository at this point in the history
  • Loading branch information
Cortey authored Jul 29, 2024
1 parent 5c054ca commit 5e46d0b
Show file tree
Hide file tree
Showing 9 changed files with 356 additions and 43 deletions.
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ require (
k8s.io/api v0.30.3
k8s.io/apimachinery v0.30.3
k8s.io/client-go v0.30.3
sigs.k8s.io/controller-runtime v0.18.4
sigs.k8s.io/yaml v1.4.0
)

require (
Expand All @@ -29,6 +31,7 @@ require (
github.com/docker/go-units v0.5.0 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
github.com/evanphx/json-patch/v5 v5.9.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
Expand All @@ -55,8 +58,6 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
github.com/onsi/ginkgo/v2 v2.17.1 // indirect
github.com/onsi/gomega v1.32.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
Expand Down Expand Up @@ -90,5 +91,4 @@ require (
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)
14 changes: 14 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxER
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84=
github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg=
github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/gboddin/go-www-authenticate-parser v0.0.0-20230926203616-ec0b649bb077 h1:JvEO7eltd2aCHF+ABLquTUziO7hzC6G7H3tgENYkDBc=
Expand All @@ -44,6 +46,8 @@ github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
Expand Down Expand Up @@ -175,9 +179,15 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI=
go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
Expand Down Expand Up @@ -247,6 +257,8 @@ gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
k8s.io/api v0.30.3 h1:ImHwK9DCsPA9uoU3rVh4QHAHHK5dTSv1nxJUapx8hoQ=
k8s.io/api v0.30.3/go.mod h1:GPc8jlzoe5JG3pb0KJCSLX5oAFIW3/qNJITlDj8BH04=
k8s.io/apiextensions-apiserver v0.30.1 h1:4fAJZ9985BmpJG6PkoxVRpXv9vmPUOVzl614xarePws=
k8s.io/apiextensions-apiserver v0.30.1/go.mod h1:R4GuSrlhgq43oRY9sF2IToFh7PVlF1JjfWdoG3pixk4=
k8s.io/apimachinery v0.30.3 h1:q1laaWCmrszyQuSQCfNB8cFgCuDAoPszKY4ucAjDwHc=
k8s.io/apimachinery v0.30.3/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc=
k8s.io/client-go v0.30.3 h1:bHrJu3xQZNXIi8/MoxYtZBBWQQXwy16zqJwloXXfD3k=
Expand All @@ -257,6 +269,8 @@ k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7F
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98=
k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI=
k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/controller-runtime v0.18.4 h1:87+guW1zhvuPLh1PHybKdYFLU0YJp4FhJRmiHvm5BZw=
sigs.k8s.io/controller-runtime v0.18.4/go.mod h1:TVoGrfdpbA9VRFaRnKgk9P5/atA0pMwq+f+msb9M8Sg=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
Expand Down
176 changes: 171 additions & 5 deletions internal/cmd/alpha/add/add.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,186 @@
package add

import (
"github.com/kyma-project/cli.v3/internal/cmd/alpha/add/managed"
"bytes"
"fmt"
"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/spf13/cobra"
"io"
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"
"net/http"
yaml "sigs.k8s.io/yaml/goyaml.v3"
)

type addConfig struct {
*cmdcommon.KymaConfig
cmdcommon.KubeClientConfig

wantedModules []string
//custom string
}

func NewAddCMD(kymaConfig *cmdcommon.KymaConfig) *cobra.Command {
cfg := addConfig{
KymaConfig: kymaConfig,
KubeClientConfig: cmdcommon.KubeClientConfig{},
}

cmd := &cobra.Command{
Use: "add",
Short: "Adds Kyma modules.",
Long: `Use this command to add Kyma modules`,
DisableFlagsInUseLine: true,
Use: "add",
Short: "Adds Kyma modules.",
Long: `Use this command to add Kyma modules`,
PreRun: func(_ *cobra.Command, _ []string) {
clierror.Check(cfg.KubeClientConfig.Complete())
},
Run: func(_ *cobra.Command, _ []string) {
clierror.Check(runAdd(&cfg))
},
}

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")

return cmd
}

func runAdd(cfg *addConfig) clierror.Error {
err := assureNamespace("kyma-system", cfg)
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
}
23 changes: 17 additions & 6 deletions internal/communitymodules/modules.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func modulesCatalog(url string) (moduleMap, clierror.Error) {

catalog := make(moduleMap)
for _, rec := range modules {
latestVersion := getLatestVersion(rec.Versions)
latestVersion := GetLatestVersion(rec.Versions)
catalog[rec.Name] = row{
Name: rec.Name,
Repository: chooseRepository(rec, latestVersion),
Expand All @@ -63,7 +63,7 @@ func chooseRepository(module Module, version Version) string {
}
return "Unknown"
}
func getLatestVersion(versions []Version) Version {
func GetLatestVersion(versions []Version) Version {
return slices.MaxFunc(versions, func(a, b Version) int {
cmpA := a.Version
if !semver.IsValid(cmpA) {
Expand All @@ -86,18 +86,18 @@ func getCommunityModules(url string) (Modules, clierror.Error) {
defer resp.Body.Close()

var modules Modules
modules, respErr := decodeCommunityModulesResponse(err, resp, modules)
modules, respErr := decodeCommunityModulesResponse(resp, modules)
if respErr != nil {
return nil, clierror.WrapE(respErr, clierror.New("while handling response"))
}
return modules, nil
}

// decodeCommunityModulesResponse reads the response body and unmarshals it into the template
func decodeCommunityModulesResponse(err error, resp *http.Response, modules Modules) (Modules, clierror.Error) {
func decodeCommunityModulesResponse(resp *http.Response, modules Modules) (Modules, clierror.Error) {
if resp.StatusCode != 200 {
errMsg := fmt.Sprintf("error response: %s", resp.Status)
return nil, clierror.Wrap(err, clierror.New(errMsg))
return nil, clierror.New(errMsg)
}

bodyText, err := io.ReadAll(resp.Body)
Expand Down Expand Up @@ -165,7 +165,7 @@ func installedModules(url string, client cmdcommon.KubeClientConfig, cfg cmdcomm
func getInstalledModules(modules Modules, client cmdcommon.KubeClientConfig, cfg cmdcommon.KymaConfig) (moduleMap, clierror.Error) {
installed := make(moduleMap)
for _, module := range modules {
latestVersion := getLatestVersion(module.Versions)
latestVersion := GetLatestVersion(module.Versions)
managerName := getManagerName(latestVersion)
deployment, err := client.KubeClient.Static().AppsV1().Deployments("kyma-system").
Get(cfg.Ctx, managerName, metav1.GetOptions{})
Expand Down Expand Up @@ -204,3 +204,14 @@ func calculateVersion(moduleVersion string, installedVersion string) string {
}
return "outdated moduleVersion, latest is " + moduleVersion
}

func GetAvailableModules() (Modules, clierror.Error) {
resp, err := http.Get(URL)
if err != nil {
return nil, clierror.Wrap(err, clierror.New("failed to get available modules"))
}
defer resp.Body.Close()

var modules Modules
return decodeCommunityModulesResponse(resp, modules)
}
8 changes: 4 additions & 4 deletions internal/communitymodules/modules_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,9 +331,9 @@ func fixCommunityModulesResponse() string {
]`
}

func Test_getLatestVersion(t *testing.T) {
func Test_GetLatestVersion(t *testing.T) {
t.Run("simple versions", func(t *testing.T) {
result := getLatestVersion([]Version{
result := GetLatestVersion([]Version{
{
Version: "1.5.3",
},
Expand All @@ -349,7 +349,7 @@ func Test_getLatestVersion(t *testing.T) {
}, result)
})
t.Run("v prefix", func(t *testing.T) {
result := getLatestVersion([]Version{
result := GetLatestVersion([]Version{
{
Version: "v1.5.3",
},
Expand All @@ -365,7 +365,7 @@ func Test_getLatestVersion(t *testing.T) {
}, result)
})
t.Run("with suffix", func(t *testing.T) {
result := getLatestVersion([]Version{
result := GetLatestVersion([]Version{
{
Version: "1.5.3-experimental",
},
Expand Down
10 changes: 7 additions & 3 deletions internal/communitymodules/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,19 @@ import "k8s.io/apimachinery/pkg/runtime/schema"
type Modules []Module

type Module struct {
Name string `json:"name,omitempty"`
Versions []Version `json:"versions,omitempty"`
Repository string `json:"repository,omitempty"`
Name string `json:"name,omitempty"`
Versions []Version `json:"versions,omitempty"`
Repository string `json:"repository,omitempty"`
ManagedResources []string `json:"managedResources,omitempty"`
}

type Version struct {
Version string `json:"version,omitempty"`
ManagerPath string `json:"managerPath,omitempty"`
Repository string `json:"repository,omitempty"`
//CrPath string `json:"crPath,omitempty"`
DeploymentYaml string `json:"deploymentYaml,omitempty"`
CrYaml string `json:"crYaml,omitempty"`
}

var (
Expand Down
Loading

0 comments on commit 5e46d0b

Please sign in to comment.