Cli fix pipeline logs (#3913)

Co-authored-by: Thomas Anderson <127358482+zc-devs@users.noreply.github.com>
Co-authored-by: 6543 <6543@obermui.de>
This commit is contained in:
smainz 2024-07-18 20:39:18 +02:00 committed by GitHub
parent 7b7c83d040
commit 49c2029cad
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 118 additions and 21 deletions

View file

@ -161,3 +161,47 @@ func ParseKeyPair(p []string) map[string]string {
} }
return params 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)
}

View file

@ -17,18 +17,22 @@ package pipeline
import ( import (
"context" "context"
"fmt" "fmt"
"os"
"strconv" "strconv"
"text/template"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal" "go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
) )
var pipelineLogsCmd = &cli.Command{ var pipelineLogsCmd = &cli.Command{
Name: "logs", Name: "logs",
Usage: "show pipeline logs", Usage: "show pipeline logs",
ArgsUsage: "<repo-id|repo-full-name> [pipeline] [stepID]", ArgsUsage: "<repo-id|repo-full-name> <pipeline> [step-id|step-name]",
Action: pipelineLogs, // TODO: for v3.0 do `ArgsUsage: "<repo-id|repo-full-name> <pipeline> [step-number|step-name]",`
Action: pipelineLogs,
} }
func pipelineLogs(ctx context.Context, c *cli.Command) error { 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 { if err != nil {
return err return err
} }
if len(repoIDOrFullName) == 0 {
return fmt.Errorf("missing required argument repo-id / repo-full-name")
}
repoID, err := internal.ParseRepo(client, repoIDOrFullName) repoID, err := internal.ParseRepo(client, repoIDOrFullName)
if err != nil { if err != nil {
return err return fmt.Errorf("invalid repo '%s': %w ", repoIDOrFullName, err)
} }
numberArgIndex := 1 pipelineArg := c.Args().Get(1)
number, err := strconv.ParseInt(c.Args().Get(numberArgIndex), 10, 64) 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 { if err != nil {
return err return err
} }
stepArgIndex := 2 tmpl, err := template.New("_").Parse(tmplPipelineLogs + "\n")
step, err := strconv.ParseInt(c.Args().Get(stepArgIndex), 10, 64)
if err != nil { if err != nil {
return err 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) logs, err := client.StepLogEntries(repoID, number, step)
if err != nil { if err != nil {
return err return err
} }
for _, log := range logs { for _, log := range logs {
fmt.Print(string(log.Data)) fmt.Println(string(log.Data))
} }
return nil return nil
} }
// template for pipeline ps information.
var tmplPipelineLogs = "\x1b[33m{{ .workflow.Name }} > {{ .step.Name }} (#{{ .step.PID }}):\x1b[0m"

View file

@ -16,6 +16,7 @@ package pipeline
import ( import (
"context" "context"
"fmt"
"os" "os"
"strconv" "strconv"
"text/template" "text/template"
@ -29,7 +30,7 @@ import (
var pipelinePsCmd = &cli.Command{ var pipelinePsCmd = &cli.Command{
Name: "ps", Name: "ps",
Usage: "show pipeline steps", Usage: "show pipeline steps",
ArgsUsage: "<repo-id|repo-full-name> [pipeline]", ArgsUsage: "<repo-id|repo-full-name> <pipeline>",
Action: pipelinePs, Action: pipelinePs,
Flags: []cli.Flag{common.FormatFlag(tmplPipelinePs)}, 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) repoID, err := internal.ParseRepo(client, repoIDOrFullName)
if err != nil { if err != nil {
return err return fmt.Errorf("invalid repo '%s': %w", repoIDOrFullName, err)
} }
pipelineArg := c.Args().Get(1) pipelineArg := c.Args().Get(1)
@ -59,7 +60,7 @@ func pipelinePs(ctx context.Context, c *cli.Command) error {
} else { } else {
number, err = strconv.ParseInt(pipelineArg, 10, 64) number, err = strconv.ParseInt(pipelineArg, 10, 64)
if err != nil { 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 return err
} }
for _, step := range pipeline.Workflows { for _, workflow := range pipeline.Workflows {
for _, child := range step.Children { for _, step := range workflow.Children {
if err := tmpl.Execute(os.Stdout, child); err != nil { if err := tmpl.Execute(os.Stdout, map[string]any{"workflow": workflow, "step": step}); err != nil {
return err return err
} }
} }
@ -84,8 +85,11 @@ func pipelinePs(ctx context.Context, c *cli.Command) error {
return nil return nil
} }
// Template for pipeline ps information. // template for pipeline ps information.
var tmplPipelinePs = "\x1b[33mStep #{{ .PID }} \x1b[0m" + ` var tmplPipelinePs = "\x1b[33m{{ .workflow.Name }} > {{ .step.Name }} (#{{ .step.PID }}):\x1b[0m" + `
Step: {{ .Name }} Step: {{ .step.Name }}
State: {{ .State }} Started: {{ .step.Started }}
Stopped: {{ .step.Stopped }}
Type: {{ .step.Type }}
State: {{ .step.State }}
` `

View file

@ -345,9 +345,12 @@ Message: {{ .Message }}
show pipeline steps show pipeline steps
**--format**="": format output (default: Step #{{ .PID }}  **--format**="": format output (default: {{ .workflow.Name }} > {{ .step.Name }} (#{{ .step.PID }}):
Step: {{ .Name }} Step: {{ .step.Name }}
State: {{ .State }} Started: {{ .step.Started }}
Stopped: {{ .step.Stopped }}
Type: {{ .step.Type }}
State: {{ .step.State }}
) )
### create ### create