From dcba43ab7cad4a6670c25939337344282e55f235 Mon Sep 17 00:00:00 2001 From: Juan Antonio Osorio Date: Fri, 22 Dec 2023 11:34:29 +0200 Subject: [PATCH] Re-introduce variadic parameters to table API (#2022) * Reapply "Use variadic parameters for the table interface (#2008)" (#2010) This reverts commit d2d8c196c9d60b75791425792599dc7f6a416f79. * Introduce struct to handle colored columns This makes adding colored columns a little more ergonomic --- cmd/cli/app/artifact/artifact_get.go | 8 +- cmd/cli/app/artifact/artifact_list.go | 4 +- cmd/cli/app/auth/common.go | 20 ++-- cmd/cli/app/profile/table_render.go | 86 ++++++-------- cmd/cli/app/repo/repo_list.go | 4 +- cmd/cli/app/repo/repo_register.go | 2 +- cmd/cli/app/ruletype/common.go | 4 +- cmd/cli/app/ruletype/ruletype_get.go | 4 +- cmd/cli/app/ruletype/ruletype_list.go | 4 +- internal/util/cli/table/layouts/constants.go | 38 ------ internal/util/cli/table/layouts/layouts.go | 119 +++++++++++++++++++ internal/util/cli/table/simple/simple.go | 19 +-- internal/util/cli/table/table.go | 12 +- 13 files changed, 184 insertions(+), 140 deletions(-) delete mode 100644 internal/util/cli/table/layouts/constants.go create mode 100644 internal/util/cli/table/layouts/layouts.go diff --git a/cmd/cli/app/artifact/artifact_get.go b/cmd/cli/app/artifact/artifact_get.go index 38902c5a9d..98fe7ab6aa 100644 --- a/cmd/cli/app/artifact/artifact_get.go +++ b/cmd/cli/app/artifact/artifact_get.go @@ -73,7 +73,7 @@ func getCommand(ctx context.Context, cmd *cobra.Command, conn *grpc.ClientConn) case app.Table: ta := table.New(table.Simple, layouts.Default, []string{"ID", "Type", "Owner", "Name", "Repository", "Visibility", "Creation date"}) - ta.AddRow([]string{ + ta.AddRow( art.ArtifactPk, art.Type, art.GetOwner(), @@ -81,19 +81,19 @@ func getCommand(ctx context.Context, cmd *cobra.Command, conn *grpc.ClientConn) art.Repository, art.Visibility, art.CreatedAt.AsTime().Format(time.RFC3339), - }) + ) ta.Render() tv := table.New(table.Simple, layouts.Default, []string{"ID", "Tags", "Signature", "Identity", "Creation date"}) for _, version := range versions { - tv.AddRow([]string{ + tv.AddRow( fmt.Sprintf("%d", version.VersionId), strings.Join(version.Tags, ","), getSignatureStatusText(version.SignatureVerification), version.GetSignatureVerification().GetCertIdentity(), version.CreatedAt.AsTime().Format(time.RFC3339), - }) + ) } tv.Render() case app.JSON: diff --git a/cmd/cli/app/artifact/artifact_list.go b/cmd/cli/app/artifact/artifact_list.go index 4d4eafd139..833b08caab 100644 --- a/cmd/cli/app/artifact/artifact_list.go +++ b/cmd/cli/app/artifact/artifact_list.go @@ -77,7 +77,7 @@ func listCommand(ctx context.Context, cmd *cobra.Command, conn *grpc.ClientConn) t := table.New(table.Simple, layouts.Default, []string{"ID", "Type", "Owner", "Name", "Repository", "Visibility", "Creation date"}) for _, artifact := range artifactList.Results { - t.AddRow([]string{ + t.AddRow( artifact.ArtifactPk, artifact.Type, artifact.GetOwner(), @@ -85,7 +85,7 @@ func listCommand(ctx context.Context, cmd *cobra.Command, conn *grpc.ClientConn) artifact.Repository, artifact.Visibility, artifact.CreatedAt.AsTime().Format(time.RFC3339), - }) + ) } t.Render() diff --git a/cmd/cli/app/auth/common.go b/cmd/cli/app/auth/common.go index 657bc38a8b..45fe8ba753 100644 --- a/cmd/cli/app/auth/common.go +++ b/cmd/cli/app/auth/common.go @@ -41,29 +41,29 @@ func userRegistered(ctx context.Context, client minderv1.UserServiceClient) (boo func renderNewUser(conn string, newUser *minderv1.CreateUserResponse) { t := table.New(table.Simple, layouts.KeyValue, nil) - t.AddRow([]string{"Project ID", newUser.ProjectId}) - t.AddRow([]string{"Project Name", newUser.ProjectName}) - t.AddRow([]string{"Minder Server", conn}) + t.AddRow("Project ID", newUser.ProjectId) + t.AddRow("Project Name", newUser.ProjectName) + t.AddRow("Minder Server", conn) t.Render() } func renderUserInfo(conn string, user *minderv1.GetUserResponse) { t := table.New(table.Simple, layouts.KeyValue, nil) - t.AddRow([]string{"Minder Server", conn}) + t.AddRow("Minder Server", conn) for _, project := range getProjectTableRows(user.Projects) { - t.AddRow(project) + t.AddRow(project...) } t.Render() } func renderUserInfoWhoami(conn string, user *minderv1.GetUserResponse) { t := table.New(table.Simple, layouts.KeyValue, nil) - t.AddRow([]string{"Subject", user.GetUser().GetIdentitySubject()}) - t.AddRow([]string{"Created At", user.GetUser().GetCreatedAt().AsTime().String()}) - t.AddRow([]string{"Updated At", user.GetUser().GetUpdatedAt().AsTime().String()}) - t.AddRow([]string{"Minder Server", conn}) + t.AddRow("Subject", user.GetUser().GetIdentitySubject()) + t.AddRow("Created At", user.GetUser().GetCreatedAt().AsTime().String()) + t.AddRow("Updated At", user.GetUser().GetUpdatedAt().AsTime().String()) + t.AddRow("Minder Server", conn) for _, project := range getProjectTableRows(user.Projects) { - t.AddRow(project) + t.AddRow(project...) } t.Render() } diff --git a/cmd/cli/app/profile/table_render.go b/cmd/cli/app/profile/table_render.go index 2f59146db1..3f8526664b 100644 --- a/cmd/cli/app/profile/table_render.go +++ b/cmd/cli/app/profile/table_render.go @@ -60,11 +60,11 @@ func NewProfileSettingsTable() table.Table { // RenderProfileSettingsTable renders the profile settings table func RenderProfileSettingsTable(p *minderv1.Profile, t table.Table) { - t.AddRow([]string{"ID", p.GetId()}) - t.AddRow([]string{"Name", p.GetName()}) - t.AddRow([]string{"Provider", p.GetContext().GetProvider()}) - t.AddRow([]string{"Alert", p.GetAlert()}) - t.AddRow([]string{"Remediate", p.GetRemediate()}) + t.AddRow("ID", p.GetId()) + t.AddRow("Name", p.GetName()) + t.AddRow("Provider", p.GetContext().GetProvider()) + t.AddRow("Alert", p.GetAlert()) + t.AddRow("Remediate", p.GetRemediate()) } // NewProfileTable creates a new table for rendering profiles @@ -99,13 +99,12 @@ func renderRuleTable(entType minderv1.EntityType, rule *minderv1.Profile_Rule, t params := marshalStructOrEmpty(rule.Params) def := marshalStructOrEmpty(rule.Def) - row := []string{ + t.AddRow( entType.String(), rule.Type, params, def, - } - t.AddRow(row) + ) } // NewProfileStatusTable creates a new table for rendering profile status @@ -115,33 +114,28 @@ func NewProfileStatusTable() table.Table { // RenderProfileStatusTable renders the profile status table func RenderProfileStatusTable(ps *minderv1.ProfileStatus, t table.Table) { - row := []string{ - ps.ProfileId, - ps.ProfileName, - getEvalStatusText(ps.ProfileStatus), - ps.LastUpdated.AsTime().Format(time.RFC3339), - } - t.AddRowWithColor(row, []string{ - "", - "", - getEvalStatusColor(ps.ProfileStatus), - "", - }) + t.AddRowWithColor( + layouts.NoColor(ps.ProfileId), + layouts.NoColor(ps.ProfileName), + getColoredEvalStatus(ps.ProfileStatus), + layouts.NoColor(ps.LastUpdated.AsTime().Format(time.RFC3339)), + ) } -func getEvalStatusColor(status string) string { +func getColoredEvalStatus(status string) layouts.ColoredColumn { + txt := getEvalStatusText(status) // eval statuses can be 'success', 'failure', 'error', 'skipped', 'pending' switch strings.ToLower(status) { case successStatus: - return table.ColorGreen + return layouts.GreenColumn(txt) case failureStatus: - return table.ColorRed + return layouts.RedColumn(txt) case errorStatus: - return table.ColorRed + return layouts.RedColumn(txt) case skippedStatus: - return table.ColorYellow + return layouts.YellowColumn(txt) default: - return "" + return layouts.NoColor(txt) } } @@ -156,42 +150,32 @@ func RenderRuleEvaluationStatusTable( t table.Table, ) { for _, eval := range statuses { - row := []string{ - eval.RuleId, - eval.RuleName, - eval.Entity, - getEvalStatusText(eval.Status), - getRemediationStatusText(eval.RemediationStatus), - mapToYAMLOrEmpty(eval.EntityInfo), - guidanceOrEncouragement(eval.Status, eval.Guidance), - } - - t.AddRowWithColor(row, []string{ - "", - "", - "", - "", - getEvalStatusColor(eval.Status), + t.AddRowWithColor( + layouts.NoColor(eval.RuleId), + layouts.NoColor(eval.RuleName), + layouts.NoColor(eval.Entity), + getColoredEvalStatus(eval.Status), getRemediateStatusColor(eval.RemediationStatus), - "", - "", - }) + layouts.NoColor(mapToYAMLOrEmpty(eval.EntityInfo)), + layouts.NoColor(guidanceOrEncouragement(eval.Status, eval.Guidance)), + ) } } -func getRemediateStatusColor(status string) string { +func getRemediateStatusColor(status string) layouts.ColoredColumn { + txt := getRemediationStatusText(status) // remediation statuses can be 'success', 'failure', 'error', 'skipped', 'not supported' switch strings.ToLower(status) { case successStatus: - return table.ColorGreen + return layouts.GreenColumn(txt) case failureStatus: - return table.ColorRed + return layouts.RedColumn(txt) case errorStatus: - return table.ColorRed + return layouts.RedColumn(txt) case notAvailableStatus: - return table.ColorYellow + return layouts.YellowColumn(txt) default: - return "" + return layouts.NoColor(txt) } } diff --git a/cmd/cli/app/repo/repo_list.go b/cmd/cli/app/repo/repo_list.go index e23a8b298f..b2ca1aba99 100644 --- a/cmd/cli/app/repo/repo_list.go +++ b/cmd/cli/app/repo/repo_list.go @@ -71,14 +71,14 @@ func listCommand(ctx context.Context, cmd *cobra.Command, conn *grpc.ClientConn) case app.Table: t := table.New(table.Simple, layouts.RepoList, nil) for _, v := range resp.Results { - t.AddRow([]string{ + t.AddRow( *v.Id, *v.Context.Project, *v.Context.Provider, fmt.Sprintf("%d", v.GetRepoId()), v.GetOwner(), v.GetName(), - }) + ) } t.Render() case app.JSON: diff --git a/cmd/cli/app/repo/repo_register.go b/cmd/cli/app/repo/repo_register.go index cc1d249ed1..0a8eb2ec60 100644 --- a/cmd/cli/app/repo/repo_register.go +++ b/cmd/cli/app/repo/repo_register.go @@ -143,7 +143,7 @@ func RegisterCmd(ctx context.Context, cmd *cobra.Command, conn *grpc.ClientConn) } else { row = append(row, "") } - t.AddRow(row) + t.AddRow(row...) } t.Render() return nil diff --git a/cmd/cli/app/ruletype/common.go b/cmd/cli/app/ruletype/common.go index ccfc1f2bb8..f6aa1a4c57 100644 --- a/cmd/cli/app/ruletype/common.go +++ b/cmd/cli/app/ruletype/common.go @@ -60,13 +60,13 @@ func execOnOneRuleType( } // add the rule type to the table rows - t.AddRow([]string{ + t.AddRow( *rt.Context.Provider, *rt.Context.Project, *rt.Id, rt.Name, rt.Description, - }) + ) return nil } diff --git a/cmd/cli/app/ruletype/ruletype_get.go b/cmd/cli/app/ruletype/ruletype_get.go index ab4deacbc7..1804fa81d4 100644 --- a/cmd/cli/app/ruletype/ruletype_get.go +++ b/cmd/cli/app/ruletype/ruletype_get.go @@ -83,13 +83,13 @@ func getCommand(ctx context.Context, cmd *cobra.Command, conn *grpc.ClientConn) table := initializeTable() rt := rtype.GetRuleType() // add the rule type to the table rows - table.AddRow([]string{ + table.AddRow( *rt.Context.Provider, *rt.Context.Project, *rt.Id, rt.Name, rt.Description, - }) + ) table.Render() } return nil diff --git a/cmd/cli/app/ruletype/ruletype_list.go b/cmd/cli/app/ruletype/ruletype_list.go index c6e8ebe5a2..10f762b6cb 100644 --- a/cmd/cli/app/ruletype/ruletype_list.go +++ b/cmd/cli/app/ruletype/ruletype_list.go @@ -78,13 +78,13 @@ func listCommand(ctx context.Context, cmd *cobra.Command, conn *grpc.ClientConn) case app.Table: table := initializeTable() for _, rt := range resp.RuleTypes { - table.AddRow([]string{ + table.AddRow( *rt.Context.Provider, *rt.Context.Project, *rt.Id, rt.Name, rt.Description, - }) + ) } table.Render() } diff --git a/internal/util/cli/table/layouts/constants.go b/internal/util/cli/table/layouts/constants.go deleted file mode 100644 index 9329ddd373..0000000000 --- a/internal/util/cli/table/layouts/constants.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2023 Stacklok, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package layouts defines the available table layouts -package layouts - -// TableLayout is the type for table layouts -type TableLayout string - -const ( - // KeyValue is the key value table layout - KeyValue TableLayout = "keyvalue" - // RuleType is the rule type table layout - RuleType TableLayout = "ruletype" - // ProfileSettings is the profile settings table layout - ProfileSettings TableLayout = "profile_settings" - // Profile is the profile table layout - Profile TableLayout = "profile" - // RepoList is the repo list table layout - RepoList TableLayout = "repolist" - // ProfileStatus is the profile status table layout - ProfileStatus TableLayout = "profile_status" - // RuleEvaluations is the rule evaluations table layout - RuleEvaluations TableLayout = "rule_evaluations" - // Default is the default table layout - Default TableLayout = "" -) diff --git a/internal/util/cli/table/layouts/layouts.go b/internal/util/cli/table/layouts/layouts.go new file mode 100644 index 0000000000..6d4118870d --- /dev/null +++ b/internal/util/cli/table/layouts/layouts.go @@ -0,0 +1,119 @@ +// Copyright 2023 Stacklok, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package layouts defines the available table layouts +package layouts + +import "github.com/olekukonko/tablewriter" + +// TableLayout is the type for table layouts +type TableLayout string + +const ( + // KeyValue is the key value table layout + KeyValue TableLayout = "keyvalue" + // RuleType is the rule type table layout + RuleType TableLayout = "ruletype" + // ProfileSettings is the profile settings table layout + ProfileSettings TableLayout = "profile_settings" + // Profile is the profile table layout + Profile TableLayout = "profile" + // RepoList is the repo list table layout + RepoList TableLayout = "repolist" + // ProfileStatus is the profile status table layout + ProfileStatus TableLayout = "profile_status" + // RuleEvaluations is the rule evaluations table layout + RuleEvaluations TableLayout = "rule_evaluations" + // Default is the default table layout + Default TableLayout = "" +) + +// Color is the type for table colors +type Color string + +const ( + // ColorRed is the color red + ColorRed Color = "red" + // ColorGreen is the color green + ColorGreen Color = "green" + // ColorYellow is the color yellow + ColorYellow Color = "yellow" +) + +// ColoredColumn is a column with a color +type ColoredColumn struct { + Column string + Color Color +} + +// RowsFromColoredColumns returns the rows of the colored columns +func RowsFromColoredColumns(c []ColoredColumn) []string { + var columns []string + for _, col := range c { + columns = append(columns, col.Column) + } + return columns +} + +// ColorsFromColoredColumns returns the colors of the colored columns +func ColorsFromColoredColumns(r []ColoredColumn) []tablewriter.Colors { + colors := make([]tablewriter.Colors, len(r)) + for i := range r { + c := r[i].Color + switch c { + case ColorRed: + colors[i] = tablewriter.Colors{tablewriter.FgRedColor} + case ColorGreen: + colors[i] = tablewriter.Colors{tablewriter.FgGreenColor} + case ColorYellow: + colors[i] = tablewriter.Colors{tablewriter.FgYellowColor} + default: + colors[i] = tablewriter.Colors{} + } + } + + return colors +} + +// RedColumn returns a red colored column +func RedColumn(column string) ColoredColumn { + return ColoredColumn{ + Column: column, + Color: ColorRed, + } +} + +// GreenColumn returns a green colored column +func GreenColumn(column string) ColoredColumn { + return ColoredColumn{ + Column: column, + Color: ColorGreen, + } +} + +// YellowColumn returns a yellow colored column +func YellowColumn(column string) ColoredColumn { + return ColoredColumn{ + Column: column, + Color: ColorYellow, + } +} + +// NoColor returns a column with no color +func NoColor(column string) ColoredColumn { + return ColoredColumn{ + Column: column, + Color: "", + } +} diff --git a/internal/util/cli/table/simple/simple.go b/internal/util/cli/table/simple/simple.go index 7343e3e948..c8b2854848 100644 --- a/internal/util/cli/table/simple/simple.go +++ b/internal/util/cli/table/simple/simple.go @@ -59,26 +59,13 @@ func New(layout layouts.TableLayout, header []string) *Table { } // AddRow adds a row -func (t *Table) AddRow(row []string) { +func (t *Table) AddRow(row ...string) { t.table.Append(row) } // AddRowWithColor adds a row with the given colors -func (t *Table) AddRowWithColor(row []string, rowColors []string) { - colors := make([]tablewriter.Colors, len(rowColors)) - for i := range rowColors { - switch rowColors[i] { - case "red": - colors[i] = tablewriter.Colors{tablewriter.FgRedColor} - case "green": - colors[i] = tablewriter.Colors{tablewriter.FgGreenColor} - case "yellow": - colors[i] = tablewriter.Colors{tablewriter.FgYellowColor} - default: - colors[i] = tablewriter.Colors{} - } - } - t.table.Rich(row, colors) +func (t *Table) AddRowWithColor(row ...layouts.ColoredColumn) { + t.table.Rich(layouts.RowsFromColoredColumns(row), layouts.ColorsFromColoredColumns(row)) } // Render renders the table diff --git a/internal/util/cli/table/table.go b/internal/util/cli/table/table.go index fbd0b8f6cf..7be4e4c8fa 100644 --- a/internal/util/cli/table/table.go +++ b/internal/util/cli/table/table.go @@ -24,19 +24,11 @@ const ( // Simple is a simple table Simple = "simple" ) -const ( - // ColorRed is the color red - ColorRed = "red" - // ColorGreen is the color green - ColorGreen = "green" - // ColorYellow is the color yellow - ColorYellow = "yellow" -) // Table is an interface for rendering tables type Table interface { - AddRow(row []string) - AddRowWithColor(row []string, rowColors []string) + AddRow(row ...string) + AddRowWithColor(row ...layouts.ColoredColumn) Render() }