Skip to content

Commit 85618a5

Browse files
committed
feat(peridot-cli/task-info): fetch and display task details
given a task ID, fetch its details and display them to a table or to json with `-o json`. Table view also adds a calculated task duration and can optionally include the submitter information as well as a link to logs for the task.
1 parent 0d3255c commit 85618a5

File tree

24 files changed

+3501
-0
lines changed

24 files changed

+3501
-0
lines changed

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ require (
117117
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
118118
github.com/modern-go/reflect2 v1.0.2 // indirect
119119
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
120+
github.com/olekukonko/tablewriter v0.0.5 // indirect
120121
github.com/pborman/uuid v1.2.1 // indirect
121122
github.com/pelletier/go-toml v1.8.1 // indirect
122123
github.com/pmezard/go-difflib v1.0.0 // indirect
@@ -184,4 +185,5 @@ replace (
184185
peridot.resf.org/peridot/pb => ./bazel-bin/peridot/proto/v1/peridotpb_go_proto_/peridot.resf.org/peridot/pb
185186
peridot.resf.org/peridot/yumrepofs/pb => ./bazel-bin/peridot/proto/v1/yumrepofs/yumrepofspb_go_proto_/peridot.resf.org/peridot/yumrepofs/pb
186187
)
188+
187189
// sync-replace-end

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW
452452
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
453453
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
454454
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
455+
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
456+
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
455457
github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
456458
github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
457459
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=

peridot/cmd/v1/peridot/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ go_library(
2323
"project_list.go",
2424
"task.go",
2525
"task_logs.go",
26+
"task_info.go",
2627
"utils.go",
2728
],
2829
data = [
@@ -39,6 +40,7 @@ go_library(
3940
"//vendor/github.com/spf13/cobra",
4041
"//vendor/github.com/spf13/viper",
4142
"//vendor/openapi.peridot.resf.org/peridotopenapi",
43+
"//vendor/github.com/olekukonko/tablewriter",
4244
"@org_golang_x_oauth2//:oauth2",
4345
"@org_golang_x_oauth2//clientcredentials",
4446
],

peridot/cmd/v1/peridot/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ func init() {
6565

6666
root.AddCommand(task)
6767
task.AddCommand(taskLogs)
68+
task.AddCommand(taskInfo)
6869

6970
root.AddCommand(project)
7071
project.AddCommand(projectInfo)
Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
// Copyright (c) All respective contributors to the Peridot Project. All rights reserved.
2+
// Copyright (c) 2021-2022 Rocky Enterprise Software Foundation, Inc. All rights reserved.
3+
// Copyright (c) 2021-2022 Ctrl IQ, Inc. All rights reserved.
4+
//
5+
// Redistribution and use in source and binary forms, with or without
6+
// modification, are permitted provided that the following conditions are met:
7+
//
8+
// 1. Redistributions of source code must retain the above copyright notice,
9+
// this list of conditions and the following disclaimer.
10+
//
11+
// 2. Redistributions in binary form must reproduce the above copyright notice,
12+
// this list of conditions and the following disclaimer in the documentation
13+
// and/or other materials provided with the distribution.
14+
//
15+
// 3. Neither the name of the copyright holder nor the names of its contributors
16+
// may be used to endorse or promote products derived from this software without
17+
// specific prior written permission.
18+
//
19+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20+
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21+
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22+
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23+
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24+
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25+
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26+
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27+
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28+
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29+
// POSSIBILITY OF SUCH DAMAGE.
30+
31+
package main
32+
33+
import (
34+
"errors"
35+
"fmt"
36+
"log"
37+
"os"
38+
"slices"
39+
"strings"
40+
"time"
41+
42+
"github.com/google/uuid"
43+
"github.com/olekukonko/tablewriter"
44+
"github.com/spf13/cobra"
45+
"openapi.peridot.resf.org/peridotopenapi"
46+
)
47+
48+
var taskInfo = &cobra.Command{
49+
Use: "info [name-or-buildId]",
50+
Args: cobra.ExactArgs(1),
51+
Run: taskInfoMn,
52+
}
53+
54+
var (
55+
showLogLink bool
56+
showSubmitterInfo bool
57+
showDuration bool
58+
)
59+
60+
func init() {
61+
taskInfo.Flags().BoolVar(&succeeded, "succeeded", true, "only query successful tasks")
62+
taskInfo.Flags().BoolVar(&cancelled, "cancelled", false, "only query cancelled tasks")
63+
taskInfo.Flags().BoolVar(&failed, "failed", false, "only query failed tasks")
64+
taskInfo.MarkFlagsMutuallyExclusive("cancelled", "failed", "succeeded")
65+
66+
taskInfo.Flags().BoolVarP(&showLogLink, "logs", "L", false, "include log link in output (table format only)")
67+
taskInfo.Flags().BoolVar(&showSubmitterInfo, "submitter", false, "include submitter details (table format only)")
68+
taskInfo.Flags().BoolVar(&showDuration, "duration", true, "include duration from start to stop (table format only)")
69+
}
70+
71+
func getNextColor(color int) int {
72+
switch color {
73+
case 0:
74+
return tablewriter.FgRedColor
75+
case tablewriter.FgCyanColor:
76+
return tablewriter.FgHiRedColor
77+
case tablewriter.FgHiWhiteColor:
78+
return tablewriter.FgRedColor
79+
default:
80+
color++
81+
return color
82+
}
83+
}
84+
85+
func convertSubTaskSliceToCSV(task peridotopenapi.V1AsyncTask) {
86+
subtasks, ok := task.GetSubtasksOk()
87+
if !ok {
88+
errFatal(fmt.Errorf("error getting subtasks: %v", ok))
89+
}
90+
91+
var parentTask = (*subtasks)[0]
92+
93+
var table = tablewriter.NewWriter(os.Stdout)
94+
// var data [][]string
95+
var header = []string{"ptid", "tid", "status", "type", "arch", "created", "finished"}
96+
var autoMergeCells = []int{
97+
0, // parent task id
98+
3, // type
99+
4, // architecture
100+
}
101+
102+
var mergable = []string{"ptid", "type", "arch", "submitter"}
103+
104+
if showDuration {
105+
header = append(header, "duration")
106+
}
107+
108+
if showSubmitterInfo {
109+
header = append(header, "submitter")
110+
}
111+
112+
if showLogLink {
113+
header = append(header, "logs")
114+
}
115+
116+
for _, item := range mergable {
117+
autoMergeCells = append(autoMergeCells, slices.Index(header, item))
118+
}
119+
120+
var parentTaskIds []string
121+
var seenTasksColors = make(map[string]int)
122+
var lastColor = 0
123+
124+
// precache all the subtask's parent tasks so we know if we should color them
125+
for _, subtask := range *subtasks {
126+
parentTaskIds = append(parentTaskIds, subtask.GetParentTaskId())
127+
}
128+
129+
for _, subtask := range *subtasks {
130+
json, err := subtask.MarshalJSON()
131+
if err != nil {
132+
errFatal(err)
133+
}
134+
135+
if debug() {
136+
err = PrettyPrintJSON(json)
137+
if err != nil {
138+
errFatal(err)
139+
}
140+
// taskResponse, _ := subtask.GetResponse().MarshalJSON()
141+
// taskMetadata, _ := subtask.GetMetadata().MarshalJSON()
142+
}
143+
144+
subtaskId := subtask.GetId()
145+
subtaskParentTaskId := subtask.GetParentTaskId()
146+
createdAt := subtask.GetCreatedAt()
147+
finishedAt := subtask.GetFinishedAt()
148+
149+
row := []string{
150+
subtaskParentTaskId,
151+
subtaskId,
152+
string(subtask.GetStatus()),
153+
string(subtask.GetType()),
154+
subtask.GetArch(),
155+
createdAt.Format("2006-01-02 15:04:05"),
156+
finishedAt.Format("2006-01-02 15:04:05"),
157+
}
158+
159+
if showDuration {
160+
duration := finishedAt.Sub(createdAt)
161+
formatted := time.Time{}.Add(duration).Format("15:04:05")
162+
row = append(row, formatted)
163+
}
164+
165+
if showSubmitterInfo {
166+
effectiveSubmitter := fmt.Sprintf("%s <%s>", parentTask.GetSubmitterId(), parentTask.GetSubmitterEmail())
167+
row = append(row, effectiveSubmitter)
168+
}
169+
170+
if showLogLink {
171+
logLink := fmt.Sprintf("https://%s/api/v1/projects/%s/tasks/%s/logs", strings.Replace(endpoint(), "-api", "", 1), mustGetProjectID(), subtaskId)
172+
row = append(row, logLink)
173+
}
174+
175+
var nextColor = tablewriter.FgWhiteColor
176+
_, seen := seenTasksColors[subtaskId]
177+
var shouldColor = taskIdIsAnyParentTaskId(parentTaskIds, subtaskId)
178+
179+
if !seen && shouldColor {
180+
// log.Printf("new: lastcolor: %s nextcolor %s", lastColor, nextColor)
181+
nextColor = getNextColor(lastColor)
182+
lastColor = nextColor
183+
// log.Printf("after: lastcolor: %s nextcolor %s", lastColor, nextColor)
184+
// insert our color into the table
185+
seenTasksColors[subtaskId] = nextColor
186+
}
187+
188+
ptidColor, seen := seenTasksColors[subtaskParentTaskId]
189+
if !seen {
190+
ptidColor = tablewriter.FgWhiteColor
191+
}
192+
193+
var colors = make([]tablewriter.Colors, len(row))
194+
195+
// log.Printf("row: %s color: %s ptidcolor %s", i, nextColor, ptidColor)
196+
for i, v := range header {
197+
switch v {
198+
case "ptid":
199+
colors[i] = tablewriter.Colors{ptidColor}
200+
case "tid":
201+
if shouldColor {
202+
colors[i] = tablewriter.Colors{nextColor} // if it is a parent
203+
} else {
204+
colors[i] = tablewriter.Colors{tablewriter.BgBlackColor, tablewriter.FgWhiteColor} // childless cat ladies
205+
}
206+
default:
207+
colors[i] = tablewriter.Colors{}
208+
}
209+
}
210+
211+
table.Rich(row, colors)
212+
}
213+
214+
table.SetHeader(header)
215+
table.SetAutoMergeCellsByColumnIndex(autoMergeCells)
216+
table.SetRowLine(true)
217+
table.Render()
218+
219+
}
220+
221+
func taskIdIsAnyParentTaskId(parentTaskIds []string, subtaskId string) bool {
222+
if idx := slices.Index(parentTaskIds, subtaskId); idx > 0 {
223+
return true
224+
}
225+
return false
226+
}
227+
228+
func taskInfoMn(_ *cobra.Command, args []string) {
229+
// Ensure project id exists
230+
projectId := mustGetProjectID()
231+
232+
taskId := args[0]
233+
234+
err := uuid.Validate(taskId)
235+
if err != nil {
236+
errFatal(errors.New("invalid task id"))
237+
}
238+
239+
taskCl := getClient(serviceTask).(peridotopenapi.TaskServiceApi)
240+
log.Printf("Searching for task %s in project %s\n", taskId, projectId)
241+
242+
res, _, err := taskCl.GetTask(getContext(), projectId, taskId).Execute()
243+
if err != nil {
244+
errFatal(fmt.Errorf("error getting task: %s", err.Error()))
245+
}
246+
247+
switch output() {
248+
case "table":
249+
convertSubTaskSliceToCSV(res.GetTask())
250+
251+
case "json":
252+
taskJSON, err := res.MarshalJSON()
253+
if err != nil {
254+
errFatal(err)
255+
}
256+
257+
err = PrettyPrintJSON(taskJSON)
258+
if err != nil {
259+
errFatal(err)
260+
}
261+
}
262+
}

repositories.bzl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,6 +1068,12 @@ def go_repositories():
10681068
sum = "h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=",
10691069
version = "v1.3.1",
10701070
)
1071+
go_repository(
1072+
name = "com_github_olekukonko_tablewriter",
1073+
importpath = "github.com/olekukonko/tablewriter",
1074+
sum = "h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=",
1075+
version = "v0.0.5",
1076+
)
10711077
go_repository(
10721078
name = "com_github_oneofone_xxhash",
10731079
importpath = "github.com/OneOfOne/xxhash",

vendor/github.com/olekukonko/tablewriter/BUILD.bazel

Lines changed: 27 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/olekukonko/tablewriter/LICENSE.md

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)