diff --git a/docs/user_docs/cli/cli.md b/docs/user_docs/cli/cli.md index 8277bb097..47358e2e8 100644 --- a/docs/user_docs/cli/cli.md +++ b/docs/user_docs/cli/cli.md @@ -13,6 +13,7 @@ Addon command. * [kbcli addon enable](kbcli_addon_enable.md) - Enable an addon. * [kbcli addon index](kbcli_addon_index.md) - Manage custom addon indexes * [kbcli addon list](kbcli_addon_list.md) - List addons. +* [kbcli addon search](kbcli_addon_search.md) - search the addon from index ## [alert](kbcli_alert.md) diff --git a/docs/user_docs/cli/kbcli_addon.md b/docs/user_docs/cli/kbcli_addon.md index 265b42d78..a0d36c2fe 100644 --- a/docs/user_docs/cli/kbcli_addon.md +++ b/docs/user_docs/cli/kbcli_addon.md @@ -42,6 +42,7 @@ Addon command. * [kbcli addon enable](kbcli_addon_enable.md) - Enable an addon. * [kbcli addon index](kbcli_addon_index.md) - Manage custom addon indexes * [kbcli addon list](kbcli_addon_list.md) - List addons. +* [kbcli addon search](kbcli_addon_search.md) - search the addon from index #### Go Back to [CLI Overview](cli.md) Homepage. diff --git a/pkg/cmd/addon/addon.go b/pkg/cmd/addon/addon.go index c2f91fd02..b0dc5b67b 100644 --- a/pkg/cmd/addon/addon.go +++ b/pkg/cmd/addon/addon.go @@ -101,6 +101,7 @@ func NewAddonCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.C newEnableCmd(f, streams), newDisableCmd(f, streams), newIndexCmd(streams), + newSearchCmd(streams), ) return cmd } diff --git a/pkg/cmd/addon/index.go b/pkg/cmd/addon/index.go index 434416b23..e455b72a2 100644 --- a/pkg/cmd/addon/index.go +++ b/pkg/cmd/addon/index.go @@ -115,7 +115,11 @@ type updateOption struct { // validate will check the update index whether existed func (o *updateOption) validate(args []string) error { - indexes, err := getAllIndexes() + addonDir, err := util.GetCliAddonDir() + if err != nil { + return err + } + indexes, err := getAllIndexes(addonDir) if err != nil { return err } @@ -215,11 +219,14 @@ func addIndex(args []string) error { } func listIndexes(out io.Writer) error { + addonDir, err := util.GetCliAddonDir() + if err != nil { + return err + } tbl := printer.NewTablePrinter(out) tbl.SortBy(1) tbl.SetHeader("INDEX", "URL") - - indexes, err := getAllIndexes() + indexes, err := getAllIndexes(addonDir) if err != nil { return err } @@ -267,7 +274,7 @@ func addDefaultIndex() error { if err = util.EnsureCloned(types.DefaultAddonIndexURL, defaultIndexDir); err != nil { return err } - fmt.Printf("Default addon index \"kubeblocks\" has been added.") + fmt.Printf("Default addon index \"kubeblocks\" has been added.\n") return nil } else if err != nil { return err @@ -276,12 +283,8 @@ func addDefaultIndex() error { return nil } -func getAllIndexes() ([]index, error) { - addonDir, err := util.GetCliAddonDir() - if err != nil { - return nil, err - } - entries, err := os.ReadDir(addonDir) +func getAllIndexes(indexDir string) ([]index, error) { + entries, err := os.ReadDir(indexDir) if err != nil { return nil, err } @@ -291,7 +294,7 @@ func getAllIndexes() ([]index, error) { continue } indexName := e.Name() - remote, err := util.GitGetRemoteURL(path.Join(addonDir, indexName)) + remote, err := util.GitGetRemoteURL(path.Join(indexDir, indexName)) if err != nil { return nil, err } @@ -306,7 +309,11 @@ func getAllIndexes() ([]index, error) { func indexCompletion() func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { availableComps := []string{} - indexes, err := getAllIndexes() + addonDir, err := util.GetCliAddonDir() + if err != nil { + return availableComps, cobra.ShellCompDirectiveNoFileComp + } + indexes, err := getAllIndexes(addonDir) if err != nil { return availableComps, cobra.ShellCompDirectiveNoFileComp } diff --git a/pkg/cmd/addon/index_test.go b/pkg/cmd/addon/index_test.go index d3045fc1c..d2a3a3b43 100644 --- a/pkg/cmd/addon/index_test.go +++ b/pkg/cmd/addon/index_test.go @@ -1,3 +1,22 @@ +/* +Copyright (C) 2022-2023 ApeCloud Co., Ltd + +This file is part of KubeBlocks project + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + package addon import ( @@ -16,6 +35,7 @@ var _ = Describe("index test", func() { defaultIndexName = "kubeblocks" testIndexName = "kb-other" testIndexURL = "unknown" + testIndexDir = "./testdata" ) BeforeEach(func() { streams, _, out, _ = genericiooptions.NewTestIOStreams() @@ -80,4 +100,11 @@ kubeblocks https://github.com/apecloud/block-index.git } } }) + + It("test get index", func() { + indexes, err := getAllIndexes(testIndexDir) + Expect(err).Should(Succeed()) + Expect(indexes).Should(HaveLen(1)) + Expect(indexes[0].name).Should(Equal(defaultIndexName)) + }) }) diff --git a/pkg/cmd/addon/install.go b/pkg/cmd/addon/install.go new file mode 100644 index 000000000..6a32e487c --- /dev/null +++ b/pkg/cmd/addon/install.go @@ -0,0 +1,20 @@ +/* +Copyright (C) 2022-2023 ApeCloud Co., Ltd + +This file is part of KubeBlocks project + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package addon diff --git a/pkg/cmd/addon/search.go b/pkg/cmd/addon/search.go new file mode 100644 index 000000000..682a922e1 --- /dev/null +++ b/pkg/cmd/addon/search.go @@ -0,0 +1,132 @@ +/* +Copyright (C) 2022-2023 ApeCloud Co., Ltd + +This file is part of KubeBlocks project + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package addon + +import ( + "fmt" + "io" + "io/fs" + "os" + "path/filepath" + "regexp" + "strings" + + "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/util/yaml" + "k8s.io/cli-runtime/pkg/genericiooptions" + "k8s.io/klog/v2" + + extensionsv1alpha1 "github.com/apecloud/kubeblocks/apis/extensions/v1alpha1" + "github.com/apecloud/kubeblocks/pkg/constant" + + "github.com/apecloud/kbcli/pkg/printer" + "github.com/apecloud/kbcli/pkg/util" +) + +type searchResult struct { + index index + addon *extensionsv1alpha1.Addon +} + +func newSearchCmd(streams genericiooptions.IOStreams) *cobra.Command { + cmd := &cobra.Command{ + Use: "search", + Short: "search the addon from index", + Args: cobra.ExactArgs(1), + PersistentPreRun: func(cmd *cobra.Command, _ []string) { + util.CheckErr(util.EnableLogToFile(cmd.Flags())) + }, + Run: func(_ *cobra.Command, args []string) { + util.CheckErr(search(args, streams.Out)) + }, + } + return cmd +} + +func search(args []string, out io.Writer) error { + tbl := printer.NewTablePrinter(out) + tbl.AddRow("ADDON", "VERSION", "INDEX") + dir, err := util.GetCliAddonDir() + if err != nil { + return err + } + results, err := searchAddon(args[0], dir) + if err != nil { + return err + } + if len(results) == 0 { + fmt.Fprintf(out, "%s addon not found. Please update your index or check the addon name", args[0]) + return nil + } + for _, res := range results { + label := res.addon.Labels + tbl.AddRow(res.addon.Name, label[constant.AppVersionLabelKey], res.index.name) + } + tbl.Print() + return nil +} + +// searchAddon function will search for the addons with the specified name in the index of the specified directory and return them. +func searchAddon(name string, indexDir string) ([]searchResult, error) { + indexes, err := getAllIndexes(indexDir) + if err != nil { + return nil, err + } + var res []searchResult + searchInDir := func(i index) error { + return filepath.WalkDir(filepath.Join(indexDir, i.name), func(path string, d fs.DirEntry, err error) error { + // skip .git .github + if ok, _ := regexp.MatchString(`^\..*`, d.Name()); ok && d.IsDir() { + return filepath.SkipDir + } + if d.IsDir() { + return nil + } + if err != nil { + klog.V(2).Infof("read the file %s fail : %s", path, err.Error()) + return nil + } + if strings.HasSuffix(strings.ToLower(d.Name()), ".yaml") { + addon := &extensionsv1alpha1.Addon{} + content, _ := os.ReadFile(path) + err = yaml.Unmarshal(content, addon) + if err != nil { + klog.V(2).Infof("unmarshal the yaml %s fail : %s", path, err.Error()) + } + // if there are other types of resources in the current folder, skip it + if addon.Kind != "Addon" { + return filepath.SkipDir + } + if addon.Name == name { + res = append(res, searchResult{i, addon}) + } + } + return nil + }) + } + + for _, e := range indexes { + err = searchInDir(e) + if err != nil { + klog.V(2).Infof("search addon failed due to %s", err.Error()) + } + } + return res, nil +} diff --git a/pkg/cmd/addon/search_test.go b/pkg/cmd/addon/search_test.go new file mode 100644 index 000000000..21bcb1018 --- /dev/null +++ b/pkg/cmd/addon/search_test.go @@ -0,0 +1,82 @@ +/* +Copyright (C) 2022-2023 ApeCloud Co., Ltd + +This file is part of KubeBlocks project + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package addon + +import ( + "bytes" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/apecloud/kubeblocks/pkg/constant" + + "k8s.io/cli-runtime/pkg/genericiooptions" +) + +var _ = Describe("search test", func() { + var streams genericiooptions.IOStreams + var out *bytes.Buffer + const ( + testAddonName = "apecloud-mysql" + testAddonNotExisted = "fake-addon" + testIndexDir = "./testdata" + ) + BeforeEach(func() { + streams, _, out, _ = genericiooptions.NewTestIOStreams() + }) + It("test search cmd", func() { + Expect(newSearchCmd(streams)).ShouldNot(BeNil()) + }) + + It("test search", func() { + cmd := newSearchCmd(streams) + cmd.Run(cmd, []string{testAddonNotExisted}) + Expect(out.String()).Should(Equal("fake-addon addon not found. Please update your index or check the addon name")) + }) + + It("test addon search", func() { + expect := []struct { + index string + kind string + addonName string + version string + }{ + { + "kubeblocks", "Addon", "apecloud-mysql", "0.7.0", + }, + { + "kubeblocks", "Addon", "apecloud-mysql", "0.8.0-alpha.5", + }, + { + "kubeblocks", "Addon", "apecloud-mysql", "0.8.0-alpha.6", + }, + } + result, err := searchAddon(testAddonName, testIndexDir) + Expect(err).Should(Succeed()) + Expect(result).Should(HaveLen(3)) + for i := range result { + Expect(result[i].index.name).Should(Equal(expect[i].index)) + Expect(result[i].addon.Name).Should(Equal(expect[i].addonName)) + Expect(result[i].addon.Kind).Should(Equal(expect[i].kind)) + Expect(result[i].addon.Labels[constant.AppVersionLabelKey]).Should(Equal(expect[i].version)) + } + }) + +}) diff --git a/pkg/cmd/addon/testdata/kubeblocks/apecloud-mysql/0.7.0.yaml b/pkg/cmd/addon/testdata/kubeblocks/apecloud-mysql/0.7.0.yaml new file mode 100644 index 000000000..f2a0d6e20 --- /dev/null +++ b/pkg/cmd/addon/testdata/kubeblocks/apecloud-mysql/0.7.0.yaml @@ -0,0 +1,21 @@ +apiVersion: extensions.kubeblocks.io/v1alpha1 +kind: Addon +metadata: + annotations: + addon.kubeblocks.io/kubeblocks-version: '>=0.7.0' + labels: + addon.kubeblocks.io/model: RDBMS + addon.kubeblocks.io/provider: apecloud + app.kubernetes.io/name: apecloud-mysql + app.kubernetes.io/version: 0.7.0 + name: apecloud-mysql +spec: + defaultInstallValues: + - enabled: true + description: ApeCloud MySQL is a database that is compatible with MySQL syntax and + achieves high availability through the utilization of the RAFT consensus protocol. + helm: + chartLocationURL: https://jihulab.com/api/v4/projects/150246/packages/helm/stable/charts/apecloud-mysql-0.7.0.tgz + installable: + autoInstall: true + type: Helm diff --git a/pkg/cmd/addon/testdata/kubeblocks/apecloud-mysql/0.8.0-alpha.5.yaml b/pkg/cmd/addon/testdata/kubeblocks/apecloud-mysql/0.8.0-alpha.5.yaml new file mode 100644 index 000000000..08a7537dd --- /dev/null +++ b/pkg/cmd/addon/testdata/kubeblocks/apecloud-mysql/0.8.0-alpha.5.yaml @@ -0,0 +1,21 @@ +apiVersion: extensions.kubeblocks.io/v1alpha1 +kind: Addon +metadata: + annotations: + addon.kubeblocks.io/kubeblocks-version: '>=0.7.0' + labels: + addon.kubeblocks.io/model: RDBMS + addon.kubeblocks.io/provider: apecloud + app.kubernetes.io/name: apecloud-mysql + app.kubernetes.io/version: 0.8.0-alpha.5 + name: apecloud-mysql +spec: + defaultInstallValues: + - enabled: true + description: ApeCloud MySQL is a database that is compatible with MySQL syntax and + achieves high availability through the utilization of the RAFT consensus protocol. + helm: + chartLocationURL: https://jihulab.com/api/v4/projects/150246/packages/helm/stable/charts/apecloud-mysql-0.8.0-alpha.5.tgz + installable: + autoInstall: true + type: Helm diff --git a/pkg/cmd/addon/testdata/kubeblocks/apecloud-mysql/0.8.0-alpha.6.yaml b/pkg/cmd/addon/testdata/kubeblocks/apecloud-mysql/0.8.0-alpha.6.yaml new file mode 100644 index 000000000..314f263d0 --- /dev/null +++ b/pkg/cmd/addon/testdata/kubeblocks/apecloud-mysql/0.8.0-alpha.6.yaml @@ -0,0 +1,22 @@ +apiVersion: extensions.kubeblocks.io/v1alpha1 +kind: Addon +metadata: + annotations: + addon.kubeblocks.io/kubeblocks-version: '>=0.7.0' + addons.extensions.kubeblocks.io/default-is-empty: 'true' + labels: + addon.kubeblocks.io/model: RDBMS + addon.kubeblocks.io/provider: apecloud + app.kubernetes.io/name: apecloud-mysql + app.kubernetes.io/version: 0.8.0-alpha.6 + name: apecloud-mysql +spec: + defaultInstallValues: + - enabled: true + description: ApeCloud MySQL is a database that is compatible with MySQL syntax and + achieves high availability through the utilization of the RAFT consensus protocol. + helm: + chartLocationURL: https://jihulab.com/api/v4/projects/150246/packages/helm/stable/charts/apecloud-mysql-0.8.0-alpha.6.tgz + installable: + autoInstall: true + type: Helm diff --git a/pkg/cmd/addon/testdata/kubeblocks/not-addon/test.txt b/pkg/cmd/addon/testdata/kubeblocks/not-addon/test.txt new file mode 100644 index 000000000..8d5f8cc3b --- /dev/null +++ b/pkg/cmd/addon/testdata/kubeblocks/not-addon/test.txt @@ -0,0 +1 @@ +Testing a non-YAML file. diff --git a/pkg/cmd/addon/testdata/kubeblocks/not-addon/test2.yaml b/pkg/cmd/addon/testdata/kubeblocks/not-addon/test2.yaml new file mode 100644 index 000000000..5de712098 --- /dev/null +++ b/pkg/cmd/addon/testdata/kubeblocks/not-addon/test2.yaml @@ -0,0 +1,11 @@ +# Testing a YAML file that is not of addon type. +apiVersion: v1 +kind: Pod +metadata: + name: random-pod +spec: + containers: + - name: random-container + image: nginx:latest + ports: + - containerPort: 80 diff --git a/pkg/cmd/auth/login.go b/pkg/cmd/auth/login.go index 98c226ed3..09d840b52 100644 --- a/pkg/cmd/auth/login.go +++ b/pkg/cmd/auth/login.go @@ -246,7 +246,7 @@ func getFirstContext(token string, orgName string) string { return "" } - if contexts != nil && len(contexts) > 0 { + if len(contexts) > 0 { return contexts[0].Name } return ""