Merge pull request #480 from zzxwill/app-status

Refactor "vela status"
This commit is contained in:
Zheng Xi Zhou
2020-11-02 09:57:21 +08:00
committed by GitHub
8 changed files with 98 additions and 193 deletions

View File

@@ -26,8 +26,8 @@ var _ = ginkgo.Describe("Application", func() {
e2e.ComponentListContext("ls", applicationName, "")
e2e.TraitManualScalerAttachContext("vela attach scaler trait", traitAlias, applicationName)
e2e.ApplicationShowContext("show", applicationName, workloadType)
e2e.ApplicationStatusContext("app status", applicationName, workloadType)
e2e.ApplicationCompStatusContext("svc status", applicationName, workloadType, envName)
e2e.ApplicationStatusContext("status", applicationName, workloadType)
e2e.ApplicationStatusDeeplyContext("status", applicationName, workloadType, envName)
e2e.ApplicationExecContext("exec -- COMMAND", applicationName)
e2e.ApplicationPortForwardContext("port-forward", applicationName)
e2e.ApplicationInitIntercativeCliContext("init", appNameForInit, workloadType)

View File

@@ -183,7 +183,7 @@ var (
ApplicationStatusContext = func(context string, applicationName string, workloadType string) bool {
return ginkgo.Context(context, func() {
ginkgo.It("should get status for the application", func() {
cli := fmt.Sprintf("vela app status %s", applicationName)
cli := fmt.Sprintf("vela status %s", applicationName)
output, err := Exec(cli)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
gomega.Expect(output).To(gomega.ContainSubstring(applicationName))
@@ -192,7 +192,7 @@ var (
})
}
ApplicationCompStatusContext = func(context string, applicationName, workloadType, envName string) bool {
ApplicationStatusDeeplyContext = func(context string, applicationName, workloadType, envName string) bool {
return ginkgo.Context(context, func() {
ginkgo.It("should get status of the service", func() {
ginkgo.By("init new k8s client")
@@ -206,7 +206,7 @@ var (
return len(appConfig.Status.Workloads)
}, 90*time.Second, 1*time.Second).ShouldNot(gomega.Equal(0))
cli := fmt.Sprintf("vela svc status %s", applicationName)
cli := fmt.Sprintf("vela status %s", applicationName)
output, err := LongTimeExec(cli, 120*time.Second)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
gomega.Expect(output).To(gomega.ContainSubstring("Checking health status"))

View File

@@ -20,7 +20,6 @@ func NewAppsCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command {
}
cmd.AddCommand(
NewAppStatusCommand(c, ioStreams),
NewRunCommand(c, ioStreams))
return cmd
}

View File

@@ -73,6 +73,7 @@ func NewCommand() *cobra.Command {
NewListCommand(commandArgs, ioStream),
NewDeleteCommand(commandArgs, ioStream),
NewAppShowCommand(ioStream),
NewAppStatusCommand(commandArgs, ioStream),
// Workloads
AddCompCommands(commandArgs, ioStream),

View File

@@ -41,9 +41,7 @@ func AddCompCommands(c types.Args, ioStreams util.IOStreams) *cobra.Command {
compCommands.PersistentFlags().StringP(App, "a", "", "specify the name of application containing the services")
compCommands.AddCommand(
NewCompDeployCommands(c, ioStreams),
NewCompStatusCommand(c, ioStreams),
)
return compCommands
}

View File

@@ -6,7 +6,6 @@ import (
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/AlecAivazis/survey/v2"
"github.com/gosuri/uitable"
"github.com/oam-dev/kubevela/api/types"
"github.com/oam-dev/kubevela/pkg/application"
@@ -49,44 +48,9 @@ func showApplication(cmd *cobra.Command, env *types.EnvMeta, appName string) err
if err != nil {
return err
}
var svcFlag, chosenSvc string
var svcFlagStatus string
// to store the value of flag `--svc` set in Cli, or selected value in survey
var targetServices []string
if svcFlag = cmd.Flag("svc").Value.String(); svcFlag == "" {
svcFlagStatus = oam.FlagNotSet
} else {
svcFlagStatus = oam.FlagIsInvalid
}
// all services name of the application `appName`
var services []string
for svcName := range app.Services {
services = append(services, svcName)
if svcFlag == svcName {
svcFlagStatus = oam.FlagIsValid
targetServices = append(targetServices, svcName)
}
}
totalServices := len(services)
if svcFlagStatus == oam.FlagNotSet && totalServices == 1 {
targetServices = services
}
if svcFlagStatus == oam.FlagIsInvalid || (svcFlagStatus == oam.FlagNotSet && totalServices > 1) {
if svcFlagStatus == oam.FlagIsInvalid {
cmd.Printf("The service name '%s' is not valid\n", svcFlag)
}
chosenSvc, err = chooseSvc(services)
if err != nil {
return err
}
if chosenSvc == oam.DefaultChosenAllSvc {
targetServices = services
} else {
targetServices = targetServices[:0]
targetServices = append(targetServices, chosenSvc)
}
targetServices, err := oam.GetServicesWhenDescribingApplication(cmd, app)
if err != nil {
return err
}
cmd.Printf("About:\n\n")
@@ -160,18 +124,3 @@ func showComponent(cmd *cobra.Command, env *types.EnvMeta, compName, appName str
}
return nil
}
func chooseSvc(services []string) (string, error) {
var svcName string
services = append(services, oam.DefaultChosenAllSvc)
prompt := &survey.Select{
Message: "Please choose one service: ",
Options: services,
Default: oam.DefaultChosenAllSvc,
}
err := survey.AskOne(prompt, &svcName)
if err != nil {
return "", fmt.Errorf("failed to retrieve services of the application, err %v", err)
}
return svcName, nil
}

View File

@@ -21,7 +21,6 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/oam-dev/kubevela/api/types"
"github.com/oam-dev/kubevela/pkg/appfile"
"github.com/oam-dev/kubevela/pkg/application"
cmdutil "github.com/oam-dev/kubevela/pkg/commands/util"
oam2 "github.com/oam-dev/kubevela/pkg/oam"
@@ -78,14 +77,7 @@ const (
ErrServiceNotFound = "service %s not found in app"
)
const (
firstElemPrefix = `├─`
lastElemPrefix = `└─`
pipe = ``
)
var (
gray = color.New(color.FgHiBlack)
red = color.New(color.FgRed)
green = color.New(color.FgGreen)
yellow = color.New(color.FgYellow)
@@ -131,129 +123,48 @@ func NewAppStatusCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Comma
if err != nil {
return err
}
return printAppStatus(ctx, newClient, ioStreams, appName, env)
return printAppStatus(ctx, newClient, ioStreams, appName, env, cmd)
},
Annotations: map[string]string{
types.TagCommandType: types.TypeApp,
},
}
cmd.Flags().StringP("svc", "s", "", "service name")
cmd.SetOut(ioStreams.Out)
return cmd
}
func printAppStatus(ctx context.Context, c client.Client, ioStreams cmdutil.IOStreams, appName string, env *types.EnvMeta) error {
func printAppStatus(ctx context.Context, c client.Client, ioStreams cmdutil.IOStreams, appName string, env *types.EnvMeta, cmd *cobra.Command) error {
app, err := application.Load(env.Name, appName)
if err != nil {
return err
}
namespace := env.Name
tbl := uitable.New()
tbl.Separator = " "
tbl.AddRow(
white.Sprint("NAMESPCAE"),
white.Sprint("NAME"),
white.Sprint("INFO"))
tbl.AddRow(
namespace,
fmt.Sprintf("%s/%s",
"Application",
appName))
components := app.GetComponents()
// get a map coantaining all workloads health condition
wlConditionsMap, err := getWorkloadHealthConditions(ctx, c, app, namespace)
targetServices, err := oam2.GetServicesWhenDescribingApplication(cmd, app)
if err != nil {
return err
}
for cIndex, compName := range components {
var cPrefix string
switch cIndex {
case len(components) - 1:
cPrefix = lastElemPrefix
default:
cPrefix = firstElemPrefix
cmd.Printf("About:\n\n")
table := uitable.New()
table.AddRow(" Name:", appName)
table.AddRow(" Namespace:", namespace)
table.AddRow(" Created at:", app.CreateTime.String())
table.AddRow(" Updated at:", app.UpdateTime.String())
cmd.Printf("%s\n\n", table.String())
cmd.Printf("Services:\n\n")
for _, svcName := range targetServices {
if err := printComponentStatus(ctx, c, ioStreams, svcName, appName, env); err != nil {
return err
}
wlHealthCondition := wlConditionsMap[compName]
wlHealthStatus := wlHealthCondition.HealthStatus
healthColor := getHealthStatusColor(wlHealthStatus)
// print component info
tbl.AddRow("",
fmt.Sprintf("%s%s/%s",
gray.Sprint(printPrefix(cPrefix)),
"Component",
compName),
healthColor.Sprintf("%s %s", wlHealthStatus, wlHealthCondition.Diagnosis))
}
ioStreams.Info(tbl)
return nil
}
// map componentName <=> WorkloadHealthCondition
func getWorkloadHealthConditions(ctx context.Context, c client.Client, app *application.Application, ns string) (map[string]*WorkloadHealthCondition, error) {
hs := &v1alpha2.HealthScope{}
// only use default health scope
hsName := appfile.FormatDefaultHealthScopeName(app.Name)
if err := c.Get(ctx, client.ObjectKey{Namespace: ns, Name: hsName}, hs); err != nil {
return nil, err
}
wlConditions := hs.Status.WorkloadHealthConditions
r := map[string]*WorkloadHealthCondition{}
components := app.GetComponents()
for _, compName := range components {
for _, wlhc := range wlConditions {
if wlhc.ComponentName == compName {
r[compName] = wlhc
break
}
}
if r[compName] == nil {
r[compName] = &WorkloadHealthCondition{
HealthStatus: HealthStatusNotDiagnosed,
}
}
}
return r, nil
}
func NewCompStatusCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command {
ctx := context.Background()
cmd := &cobra.Command{
Use: "status <SERVICE-NAME>",
Short: "get status of a service",
Long: "get status of a service, including its workload and health status",
Example: `vela svc status <SERVICE-NAME>`,
RunE: func(cmd *cobra.Command, args []string) error {
argsLength := len(args)
if argsLength == 0 {
ioStreams.Errorf("Hint: please specify the service name")
os.Exit(1)
}
compName := args[0]
env, err := GetEnv(cmd)
if err != nil {
ioStreams.Errorf("Error: failed to get Env: %s", err)
return err
}
newClient, err := client.New(c.Config, client.Options{Scheme: c.Schema})
if err != nil {
return err
}
appName, _ := cmd.Flags().GetString(App)
return printComponentStatus(ctx, newClient, ioStreams, compName, appName, env)
},
Annotations: map[string]string{
types.TagCommandType: types.TypeApp,
},
}
cmd.SetOut(ioStreams.Out)
return cmd
}
func printComponentStatus(ctx context.Context, c client.Client, ioStreams cmdutil.IOStreams, compName, appName string, env *types.EnvMeta) error {
app, appConfig, err := getApp(ctx, c, compName, appName, env)
if err != nil {
@@ -267,20 +178,25 @@ func printComponentStatus(ctx context.Context, c client.Client, ioStreams cmduti
return fmt.Errorf(ErrServiceNotFound, compName)
}
workloadType := svc.GetType()
ioStreams.Infof("Showing status of service(type: %s) %s deployed in Environment %s\n", workloadType, white.Sprint(compName), env.Name)
healthStatus, healthInfo, err := healthCheckLoop(ctx, c, compName, appName, env)
if err != nil {
ioStreams.Info(healthInfo)
return err
}
ioStreams.Infof(white.Sprintf("Service %s Status:", compName))
ioStreams.Infof(white.Sprintf(" - Name: %s\n", compName))
ioStreams.Infof(" Type: %s\n", workloadType)
healthColor := getHealthStatusColor(healthStatus)
healthInfo = strings.ReplaceAll(healthInfo, "\n", "\n\t") // formart healthInfo output
ioStreams.Infof("\t %s %s\n", healthColor.Sprint(healthStatus), healthColor.Sprint(healthInfo))
healthInfo = strings.ReplaceAll(healthInfo, "\n", "\n\t") // format healthInfo output
ioStreams.Infof(" %s %s\n", healthColor.Sprint(healthStatus), healthColor.Sprint(healthInfo))
ioStreams.Infof(" Last Deployment:\n")
ioStreams.Infof(" Created at: %v\n", appConfig.CreationTimestamp)
ioStreams.Infof(" Updated at: %v\n", app.UpdateTime.Format(time.RFC3339))
// workload Must found
ioStreams.Infof(" Routes:\n")
workloadStatus, _ := getWorkloadStatusFromAppConfig(appConfig, compName)
for _, tr := range workloadStatus.Traits {
traitType, traitInfo, err := traitCheckLoop(ctx, c, tr.Reference, compName, appConfig, app, 60*time.Second)
@@ -288,12 +204,9 @@ func printComponentStatus(ctx context.Context, c client.Client, ioStreams cmduti
ioStreams.Infof("%s status: %s", white.Sprint(traitType), traitInfo)
return err
}
ioStreams.Infof("\t%s: %s", white.Sprint(traitType), traitInfo)
ioStreams.Infof(" - %s: %s", white.Sprint(traitType), traitInfo)
}
ioStreams.Infof(white.Sprint("\nLast Deployment:\n"))
ioStreams.Infof("\tCreated at: %v\n", appConfig.CreationTimestamp)
ioStreams.Infof("\tUpdated at: %v\n", app.UpdateTime.Format(time.RFC3339))
ioStreams.Info("")
return nil
}
@@ -527,21 +440,6 @@ func applySpinnerNewSuffix(s *spinner.Spinner, suffix string) {
s.Suffix = suffixColor.Sprintf(" %s", suffix)
}
func printPrefix(p string) string {
if strings.HasSuffix(p, firstElemPrefix) {
p = strings.Replace(p, firstElemPrefix, pipe, strings.Count(p, firstElemPrefix)-1)
} else {
p = strings.ReplaceAll(p, firstElemPrefix, pipe)
}
if strings.HasSuffix(p, lastElemPrefix) {
p = strings.Replace(p, lastElemPrefix, strings.Repeat(" ", len([]rune(lastElemPrefix))), strings.Count(p, lastElemPrefix)-1)
} else {
p = strings.ReplaceAll(p, lastElemPrefix, strings.Repeat(" ", len([]rune(lastElemPrefix))))
}
return p
}
func getHealthStatusColor(s HealthStatus) *color.Color {
var c *color.Color
switch s {

View File

@@ -6,11 +6,13 @@ import (
"os"
"sort"
"github.com/AlecAivazis/survey/v2"
"github.com/oam-dev/kubevela/api/types"
"github.com/oam-dev/kubevela/pkg/appfile"
"github.com/oam-dev/kubevela/pkg/application"
cmdutil "github.com/oam-dev/kubevela/pkg/commands/util"
"github.com/oam-dev/kubevela/pkg/server/apis"
"github.com/spf13/cobra"
corev1alpha2 "github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2"
apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -241,3 +243,61 @@ func (o *DeleteOptions) DeleteComponent(io cmdutil.IOStreams) (string, error) {
return fmt.Sprintf("delete component succeed %s from %s", o.CompName, o.AppName), nil
}
func chooseSvc(services []string) (string, error) {
var svcName string
services = append(services, DefaultChosenAllSvc)
prompt := &survey.Select{
Message: "Please choose one service: ",
Options: services,
Default: DefaultChosenAllSvc,
}
err := survey.AskOne(prompt, &svcName)
if err != nil {
return "", fmt.Errorf("failed to retrieve services of the application, err %v", err)
}
return svcName, nil
}
// GetServicesWhenDescribingApplication gets the target services list either from cli `--svc` flag or from survey
func GetServicesWhenDescribingApplication(cmd *cobra.Command, app *application.Application) ([]string, error) {
var svcFlag string
var svcFlagStatus string
// to store the value of flag `--svc` set in Cli, or selected value in survey
var targetServices []string
if svcFlag = cmd.Flag("svc").Value.String(); svcFlag == "" {
svcFlagStatus = FlagNotSet
} else {
svcFlagStatus = FlagIsInvalid
}
// all services name of the application `appName`
var services []string
for svcName := range app.Services {
services = append(services, svcName)
if svcFlag == svcName {
svcFlagStatus = FlagIsValid
targetServices = append(targetServices, svcName)
}
}
totalServices := len(services)
if svcFlagStatus == FlagNotSet && totalServices == 1 {
targetServices = services
}
if svcFlagStatus == FlagIsInvalid || (svcFlagStatus == FlagNotSet && totalServices > 1) {
if svcFlagStatus == FlagIsInvalid {
cmd.Printf("The service name '%s' is not valid\n", svcFlag)
}
chosenSvc, err := chooseSvc(services)
if err != nil {
return []string{}, err
}
if chosenSvc == DefaultChosenAllSvc {
targetServices = services
} else {
targetServices = targetServices[:0]
targetServices = append(targetServices, chosenSvc)
}
}
return targetServices, nil
}