Skip to content

Commit

Permalink
feat: tkn-result add pipelinerun list and result describe command
Browse files Browse the repository at this point in the history
  • Loading branch information
xinnjie committed Dec 13, 2024
1 parent ed85c09 commit 63ade1e
Show file tree
Hide file tree
Showing 14 changed files with 617 additions and 5 deletions.
1 change: 1 addition & 0 deletions pkg/api/server/v1alpha2/plugin/plugin_logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,7 @@ func (s *LogServer) setLogPlugin() bool {
s.IsLogPluginEnabled = true
s.getLog = getBlobLogs
default:
// TODO(xinnjie) when s.config.LOGS_TYPE is File also show this error log
s.IsLogPluginEnabled = false
s.logger.Errorf("unsupported type of logs given for plugin")
}
Expand Down
97 changes: 97 additions & 0 deletions pkg/cli/cmd/pipelinerun/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package pipelinerun

import (
"fmt"
"text/tabwriter"
"text/template"

"github.com/tektoncd/cli/pkg/cli"

"github.com/jonboulle/clockwork"
"github.com/spf13/cobra"
"github.com/tektoncd/results/pkg/cli/flags"
"github.com/tektoncd/results/pkg/cli/format"
pb "github.com/tektoncd/results/proto/v1alpha2/results_go_proto"
)

const listTemplate = `{{- $size := len .Results -}}{{- if eq $size 0 -}}
No PipelineRuns found
{{ else -}}
NAMESPACE UID STARTED DURATION STATUS
{{- range $_, $result := .Results }}
{{ formatNamespace $result.Name }} {{ $result.Uid }} {{ formatAge $result.Summary.StartTime $.Time }} {{ formatDuration $result.Summary.StartTime $result.Summary.EndTime }} {{ formatStatus $result.Summary.Status }}
{{- end -}}
{{- end -}}`

type ListOptions struct {
Namespace string
Limit int
}

// listCommand initializes a cobra command to list PipelineRuns
func listCommand(params *flags.Params) *cobra.Command {
opts := &ListOptions{Limit: 0, Namespace: "default"}

eg := `List all PipelineRuns in a namespace 'foo':
tkn-results pipelinerun list -n foo
List all PipelineRuns in 'default' namespace:
tkn-results pipelinerun list -n default
`
cmd := &cobra.Command{
Use: "list",
Aliases: []string{"ls"},
Short: "List PipelineRuns in a namespace",
Annotations: map[string]string{
"commandType": "main",
},
Example: eg,
RunE: func(cmd *cobra.Command, args []string) error {
if opts.Limit < 0 {
return fmt.Errorf("limit was %d, but must be greater than 0", opts.Limit)
}

resp, err := params.ResultsClient.ListResults(cmd.Context(), &pb.ListResultsRequest{
Parent: opts.Namespace,
PageSize: int32(opts.Limit),
Filter: `summary.type==PIPELINE_RUN`,
})
if err != nil {
return fmt.Errorf("failed to list PipelineRuns from namespace %s: %v", opts.Namespace, err)
}
stream := &cli.Stream{
Out: cmd.OutOrStdout(),
Err: cmd.OutOrStderr(),
}
return printFormatted(stream, resp.Results, params.Clock)
},
}
cmd.Flags().StringVarP(&opts.Namespace, "namespace", "n", "default", "Namespace to list PipelineRuns in")
cmd.Flags().IntVarP(&opts.Limit, "limit", "l", 0, "Limit the number of PipelineRuns to return")
return cmd
}

func printFormatted(s *cli.Stream, results []*pb.Result, c clockwork.Clock) error {
var data = struct {
Results []*pb.Result
Time clockwork.Clock
}{
Results: results,
Time: c,
}
funcMap := template.FuncMap{
"formatAge": format.Age,
"formatDuration": format.Duration,
"formatStatus": format.Status,
"formatNamespace": format.Namespace,
}

w := tabwriter.NewWriter(s.Out, 0, 5, 3, ' ', tabwriter.TabIndent)
t := template.Must(template.New("List PipelineRuns").Funcs(funcMap).Parse(listTemplate))

err := t.Execute(w, data)
if err != nil {
return err
}
return w.Flush()
}
90 changes: 90 additions & 0 deletions pkg/cli/cmd/pipelinerun/list_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package pipelinerun

