mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-19 04:26:39 +00:00
Feat: vela logs support multicluster (#2593)
* Feat: add basic multiple cluster logs * fix context * Fix select style * Fix select style * remove useless env * fix naming
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+44
-43
@@ -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 <appName>",
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user