diff --git a/pkg/utils/common/common.go b/pkg/utils/common/common.go index 6214af186..d44b42804 100644 --- a/pkg/utils/common/common.go +++ b/pkg/utils/common/common.go @@ -53,6 +53,7 @@ import ( "sigs.k8s.io/yaml" oamcore "github.com/oam-dev/kubevela/apis/core.oam.dev" + "github.com/oam-dev/kubevela/apis/core.oam.dev/common" oamstandard "github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1" velacue "github.com/oam-dev/kubevela/pkg/cue" "github.com/oam-dev/kubevela/pkg/cue/model" @@ -239,6 +240,51 @@ func RealtimePrintCommandOutput(cmd *exec.Cmd, logFile string) error { return nil } +// ClusterObject2Map convert ClusterObjectReference to a readable map +func ClusterObject2Map(refs []common.ClusterObjectReference) map[string]string { + clusterResourceRefTmpl := "Cluster: %s | Namespace: %s | GVK: %s/%s | Name: %s" + objs := make(map[string]string, len(refs)) + for _, r := range refs { + if r.Cluster == "" { + r.Cluster = "local" + } + objs[r.Name] = fmt.Sprintf(clusterResourceRefTmpl, r.Cluster, r.Namespace, r.APIVersion, r.ResourceVersion, r.Name) + } + return objs +} + +// AskToChooseOneAppliedResource will ask users to select one applied resource of the application if more than one +// resources is a map for component to applied resources +// return the selected ClusterObjectReference +func AskToChooseOneAppliedResource(resources []common.ClusterObjectReference) (*common.ClusterObjectReference, error) { + if len(resources) == 0 { + return nil, fmt.Errorf("no applied resources exist in the application") + } + if len(resources) == 1 { + return &resources[0], nil + } + opMap := ClusterObject2Map(resources) + var ops []string + for _, r := range opMap { + ops = append(ops, r) + } + prompt := &survey.Select{ + Message: "You have multiple applied resources in your app. Please choose one:", + Options: ops, + } + var selectedRsc string + err := survey.AskOne(prompt, &selectedRsc) + if err != nil { + return nil, fmt.Errorf("choosing resource err %w", err) + } + for k, resource := range ops { + if selectedRsc == resource { + return &resources[k], nil + } + } + return nil, fmt.Errorf("choosing resource err %w", err) +} + // AskToChooseOneService will ask users to select one service of the application if more than one exidi func AskToChooseOneService(svcNames []string) (string, error) { if len(svcNames) == 0 { diff --git a/references/appfile/app.go b/references/appfile/app.go index aa1397b49..4685305dc 100644 --- a/references/appfile/app.go +++ b/references/appfile/app.go @@ -84,7 +84,7 @@ func LoadApplication(namespace, appName string, c common.Args) (*v1beta1.Applica return app, nil } -// GetComponents will get oam components from Appfile. +// GetComponents will get oam components from v1beta1.Application. func GetComponents(app *v1beta1.Application) []string { var components []string for _, cmp := range app.Spec.Components { diff --git a/references/cli/logs.go b/references/cli/logs.go index 89c818ad1..04aa3b495 100644 --- a/references/cli/logs.go +++ b/references/cli/logs.go @@ -24,6 +24,8 @@ import ( "text/template" "time" + "github.com/oam-dev/kubevela/pkg/multicluster" + "github.com/fatih/color" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -40,73 +42,72 @@ import ( // NewLogsCommand creates `logs` command to tail logs of application func NewLogsCommand(c common.Args, ioStreams util.IOStreams) *cobra.Command { - largs := &Args{C: c} - cmd := &cobra.Command{} - cmd.Use = "logs" - cmd.Short = "Tail logs for application" - cmd.Long = "Tail logs for application" - cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error { - if err := c.SetConfig(); err != nil { - return err - } - largs.C = c - return nil - } - cmd.RunE = func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - ioStreams.Errorf("please specify app name") + largs := &Args{Args: c} + cmd := &cobra.Command{ + Use: "logs ", + Short: "Tail logs for application in multicluster", + Long: "Tail logs for application in multicluster", + Args: cobra.ExactArgs(1), + PreRunE: func(cmd *cobra.Command, args []string) error { + if err := c.SetConfig(); err != nil { + return err + } + largs.Args = c + largs.Args.Config.Wrap(multicluster.NewSecretModeMultiClusterRoundTripper) return nil - } - env, err := GetFlagEnvOrCurrent(cmd, c) - if err != nil { - return err - } - app, err := appfile.LoadApplication(env.Namespace, args[0], c) - if err != nil { - return err - } - largs.App = app - largs.Env = env - ctx := context.Background() - if err := largs.Run(ctx, ioStreams); err != nil { - return err - } - return nil - } - cmd.Annotations = map[string]string{ - types.TagCommandType: types.TypeApp, + }, + RunE: func(cmd *cobra.Command, args []string) error { + app, err := appfile.LoadApplication(largs.Namespace, args[0], c) + if err != nil { + return err + } + largs.App = app + ctx := context.Background() + if err := largs.Run(ctx, ioStreams); err != nil { + return err + } + return nil + }, + Annotations: map[string]string{ + types.TagCommandType: types.TypeApp, + }, } cmd.Flags().StringVarP(&largs.Output, "output", "o", "default", "output format for logs, support: [default, raw, json]") + cmd.Flags().StringVarP(&largs.Namespace, "namespace", "n", "default", "application namespace") + return cmd } // Args creates arguments for `logs` command type Args struct { - Output string - Env *types.EnvMeta - C common.Args - App *v1beta1.Application + Output string + Args common.Args + Namespace string + App *v1beta1.Application } // Run refer to the implementation at https://github.com/oam-dev/stern/blob/master/stern/main.go func (l *Args) Run(ctx context.Context, ioStreams util.IOStreams) error { - clientSet, err := kubernetes.NewForConfig(l.C.Config) + clientSet, err := kubernetes.NewForConfig(l.Args.Config) if err != nil { return err } - compName, err := common.AskToChooseOneService(appfile.GetComponents(l.App)) + appliedResources := l.App.Status.AppliedResources + + selectedRes, err := common.AskToChooseOneAppliedResource(appliedResources) if err != nil { return err } + ctx = multicluster.ContextWithClusterName(ctx, selectedRes.Cluster) // TODO(wonderflow): we could get labels from service to narrow the pods scope selected labelSelector := labels.Everything() - pod, err := regexp.Compile(compName + "-.*") + pod, err := regexp.Compile(selectedRes.Name + "-.*") if err != nil { - return fmt.Errorf("fail to compile '%s' for logs query", compName+".*") + return fmt.Errorf("fail to compile '%s' for logs query", selectedRes.Name+".*") } container := regexp.MustCompile(".*") - namespace := l.Env.Namespace + namespace := selectedRes.Namespace added, removed, err := stern.Watch(ctx, clientSet.CoreV1().Pods(namespace), pod, container, nil, stern.RUNNING, labelSelector) if err != nil { return err