import (
"testing"
"time"

"github.com/tektoncd/results/pkg/test"

"github.com/tektoncd/results/pkg/cli/flags"
"github.com/tektoncd/results/pkg/test/fake"

"github.com/jonboulle/clockwork"

"github.com/spf13/cobra"
pb "github.com/tektoncd/results/proto/v1alpha2/results_go_proto"
"google.golang.org/protobuf/types/known/timestamppb"
)

func TestListPipelineRuns_empty(t *testing.T) {
results := []*pb.Result{}
now := time.Now()
cmd := command(results, now)

output, err := test.ExecuteCommand(cmd, "list")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
test.AssertOutput(t, "No PipelineRuns found\n", output)
}

func TestListPipelineRuns(t *testing.T) {
clock := clockwork.NewFakeClock()
createTime := clock.Now().Add(time.Duration(-3) * time.Minute)
updateTime := clock.Now().Add(time.Duration(-2) * time.Minute)
startTime := clock.Now().Add(time.Duration(-3) * time.Minute)
endTime := clock.Now().Add(time.Duration(-1) * time.Minute)
results := []*pb.Result{
{
Name: "default/results/e6b4b2e3-d876-4bbe-a927-95c691b6fdc7",
Uid: "949eebd9-1cf7-478f-a547-9ee313035f10",
CreateTime: timestamppb.New(createTime),
UpdateTime: timestamppb.New(updateTime),
Annotations: map[string]string{
"object.metadata.name": "hello-goodbye-run-vfsxn",
"tekton.dev/pipeline": "hello-goodbye",
},
Summary: &pb.RecordSummary{
Record: "default/results/e6b4b2e3-d876-4bbe-a927-95c691b6fdc7/records/e6b4b2e3-d876-4bbe-a927-95c691b6fdc7",
Type: "tekton.dev/v1.PipelineRun",
StartTime: timestamppb.New(startTime),
EndTime: timestamppb.New(endTime),
Status: pb.RecordSummary_SUCCESS,
},
},
{
Name: "default/results/3dacd30b-ce42-476c-be7e-84b0f664df55",
Uid: "c8d4cd50-06e8-4325-9ba2-044e6cc45235",
CreateTime: timestamppb.New(createTime),
UpdateTime: timestamppb.New(updateTime),
Annotations: map[string]string{
"object.metadata.name": "hello-goodbye-run-xtw2j",
},
Summary: &pb.RecordSummary{
Record: "default/results/3dacd30b-ce42-476c-be7e-84b0f664df55/records/3dacd30b-ce42-476c-be7e-84b0f664df55",
Type: "tekton.dev/v1.PipelineRun",
},
},
}
cmd := command(results, clock.Now())
output, err := test.ExecuteCommand(cmd, "list")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
test.AssertOutput(t, `NAMESPACE UID STARTED DURATION STATUS
default 949eebd9-1cf7-478f-a547-9ee313035f10 3 minutes ago 2m0s Succeeded
default c8d4cd50-06e8-4325-9ba2-044e6cc45235 --- --- Unknown`, output)
}

func command(results []*pb.Result, now time.Time) *cobra.Command {
clock := clockwork.NewFakeClockAt(now)

param := &flags.Params{
ResultsClient: fake.NewResultsClient(results),
LogsClient: nil,
PluginLogsClient: nil,
Clock: clock,
}
cmd := Command(param)
return cmd
}
22 changes: 22 additions & 0 deletions pkg/cli/cmd/pipelinerun/pipelinerun.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package pipelinerun

import (
"github.com/spf13/cobra"
"github.com/tektoncd/results/pkg/cli/flags"
)

// Command initializes a cobra command for `pipelinerun` sub commands
func Command(params *flags.Params) *cobra.Command {
cmd := &cobra.Command{
Use: "pipelinerun",
Aliases: []string{"pr", "pipelineruns"},
Short: "Query PipelineRuns",
Annotations: map[string]string{
"commandType": "main",
},
}

cmd.AddCommand(listCommand(params))

return cmd
}
121 changes: 121 additions & 0 deletions pkg/cli/cmd/result/describe.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package result

