diff --git a/pkg/stdlib/pkgs/util.cue b/pkg/stdlib/pkgs/util.cue index 16df0c573..d91db1c03 100644 --- a/pkg/stdlib/pkgs/util.cue +++ b/pkg/stdlib/pkgs/util.cue @@ -20,5 +20,17 @@ #do: "log" #provider: "util" - data: {...} + data?: {...} | string + level: *3 | int + // note that if you set source in multiple op.#Log, only the latest one will work + source?: close({ + url: string + }) | close({ + resources?: [...{ + name?: string + cluster?: string + namespace?: string + labelSelector?: {...} + }] + }) } diff --git a/references/cli/cli.go b/references/cli/cli.go index b0379a383..8737936b6 100644 --- a/references/cli/cli.go +++ b/references/cli/cli.go @@ -25,9 +25,11 @@ import ( gov "github.com/hashicorp/go-version" "github.com/pkg/errors" "github.com/spf13/cobra" - "k8s.io/klog" + "k8s.io/klog/v2" "sigs.k8s.io/controller-runtime/pkg/client/config" + workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1" + "github.com/oam-dev/kubevela/apis/types" velacmd "github.com/oam-dev/kubevela/pkg/cmd" "github.com/oam-dev/kubevela/pkg/utils/common" @@ -68,8 +70,13 @@ func NewCommandWithIOStreams(ioStream util.IOStreams) *cobra.Command { }, } + scheme := common.Scheme + err := workflowv1alpha1.AddToScheme(scheme) + if err != nil { + klog.Fatal(err) + } commandArgs := common.Args{ - Schema: common.Scheme, + Schema: scheme, } f := velacmd.NewDeferredFactory(config.GetConfig) diff --git a/references/cli/logs.go b/references/cli/logs.go index e9cc7b792..9d64f0de5 100644 --- a/references/cli/logs.go +++ b/references/cli/logs.go @@ -20,10 +20,13 @@ import ( "context" "encoding/json" "fmt" + "io" "regexp" + "strings" "text/template" "time" + "github.com/AlecAivazis/survey/v2" "github.com/fatih/color" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -31,6 +34,11 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/selection" "k8s.io/client-go/kubernetes" + "sigs.k8s.io/controller-runtime/pkg/client" + + workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1" + wfTypes "github.com/kubevela/workflow/pkg/types" + wfUtils "github.com/kubevela/workflow/pkg/utils" "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" "github.com/oam-dev/kubevela/apis/types" @@ -55,12 +63,25 @@ func NewLogsCommand(c common.Args, order string, ioStreams util.IOStreams) *cobr if err != nil { return err } + largs.Name = args[0] + ctx := context.Background() + if largs.StepName != "" { + cli, err := c.GetClient() + if err != nil { + return err + } + ctxName, label, err := getContextFromInstance(ctx, cli, largs.Namespace, largs.Name) + if err != nil { + return err + } + largs.CtxName = ctxName + return largs.printStepLogs(ctx, ioStreams, label) + } 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 } @@ -77,6 +98,7 @@ func NewLogsCommand(c common.Args, order string, ioStreams util.IOStreams) *cobr cmd.Flags().StringVarP(&largs.ClusterName, "cluster", "", "", "filter the pod by the cluster name") cmd.Flags().StringVarP(&largs.PodName, "pod", "p", "", "specify the pod name") cmd.Flags().StringVarP(&largs.ContainerName, "container", "", "", "specify the container name") + cmd.Flags().StringVarP(&largs.StepName, "step", "s", "", "specify the step name, note that this flag cannot be used together with the pod, container or component flags") addNamespaceAndEnvArg(cmd) return cmd } @@ -85,49 +107,100 @@ func NewLogsCommand(c common.Args, order string, ioStreams util.IOStreams) *cobr type Args struct { Output string Args common.Args + Name string + CtxName string Namespace string ContainerName string PodName string ClusterName string ComponentName string + StepName 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 { - pods, err := GetApplicationPods(ctx, l.App.Name, l.App.Namespace, l.Args, Filter{ - Component: l.ComponentName, - Cluster: l.ClusterName, - }) +func (l *Args) printStepLogs(ctx context.Context, ioStreams util.IOStreams, label map[string]string) error { + cli, err := l.Args.GetClient() if err != nil { return err } - var selectPod *querytypes.PodBase - if l.PodName != "" { - for i, pod := range pods { - if pod.Metadata.Name == l.PodName { - selectPod = &pods[i] - break + logConfig, err := wfUtils.GetLogConfigFromStep(ctx, cli, l.CtxName, l.Name, l.Namespace, l.StepName) + if err != nil { + return err + } + if err := selectStepLogSource(logConfig); err != nil { + return err + } + switch { + case logConfig.Data: + return l.printResourceLogs(ctx, cli, ioStreams, []wfTypes.Resource{{ + Namespace: types.DefaultKubeVelaNS, + LabelSelector: label, + }}, []string{fmt.Sprintf(`step_name="%s"`, l.StepName), fmt.Sprintf("%s/%s", l.Namespace, l.Name)}) + case logConfig.Source != nil: + if len(logConfig.Source.Resources) > 0 { + return l.printResourceLogs(ctx, cli, ioStreams, logConfig.Source.Resources, nil) + } + if logConfig.Source.URL != "" { + readCloser, err := wfUtils.GetLogsFromURL(ctx, logConfig.Source.URL) + if err != nil { + return err + } + //nolint:errcheck + defer readCloser.Close() + if _, err := io.Copy(ioStreams.Out, readCloser); err != nil { + return err } } - if selectPod == nil { - fmt.Println("The Pod you specified does not exist, please select it from the list.") + } + return nil +} + +func selectStepLogSource(logConfig *wfTypes.LogConfig) error { + var source string + if logConfig.Data && logConfig.Source != nil { + prompt := &survey.Select{ + Message: "Select logs from data or source", + Options: []string{"data", "source"}, + } + err := survey.AskOne(prompt, &source, survey.WithValidator(survey.Required)) + if err != nil { + return fmt.Errorf("failed to select %s: %w", source, err) + } + if source != "data" { + logConfig.Data = false } } - if selectPod == nil { - selectPod, err = AskToChooseOnePod(pods) + return nil +} + +func (l *Args) printResourceLogs(ctx context.Context, cli client.Client, ioStreams util.IOStreams, resources []wfTypes.Resource, filters []string) error { + pods, err := wfUtils.GetPodListFromResources(ctx, cli, resources) + if err != nil { + return err + } + podList := make([]querytypes.PodBase, 0) + for _, pod := range pods { + podBase := querytypes.PodBase{} + podBase.Metadata.Name = pod.Name + podBase.Metadata.Namespace = pod.Namespace + podList = append(podList, podBase) + } + if len(pods) == 0 { + return errors.New("no pod found") + } + var selectPod *querytypes.PodBase + if len(pods) > 1 { + selectPod, err = AskToChooseOnePod(podList) if err != nil { return err } + } else { + selectPod = &podList[0] } + return l.printPodLogs(ctx, ioStreams, selectPod, filters) +} - if selectPod == nil { - return nil - } - - if selectPod.Cluster != "" { - ctx = multicluster.ContextWithClusterName(ctx, selectPod.Cluster) - } +func (l *Args) printPodLogs(ctx context.Context, ioStreams util.IOStreams, selectPod *querytypes.PodBase, filters []string) error { pod, err := regexp.Compile(selectPod.Metadata.Name + ".*") if err != nil { return fmt.Errorf("fail to compile '%s' for logs query", selectPod.Metadata.Name+".*") @@ -171,7 +244,16 @@ func (l *Args) Run(ctx context.Context, ioStreams util.IOStreams) error { for { select { case str := <-logC: - ioStreams.Infonln(str) + show := true + for _, filter := range filters { + if !strings.Contains(str, filter) { + show = false + break + } + } + if show { + ioStreams.Infonln(str) + } case <-ctx.Done(): return } @@ -245,3 +327,59 @@ func (l *Args) Run(ctx context.Context, ioStreams util.IOStreams) error { return nil } + +// 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 { + pods, err := GetApplicationPods(ctx, l.App.Name, l.App.Namespace, l.Args, Filter{ + Component: l.ComponentName, + Cluster: l.ClusterName, + }) + if err != nil { + return err + } + var selectPod *querytypes.PodBase + if l.PodName != "" { + for i, pod := range pods { + if pod.Metadata.Name == l.PodName { + selectPod = &pods[i] + break + } + } + if selectPod == nil { + fmt.Println("The Pod you specified does not exist, please select it from the list.") + } + } + if selectPod == nil { + selectPod, err = AskToChooseOnePod(pods) + if err != nil { + return err + } + } + + if selectPod == nil { + return nil + } + + if selectPod.Cluster != "" { + ctx = multicluster.ContextWithClusterName(ctx, selectPod.Cluster) + } + return l.printPodLogs(ctx, ioStreams, selectPod, nil) +} + +func getContextFromInstance(ctx context.Context, cli client.Client, namespace, name string) (string, map[string]string, error) { + app := &v1beta1.Application{} + if err := cli.Get(ctx, client.ObjectKey{Namespace: namespace, Name: name}, app); err == nil { + if app.Status.Workflow != nil && app.Status.Workflow.ContextBackend != nil { + return app.Status.Workflow.ContextBackend.Name, map[string]string{"app.kubernetes.io/name": "vela-core"}, nil + } + return "", nil, fmt.Errorf("no context found in application %s", name) + } + wr := &workflowv1alpha1.WorkflowRun{} + if err := cli.Get(ctx, client.ObjectKey{Namespace: namespace, Name: name}, wr); err == nil { + if wr.Status.ContextBackend != nil { + return wr.Status.ContextBackend.Name, map[string]string{"app.kubernetes.io/name": "vela-workflow"}, nil + } + return "", nil, fmt.Errorf("no context found in workflowrun %s", name) + } + return "", nil, fmt.Errorf("no context found in application %s", name) +}