Skip to content

Commit

Permalink
feat(peridot-cli/task-info): fetch and display task details
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
NeilHanlon committed Jul 27, 2024
1 parent 0d3255c commit 85618a5
Show file tree
Hide file tree
Showing 24 changed files with 3,501 additions and 0 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/pborman/uuid v1.2.1 // indirect
github.com/pelletier/go-toml v1.8.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
Expand Down Expand Up @@ -184,4 +185,5 @@ replace (
peridot.resf.org/peridot/pb => ./bazel-bin/peridot/proto/v1/peridotpb_go_proto_/peridot.resf.org/peridot/pb
peridot.resf.org/peridot/yumrepofs/pb => ./bazel-bin/peridot/proto/v1/yumrepofs/yumrepofspb_go_proto_/peridot.resf.org/peridot/yumrepofs/pb
)

// sync-replace-end
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
Expand Down
2 changes: 2 additions & 0 deletions peridot/cmd/v1/peridot/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ go_library(
"project_list.go",
"task.go",
"task_logs.go",
"task_info.go",
"utils.go",
],
data = [
Expand All @@ -39,6 +40,7 @@ go_library(
"//vendor/github.com/spf13/cobra",
"//vendor/github.com/spf13/viper",
"//vendor/openapi.peridot.resf.org/peridotopenapi",
"//vendor/github.com/olekukonko/tablewriter",
"@org_golang_x_oauth2//:oauth2",
"@org_golang_x_oauth2//clientcredentials",
],
Expand Down
1 change: 1 addition & 0 deletions peridot/cmd/v1/peridot/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ func init() {

root.AddCommand(task)
task.AddCommand(taskLogs)
task.AddCommand(taskInfo)

root.AddCommand(project)
project.AddCommand(projectInfo)
Expand Down
262 changes: 262 additions & 0 deletions peridot/cmd/v1/peridot/task_info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
// Copyright (c) All respective contributors to the Peridot Project. All rights reserved.
// Copyright (c) 2021-2022 Rocky Enterprise Software Foundation, Inc. All rights reserved.
// Copyright (c) 2021-2022 Ctrl IQ, Inc. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors
// may be used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package main

import (
"errors"
"fmt"
"log"
"os"
"slices"
"strings"
"time"

"github.com/google/uuid"
"github.com/olekukonko/tablewriter"
"github.com/spf13/cobra"
"openapi.peridot.resf.org/peridotopenapi"
)

var taskInfo = &cobra.Command{
Use: "info [name-or-buildId]",
Args: cobra.ExactArgs(1),
Run: taskInfoMn,
}

var (
showLogLink bool
showSubmitterInfo bool
showDuration bool
)

func init() {
taskInfo.Flags().BoolVar(&succeeded, "succeeded", true, "only query successful tasks")
taskInfo.Flags().BoolVar(&cancelled, "cancelled", false, "only query cancelled tasks")
taskInfo.Flags().BoolVar(&failed, "failed", false, "only query failed tasks")
taskInfo.MarkFlagsMutuallyExclusive("cancelled", "failed", "succeeded")

taskInfo.Flags().BoolVarP(&showLogLink, "logs", "L", false, "include log link in output (table format only)")
taskInfo.Flags().BoolVar(&showSubmitterInfo, "submitter", false, "include submitter details (table format only)")
taskInfo.Flags().BoolVar(&showDuration, "duration", true, "include duration from start to stop (table format only)")
}

func getNextColor(color int) int {
switch color {
case 0:
return tablewriter.FgRedColor
case tablewriter.FgCyanColor:
return tablewriter.FgHiRedColor
case tablewriter.FgHiWhiteColor:
return tablewriter.FgRedColor
default:
color++
return color
}
}

func convertSubTaskSliceToCSV(task peridotopenapi.V1AsyncTask) {
subtasks, ok := task.GetSubtasksOk()
if !ok {
errFatal(fmt.Errorf("error getting subtasks: %v", ok))
}

var parentTask = (*subtasks)[0]

var table = tablewriter.NewWriter(os.Stdout)
// var data [][]string
var header = []string{"ptid", "tid", "status", "type", "arch", "created", "finished"}
var autoMergeCells = []int{
0, // parent task id
3, // type
4, // architecture
}

var mergable = []string{"ptid", "type", "arch", "submitter"}

if showDuration {
header = append(header, "duration")
}

if showSubmitterInfo {
header = append(header, "submitter")
}

if showLogLink {
header = append(header, "logs")
}

for _, item := range mergable {
autoMergeCells = append(autoMergeCells, slices.Index(header, item))
}

var parentTaskIds []string
var seenTasksColors = make(map[string]int)
var lastColor = 0

// precache all the subtask's parent tasks so we know if we should color them
for _, subtask := range *subtasks {
parentTaskIds = append(parentTaskIds, subtask.GetParentTaskId())
}

for _, subtask := range *subtasks {
json, err := subtask.MarshalJSON()
if err != nil {
errFatal(err)
}

if debug() {
err = PrettyPrintJSON(json)
if err != nil {
errFatal(err)
}
// taskResponse, _ := subtask.GetResponse().MarshalJSON()
// taskMetadata, _ := subtask.GetMetadata().MarshalJSON()
}

subtaskId := subtask.GetId()
subtaskParentTaskId := subtask.GetParentTaskId()
createdAt := subtask.GetCreatedAt()
finishedAt := subtask.GetFinishedAt()

row := []string{
subtaskParentTaskId,
subtaskId,
string(subtask.GetStatus()),
string(subtask.GetType()),
subtask.GetArch(),
createdAt.Format("2006-01-02 15:04:05"),
finishedAt.Format("2006-01-02 15:04:05"),
}

if showDuration {
duration := finishedAt.Sub(createdAt)
formatted := time.Time{}.Add(duration).Format("15:04:05")
row = append(row, formatted)
}

if showSubmitterInfo {
effectiveSubmitter := fmt.Sprintf("%s <%s>", parentTask.GetSubmitterId(), parentTask.GetSubmitterEmail())
row = append(row, effectiveSubmitter)
}

if showLogLink {
logLink := fmt.Sprintf("https://%s/api/v1/projects/%s/tasks/%s/logs", strings.Replace(endpoint(), "-api", "", 1), mustGetProjectID(), subtaskId)
row = append(row, logLink)
}

var nextColor = tablewriter.FgWhiteColor
_, seen := seenTasksColors[subtaskId]
var shouldColor = taskIdIsAnyParentTaskId(parentTaskIds, subtaskId)

if !seen && shouldColor {
// log.Printf("new: lastcolor: %s nextcolor %s", lastColor, nextColor)
nextColor = getNextColor(lastColor)
lastColor = nextColor
// log.Printf("after: lastcolor: %s nextcolor %s", lastColor, nextColor)
// insert our color into the table
seenTasksColors[subtaskId] = nextColor
}

ptidColor, seen := seenTasksColors[subtaskParentTaskId]
if !seen {
ptidColor = tablewriter.FgWhiteColor
}

var colors = make([]tablewriter.Colors, len(row))

// log.Printf("row: %s color: %s ptidcolor %s", i, nextColor, ptidColor)
for i, v := range header {
switch v {
case "ptid":
colors[i] = tablewriter.Colors{ptidColor}
case "tid":
if shouldColor {
colors[i] = tablewriter.Colors{nextColor} // if it is a parent
} else {
colors[i] = tablewriter.Colors{tablewriter.BgBlackColor, tablewriter.FgWhiteColor} // childless cat ladies
}
default:
colors[i] = tablewriter.Colors{}
}
}

table.Rich(row, colors)
}

table.SetHeader(header)
table.SetAutoMergeCellsByColumnIndex(autoMergeCells)
table.SetRowLine(true)
table.Render()

}

func taskIdIsAnyParentTaskId(parentTaskIds []string, subtaskId string) bool {
if idx := slices.Index(parentTaskIds, subtaskId); idx > 0 {
return true
}
return false
}

func taskInfoMn(_ *cobra.Command, args []string) {
// Ensure project id exists
projectId := mustGetProjectID()

taskId := args[0]

err := uuid.Validate(taskId)
if err != nil {
errFatal(errors.New("invalid task id"))
}

taskCl := getClient(serviceTask).(peridotopenapi.TaskServiceApi)
log.Printf("Searching for task %s in project %s\n", taskId, projectId)

res, _, err := taskCl.GetTask(getContext(), projectId, taskId).Execute()
if err != nil {
errFatal(fmt.Errorf("error getting task: %s", err.Error()))
}

switch output() {
case "table":
convertSubTaskSliceToCSV(res.GetTask())

case "json":
taskJSON, err := res.MarshalJSON()
if err != nil {
errFatal(err)
}

err = PrettyPrintJSON(taskJSON)
if err != nil {
errFatal(err)
}
}
}
6 changes: 6 additions & 0 deletions repositories.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -1068,6 +1068,12 @@ def go_repositories():
sum = "h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=",
version = "v1.3.1",
)
go_repository(
name = "com_github_olekukonko_tablewriter",
importpath = "github.com/olekukonko/tablewriter",
sum = "h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=",
version = "v0.0.5",
)
go_repository(
name = "com_github_oneofone_xxhash",
importpath = "github.com/OneOfOne/xxhash",
Expand Down
27 changes: 27 additions & 0 deletions vendor/github.com/olekukonko/tablewriter/BUILD.bazel

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions vendor/github.com/olekukonko/tablewriter/LICENSE.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 85618a5

Please sign in to comment.