From 49c2029cadfbe41d92ae25e047774eb9f8e89b00 Mon Sep 17 00:00:00 2001 From: smainz Date: Thu, 18 Jul 2024 20:39:18 +0200 Subject: [PATCH] Cli fix pipeline logs (#3913) Co-authored-by: Thomas Anderson <127358482+zc-devs@users.noreply.github.com> Co-authored-by: 6543 <6543@obermui.de> --- cli/internal/util.go | 44 ++++++++++++++++ cli/pipeline/logs.go | 62 ++++++++++++++++++++--- cli/pipeline/ps.go | 24 +++++---- docs/versioned_docs/version-2.7/40-cli.md | 9 ++-- 4 files changed, 118 insertions(+), 21 deletions(-) diff --git a/cli/internal/util.go b/cli/internal/util.go index e372d52bd..2e8cbb63e 100644 --- a/cli/internal/util.go +++ b/cli/internal/util.go @@ -161,3 +161,47 @@ func ParseKeyPair(p []string) map[string]string { } return params } + +/* +ParseStep parses the step id form a string which may either be the step PID (step number) or a step name. +These rules apply: + +- Step ID take precedence over step name when searching for a match. +- First match is used, when there are multiple steps with the same name. + +Strictly speaking, this is not parsing, but a lookup. + +TODO: Use PID instead of StepID +*/ +func ParseStep(client woodpecker.Client, repoID, number int64, stepArg string) (stepID int64, err error) { + pipeline, err := client.Pipeline(repoID, number) + if err != nil { + return 0, err + } + + stepID, err = strconv.ParseInt(stepArg, 10, 64) + // TODO: for 3.0 do "stepPID, err := strconv.ParseInt(stepArg, 10, 64)" + if err == nil { + return stepID, nil + /* + // TODO: for 3.0 + for _, wf := range pipeline.Workflows { + for _, step := range wf.Children { + if int64(step.PID) == stepPID { + return step.ID, nil + } + } + } + */ + } + + for _, wf := range pipeline.Workflows { + for _, step := range wf.Children { + if step.Name == stepArg { + return step.ID, nil + } + } + } + + return 0, fmt.Errorf("no step with number or name '%s' found", stepArg) +} diff --git a/cli/pipeline/logs.go b/cli/pipeline/logs.go index ce3bacb40..6ad19fc6f 100644 --- a/cli/pipeline/logs.go +++ b/cli/pipeline/logs.go @@ -17,18 +17,22 @@ package pipeline import ( "context" "fmt" + "os" "strconv" + "text/template" "github.com/urfave/cli/v3" "go.woodpecker-ci.org/woodpecker/v2/cli/internal" + "go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker" ) var pipelineLogsCmd = &cli.Command{ Name: "logs", Usage: "show pipeline logs", - ArgsUsage: " [pipeline] [stepID]", - Action: pipelineLogs, + ArgsUsage: " [step-id|step-name]", + // TODO: for v3.0 do `ArgsUsage: " [step-number|step-name]",` + Action: pipelineLogs, } func pipelineLogs(ctx context.Context, c *cli.Command) error { @@ -37,31 +41,73 @@ func pipelineLogs(ctx context.Context, c *cli.Command) error { if err != nil { return err } + if len(repoIDOrFullName) == 0 { + return fmt.Errorf("missing required argument repo-id / repo-full-name") + } repoID, err := internal.ParseRepo(client, repoIDOrFullName) if err != nil { - return err + return fmt.Errorf("invalid repo '%s': %w ", repoIDOrFullName, err) } - numberArgIndex := 1 - number, err := strconv.ParseInt(c.Args().Get(numberArgIndex), 10, 64) + pipelineArg := c.Args().Get(1) + if len(pipelineArg) == 0 { + return fmt.Errorf("missing required argument pipeline") + } + number, err := strconv.ParseInt(pipelineArg, 10, 64) + if err != nil { + return fmt.Errorf("invalid pipeline '%s': %w", pipelineArg, err) + } + + stepArg := c.Args().Get(2) //nolint:mnd + if len(stepArg) == 0 { + return showPipelineLog(client, repoID, number) + } + + step, err := internal.ParseStep(client, repoID, number, stepArg) + if err != nil { + return fmt.Errorf("invalid step '%s': %w", stepArg, err) + } + return showStepLog(client, repoID, number, step) +} + +func showPipelineLog(client woodpecker.Client, repoID, number int64) error { + pipeline, err := client.Pipeline(repoID, number) if err != nil { return err } - stepArgIndex := 2 - step, err := strconv.ParseInt(c.Args().Get(stepArgIndex), 10, 64) + tmpl, err := template.New("_").Parse(tmplPipelineLogs + "\n") if err != nil { return err } + for _, workflow := range pipeline.Workflows { + for _, step := range workflow.Children { + if err := tmpl.Execute(os.Stdout, map[string]any{"workflow": workflow, "step": step}); err != nil { + return err + } + err := showStepLog(client, repoID, number, step.ID) + if err != nil { + return err + } + } + } + + return nil +} + +func showStepLog(client woodpecker.Client, repoID, number, step int64) error { logs, err := client.StepLogEntries(repoID, number, step) if err != nil { return err } for _, log := range logs { - fmt.Print(string(log.Data)) + fmt.Println(string(log.Data)) } return nil } + +// template for pipeline ps information. +var tmplPipelineLogs = "\x1b[33m{{ .workflow.Name }} > {{ .step.Name }} (#{{ .step.PID }}):\x1b[0m" diff --git a/cli/pipeline/ps.go b/cli/pipeline/ps.go index 737ef7301..6aca08545 100644 --- a/cli/pipeline/ps.go +++ b/cli/pipeline/ps.go @@ -16,6 +16,7 @@ package pipeline import ( "context" + "fmt" "os" "strconv" "text/template" @@ -29,7 +30,7 @@ import ( var pipelinePsCmd = &cli.Command{ Name: "ps", Usage: "show pipeline steps", - ArgsUsage: " [pipeline]", + ArgsUsage: " ", Action: pipelinePs, Flags: []cli.Flag{common.FormatFlag(tmplPipelinePs)}, } @@ -42,7 +43,7 @@ func pipelinePs(ctx context.Context, c *cli.Command) error { } repoID, err := internal.ParseRepo(client, repoIDOrFullName) if err != nil { - return err + return fmt.Errorf("invalid repo '%s': %w", repoIDOrFullName, err) } pipelineArg := c.Args().Get(1) @@ -59,7 +60,7 @@ func pipelinePs(ctx context.Context, c *cli.Command) error { } else { number, err = strconv.ParseInt(pipelineArg, 10, 64) if err != nil { - return err + return fmt.Errorf("invalid pipeline '%s': %w", pipelineArg, err) } } @@ -73,9 +74,9 @@ func pipelinePs(ctx context.Context, c *cli.Command) error { return err } - for _, step := range pipeline.Workflows { - for _, child := range step.Children { - if err := tmpl.Execute(os.Stdout, child); err != nil { + for _, workflow := range pipeline.Workflows { + for _, step := range workflow.Children { + if err := tmpl.Execute(os.Stdout, map[string]any{"workflow": workflow, "step": step}); err != nil { return err } } @@ -84,8 +85,11 @@ func pipelinePs(ctx context.Context, c *cli.Command) error { return nil } -// Template for pipeline ps information. -var tmplPipelinePs = "\x1b[33mStep #{{ .PID }} \x1b[0m" + ` -Step: {{ .Name }} -State: {{ .State }} +// template for pipeline ps information. +var tmplPipelinePs = "\x1b[33m{{ .workflow.Name }} > {{ .step.Name }} (#{{ .step.PID }}):\x1b[0m" + ` +Step: {{ .step.Name }} +Started: {{ .step.Started }} +Stopped: {{ .step.Stopped }} +Type: {{ .step.Type }} +State: {{ .step.State }} ` diff --git a/docs/versioned_docs/version-2.7/40-cli.md b/docs/versioned_docs/version-2.7/40-cli.md index c3fe425ae..8e4e62358 100644 --- a/docs/versioned_docs/version-2.7/40-cli.md +++ b/docs/versioned_docs/version-2.7/40-cli.md @@ -345,9 +345,12 @@ Message: {{ .Message }} show pipeline steps -**--format**="": format output (default: Step #{{ .PID }}  -Step: {{ .Name }} -State: {{ .State }} +**--format**="": format output (default: {{ .workflow.Name }} > {{ .step.Name }} (#{{ .step.PID }}): +Step: {{ .step.Name }} +Started: {{ .step.Started }} +Stopped: {{ .step.Stopped }} +Type: {{ .step.Type }} +State: {{ .step.State }} ) ### create