Skip to content

Commit

Permalink
Refactor output columns into interfaces. Add custom output option (#103)
Browse files Browse the repository at this point in the history
* refactor output columns into interfaces

* add custom columns

* custom columns output

* unnecessary empty lines

* Check for invalid custom column options in functional tests

* functional testing for custom column output

* custom column output with no columns

* Functional

* Apply suggestions from code review

Co-authored-by: Luke Reed <[email protected]>

* Uppercase all column names passed in based on review suggestion

* Centralize the possible column lists

Co-authored-by: Luke Reed <[email protected]>
  • Loading branch information
Andrew Suderman and Luke Reed authored Jul 14, 2020
1 parent 8c8b057 commit 92c5f13
Show file tree
Hide file tree
Showing 6 changed files with 266 additions and 60 deletions.
39 changes: 37 additions & 2 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"fmt"
"io/ioutil"
"os"
"strings"

"github.com/fairwindsops/pluto/pkg/api"
"github.com/fairwindsops/pluto/pkg/finder"
Expand All @@ -43,17 +44,27 @@ var (
namespace string
apiInstance *api.Instance
targetVersions map[string]string
customColumns []string
)

var outputOptions = []string{
"json",
"yaml",
"normal",
"wide",
"custom",
}

func init() {
rootCmd.PersistentFlags().BoolVar(&ignoreDeprecations, "ignore-deprecations", false, "Ignore the default behavior to exit 2 if deprecated apiVersions are found.")
rootCmd.PersistentFlags().BoolVar(&ignoreRemovals, "ignore-removals", false, "Ignore the default behavior to exit 3 if removed apiVersions are found.")
rootCmd.PersistentFlags().StringVarP(&additionalVersionsFile, "additional-versions", "f", "", "Additional deprecated versions file to add to the list. Cannot contain any existing versions")
rootCmd.PersistentFlags().StringToStringVarP(&targetVersions, "target-versions", "t", targetVersions, "A map of targetVersions to use. This flag supersedes all defaults in version files.")
rootCmd.PersistentFlags().StringVarP(&outputFormat, "output", "o", "normal", "The output format to use. (normal|wide|json|yaml)")
rootCmd.PersistentFlags().StringVarP(&outputFormat, "output", "o", "normal", "The output format to use. (normal|wide|custom|json|yaml)")
rootCmd.PersistentFlags().StringSliceVar(&customColumns, "columns", nil, "A list of columns to print when using --output custom")

rootCmd.AddCommand(detectFilesCmd)
detectFilesCmd.PersistentFlags().StringVarP(&directory, "directory", "d", "", "The directory to scan. If blank, defaults to current workding dir.")
detectFilesCmd.PersistentFlags().StringVarP(&directory, "directory", "d", "", "The directory to scan. If blank, defaults to current working dir.")

rootCmd.AddCommand(detectHelmCmd)
detectHelmCmd.PersistentFlags().StringVar(&helmVersion, "helm-version", "3", "Helm version in current cluster (2|3)")
Expand Down Expand Up @@ -81,6 +92,29 @@ var rootCmd = &cobra.Command{
os.Exit(1)
},
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
//verify output option
if !stringInSlice(outputFormat, outputOptions) {
return fmt.Errorf("--output must be one of %v", outputOptions)
}

if outputFormat == "custom" {
if len(customColumns) < 1 {
return fmt.Errorf("when --output=custom you must specify --columns")
}
// Uppercase all columns entered on CLI
var tempColumns []string
for _, colString := range customColumns {
tempColumns = append(tempColumns, strings.ToUpper(colString))
}

customColumns = tempColumns
for _, c := range customColumns {
if !stringInSlice(c, api.PossibleColumnNames) {
return fmt.Errorf("invalid custom column option %s - must be one of %v", c, api.PossibleColumnNames)
}
}
}

defaultVersions, defaultTargetVersions, err := api.GetDefaultVersionList()
if err != nil {
return err
Expand Down Expand Up @@ -148,6 +182,7 @@ var rootCmd = &cobra.Command{
apiInstance = &api.Instance{
TargetVersions: targetVersions,
OutputFormat: outputFormat,
CustomColumns: customColumns,
IgnoreDeprecations: ignoreDeprecations,
IgnoreRemovals: ignoreRemovals,
DeprecatedVersions: deprecatedVersionList,
Expand Down
8 changes: 8 additions & 0 deletions e2e/tests/00_static_files.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,11 @@ testcases:
- result.code ShouldEqual 3
- result.systemout ShouldContainSubstring "NAME NAMESPACE KIND VERSION REPLACEMENT DEPRECATED DEPRECATED IN REMOVED REMOVED IN"
- result.systemout ShouldContainSubstring "utilities <UNKNOWN> Deployment extensions/v1beta1 apps/v1 true v1.9.0 true v1.16.0"

- name: static files custom
steps:
- script: pluto detect-files -d assets/ -o custom --columns "NAME,NAMESPACE,VERSION,KIND,DEPRECATED,DEPRECATED IN"
assertions:
- result.code ShouldEqual 3
- result.systemout ShouldContainSubstring "NAME NAMESPACE KIND VERSION DEPRECATED DEPRECATED IN"
- result.systemout ShouldContainSubstring "utilities <UNKNOWN> Deployment extensions/v1beta1 true v1.9.0"
12 changes: 12 additions & 0 deletions e2e/tests/03-cli-validation.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,15 @@ testcases:
assertions:
- result.code ShouldEqual 0
- result.systemout ShouldContainSubstring "AnotherCRD someother/v1beta1 v1.9.0 v1.16.0 apps/v1"
- name: Pass bad column list to custom
steps:
- script: pluto detect-files -d assets/deprecated116 --target-versions foo=vfoo -o custom --columns "FOO"
assertions:
- result.code ShouldEqual 1
- result.systemerr ShouldContainSubstring "invalid custom column option FOO"
- name: Custom output with no columns falg
steps:
- script: pluto detect-files -d assets/deprecated116 --target-versions foo=vfoo -o custom
assertions:
- result.code ShouldEqual 1
- result.systemerr ShouldContainSubstring "when --output=custom you must specify --columns"
137 changes: 137 additions & 0 deletions pkg/api/columns.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package api

import "fmt"

// Column is an interface for printing columns
type column interface {
header() string
value(output *Output) string
}

type columnList map[int]column

//PossibleColumnNames is the list of implmented columns
var PossibleColumnNames = []string{
"NAME",
"NAMESPACE",
"KIND",
"VERSION",
"REPLACEMENT",
"DEPRECATED",
"DEPRECATED IN",
"REMOVED",
"REMOVED IN",
}

var possibleColumns = []column{
new(name),
new(namespace),
new(kind),
new(version),
new(replacement),
new(deprecated),
new(deprecatedIn),
new(removed),
new(removedIn),
}

// name is the output name
type name struct{}

func (n name) header() string { return "NAME" }
func (n name) value(output *Output) string { return output.Name }

// namespace is the output namespace if available
type namespace struct{}

func (ns namespace) header() string { return "NAMESPACE" }
func (ns namespace) value(output *Output) string {
if output.Namespace == "" {
return "<UNKNOWN>"
}
return output.Namespace
}

// kind is the output apiVersion kind
type kind struct{}

func (k kind) header() string { return "KIND" }
func (k kind) value(output *Output) string { return output.APIVersion.Kind }

// version is the output apiVersion
type version struct{}

func (v version) header() string { return "VERSION" }
func (v version) value(output *Output) string { return output.APIVersion.Name }

// replacement is the output replacement apiVersion
type replacement struct{}

func (r replacement) header() string { return "REPLACEMENT" }
func (r replacement) value(output *Output) string { return output.APIVersion.ReplacementAPI }

// deprecated is the output for the boolean Deprecated
type deprecated struct{}

func (d deprecated) header() string { return "DEPRECATED" }
func (d deprecated) value(output *Output) string { return fmt.Sprintf("%t", output.Deprecated) }

// removed is the output for the boolean Deprecated
type removed struct{}

func (r removed) header() string { return "REMOVED" }
func (r removed) value(output *Output) string { return fmt.Sprintf("%t", output.Removed) }

// deprecatedIn is the string value of when an output was deprecated
type deprecatedIn struct{}

func (di deprecatedIn) header() string { return "DEPRECATED IN" }
func (di deprecatedIn) value(output *Output) string { return output.APIVersion.DeprecatedIn }

// removedIn is the string value of when an output was deprecated
type removedIn struct{}

func (ri removedIn) header() string { return "REMOVED IN" }
func (ri removedIn) value(output *Output) string { return output.APIVersion.RemovedIn }

// normalColumns returns the list of columns for -onormal
func (instance *Instance) normalColumns() columnList {
columnList := columnList{
0: new(name),
1: new(kind),
2: new(version),
3: new(replacement),
4: new(removed),
5: new(deprecated),
}
return columnList
}

// wideColumns returns the list of columns for -owide
func (instance *Instance) wideColumns() columnList {
columnList := columnList{
0: new(name),
1: new(namespace),
2: new(kind),
3: new(version),
4: new(replacement),
5: new(deprecated),
6: new(deprecatedIn),
7: new(removed),
8: new(removedIn),
}
return columnList
}

// customColumns returns a custom list of columns based on names
func (instance *Instance) customColumns() columnList {
var outputColumns = make(map[int]column)
for _, d := range instance.CustomColumns {
for i, c := range possibleColumns {
if d == c.header() {
outputColumns[i] = c
}
}
}
return outputColumns
}
Loading

0 comments on commit 92c5f13

Please sign in to comment.