From f468814371fb46bad2cdace60f2728eaaa66cf87 Mon Sep 17 00:00:00 2001 From: barnettZQG Date: Thu, 30 Dec 2021 14:38:00 +0800 Subject: [PATCH] Feat: add uischema manage command (#3021) Signed-off-by: barnettZQG --- apis/types/types.go | 2 + pkg/apiserver/rest/usecase/definition.go | 3 + references/cli/cli.go | 4 +- .../component-uischema-k8s-objects.yaml | 2 + .../component/component-alibaba-rds.yaml | 23 +++ references/cli/uischema.go | 193 ++++++++++++++++++ references/cli/uischema_test.go | 42 ++++ 7 files changed, 268 insertions(+), 1 deletion(-) create mode 100644 references/cli/test-data/uischema/component-uischema-k8s-objects.yaml create mode 100644 references/cli/test-data/uischema/component/component-alibaba-rds.yaml create mode 100644 references/cli/uischema.go create mode 100644 references/cli/uischema_test.go diff --git a/apis/types/types.go b/apis/types/types.go index 585257fa6..b3b5968d7 100644 --- a/apis/types/types.go +++ b/apis/types/types.go @@ -82,6 +82,8 @@ const ( TypeDefinition = "Managing Definitions" // TypePlugin defines one category used in Kubectl Plugin TypePlugin = "Plugin Command" + // TypeUISchema defines one category + TypeUISchema = "Managing UISchema" ) // LabelArg is the argument `label` of a definition diff --git a/pkg/apiserver/rest/usecase/definition.go b/pkg/apiserver/rest/usecase/definition.go index b81cb36b8..25519e73f 100644 --- a/pkg/apiserver/rest/usecase/definition.go +++ b/pkg/apiserver/rest/usecase/definition.go @@ -263,6 +263,9 @@ func patchSchema(defaultSchema, customSchema []*utils.UIParameter) []*utils.UIPa for i, custom := range customSchema { customSchemaMap[custom.JSONKey] = customSchema[i] } + if len(defaultSchema) == 0 { + return customSchema + } for i := range defaultSchema { dSchema := defaultSchema[i] if cusSchema, exist := customSchemaMap[dSchema.JSONKey]; exist { diff --git a/references/cli/cli.go b/references/cli/cli.go index c0aabf03e..1e84e5259 100644 --- a/references/cli/cli.go +++ b/references/cli/cli.go @@ -90,7 +90,6 @@ func NewCommand() *cobra.Command { // Workflows NewWorkflowCommand(commandArgs, ioStream), - NewRegistryCommand(ioStream), NewTemplateCommand(ioStream), NewTraitCommand(commandArgs, ioStream), @@ -113,6 +112,9 @@ func NewCommand() *cobra.Command { // cluster ClusterCommandGroup(commandArgs, ioStream), + // ui schema + NewUISchemaCommand(commandArgs, ioStream), + // Helper SystemCommandGroup(commandArgs, ioStream), NewCompletionCommand(), diff --git a/references/cli/test-data/uischema/component-uischema-k8s-objects.yaml b/references/cli/test-data/uischema/component-uischema-k8s-objects.yaml new file mode 100644 index 000000000..a35528eaf --- /dev/null +++ b/references/cli/test-data/uischema/component-uischema-k8s-objects.yaml @@ -0,0 +1,2 @@ +- jsonKey: objects + uiType: K8sObjectsCode \ No newline at end of file diff --git a/references/cli/test-data/uischema/component/component-alibaba-rds.yaml b/references/cli/test-data/uischema/component/component-alibaba-rds.yaml new file mode 100644 index 000000000..16553419a --- /dev/null +++ b/references/cli/test-data/uischema/component/component-alibaba-rds.yaml @@ -0,0 +1,23 @@ +- jsonKey: writeConnectionSecretToRef + disable: true +- jsonKey: providerRef + disable: true +- jsonKey: region + disable: true +- jsonKey: instance_name + label: InstanceName + sort: 1 +- jsonKey: database_name + label: DatabaseName + sort: 2 +- jsonKey: account_name + label: AccountName + sort: 3 +- jsonKey: password + sort: 4 +- jsonKey: allocate_public_connection + label: AllocatePublicConnection + sort: 5 +- jsonKey: deleteResource + label: DeleteResource + sort: 10 diff --git a/references/cli/uischema.go b/references/cli/uischema.go new file mode 100644 index 000000000..b1ad7e952 --- /dev/null +++ b/references/cli/uischema.go @@ -0,0 +1,193 @@ +/* +Copyright 2021 The KubeVela Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cli + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "os" + "path" + "path/filepath" + "strings" + + "github.com/spf13/cobra" + v1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + k8stypes "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/yaml" + + "github.com/oam-dev/kubevela/apis/types" + "github.com/oam-dev/kubevela/pkg/apiserver/rest/utils" + "github.com/oam-dev/kubevela/pkg/utils/common" + "github.com/oam-dev/kubevela/pkg/utils/util" +) + +// NewUISchemaCommand creates `uischema` command +func NewUISchemaCommand(c common.Args, ioStreams util.IOStreams) *cobra.Command { + cmd := &cobra.Command{ + Use: "uischema", + Aliases: []string{"ui"}, + Annotations: map[string]string{ + types.TagCommandType: types.TypeUISchema, + }, + } + cmd.AddCommand(&cobra.Command{ + Use: "apply", + Short: "apply ", + Args: cobra.ExactValidArgs(1), + Long: "apply uischema from a file or dir", + Annotations: map[string]string{ + types.TagCommandType: types.TypeUISchema, + }, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + return c.SetConfig() + }, + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return errors.New("please provider the uischema file or dir path") + } + allUISchemaFiles, err := loadUISchemaFiles(args[0]) + if err != nil { + return err + } + client, err := c.GetClient() + if err != nil { + return err + } + for _, file := range allUISchemaFiles { + if err := applyUISchemaFile(client, file); err != nil { + ioStreams.Errorf("apply %s failure %s \n", file, err.Error()) + continue + } + ioStreams.Infof("apply %s success \n", file) + } + return nil + }, + }) + + return cmd +} + +func loadUISchemaFiles(setpath string) ([]string, error) { + schema, err := os.Stat(setpath) + if err != nil { + return nil, err + } + var readDir func(string) ([]string, error) + readDir = func(filepath string) ([]string, error) { + files, err := os.ReadDir(filepath) + if err != nil { + return nil, err + } + var subFiles []string + for _, f := range files { + if f.IsDir() { + files, err := readDir(path.Join(filepath, f.Name())) + if err != nil { + return nil, err + } + subFiles = append(subFiles, files...) + } else if strings.HasSuffix(f.Name(), ".yaml") { + subFiles = append(subFiles, path.Join(filepath, f.Name())) + } + } + return subFiles, nil + } + var allUISchemaFiles = []string{} + if schema.IsDir() { + allUISchemaFiles, err = readDir(setpath) + if err != nil { + return nil, err + } + } else { + allUISchemaFiles = append(allUISchemaFiles, setpath) + } + return allUISchemaFiles, nil +} + +func applyUISchemaFile(client client.Client, uischemaFile string) error { + cdata, err := ioutil.ReadFile(filepath.Clean(uischemaFile)) + if err != nil { + return err + } + fileBaseName := path.Base(uischemaFile) + fileBaseNameWithoutExt := fileBaseName[:strings.Index(fileBaseName, ".")] + + infos := strings.SplitN(fileBaseNameWithoutExt, "-", func() int { + if strings.Contains(fileBaseNameWithoutExt, "uischema") { + return 3 + } + return 2 + }()) + if len(infos) == 2 || len(infos) == 3 && infos[1] == "uischema" { + name := infos[1] + if len(infos) == 3 { + name = infos[2] + } + err = addDefinitionUISchema(context.Background(), client, name, infos[0], string(cdata)) + if err != nil { + return err + } + return nil + } + return fmt.Errorf("file name is invalid") +} + +func addDefinitionUISchema(ctx context.Context, client client.Client, name, defType, configRaw string) error { + var uiParameters []*utils.UIParameter + err := yaml.Unmarshal([]byte(configRaw), &uiParameters) + if err != nil { + return err + } + dataBate, err := json.Marshal(uiParameters) + if err != nil { + return err + } + + var cm v1.ConfigMap + if err := client.Get(ctx, k8stypes.NamespacedName{ + Namespace: types.DefaultKubeVelaNS, + Name: fmt.Sprintf("%s-uischema-%s", defType, name), + }, &cm); err != nil { + if apierrors.IsNotFound(err) { + err = client.Create(ctx, &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: types.DefaultKubeVelaNS, + Name: fmt.Sprintf("%s-uischema-%s", defType, name), + }, + Data: map[string]string{ + types.UISchema: string(dataBate), + }, + }) + } + if err != nil { + return err + } + } else { + cm.Data[types.UISchema] = string(dataBate) + err := client.Update(ctx, &cm) + if err != nil { + return err + } + } + return nil +} diff --git a/references/cli/uischema_test.go b/references/cli/uischema_test.go new file mode 100644 index 000000000..17b214758 --- /dev/null +++ b/references/cli/uischema_test.go @@ -0,0 +1,42 @@ +/* +Copyright 2021 The KubeVela Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cli + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + + common2 "github.com/oam-dev/kubevela/pkg/utils/common" + "github.com/oam-dev/kubevela/pkg/utils/util" +) + +func TestLoadUISchemaFiles(t *testing.T) { + files, err := loadUISchemaFiles("test-data/uischema") + assert.Nil(t, err) + assert.True(t, len(files) == 2) +} + +func TestNewUISchemaCommand(t *testing.T) { + cmd := NewUISchemaCommand(common2.Args{}, util.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}) + initCommand(cmd) + cmd.SetArgs([]string{"apply", "test-data/uischema"}) + if err := cmd.Execute(); err != nil { + t.Fatalf("failed to execute definition command: %v", err) + } +}