diff --git a/pkg/appfile/parser.go b/pkg/appfile/parser.go index a8c1fd8cd..392a7048a 100644 --- a/pkg/appfile/parser.go +++ b/pkg/appfile/parser.go @@ -77,6 +77,7 @@ type Trait struct { Params map[string]interface{} Template string Health string + Status string } // EvalContext eval trait template and set result to context @@ -84,6 +85,11 @@ func (trait *Trait) EvalContext(ctx process.Context) error { return definition.NewTDTemplater(trait.Name, trait.Template, "").Params(trait.Params).Complete(ctx) } +// EvalStatus eval trait status +func (trait *Trait) EvalStatus(ctx process.Context, cli client.Client, ns string) (string, error) { + return definition.NewTDTemplater(trait.Name, "", "").Status(ctx, cli, ns, trait.Status) +} + // EvalHealth eval trait health check func (trait *Trait) EvalHealth(ctx process.Context, client client.Client, name string) error { return definition.NewTDTemplater(trait.Name, "", trait.Health).Output(ctx, client, name).HealthCheck() @@ -188,6 +194,7 @@ func (p *Parser) parseTrait(name string, properties map[string]interface{}) (*Tr Params: properties, Template: templ.TemplateStr, Health: templ.Health, + Status: templ.CustomStatus, }, nil } @@ -244,6 +251,37 @@ func (p *Parser) GenerateApplicationConfiguration(app *Appfile, ns string) (*v1a return appconfig, components, nil } +// PrintApplicationComponents print appComponent status for application +func PrintApplicationComponents(app *Appfile, cli client.Client, ns string, + printer func(compName string, appName string, traitsStatus map[string]string) error) error { + + for _, wl := range app.Workloads { + traitsStatus := map[string]string{} + pCtx, err := PrepareProcessContext(cli, wl, app.Name, ns) + if err != nil { + return err + } + for _, tr := range wl.Traits { + if err := tr.EvalContext(pCtx); err != nil { + return err + } + } + + for _, tr := range wl.Traits { + status, err := tr.EvalStatus(pCtx, cli, ns) + if err != nil { + return errors.WithMessagef(err, "[%s.%s] eval error", wl.Name, tr.Name) + } + + traitsStatus[tr.Name] = status + } + if err := printer(wl.Name, app.Name, traitsStatus); err != nil { + return err + } + } + return nil +} + // evalWorkloadWithContext evaluate the workload's template to generate component and ACComponent func evalWorkloadWithContext(pCtx process.Context, wl *Workload) (*v1alpha2.Component, *v1alpha2.ApplicationConfigurationComponent, error) { base, assists := pCtx.Output() diff --git a/pkg/commands/init.go b/pkg/commands/init.go index b0dff468d..c40305676 100644 --- a/pkg/commands/init.go +++ b/pkg/commands/init.go @@ -19,6 +19,7 @@ import ( "github.com/oam-dev/kubevela/pkg/appfile" "github.com/oam-dev/kubevela/pkg/appfile/api" cmdutil "github.com/oam-dev/kubevela/pkg/commands/util" + "github.com/oam-dev/kubevela/pkg/oam/discoverymapper" "github.com/oam-dev/kubevela/pkg/plugins" "github.com/oam-dev/kubevela/pkg/serverlib" "github.com/oam-dev/kubevela/pkg/utils/env" @@ -103,8 +104,12 @@ func NewInitCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command { if deployStatus != compStatusDeployed { return nil } + dm, err := discoverymapper.New(c.Config) + if err != nil { + return err + } - return printComponentStatus(context.Background(), o.client, o.IOStreams, o.workloadName, o.appName, o.Env) + return printAppStatus(context.Background(), newClient, dm, ioStreams, o.appName, o.Env, cmd) }, Annotations: map[string]string{ types.TagCommandType: types.TypeStart, diff --git a/pkg/commands/status.go b/pkg/commands/status.go index a46e26302..154cf662f 100644 --- a/pkg/commands/status.go +++ b/pkg/commands/status.go @@ -19,7 +19,7 @@ import ( "github.com/oam-dev/kubevela/pkg/appfile" "github.com/oam-dev/kubevela/pkg/appfile/api" cmdutil "github.com/oam-dev/kubevela/pkg/commands/util" - "github.com/oam-dev/kubevela/pkg/oam" + "github.com/oam-dev/kubevela/pkg/oam/discoverymapper" oam2 "github.com/oam-dev/kubevela/pkg/serverlib" ) @@ -103,7 +103,11 @@ func NewAppStatusCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Comma if err != nil { return err } - return printAppStatus(ctx, newClient, ioStreams, appName, env, cmd) + dm, err := discoverymapper.New(c.Config) + if err != nil { + return err + } + return printAppStatus(ctx, newClient, dm, ioStreams, appName, env, cmd) }, Annotations: map[string]string{ types.TagCommandType: types.TypeApp, @@ -114,17 +118,12 @@ func NewAppStatusCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Comma return cmd } -func printAppStatus(ctx context.Context, c client.Client, ioStreams cmdutil.IOStreams, appName string, env *types.EnvMeta, cmd *cobra.Command) error { +func printAppStatus(ctx context.Context, c client.Client, dm discoverymapper.DiscoveryMapper, ioStreams cmdutil.IOStreams, appName string, env *types.EnvMeta, cmd *cobra.Command) error { app, err := appfile.LoadApplication(env.Name, appName) if err != nil { return err } - namespace := env.Name - - targetServices, err := oam2.GetServicesWhenDescribingApplication(cmd, app) - if err != nil { - return err - } + namespace := env.Namespace cmd.Printf("About:\n\n") table := newUITable() @@ -136,16 +135,36 @@ func printAppStatus(ctx context.Context, c client.Client, ioStreams cmdutil.IOSt cmd.Printf("Services:\n\n") - for _, svcName := range targetServices { - if err := printComponentStatus(ctx, c, ioStreams, svcName, appName, env); err != nil { - return err - } + remoteApp, err := loadRemoteApplication(c, namespace, appName) + if err != nil { + return err } - return nil + parser := appfile.NewApplicationParser(c, dm) + appFile, err := parser.GenerateAppFile(appName, remoteApp) + if err != nil { + return err + } + return appfile.PrintApplicationComponents(appFile, c, namespace, componentPrinter(ctx, c, ioStreams, env)) } -func printComponentStatus(ctx context.Context, c client.Client, ioStreams cmdutil.IOStreams, compName, appName string, env *types.EnvMeta) error { +func loadRemoteApplication(c client.Client, ns string, name string) (*v1alpha2.Application, error) { + app := new(v1alpha2.Application) + err := c.Get(context.Background(), client.ObjectKey{ + Namespace: ns, + Name: name, + }, app) + + return app, err +} + +func componentPrinter(ctx context.Context, c client.Client, ioStreams cmdutil.IOStreams, env *types.EnvMeta) func(compName string, appName string, traitsStatus map[string]string) error { + return func(compName string, appName string, traitsStatus map[string]string) error { + return printComponentStatus(ctx, c, ioStreams, compName, appName, env, traitsStatus) + } +} + +func printComponentStatus(ctx context.Context, c client.Client, ioStreams cmdutil.IOStreams, compName string, appName string, env *types.EnvMeta, traitsStatus map[string]string) error { app, appConfig, err := getAppConfig(ctx, c, compName, appName, env) if err != nil { return err @@ -173,14 +192,9 @@ func printComponentStatus(ctx context.Context, c client.Client, ioStreams cmduti // workload Must found ioStreams.Infof(" Traits:\n") - workloadStatus, _ := getWorkloadStatusFromAppConfig(appConfig, compName) - for _, tr := range workloadStatus.Traits { - traitType, traitInfo, err := traitCheckLoop(ctx, c, tr.Reference, compName, appConfig, app, 60*time.Second) - if err != nil { - ioStreams.Infof(" - %s%s: %s, err: %v", emojiFail, white.Sprint(traitType), traitInfo, err) - continue - } - ioStreams.Infof(" - %s%s: %s", emojiSucceed, white.Sprint(traitType), traitInfo) + + for traitType, traitInfo := range traitsStatus { + ioStreams.Infof(" - %s: %s", white.Sprint(traitType), traitInfo) } ioStreams.Info("") ioStreams.Infof(" Last Deployment:\n") @@ -189,43 +203,6 @@ func printComponentStatus(ctx context.Context, c client.Client, ioStreams cmduti return nil } -func traitCheckLoop(ctx context.Context, c client.Client, reference runtimev1alpha1.TypedReference, compName string, appConfig *v1alpha2.ApplicationConfiguration, app *api.Application, timeout time.Duration) (string, string, error) { - tr, err := oam2.GetUnstructured(ctx, c, appConfig.Namespace, reference) - if err != nil { - return "", "", err - } - traitType, ok := tr.GetLabels()[oam.TraitTypeLabel] - if !ok { - message, err := oam2.GetStatusFromObject(tr) - return traitType, message, err - } - - checker := oam2.GetChecker(traitType, c) - - // Health Check Loop For Trait - var message string - sHealthCheck := newTrackingSpinner(fmt.Sprintf("Checking %s status ...", traitType)) - sHealthCheck.Start() - defer sHealthCheck.Stop() -CheckLoop: - for { - time.Sleep(trackingInterval) - var check oam2.CheckStatus - check, message, err = checker.Check(ctx, reference, compName, appConfig, app) - if err != nil { - message = red.Sprintf("%s check failed!", traitType) - return traitType, message, err - } - if check == oam2.StatusDone { - break CheckLoop - } - if time.Since(tr.GetCreationTimestamp().Time) >= timeout { - return traitType, fmt.Sprintf("Checking timeout: %s", message), nil - } - } - return traitType, message, nil -} - func healthCheckLoop(ctx context.Context, c client.Client, compName, appName string, env *types.EnvMeta) (HealthStatus, string, error) { // Health Check Loop For Workload var healthInfo string diff --git a/pkg/dsl/definition/template.go b/pkg/dsl/definition/template.go index 4014072de..51f713d70 100644 --- a/pkg/dsl/definition/template.go +++ b/pkg/dsl/definition/template.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "strings" "cuelang.org/go/cue" "cuelang.org/go/cue/build" @@ -25,6 +26,8 @@ const ( OutputFieldName = "output" // OutputsFieldName is the name of the struct contains the map[string]CR data OutputsFieldName = "outputs" + // OutputObjectPath is the path of output object in template + OutputObjectPath = "path" // PatchFieldName is the name of the struct contains the patch of CR data PatchFieldName = "patch" ) @@ -45,6 +48,7 @@ type Template interface { Complete(ctx process.Context) error Output(ctx process.Context, client client.Client, name string) Template HealthCheck() error + Status(ctx process.Context, cli client.Client, ns string, handleTempl string) (string, error) } type def struct { @@ -174,6 +178,11 @@ func (wd *workloadDef) HealthCheck() error { return nil } +// Status get workload status +func (wd *workloadDef) Status(ctx process.Context, cli client.Client, ns string, handleTempl string) (string, error) { + return "", nil +} + type traitDef struct { def } @@ -232,6 +241,7 @@ func (td *traitDef) Complete(ctx process.Context) error { if err != nil { return errors.WithMessagef(err, "traitDef %s new Assist", td.name) } + other.SetTag(OutputObjectPath, OutputFieldName) ctx.PutAssistants(process.Assistant{Ins: other, Type: td.name}) } @@ -247,6 +257,7 @@ func (td *traitDef) Complete(ctx process.Context) error { if err != nil { return errors.WithMessagef(err, "traitDef %s new Assists(%s)", td.name, fieldInfo.Name) } + other.SetTag(OutputObjectPath, strings.Join([]string{OutputsFieldName, fieldInfo.Name}, ".")) ctx.PutAssistants(process.Assistant{Ins: other, Type: td.name}) } } @@ -266,6 +277,49 @@ func (td *traitDef) Complete(ctx process.Context) error { return nil } +// Status get trait status by handleTempl +func (td *traitDef) Status(ctx process.Context, cli client.Client, ns string, handleTempl string) (string, error) { + _, assists := ctx.Output() + var root = map[string]interface{}{} + for _, assist := range assists { + if assist.Type != td.name { + continue + } + traitRef, err := assist.Ins.Unstructured() + if err != nil { + return "", err + } + + if err := cli.Get(context.Background(), client.ObjectKey{ + Namespace: ns, + Name: traitRef.GetName(), + }, traitRef); err != nil { + return "", err + } + + paths := strings.Split(assist.Ins.GetTag(OutputObjectPath), ".") + + x := traitRef.Object + for i := len(paths) - 1; i >= 0; i-- { + x = map[string]interface{}{paths[i]: x} + } + for k, v := range x { + root[k] = v + } + } + + bt, _ := json.Marshal(root) + var buff = "context: " + string(bt) + + buff += "\n" + handleTempl + var r cue.Runtime + inst, err := r.Compile("-", buff) + if err != nil { + return "", err + } + return inst.Lookup("output").String() +} + // Output fetch the trait cr and set result to context func (td *traitDef) Output(ctx process.Context, client client.Client, name string) Template { _, assists := ctx.Output() diff --git a/pkg/dsl/model/instance.go b/pkg/dsl/model/instance.go index 467b7221e..35a2774bf 100644 --- a/pkg/dsl/model/instance.go +++ b/pkg/dsl/model/instance.go @@ -16,11 +16,14 @@ type Instance interface { IsBase() bool Unify(other Instance) error Compile() ([]byte, error) + SetTag(k, v string) + GetTag(k string) string } type instance struct { v string base bool + tags map[string]string } // String return instance's cue format string @@ -33,6 +36,16 @@ func (inst *instance) IsBase() bool { return inst.base } +// SetTag add or update tag for model +func (inst *instance) SetTag(k, v string) { + inst.tags[k] = v +} + +// GetTag get the tag of model by key +func (inst *instance) GetTag(k string) string { + return inst.tags[k] +} + func (inst *instance) Compile() ([]byte, error) { var r cue.Runtime cueInst, err := r.Compile("-", inst.v) @@ -82,6 +95,7 @@ func NewBase(v cue.Value) (Instance, error) { return &instance{ v: vs, base: true, + tags: map[string]string{}, }, nil } @@ -92,7 +106,8 @@ func NewOther(v cue.Value) (Instance, error) { return nil, err } return &instance{ - v: vs, + v: vs, + tags: map[string]string{}, }, nil } diff --git a/pkg/oam/util/template.go b/pkg/oam/util/template.go index 3149c3a37..441989605 100644 --- a/pkg/oam/util/template.go +++ b/pkg/oam/util/template.go @@ -18,6 +18,7 @@ import ( type Template struct { TemplateStr string Health string + CustomStatus string CapabilityCategory types.CapabilityCategory } @@ -46,7 +47,7 @@ func LoadTemplate(cli client.Reader, key string, kd types.CapType) (*Template, e if wd.Annotations["type"] == string(types.TerraformCategory) { capabilityCategory = types.TerraformCategory } - tmpl, err := getTemplAndHealth(wd.Spec.Extension.Raw) + tmpl, err := getTempl(wd.Spec.Extension.Raw) if err != nil { return nil, errors.WithMessagef(err, "LoadTemplate [%s] ", key) } @@ -65,7 +66,7 @@ func LoadTemplate(cli client.Reader, key string, kd types.CapType) (*Template, e if td.Annotations["type"] == string(types.TerraformCategory) { capabilityCategory = types.TerraformCategory } - tmpl, err := getTemplAndHealth(td.Spec.Extension.Raw) + tmpl, err := getTempl(td.Spec.Extension.Raw) if err != nil { return nil, errors.WithMessagef(err, "LoadTemplate [%s] ", key) } @@ -81,15 +82,23 @@ func LoadTemplate(cli client.Reader, key string, kd types.CapType) (*Template, e return nil, fmt.Errorf("kind(%s) of %s not supported", kd, key) } -func getTemplAndHealth(raw []byte) (*Template, error) { +func getTempl(raw []byte) (*Template, error) { _tmp := map[string]interface{}{} if err := json.Unmarshal(raw, &_tmp); err != nil { return nil, err } - var health string + var ( + health string + status string + ) if _, ok := _tmp["healthPolicy"]; ok { health = fmt.Sprint(_tmp["healthPolicy"]) } - return &Template{TemplateStr: fmt.Sprint(_tmp["template"]), - Health: health}, nil + if _, ok := _tmp["customStatus"]; ok { + status = fmt.Sprint(_tmp["customStatus"]) + } + return &Template{ + TemplateStr: fmt.Sprint(_tmp["template"]), + Health: health, + CustomStatus: status}, nil }