import (
"fmt"
"text/tabwriter"
"text/template"

"github.com/tektoncd/cli/pkg/cli"

"github.com/jonboulle/clockwork"
"github.com/spf13/cobra"
"github.com/tektoncd/results/pkg/cli/flags"
"github.com/tektoncd/results/pkg/cli/format"
pb "github.com/tektoncd/results/proto/v1alpha2/results_go_proto"
)

const resultDescTmpl = `Name: {{.Result.Name}}
UID: {{.Result.Uid}}
{{- if .Result.Annotations}}
Annotations:
{{- range $key, $value := .Result.Annotations}}
{{$key}}={{$value}}
{{- end}}
{{- end}}
Status:
Created: {{formatAge .Result.CreateTime .Time}} DURATION: {{formatDuration .Result.CreateTime .Result.UpdateTime}}
{{- if .Result.Summary}}
Summary:
Type: {{.Result.Summary.Type}}
Status:
STARTED DURATION STATUS
{{formatAge .Result.Summary.StartTime .Time}} {{formatDuration .Result.Summary.StartTime .Result.Summary.EndTime}} {{.Result.Summary.Status}}
{{- if .Result.Summary.Annotations}}
Annotations:
{{- range $key, $value := .Result.Summary.Annotations}}
{{$key}}={{$value}}
{{- end}}
{{- end}}
{{- end}}
`

type DescribeOptions struct {
Parent string
Uid string
}

func describeCommand(params *flags.Params) *cobra.Command {
opts := &DescribeOptions{}
eg := `Query by name:
tkn-result result describe default/results/e6b4b2e3-d876-4bbe-a927-95c691b6fdc7
Query by parent and uid:
tkn-result result desc --parent default --uid 949eebd9-1cf7-478f-a547-9ee313035f10
`
cmd := &cobra.Command{
Use: "describe [-p parent -u uid] [name]",
Aliases: []string{"desc"},
Short: "Describes a Result",
Annotations: map[string]string{
"commandType": "main",
},
Example: eg,
RunE: func(cmd *cobra.Command, args []string) error {
stream := &cli.Stream{
Out: cmd.OutOrStdout(),
Err: cmd.OutOrStderr(),
}
if len(args) > 0 {
name := args[0]
result, err := params.ResultsClient.GetResult(cmd.Context(), &pb.GetResultRequest{
Name: name,
})
if err != nil {
return fmt.Errorf("failed to get result of name %s: %v", name, err)
}
return printResultDescription(stream, result, params.Clock)
}

if opts.Parent != "" && opts.Uid != "" {
resp, err := params.ResultsClient.ListResults(cmd.Context(), &pb.ListResultsRequest{
Parent: opts.Parent,
Filter: fmt.Sprintf(`uid=="%s"`, opts.Uid),
})
if err != nil {
return fmt.Errorf("failed to get result of parent %s and uid %s: %v", opts.Parent, opts.Uid, err)
}
if len(resp.Results) == 0 {
return fmt.Errorf("no result found with parent %s and uid %s", opts.Parent, opts.Uid)
}
return printResultDescription(stream, resp.Results[0], params.Clock)
}
return nil
},
}

cmd.Flags().StringVarP(&opts.Parent, "parent", "p", "", "parent of the result")
cmd.Flags().StringVarP(&opts.Uid, "uid", "u", "", "uid of the result")
return cmd
}

func printResultDescription(s *cli.Stream, result *pb.Result, c clockwork.Clock) error {
data := struct {
Result *pb.Result
Time clockwork.Clock
}{
Result: result,
Time: c,
}
funcMap := template.FuncMap{
"formatAge": format.Age,
"formatDuration": format.Duration,
"formatStatus": format.Status,
}
w := tabwriter.NewWriter(s.Out, 0, 5, 3, ' ', tabwriter.TabIndent)
t := template.Must(template.New("Describe A Result").Funcs(funcMap).Parse(resultDescTmpl))
err := t.Execute(w, data)
if err != nil {
return err
}
return w.Flush()
}
Loading

0 comments on commit 63ade1e

Please sign in to comment.