diff --git a/cmd/rudrx/main.go b/cmd/rudrx/main.go index ed224641a..e1af6dde9 100644 --- a/cmd/rudrx/main.go +++ b/cmd/rudrx/main.go @@ -7,8 +7,6 @@ import ( "runtime" "time" - "github.com/cloud-native-application/rudrx/pkg/cmd/workload" - "github.com/crossplane/oam-kubernetes-runtime/apis/core" "github.com/spf13/cobra" k8sruntime "k8s.io/apimachinery/pkg/runtime" @@ -83,7 +81,6 @@ func newCommand() *cobra.Command { cmds.AddCommand( cmd.NewTraitsCommand(f, client, ioStream, []string{}), cmd.NewWorkloadsCommand(f, client, ioStream, os.Args[1:]), - cmd.NewBindCommand(f, client, ioStream, []string{}), cmd.NewInitCommand(f, client, ioStream), cmd.NewDeleteCommand(f, client, ioStream, os.Args[1:]), cmd.NewAppsCommand(f, client, ioStream), @@ -93,11 +90,14 @@ func newCommand() *cobra.Command { cmd.NewEnvCommand(f, ioStream), NewVersionCommand(), ) - if err = workload.AddPlugins(cmds, client, ioStream); err != nil { + if err = cmd.AddWorkloadPlugins(cmds, client, ioStream); err != nil { fmt.Println("Add plugins from workloadDefinition err", err) os.Exit(1) } - + if err = cmd.AddTraitPlugins(cmds, client, ioStream); err != nil { + fmt.Println("Add plugins from traitDefinition err", err) + os.Exit(1) + } return cmds } diff --git a/pkg/cmd/bind.go b/pkg/cmd/bind.go deleted file mode 100644 index 516ade154..000000000 --- a/pkg/cmd/bind.go +++ /dev/null @@ -1,196 +0,0 @@ -package cmd - -import ( - "context" - "errors" - "fmt" - "os" - "strconv" - "strings" - - "github.com/cloud-native-application/rudrx/api/types" - - cmdutil "github.com/cloud-native-application/rudrx/pkg/cmd/util" - "github.com/crossplane/crossplane-runtime/pkg/fieldpath" - corev1alpha2 "github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2" - "github.com/spf13/cobra" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -type commandOptions struct { - Env *EnvMeta - Template types.Template - Component corev1alpha2.Component - AppConfig corev1alpha2.ApplicationConfiguration - Client client.Client - cmdutil.IOStreams -} - -func NewCommandOptions(ioStreams cmdutil.IOStreams) *commandOptions { - return &commandOptions{IOStreams: ioStreams} -} - -func NewBindCommand(f cmdutil.Factory, c client.Client, ioStreams cmdutil.IOStreams, args []string) *cobra.Command { - - var err error - - ctx := context.Background() - - o := NewCommandOptions(ioStreams) - o.Env, err = GetEnv() - if err != nil { - fmt.Printf("Listing trait definitions hit an issue: %v\n", err) - os.Exit(1) - } - o.Client = c - cmd := &cobra.Command{ - Use: "bind APPLICATION-NAME TRAIT-NAME [FLAG]", - DisableFlagsInUseLine: true, - Short: "Attach a trait to a component", - Long: "Attach a trait to a component.", - Example: `rudr bind frontend scaler --max=5`, - Run: func(cmd *cobra.Command, args []string) { - cmdutil.CheckErr(o.Complete(f, cmd, args, ctx)) - cmdutil.CheckErr(o.Run(f, cmd, ctx)) - }, - } - cmd.SetArgs(args) - var traitDefinitions corev1alpha2.TraitDefinitionList - err = c.List(ctx, &traitDefinitions) - if err != nil { - fmt.Println("Listing trait definitions hit an issue:", err) - os.Exit(1) - } - - for _, t := range traitDefinitions.Items { - var traitTemplate types.Template - traitTemplate, err := types.ConvertTemplateJson2Object(t.Spec.Extension) - if err != nil { - fmt.Printf("extract template from traitDefinition %v err: %v, ignore it\n", t.Name, err) - continue - } - - for _, p := range traitTemplate.Parameters { - if p.Type == "int" { - v, err := strconv.Atoi(p.Default) - if err != nil { - fmt.Println("Parameters type is wrong: ", err, ".Please report this to OAM maintainer, thanks.") - os.Exit(1) - } - cmd.PersistentFlags().Int(p.Name, v, p.Usage) - } else { - cmd.PersistentFlags().String(p.Name, p.Default, p.Usage) - } - } - } - - return cmd -} - -func (o *commandOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string, ctx context.Context) error { - argsLength := len(args) - var componentName string - - c := o.Client - - namespace := o.Env.Namespace - - if argsLength == 0 { - return errors.New("please append the name of an application. Use `rudr bind -h` for more detailed information") - } else if argsLength <= 2 { - componentName = args[0] - err := c.Get(ctx, client.ObjectKey{Namespace: o.Env.Namespace, Name: componentName}, &o.AppConfig) - if err != nil { - fmt.Println(err) - os.Exit(1) - } - - var component corev1alpha2.Component - err = c.Get(ctx, client.ObjectKey{Namespace: namespace, Name: componentName}, &component) - if err != nil { - errMsg := fmt.Sprintf("%s. Please choose an existed component name.", err) - cmdutil.PrintErrorMessage(errMsg, 1) - } - - // Retrieve all traits which can be used for the following 1) help and 2) validating - traitList, _ := RetrieveTraitsByWorkload(ctx, o.Client, namespace, "") - //if err != nil { - // errMsg := fmt.Sprintf("List available traits hit an issue: %s", err) - // cmdutil.PrintErrorMessage(errMsg, 1) - //} - - switch argsLength { - case 1: - // Validate component and suggest trait - fmt.Print("Error: No trait specified.\nPlease choose a trait: ") - for _, t := range traitList { - n := t.Short - if n == "" { - n = t.Name - } - fmt.Print(n, " ") - } - os.Exit(1) - - case 2: - // validate trait - traitName := args[1] - traitLongName, _, _ := cmdutil.GetTraitNameAliasKind(ctx, c, namespace, traitName) - - traitDefinition, err := cmdutil.GetTraitDefinitionByName(ctx, c, namespace, traitLongName) - if err != nil { - errMsg := fmt.Sprintf("trait name [%s] is not valid, please try again", traitName) - cmdutil.PrintErrorMessage(errMsg, 1) - } - - traitTemplate, err := types.ConvertTemplateJson2Object(traitDefinition.Spec.Extension) - - if err != nil { - return fmt.Errorf("attaching the trait hit an issue: %s", err) - } - - pvd := fieldpath.Pave(traitTemplate.Object) - for _, v := range traitTemplate.Parameters { - flagSet := cmd.Flag(v.Name) - for _, path := range v.FieldPaths { - fValue := flagSet.Value.String() - if v.Type == "int" { - portValue, _ := strconv.ParseFloat(fValue, 64) - pvd.SetNumber(path, portValue) - continue - } - pvd.SetString(path, fValue) - } - } - - // metadata.name needs to be in lower case. - pvd.SetString("metadata.name", strings.ToLower(traitName)) - - var t corev1alpha2.ComponentTrait - t.Trait.Object = &unstructured.Unstructured{Object: pvd.UnstructuredContent()} - o.Component.Name = componentName - o.AppConfig.Spec.Components = []corev1alpha2.ApplicationConfigurationComponent{{ - ComponentName: componentName, - Traits: []corev1alpha2.ComponentTrait{t}, - }} - } - } else { - cmdutil.PrintErrorMessage("Unknown command is specified, please check and try again.", 1) - } - return nil -} - -func (o *commandOptions) Run(f cmdutil.Factory, cmd *cobra.Command, ctx context.Context) error { - fmt.Println("Applying trait for component", o.Component.Name) - c := o.Client - err := c.Update(ctx, &o.AppConfig) - if err != nil { - msg := fmt.Sprintf("Applying trait hit an issue: %s", err) - cmdutil.PrintErrorMessage(msg, 1) - } - - msg := fmt.Sprintf("Succeeded!") - fmt.Println(msg) - return nil -} diff --git a/pkg/cmd/trait_bind.go b/pkg/cmd/trait_bind.go new file mode 100644 index 000000000..b2ad2c274 --- /dev/null +++ b/pkg/cmd/trait_bind.go @@ -0,0 +1,164 @@ +package cmd + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "strconv" + "strings" + + "k8s.io/apimachinery/pkg/runtime" + + "github.com/cloud-native-application/rudrx/pkg/plugins" + + "github.com/cloud-native-application/rudrx/api/types" + + cmdutil "github.com/cloud-native-application/rudrx/pkg/cmd/util" + "github.com/crossplane/crossplane-runtime/pkg/fieldpath" + corev1alpha2 "github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2" + "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type commandOptions struct { + Env *EnvMeta + Template types.Template + Component corev1alpha2.Component + AppConfig corev1alpha2.ApplicationConfiguration + Client client.Client + cmdutil.IOStreams +} + +func NewCommandOptions(ioStreams cmdutil.IOStreams) *commandOptions { + return &commandOptions{IOStreams: ioStreams} +} + +func AddTraitPlugins(parentCmd *cobra.Command, c client.Client, ioStreams cmdutil.IOStreams) error { + templates, err := plugins.GetTraitsFromCluster(context.TODO(), types.DefaultOAMNS, c) + if err != nil { + return err + } + ctx := context.Background() + for _, tmp := range templates { + var name = tmp.Alias + o := NewCommandOptions(ioStreams) + o.Client = c + o.Env, _ = GetEnv() + pluginCmd := &cobra.Command{ + Use: name + " [args]", + DisableFlagsInUseLine: true, + Short: "Attach " + name + " trait to an app", + Long: "Attach " + name + " trait to an app", + Example: `rudr scale frontend --max=5`, + RunE: func(cmd *cobra.Command, args []string) error { + if err := o.Complete(cmd, args, ctx); err != nil { + return err + } + return o.Run(cmd, ctx) + }, + } + pluginCmd.SetOut(o.Out) + for _, v := range tmp.Parameters { + pluginCmd.Flags().StringP(v.Name, v.Short, v.Default, v.Usage) + if v.Required { + pluginCmd.MarkFlagRequired(v.Name) + } + } + + o.Template = tmp + parentCmd.AddCommand(pluginCmd) + } + return nil +} + +func (o *commandOptions) Complete(cmd *cobra.Command, args []string, ctx context.Context) error { + argsLength := len(args) + var appName string + + c := o.Client + + namespace := o.Env.Namespace + + if argsLength < 1 { + return errors.New("please specify the name of the app") + } + + // Get AppConfig + // TODO(wonderflow): appName is Component Name here, check if it's has appset with a different name + appName = args[0] + if err := c.Get(ctx, client.ObjectKey{Namespace: o.Env.Namespace, Name: appName}, &o.AppConfig); err != nil { + return err + } + + // Get component + var component corev1alpha2.Component + if err := c.Get(ctx, client.ObjectKey{Namespace: namespace, Name: appName}, &component); err != nil { + return err + } + + pvd := fieldpath.Pave(o.Template.Object) + for _, v := range o.Template.Parameters { + flagSet := cmd.Flag(v.Name) + for _, path := range v.FieldPaths { + fValue := flagSet.Value.String() + if v.Type == "int" { + portValue, _ := strconv.ParseFloat(fValue, 64) + pvd.SetNumber(path, portValue) + continue + } + pvd.SetString(path, fValue) + } + } + // metadata.name needs to be in lower case. + pvd.SetString("metadata.name", strings.ToLower(fmt.Sprintf("%s-%s-trait", appName, o.Template.Alias))) + curObj := &unstructured.Unstructured{Object: pvd.UnstructuredContent()} + var updated bool + for ic, c := range o.AppConfig.Spec.Components { + if c.ComponentName != appName { + continue + } + for it, t := range c.Traits { + g, v, k := GetGVKFromRawExtension(t.Trait) + + // TODO(wonderflow): we should get GVK from Definition instead of assuming template object contains + gvk := curObj.GroupVersionKind() + if gvk.Group == g && gvk.Version == v && gvk.Kind == k { + updated = true + c.Traits[it] = corev1alpha2.ComponentTrait{Trait: runtime.RawExtension{Object: curObj}} + break + } + } + if !updated { + c.Traits = append(c.Traits, corev1alpha2.ComponentTrait{Trait: runtime.RawExtension{Object: curObj}}) + } + o.AppConfig.Spec.Components[ic] = c + break + } + return nil +} + +func (o *commandOptions) Run(cmd *cobra.Command, ctx context.Context) error { + o.Info("Applying trait for app", o.Component.Name) + c := o.Client + err := c.Update(ctx, &o.AppConfig) + if err != nil { + return err + } + o.Info("Succeeded!") + return nil +} + +func GetGVKFromRawExtension(extension runtime.RawExtension) (string, string, string) { + if extension.Object != nil { + gvk := extension.Object.GetObjectKind().GroupVersionKind() + return gvk.Group, gvk.Version, gvk.Kind + } + var data map[string]interface{} + // leverage Admission Controller to do the check + _ = json.Unmarshal(extension.Raw, &data) + obj := unstructured.Unstructured{Object: data} + gvk := obj.GroupVersionKind() + return gvk.Group, gvk.Version, gvk.Kind +} diff --git a/pkg/cmd/bind_test.go b/pkg/cmd/trait_bind_test.go similarity index 100% rename from pkg/cmd/bind_test.go rename to pkg/cmd/trait_bind_test.go diff --git a/pkg/cmd/workload/run.go b/pkg/cmd/workload_run.go similarity index 90% rename from pkg/cmd/workload/run.go rename to pkg/cmd/workload_run.go index 88658e211..bc0b9ceb7 100644 --- a/pkg/cmd/workload/run.go +++ b/pkg/cmd/workload_run.go @@ -1,4 +1,4 @@ -package workload +package cmd import ( "context" @@ -11,8 +11,6 @@ import ( "github.com/cloud-native-application/rudrx/pkg/plugins" - "github.com/cloud-native-application/rudrx/pkg/cmd" - "github.com/crossplane/crossplane-runtime/pkg/fieldpath" corev1alpha2 "github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2" "github.com/spf13/cobra" @@ -24,7 +22,7 @@ import ( type runOptions struct { Template types.Template - Env *cmd.EnvMeta + Env *EnvMeta Component corev1alpha2.Component AppConfig corev1alpha2.ApplicationConfiguration client client.Client @@ -35,25 +33,23 @@ func newRunOptions(ioStreams cmdutil.IOStreams) *runOptions { return &runOptions{IOStreams: ioStreams} } -func AddPlugins(parentCmd *cobra.Command, c client.Client, ioStreams cmdutil.IOStreams) error { +func AddWorkloadPlugins(parentCmd *cobra.Command, c client.Client, ioStreams cmdutil.IOStreams) error { templates, err := plugins.GetWorkloadsFromCluster(context.TODO(), types.DefaultOAMNS, c) if err != nil { return err } for _, tmp := range templates { - var name string - if tmp.Alias != "" { - name = tmp.Alias - } + var name = tmp.Alias o := newRunOptions(ioStreams) o.client = c - o.Env, _ = cmd.GetEnv() + o.Env, _ = GetEnv() pluginCmd := &cobra.Command{ - Use: name + ":run [args]", + Use: name + ":run [args]", DisableFlagsInUseLine: true, Short: "Run " + name + " workloads", Long: "Run " + name + " workloads", + Example: `rudr deployment:run frontend -i nginx:latest`, RunE: func(cmd *cobra.Command, args []string) error { if err := o.Complete(cmd, args, context.TODO()); err != nil { return err diff --git a/pkg/cmd/workload/run_test.go b/pkg/cmd/workload_run_test.go similarity index 99% rename from pkg/cmd/workload/run_test.go rename to pkg/cmd/workload_run_test.go index 49cf921af..921b088e4 100644 --- a/pkg/cmd/workload/run_test.go +++ b/pkg/cmd/workload_run_test.go @@ -1,4 +1,4 @@ -package workload +package cmd /* func TestNewRunCommand(t *testing.T) { diff --git a/pkg/plugins/cluster.go b/pkg/plugins/cluster.go index 0e62b2969..62f88454a 100644 --- a/pkg/plugins/cluster.go +++ b/pkg/plugins/cluster.go @@ -40,6 +40,9 @@ func GetWorkloadsFromCluster(ctx context.Context, namespace string, c client.Cli } tmp.Type = types.TypeWorkload tmp.Name = wd.Name + if tmp.Alias == "" { + tmp.Alias = tmp.Name + } templates = append(templates, tmp) } return templates, nil @@ -62,6 +65,9 @@ func GetTraitsFromCluster(ctx context.Context, namespace string, c client.Client } tmp.Type = types.TypeTrait tmp.Name = td.Name + if tmp.Alias == "" { + tmp.Alias = tmp.Name + } templates = append(templates, tmp) } return templates, nil