1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2024-12-15 17:51:20 +00:00

feat: add table output to cli apply command (#7757)

* feat: add table output to cli apply command

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* factorise

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

---------

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
Co-authored-by: shuting <shuting@nirmata.com>
This commit is contained in:
Charles-Edouard Brétéché 2023-07-06 13:48:19 +02:00 committed by GitHub
parent b6fb496d9b
commit 9bc540e454
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 257 additions and 134 deletions

View file

@ -14,6 +14,7 @@ import (
"github.com/kyverno/kyverno/api/kyverno"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/api/kyverno/v1beta1"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/color"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/common"
sanitizederror "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/sanitizedError"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/store"
@ -146,12 +147,14 @@ More info: https://kyverno.io/docs/kyverno-cli/
func Command() *cobra.Command {
var cmd *cobra.Command
var removeColor, compact, table bool
applyCommandConfig := &ApplyCommandConfig{}
cmd = &cobra.Command{
Use: "apply",
Short: "Applies policies on resources.",
Example: applyHelp,
RunE: func(cmd *cobra.Command, policyPaths []string) (err error) {
color.InitColors(removeColor)
defer func() {
if err != nil {
if !sanitizederror.IsErrorSanitized(err) {
@ -168,6 +171,8 @@ func Command() *cobra.Command {
printSkippedAndInvalidPolicies(skipInvalidPolicies)
if applyCommandConfig.PolicyReport {
printReport(responses, applyCommandConfig.AuditWarn)
} else if table {
printTable(compact, applyCommandConfig.AuditWarn, responses...)
} else {
printViolations(rc)
}
@ -192,6 +197,9 @@ func Command() *cobra.Command {
cmd.Flags().BoolVar(&applyCommandConfig.AuditWarn, "audit-warn", false, "If set to true, will flag audit policies as warnings instead of failures")
cmd.Flags().IntVar(&applyCommandConfig.warnExitCode, "warn-exit-code", 0, "Set the exit code for warnings; if failures or errors are found, will exit 1")
cmd.Flags().BoolVar(&applyCommandConfig.warnNoPassed, "warn-no-pass", false, "Specify if warning exit code should be raised if no objects satisfied a policy; can be used together with --warn-exit-code flag")
cmd.Flags().BoolVar(&removeColor, "remove-color", false, "Remove any color from output")
cmd.Flags().BoolVar(&compact, "compact", true, "Does not show detailed results")
cmd.Flags().BoolVarP(&table, "table", "t", false, "Show results in table format")
return cmd
}

View file

@ -0,0 +1,66 @@
package apply
import (
"github.com/kyverno/kyverno/api/kyverno"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/color"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/output/table"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
)
func printTable(compact, auditWarn bool, engineResponses ...engineapi.EngineResponse) {
var resultsTable table.Table
id := 1
for _, engineResponse := range engineResponses {
var policyNamespace, policyName string
var ann map[string]string
isVAP := engineResponse.IsValidatingAdmissionPolicy()
if isVAP {
policy := engineResponse.ValidatingAdmissionPolicy()
policyNamespace = policy.GetNamespace()
policyName = policy.GetName()
ann = policy.GetAnnotations()
} else {
policy := engineResponse.Policy()
policyNamespace = policy.GetNamespace()
policyName = policy.GetName()
ann = policy.GetAnnotations()
}
resourceKind := engineResponse.Resource.GetKind()
resourceNamespace := engineResponse.Resource.GetNamespace()
resourceName := engineResponse.Resource.GetName()
for _, ruleResponse := range engineResponse.PolicyResponse.Rules {
var row table.Row
row.ID = id
id++
row.Policy = color.Policy(policyNamespace, policyName)
if !isVAP {
row.Rule = color.Rule(ruleResponse.Name())
}
row.Resource = color.Resource(resourceKind, resourceNamespace, resourceName)
if ruleResponse.Status() == engineapi.RuleStatusPass {
row.Result = color.ResultPass()
} else if ruleResponse.Status() == engineapi.RuleStatusFail {
if scored, ok := ann[kyverno.AnnotationPolicyScored]; ok && scored == "false" {
row.Result = color.ResultWarn()
} else if auditWarn && engineResponse.GetValidationFailureAction().Audit() {
row.Result = color.ResultWarn()
} else {
row.Result = color.ResultFail()
}
} else if ruleResponse.Status() == engineapi.RuleStatusWarn {
row.Result = color.ResultWarn()
} else if ruleResponse.Status() == engineapi.RuleStatusError {
row.Result = color.ResultError()
} else if ruleResponse.Status() == engineapi.RuleStatusSkip {
row.Result = color.ResultSkip()
}
row.Message = ruleResponse.Message()
resultsTable.Add(row)
}
}
printer := table.NewTablePrinter()
printer.Print(resultsTable.Rows(compact))
}

View file

@ -1,50 +0,0 @@
package test
import (
"os"
"github.com/fatih/color"
"github.com/kataras/tablewriter"
"github.com/lensesio/tableprinter"
)
var (
boldGreen *color.Color
boldRed *color.Color
boldYellow *color.Color
boldFgCyan *color.Color
headerBgColor int
headerFgColor int
)
func initColors(noColor bool) {
toggleColor := func(c *color.Color) *color.Color {
if noColor {
c.DisableColor()
}
return c
}
boldGreen = toggleColor(color.New(color.FgGreen).Add(color.Bold))
boldRed = toggleColor(color.New(color.FgRed).Add(color.Bold))
boldYellow = toggleColor(color.New(color.FgYellow).Add(color.Bold))
boldFgCyan = toggleColor(color.New(color.FgCyan).Add(color.Bold))
if !noColor {
headerBgColor = tablewriter.BgBlackColor
headerFgColor = tablewriter.FgGreenColor
}
}
func newTablePrinter() *tableprinter.Printer {
printer := tableprinter.New(os.Stdout)
printer.BorderTop, printer.BorderBottom, printer.BorderLeft, printer.BorderRight = true, true, true, true
printer.CenterSeparator = "│"
printer.ColumnSeparator = "│"
printer.RowSeparator = "─"
printer.RowCharLimit = 300
printer.HeaderBgColor = headerBgColor
printer.HeaderFgColor = headerFgColor
printer.RowLengthTitle = func(rowsLength int) bool {
return rowsLength > 10
}
return printer
}

View file

@ -7,6 +7,8 @@ import (
policyreportv1alpha2 "github.com/kyverno/kyverno/api/policyreport/v1alpha2"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/test/api"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/test/manifest"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/color"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/output/table"
sanitizederror "github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/sanitizedError"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/store"
"github.com/kyverno/kyverno/pkg/openapi"
@ -27,7 +29,7 @@ func Command() *cobra.Command {
Long: longHelp,
Example: exampleHelp,
RunE: func(cmd *cobra.Command, dirPath []string) (err error) {
initColors(removeColor)
color.InitColors(removeColor)
defer func() {
if err != nil {
if !sanitizederror.IsErrorSanitized(err) {
@ -63,47 +65,6 @@ func Command() *cobra.Command {
return cmd
}
type Table struct {
rows []Row
}
func (t *Table) Rows(compact bool) interface{} {
if !compact {
return t.rows
}
var rows []CompactRow
for _, row := range t.rows {
rows = append(rows, row.CompactRow)
}
return rows
}
func (t *Table) AddFailed(rows ...Row) {
for _, row := range rows {
if row.isFailure {
t.rows = append(t.rows, row)
}
}
}
func (t *Table) Add(rows ...Row) {
t.rows = append(t.rows, rows...)
}
type CompactRow struct {
isFailure bool
ID int `header:"id"`
Policy string `header:"policy"`
Rule string `header:"rule"`
Resource string `header:"resource"`
Result string `header:"result"`
}
type Row struct {
CompactRow `header:"inline"`
Message string `header:"message"`
}
type resultCounts struct {
Skip int
Pass int
@ -136,7 +97,7 @@ func testCommandExecute(
fmt.Printf("\n No test yamls available \n")
}
rc = &resultCounts{}
var table Table
var table table.Table
for _, p := range policies {
if reports, tests, err := applyPoliciesFromPath(
fs,
@ -152,7 +113,7 @@ func testCommandExecute(
} else if t, err := printTestResult(reports, tests, rc, failOnly, compact); err != nil {
return rc, sanitizederror.NewWithError("failed to print test result:", err)
} else {
table.AddFailed(t.rows...)
table.AddFailed(t.RawRows...)
}
}
if len(errors) > 0 && log.Log.V(1).Enabled() {
@ -177,25 +138,25 @@ func testCommandExecute(
return rc, nil
}
func printTestResult(resps map[string]policyreportv1alpha2.PolicyReportResult, testResults []api.TestResults, rc *resultCounts, failOnly bool, compact bool) (Table, error) {
printer := newTablePrinter()
var table Table
func printTestResult(resps map[string]policyreportv1alpha2.PolicyReportResult, testResults []api.TestResults, rc *resultCounts, failOnly bool, compact bool) (table.Table, error) {
printer := table.NewTablePrinter()
var resultsTable table.Table
var countDeprecatedResource int
testCount := 1
for _, v := range testResults {
var row Row
var row table.Row
row.ID = testCount
if v.Resources == nil {
testCount++
}
row.Policy = boldFgCyan.Sprint(v.Policy)
row.Rule = boldFgCyan.Sprint(v.Rule)
row.Policy = color.Policy("", v.Policy)
row.Rule = color.Rule(v.Rule)
if v.Resources != nil {
for _, resource := range v.Resources {
row.ID = testCount
testCount++
row.Resource = boldFgCyan.Sprint(v.Namespace) + "/" + boldFgCyan.Sprint(v.Kind) + "/" + boldFgCyan.Sprint(resource)
row.Resource = color.Resource(v.Kind, v.Namespace, resource)
var ruleNameInResultKey string
if !v.IsVap {
if v.AutoGeneratedRule != "" {
@ -227,11 +188,10 @@ func printTestResult(resps map[string]policyreportv1alpha2.PolicyReportResult, t
} else {
resultKey = fmt.Sprintf("%s-%s-%s-%s", ns, v.Policy, v.Kind, resource)
}
row.Policy = boldFgCyan.Sprint(ns) + "/" + boldFgCyan.Sprint(v.Policy)
row.Resource = boldFgCyan.Sprint(v.Namespace) + "/" + boldFgCyan.Sprint(v.Kind) + "/" + boldFgCyan.Sprint(resource)
row.Policy = color.Policy(ns, v.Policy)
row.Resource = color.Resource(v.Kind, v.Namespace, resource)
} else if v.Namespace != "" {
row.Resource = boldFgCyan.Sprint(v.Namespace) + "/" + boldFgCyan.Sprint(v.Kind) + "/" + boldFgCyan.Sprint(resource)
row.Resource = color.Resource(v.Kind, v.Namespace, resource)
if !v.IsVap {
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s", v.Policy, ruleNameInResultKey, v.Namespace, v.Kind, resource)
@ -245,10 +205,10 @@ func printTestResult(resps map[string]policyreportv1alpha2.PolicyReportResult, t
testRes = val
} else {
log.Log.V(2).Info("result not found", "key", resultKey)
row.Result = boldYellow.Sprint("Not found")
row.Result = color.NotFound()
rc.Fail++
row.isFailure = true
table.Add(row)
row.IsFailure = true
resultsTable.Add(row)
continue
}
row.Message = testRes.Message
@ -257,7 +217,7 @@ func printTestResult(resps map[string]policyreportv1alpha2.PolicyReportResult, t
}
if testRes.Result == v.Result {
row.Result = boldGreen.Sprint("Pass")
row.Result = color.ResultPass()
if testRes.Result == policyreportv1alpha2.StatusSkip {
rc.Skip++
} else {
@ -265,22 +225,22 @@ func printTestResult(resps map[string]policyreportv1alpha2.PolicyReportResult, t
}
} else {
log.Log.V(2).Info("result mismatch", "expected", v.Result, "received", testRes.Result, "key", resultKey)
row.Result = boldRed.Sprint("Fail")
row.Result = color.ResultFail()
rc.Fail++
row.isFailure = true
row.IsFailure = true
}
if failOnly {
if row.Result == boldRed.Sprintf("Fail") || row.Result == "Fail" {
table.Add(row)
if row.Result == color.ResultFail() || row.Result == "Fail" {
resultsTable.Add(row)
}
} else {
table.Add(row)
resultsTable.Add(row)
}
}
} else if v.Resource != "" {
countDeprecatedResource++
row.Resource = boldFgCyan.Sprint(v.Namespace) + "/" + boldFgCyan.Sprint(v.Kind) + "/" + boldFgCyan.Sprint(v.Resource)
row.Resource = color.Resource(v.Kind, v.Namespace, v.Resource)
var ruleNameInResultKey string
if !v.IsVap {
if v.AutoGeneratedRule != "" {
@ -313,10 +273,10 @@ func printTestResult(resps map[string]policyreportv1alpha2.PolicyReportResult, t
resultKey = fmt.Sprintf("%s-%s-%s-%s", ns, v.Policy, v.Kind, v.Resource)
}
row.Policy = boldFgCyan.Sprint(ns) + "/" + boldFgCyan.Sprint(v.Policy)
row.Resource = boldFgCyan.Sprint(v.Namespace) + "/" + boldFgCyan.Sprint(v.Kind) + "/" + boldFgCyan.Sprint(v.Resource)
row.Policy = color.Policy(ns, v.Policy)
row.Resource = color.Resource(v.Kind, v.Namespace, v.Resource)
} else if v.Namespace != "" {
row.Resource = boldFgCyan.Sprint(v.Namespace) + "/" + boldFgCyan.Sprint(v.Kind) + "/" + boldFgCyan.Sprint(v.Resource)
row.Resource = color.Resource(v.Kind, v.Namespace, v.Resource)
if !v.IsVap {
resultKey = fmt.Sprintf("%s-%s-%s-%s-%s", v.Policy, ruleNameInResultKey, v.Namespace, v.Kind, v.Resource)
@ -330,10 +290,10 @@ func printTestResult(resps map[string]policyreportv1alpha2.PolicyReportResult, t
testRes = val
} else {
log.Log.V(2).Info("result not found", "key", resultKey)
row.Result = boldYellow.Sprint("Not found")
row.Result = color.NotFound()
rc.Fail++
row.isFailure = true
table.Add(row)
row.IsFailure = true
resultsTable.Add(row)
continue
}
@ -344,7 +304,7 @@ func printTestResult(resps map[string]policyreportv1alpha2.PolicyReportResult, t
}
if testRes.Result == v.Result {
row.Result = boldGreen.Sprint("Pass")
row.Result = color.ResultPass()
if testRes.Result == policyreportv1alpha2.StatusSkip {
rc.Skip++
} else {
@ -352,31 +312,31 @@ func printTestResult(resps map[string]policyreportv1alpha2.PolicyReportResult, t
}
} else {
log.Log.V(2).Info("result mismatch", "expected", v.Result, "received", testRes.Result, "key", resultKey)
row.Result = boldRed.Sprint("Fail")
row.Result = color.ResultFail()
rc.Fail++
row.isFailure = true
row.IsFailure = true
}
if failOnly {
if row.Result == boldRed.Sprintf("Fail") || row.Result == "Fail" {
table.Add(row)
if row.Result == color.ResultFail() || row.Result == "Fail" {
resultsTable.Add(row)
}
} else {
table.Add(row)
resultsTable.Add(row)
}
}
}
fmt.Printf("\n")
printer.Print(table.Rows(compact))
return table, nil
printer.Print(resultsTable.Rows(compact))
return resultsTable, nil
}
func printFailedTestResult(table Table, compact bool) {
printer := newTablePrinter()
for i := range table.rows {
table.rows[i].ID = i + 1
func printFailedTestResult(resultsTable table.Table, compact bool) {
printer := table.NewTablePrinter()
for i := range resultsTable.RawRows {
resultsTable.RawRows[i].ID = i + 1
}
fmt.Printf("Aggregated Failed Test Cases : ")
fmt.Println()
printer.Print(table.Rows(compact))
printer.Print(resultsTable.Rows(compact))
}

View file

@ -0,0 +1,74 @@
package color
import (
"github.com/fatih/color"
"github.com/kataras/tablewriter"
)
var (
BoldGreen *color.Color
BoldRed *color.Color
BoldYellow *color.Color
BoldFgCyan *color.Color
HeaderBgColor int
HeaderFgColor int
)
func InitColors(noColor bool) {
toggleColor := func(c *color.Color) *color.Color {
if noColor {
c.DisableColor()
}
return c
}
BoldGreen = toggleColor(color.New(color.FgGreen).Add(color.Bold))
BoldRed = toggleColor(color.New(color.FgRed).Add(color.Bold))
BoldYellow = toggleColor(color.New(color.FgYellow).Add(color.Bold))
BoldFgCyan = toggleColor(color.New(color.FgCyan).Add(color.Bold))
if !noColor {
HeaderBgColor = tablewriter.BgBlackColor
HeaderFgColor = tablewriter.FgGreenColor
}
}
func Policy(namespace, name string) string {
if namespace == "" {
return BoldFgCyan.Sprint(name)
}
return BoldFgCyan.Sprint(namespace) + "/" + BoldFgCyan.Sprint(name)
}
func Rule(name string) string {
return BoldFgCyan.Sprint(name)
}
func Resource(kind, namespace, name string) string {
if namespace == "" {
return BoldFgCyan.Sprint(kind) + "/" + BoldFgCyan.Sprint(name)
}
return BoldFgCyan.Sprint(namespace) + "/" + BoldFgCyan.Sprint(kind) + "/" + BoldFgCyan.Sprint(name)
}
func NotFound() string {
return BoldYellow.Sprint("Not found")
}
func ResultPass() string {
return BoldGreen.Sprint("Pass")
}
func ResultFail() string {
return BoldRed.Sprint("Fail")
}
func ResultWarn() string {
return BoldYellow.Sprint("Warn")
}
func ResultError() string {
return BoldRed.Sprint("Error")
}
func ResultSkip() string {
return BoldFgCyan.Sprint("Skip")
}

View file

@ -0,0 +1,23 @@
package table
import (
"os"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/utils/color"
"github.com/lensesio/tableprinter"
)
func NewTablePrinter() *tableprinter.Printer {
printer := tableprinter.New(os.Stdout)
printer.BorderTop, printer.BorderBottom, printer.BorderLeft, printer.BorderRight = true, true, true, true
printer.CenterSeparator = "│"
printer.ColumnSeparator = "│"
printer.RowSeparator = "─"
printer.RowCharLimit = 300
printer.HeaderBgColor = color.HeaderBgColor
printer.HeaderFgColor = color.HeaderFgColor
printer.RowLengthTitle = func(rowsLength int) bool {
return rowsLength > 10
}
return printer
}

View file

@ -0,0 +1,42 @@
package table
type Table struct {
RawRows []Row
}
func (t *Table) Rows(compact bool) interface{} {
if !compact {
return t.RawRows
}
var rows []CompactRow
for _, row := range t.RawRows {
rows = append(rows, row.CompactRow)
}
return rows
}
func (t *Table) AddFailed(rows ...Row) {
for _, row := range rows {
if row.IsFailure {
t.RawRows = append(t.RawRows, row)
}
}
}
func (t *Table) Add(rows ...Row) {
t.RawRows = append(t.RawRows, rows...)
}
type CompactRow struct {
IsFailure bool
ID int `header:"id"`
Policy string `header:"policy"`
Rule string `header:"rule"`
Resource string `header:"resource"`
Result string `header:"result"`
}
type Row struct {
CompactRow `header:"inline"`
Message string `header:"message"`
}