Merge pull request #72 from wonderflow/trait

refactor trait from rudr bind to rudr <trait-type> appname
This commit is contained in:
Sun Jianbo
2020-08-02 10:55:44 +08:00
committed by GitHub
7 changed files with 183 additions and 213 deletions

View File

@@ -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
}

View File

@@ -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
}

164
pkg/cmd/trait_bind.go Normal file
View File

@@ -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 + " <appname> [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
}

View File

@@ -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 <appname> [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

View File

@@ -1,4 +1,4 @@
package workload
package cmd
/*
func TestNewRunCommand(t *testing.T) {

View File

@@ -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