github.com/oam-dev/kubevela@v1.9.11/references/cli/status.go (about) 1 /* 2 Copyright 2021 The KubeVela Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package cli 18 19 import ( 20 "context" 21 "fmt" 22 "io" 23 "os" 24 "strings" 25 "time" 26 27 "k8s.io/client-go/rest" 28 29 "github.com/fatih/color" 30 "github.com/olekukonko/tablewriter" 31 "github.com/pkg/errors" 32 "github.com/spf13/cobra" 33 "golang.org/x/term" 34 pkgtypes "k8s.io/apimachinery/pkg/types" 35 "k8s.io/utils/pointer" 36 "sigs.k8s.io/controller-runtime/pkg/client" 37 38 pkgmulticluster "github.com/kubevela/pkg/multicluster" 39 workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1" 40 "github.com/kubevela/workflow/pkg/cue/model/sets" 41 "github.com/kubevela/workflow/pkg/cue/model/value" 42 "github.com/kubevela/workflow/pkg/utils" 43 44 commontypes "github.com/oam-dev/kubevela/apis/core.oam.dev/common" 45 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1" 46 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" 47 "github.com/oam-dev/kubevela/apis/types" 48 pkgappfile "github.com/oam-dev/kubevela/pkg/appfile" 49 "github.com/oam-dev/kubevela/pkg/multicluster" 50 "github.com/oam-dev/kubevela/pkg/policy" 51 "github.com/oam-dev/kubevela/pkg/resourcetracker" 52 "github.com/oam-dev/kubevela/pkg/utils/common" 53 cmdutil "github.com/oam-dev/kubevela/pkg/utils/util" 54 types2 "github.com/oam-dev/kubevela/pkg/velaql/providers/query/types" 55 "github.com/oam-dev/kubevela/references/appfile" 56 references "github.com/oam-dev/kubevela/references/common" 57 ) 58 59 // AppDeployStatus represents the status of application during "vela init" and "vela up --wait" 60 type AppDeployStatus int 61 62 // Enums of ApplicationStatus 63 const ( 64 appDeployFail AppDeployStatus = iota 65 appDeployedHealthy 66 appDeployError 67 ) 68 69 const ( 70 trackingInterval = 1 * time.Second 71 ) 72 73 // NewAppStatusCommand creates `status` command for showing status 74 func NewAppStatusCommand(c common.Args, order string, ioStreams cmdutil.IOStreams) *cobra.Command { 75 ctx := context.Background() 76 var outputFormat string 77 var detail bool 78 cmd := &cobra.Command{ 79 Use: "status", 80 Short: "Show status of an application.", 81 Long: "Show status of vela application.", 82 Example: ` # Get basic app info 83 vela status APP_NAME 84 85 # Show detailed info in tree 86 vela status first-vela-app --tree --detail --detail-format list 87 88 # Show pod list 89 vela status first-vela-app --pod 90 vela status first-vela-app --pod --component express-server --cluster local 91 92 # Show endpoint list 93 vela status first-vela-app --endpoint 94 95 # Get raw Application yaml (without managedFields) 96 vela status first-vela-app -o yaml 97 98 # Get raw Application status using jsonpath 99 vela status first-vela-app -o jsonpath='{.status}' 100 101 # Get Application metrics status 102 vela status first-vela-app --metrics`, 103 RunE: func(cmd *cobra.Command, args []string) error { 104 // check args 105 argsLength := len(args) 106 if argsLength == 0 { 107 return fmt.Errorf("please specify an application") 108 } 109 appName := args[0] 110 // get namespace 111 namespace, err := GetFlagNamespaceOrEnv(cmd, c) 112 if err != nil { 113 return err 114 } 115 if printTree, err := cmd.Flags().GetBool("tree"); err == nil && printTree { 116 return printApplicationTree(c, cmd, appName, namespace) 117 } 118 if printPod, err := cmd.Flags().GetBool("pod"); err == nil && printPod { 119 component, _ := cmd.Flags().GetString("component") 120 cluster, _ := cmd.Flags().GetString("cluster") 121 f := Filter{ 122 Component: component, 123 Cluster: cluster, 124 } 125 return printAppPods(appName, namespace, f, c) 126 } 127 128 newClient, err := c.GetClient() 129 if err != nil { 130 return err 131 } 132 133 showEndpoints, err := cmd.Flags().GetBool("endpoint") 134 if showEndpoints && err == nil { 135 _, err := loadRemoteApplication(newClient, namespace, appName) 136 if err != nil { 137 return err 138 } 139 component, _ := cmd.Flags().GetString("component") 140 cluster, _ := cmd.Flags().GetString("cluster") 141 f := Filter{ 142 Component: component, 143 Cluster: cluster, 144 } 145 return printAppEndpoints(ctx, appName, namespace, f, c, false) 146 } 147 148 restConf, err := c.GetConfig() 149 if err != nil { 150 return err 151 } 152 153 if showMetrics, err := cmd.Flags().GetBool("metrics"); showMetrics && err == nil { 154 return printMetrics(newClient, restConf, appName, namespace) 155 } 156 157 if outputFormat != "" { 158 return printRawApplication(context.Background(), c, outputFormat, cmd.OutOrStdout(), namespace, appName) 159 } 160 return printAppStatus(ctx, newClient, ioStreams, appName, namespace, cmd, c, detail) 161 }, 162 Annotations: map[string]string{ 163 types.TagCommandOrder: order, 164 types.TagCommandType: types.TypeStart, 165 }, 166 } 167 cmd.Flags().StringP("svc", "s", "", "service name") 168 cmd.Flags().BoolP("endpoint", "p", false, "show all service endpoints of the application") 169 cmd.Flags().StringP("component", "c", "", "filter the endpoints or pods by component name") 170 cmd.Flags().StringP("cluster", "", "", "filter the endpoints or pods by cluster name") 171 cmd.Flags().BoolP("tree", "t", false, "display the application resources into tree structure") 172 cmd.Flags().BoolP("pod", "", false, "show pod list of the application") 173 cmd.Flags().BoolVarP(&detail, "detail", "d", false, "display more details in the application like input/output data in context. Note that if you want to show the realtime details of application resources, please use it with --tree") 174 cmd.Flags().StringP("detail-format", "", "inline", "the format for displaying details, must be used with --detail. Can be one of inline, wide, list, table, raw.") 175 cmd.Flags().StringVarP(&outputFormat, "output", "o", "", "raw Application output format. One of: (json, yaml, jsonpath)") 176 cmd.Flags().BoolP("metrics", "m", false, "show resource quota and consumption metrics of the application") 177 addNamespaceAndEnvArg(cmd) 178 return cmd 179 } 180 181 func printAppStatus(_ context.Context, c client.Client, ioStreams cmdutil.IOStreams, appName string, namespace string, cmd *cobra.Command, velaC common.Args, detail bool) error { 182 app, err := appfile.LoadApplication(namespace, appName, velaC) 183 if err != nil { 184 return err 185 } 186 187 cmd.Printf("About:\n\n") 188 table := newUITable() 189 table.AddRow(" Name:", appName) 190 table.AddRow(" Namespace:", namespace) 191 table.AddRow(" Created at:", app.CreationTimestamp.String()) 192 table.AddRow(" Status:", getAppPhaseColor(app.Status.Phase).Sprint(app.Status.Phase)) 193 cmd.Printf("%s\n\n", table.String()) 194 if err := printWorkflowStatus(c, ioStreams, appName, namespace, detail); err != nil { 195 return err 196 } 197 return loopCheckStatus(c, ioStreams, appName, namespace) 198 } 199 200 func formatEndpoints(endpoints []types2.ServiceEndpoint) [][]string { 201 var result [][]string 202 result = append(result, []string{"Cluster", "Component", "Ref(Kind/Namespace/Name)", "Endpoint", "Inner"}) 203 204 for _, endpoint := range endpoints { 205 if endpoint.Cluster == "" { 206 endpoint.Cluster = multicluster.ClusterLocalName 207 } 208 if endpoint.Component == "" { 209 endpoint.Component = "-" 210 } 211 result = append(result, []string{endpoint.Cluster, endpoint.Component, fmt.Sprintf("%s/%s/%s", endpoint.Ref.Kind, endpoint.Ref.Namespace, endpoint.Ref.Name), endpoint.String(), fmt.Sprintf("%v", endpoint.Endpoint.Inner)}) 212 } 213 return result 214 } 215 216 func printAppEndpoints(ctx context.Context, appName string, namespace string, f Filter, velaC common.Args, skipEmptyTable bool) error { 217 endpoints, err := GetServiceEndpoints(ctx, appName, namespace, velaC, f) 218 if err != nil { 219 return err 220 } 221 if skipEmptyTable && len(endpoints) == 0 { 222 return nil 223 } 224 fmt.Printf("Please access %s from the following endpoints:\n", appName) 225 table := tablewriter.NewWriter(os.Stdout) 226 table.SetColWidth(100) 227 228 printablePoints := formatEndpoints(endpoints) 229 table.SetHeader(printablePoints[0]) 230 for i := 1; i < len(printablePoints); i++ { 231 table.Append(printablePoints[i]) 232 } 233 table.Render() 234 return nil 235 } 236 237 func loadRemoteApplication(c client.Client, ns string, name string) (*v1beta1.Application, error) { 238 app := new(v1beta1.Application) 239 err := c.Get(context.Background(), client.ObjectKey{ 240 Namespace: ns, 241 Name: name, 242 }, app) 243 return app, err 244 } 245 246 func getComponentType(app *v1beta1.Application, name string) string { 247 for _, c := range app.Spec.Components { 248 if c.Name == name { 249 return c.Type 250 } 251 } 252 return "webservice" 253 } 254 255 func printWorkflowStatus(c client.Client, ioStreams cmdutil.IOStreams, appName string, namespace string, detail bool) error { 256 remoteApp, err := loadRemoteApplication(c, namespace, appName) 257 if err != nil { 258 return err 259 } 260 outputs := make(map[string]workflowv1alpha1.StepOutputs) 261 var v *value.Value 262 if detail { 263 for _, c := range remoteApp.Spec.Components { 264 if c.Outputs != nil { 265 outputs[c.Name] = c.Outputs 266 } 267 } 268 if remoteApp.Spec.Workflow != nil { 269 for _, s := range remoteApp.Spec.Workflow.Steps { 270 if s.Outputs != nil { 271 outputs[s.Name] = s.Outputs 272 } 273 for _, sub := range s.SubSteps { 274 if sub.Outputs != nil { 275 outputs[sub.Name] = sub.Outputs 276 } 277 } 278 } 279 } 280 if remoteApp.Status.Workflow != nil && remoteApp.Status.Workflow.ContextBackend != nil { 281 ctxBackend := remoteApp.Status.Workflow.ContextBackend 282 v, err = utils.GetDataFromContext(context.Background(), c, ctxBackend.Name, remoteApp.Name, remoteApp.Namespace) 283 if err != nil { 284 return err 285 } 286 } 287 } 288 workflowStatus := remoteApp.Status.Workflow 289 if workflowStatus != nil { 290 ioStreams.Info("Workflow:\n") 291 ioStreams.Infof(" mode: %s\n", workflowStatus.Mode) 292 ioStreams.Infof(" finished: %t\n", workflowStatus.Finished) 293 ioStreams.Infof(" Suspend: %t\n", workflowStatus.Suspend) 294 ioStreams.Infof(" Terminated: %t\n", workflowStatus.Terminated) 295 ioStreams.Info(" Steps") 296 for _, step := range workflowStatus.Steps { 297 printWorkflowStepStatus(" ", step.StepStatus, ioStreams, detail, outputs, v) 298 for _, sub := range step.SubStepsStatus { 299 printWorkflowStepStatus(" ", sub, ioStreams, detail, outputs, v) 300 } 301 } 302 ioStreams.Infof("\n") 303 } 304 return nil 305 } 306 307 func printWorkflowStepStatus(indent string, step workflowv1alpha1.StepStatus, ioStreams cmdutil.IOStreams, detail bool, outputs map[string]workflowv1alpha1.StepOutputs, v *value.Value) { 308 ioStreams.Infof("%s- id: %s\n", indent[0:len(indent)-2], step.ID) 309 ioStreams.Infof("%sname: %s\n", indent, step.Name) 310 ioStreams.Infof("%stype: %s\n", indent, step.Type) 311 ioStreams.Infof("%sphase: %s \n", indent, getWfStepColor(step.Phase).Sprint(step.Phase)) 312 if len(step.Message) > 0 { 313 ioStreams.Infof(" message: %s\n", step.Message) 314 } 315 if detail { 316 if len(outputs[step.Name]) > 0 { 317 ioStreams.Infof("%soutputs:\n", indent) 318 for _, output := range outputs[step.Name] { 319 outputValue, err := v.LookupValue(output.Name) 320 if err != nil { 321 continue 322 } 323 s, err := sets.ToString(outputValue.CueValue()) 324 if err != nil { 325 continue 326 } 327 indent += " " 328 ioStreams.Infof("%s- name: %s\n", indent[0:len(indent)-2], output.Name) 329 ioStreams.Infof("%svalue: %s", indent, s) 330 } 331 } 332 } 333 } 334 335 func loopCheckStatus(c client.Client, ioStreams cmdutil.IOStreams, appName string, namespace string) error { 336 remoteApp, err := loadRemoteApplication(c, namespace, appName) 337 if err != nil { 338 return err 339 } 340 if len(remoteApp.Status.Services) > 0 { 341 ioStreams.Infof("Services:\n\n") 342 } 343 for _, comp := range remoteApp.Status.Services { 344 compName := comp.Name 345 envStat := "" 346 if comp.Env != "" { 347 envStat = "Env: " + comp.Env 348 } 349 if comp.Cluster == "" { 350 comp.Cluster = "local" 351 } 352 nsStat := "" 353 if comp.Namespace != "" { 354 nsStat = "Namespace: " + comp.Namespace 355 } 356 ioStreams.Infof(fmt.Sprintf(" - Name: %s %s\n", compName, envStat)) 357 ioStreams.Infof(fmt.Sprintf(" Cluster: %s %s\n", comp.Cluster, nsStat)) 358 ioStreams.Infof(" Type: %s\n", getComponentType(remoteApp, compName)) 359 healthColor := getHealthStatusColor(comp.Healthy) 360 healthInfo := strings.ReplaceAll(comp.Message, "\n", "\n\t") // format healthInfo output 361 healthstats := "Healthy" 362 if !comp.Healthy { 363 healthstats = "Unhealthy" 364 } 365 ioStreams.Infof(" %s %s\n", healthColor.Sprint(healthstats), healthColor.Sprint(healthInfo)) 366 367 // load it again after health check 368 remoteApp, err = loadRemoteApplication(c, namespace, appName) 369 if err != nil { 370 return err 371 } 372 // workload Must found 373 if len(comp.Traits) > 0 { 374 ioStreams.Infof(" Traits:\n") 375 } else { 376 ioStreams.Infof(" No trait applied\n") 377 } 378 for _, tr := range comp.Traits { 379 traitBase := "" 380 if tr.Healthy { 381 traitBase = fmt.Sprintf(" %s%s", emojiSucceed, white.Sprint(tr.Type)) 382 } else { 383 traitBase = fmt.Sprintf(" %s%s", emojiFail, white.Sprint(tr.Type)) 384 } 385 if tr.Message != "" { 386 traitBase += ": " + tr.Message 387 } 388 ioStreams.Infof(traitBase) 389 } 390 ioStreams.Info("") 391 } 392 return nil 393 } 394 395 func printTrackingDeployStatus(c common.Args, ioStreams cmdutil.IOStreams, appName, namespace string, timeout time.Duration) (AppDeployStatus, error) { 396 sDeploy := newTrackingSpinnerWithDelay("Waiting app to be healthy ...", trackingInterval) 397 sDeploy.Start() 398 defer sDeploy.Stop() 399 startTime := time.Now() 400 TrackDeployLoop: 401 for { 402 time.Sleep(trackingInterval) 403 deployStatus, err := TrackDeployStatus(c, appName, namespace) 404 if err != nil { 405 return appDeployError, err 406 } 407 switch deployStatus { 408 case commontypes.ApplicationStarting, commontypes.ApplicationRendering, commontypes.ApplicationPolicyGenerating, commontypes.ApplicationRunningWorkflow, commontypes.ApplicationUnhealthy: 409 if time.Now().After(startTime.Add(timeout)) { 410 ioStreams.Info(red.Sprintf("\n%s Timeout waiting Application to be healthy!", emojiFail)) 411 return appDeployFail, nil 412 } 413 continue 414 case commontypes.ApplicationWorkflowSuspending, commontypes.ApplicationRunning: 415 ioStreams.Info(green.Sprintf("\n%sApplication Deployed Successfully!", emojiSucceed)) 416 break TrackDeployLoop 417 case commontypes.ApplicationWorkflowTerminated, commontypes.ApplicationWorkflowFailed: 418 ioStreams.Info(red.Sprintf("\n%sApplication Deployment Failed!", emojiFail)) 419 ioStreams.Info(red.Sprintf("Please run the following command to check details: \n vela status %s -n %s\n", appName, namespace)) 420 return appDeployFail, nil 421 case commontypes.ApplicationDeleting: 422 ioStreams.Info(red.Sprintf("\n%sApplication is in the deleting process!", emojiFail)) 423 return appDeployFail, nil 424 } 425 } 426 return appDeployedHealthy, nil 427 } 428 429 // TrackDeployStatus will only check AppConfig is deployed successfully, 430 func TrackDeployStatus(c common.Args, appName string, namespace string) (commontypes.ApplicationPhase, error) { 431 appObj, err := appfile.LoadApplication(namespace, appName, c) 432 if err != nil { 433 return "", err 434 } 435 return appObj.Status.Phase, nil 436 } 437 438 func getHealthStatusColor(s bool) *color.Color { 439 if s { 440 return green 441 } 442 return yellow 443 } 444 445 func getWfStepColor(phase workflowv1alpha1.WorkflowStepPhase) *color.Color { 446 switch phase { 447 case workflowv1alpha1.WorkflowStepPhaseSucceeded: 448 return green 449 case workflowv1alpha1.WorkflowStepPhaseFailed: 450 return red 451 default: 452 return yellow 453 } 454 } 455 456 func getAppPhaseColor(appPhase commontypes.ApplicationPhase) *color.Color { 457 if appPhase == commontypes.ApplicationRunning { 458 return green 459 } 460 return yellow 461 } 462 463 func printApplicationTree(c common.Args, cmd *cobra.Command, appName string, appNs string) error { 464 config, err := c.GetConfig() 465 if err != nil { 466 return err 467 } 468 config.Wrap(pkgmulticluster.NewTransportWrapper()) 469 cli, err := c.GetClient() 470 if err != nil { 471 return err 472 } 473 pd, err := c.GetPackageDiscover() 474 if err != nil { 475 return err 476 } 477 478 app, err := loadRemoteApplication(cli, appNs, appName) 479 if err != nil { 480 return err 481 } 482 ctx := context.Background() 483 _, currentRT, historyRTs, _, err := resourcetracker.ListApplicationResourceTrackers(ctx, cli, app) 484 if err != nil { 485 return err 486 } 487 488 clusterNameMapper, err := multicluster.NewClusterNameMapper(context.Background(), cli) 489 if err != nil { 490 return errors.Wrapf(err, "failed to get cluster mapper") 491 } 492 493 var placements []v1alpha1.PlacementDecision 494 af, err := pkgappfile.NewApplicationParser(cli, pd).GenerateAppFile(context.Background(), app) 495 if err == nil { 496 placements, _ = policy.GetPlacementsFromTopologyPolicies(context.Background(), cli, app.GetNamespace(), af.Policies, true) 497 } 498 format, _ := cmd.Flags().GetString("detail-format") 499 var maxWidth *int 500 if w, _, err := term.GetSize(0); err == nil && w > 0 { 501 maxWidth = pointer.Int(w) 502 } 503 options := resourcetracker.ResourceTreePrintOptions{MaxWidth: maxWidth, Format: format, ClusterNameMapper: clusterNameMapper} 504 printDetails, _ := cmd.Flags().GetBool("detail") 505 if printDetails { 506 msgRetriever, err := resourcetracker.RetrieveKubeCtlGetMessageGenerator(config) 507 if err != nil { 508 return err 509 } 510 options.DetailRetriever = msgRetriever 511 } 512 options.PrintResourceTree(cmd.OutOrStdout(), placements, currentRT, historyRTs) 513 return nil 514 } 515 516 // printRawApplication prints raw Application in yaml/json/jsonpath (without managedFields). 517 func printRawApplication(ctx context.Context, c common.Args, format string, out io.Writer, ns, appName string) error { 518 var err error 519 app := &v1beta1.Application{} 520 521 k8sClient, err := c.GetClient() 522 if err != nil { 523 return fmt.Errorf("cannot get k8s client: %w", err) 524 } 525 526 err = k8sClient.Get(ctx, pkgtypes.NamespacedName{ 527 Namespace: ns, 528 Name: appName, 529 }, app) 530 if err != nil { 531 return fmt.Errorf("cannot get application %s in namespace %s: %w", appName, ns, err) 532 } 533 534 // Set GVK, we need it 535 // because the object returned from client.Get() has empty GVK 536 // (since the type info is inherent in the typed object, so GVK is empty) 537 app.SetGroupVersionKind(v1beta1.ApplicationKindVersionKind) 538 str, err := formatApplicationString(format, app) 539 if err != nil { 540 return err 541 } 542 543 _, err = out.Write([]byte(str)) 544 return err 545 } 546 547 // printMetrics prints the resource num and resource metrics of an application 548 func printMetrics(c client.Client, conf *rest.Config, appName, appNamespace string) error { 549 app := new(v1beta1.Application) 550 err := c.Get(context.Background(), client.ObjectKey{ 551 Name: appName, 552 Namespace: appNamespace, 553 }, app) 554 if err != nil { 555 return err 556 } 557 metrics, err := references.GetApplicationMetrics(c, conf, app) 558 if err != nil { 559 return err 560 } 561 fmt.Println() 562 fmt.Printf("Kubernetes Resources created:\n") 563 fmt.Printf(" * Number of Pods: %d\n", metrics.ResourceNum.Pod) 564 fmt.Printf(" * Number of Containers: %d\n", metrics.ResourceNum.Container) 565 fmt.Printf(" * Number of Managed Resource: %d\n", metrics.ResourceNum.Subresource) 566 fmt.Printf(" * Number of Nodes: %d\n", metrics.ResourceNum.Node) 567 fmt.Printf(" * Number of Clusters: %d\n", metrics.ResourceNum.Cluster) 568 fmt.Println() 569 fmt.Printf("Underlying Physical Resoures consumed:\n") 570 fmt.Printf(" * Total CPU(cores): %d m\n", metrics.Metrics.CPUUsage) 571 fmt.Printf(" * Limit CPU(cores): %d m\n", metrics.Metrics.CPULimit) 572 fmt.Printf(" * Request CPU(cores): %d m\n", metrics.Metrics.CPURequest) 573 fmt.Printf(" * Total Memory(bytes): %d Mi\n", metrics.Metrics.MemoryUsage/(1024*1024)) 574 fmt.Printf(" * Limit Memory(bytes): %d Mi\n", metrics.Metrics.MemoryLimit/(1024*1024)) 575 fmt.Printf(" * Request Memory(bytes): %d Mi\n", metrics.Metrics.MemoryRequest/(1024*1024)) 576 fmt.Printf(" * Total Storage(bytes): %d Gi\n", metrics.Metrics.Storage/(1024*1024*1024)) 577 fmt.Println() 578 return nil 579 }