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  }