feat: allow multiple output flags

main
sundowndev 2021-07-21 17:57:05 +02:00
parent 6586aa1311
commit 8036b7a702
3 changed files with 98 additions and 37 deletions

View File

@ -61,12 +61,16 @@ func NewScanCmd() *cobra.Command {
)
}
outputFlag, _ := cmd.Flags().GetString("output")
out, err := parseOutputFlag(outputFlag)
outputFlag, _ := cmd.Flags().GetStringSlice("output")
if len(outputFlag) == 0 {
outputFlag = append(outputFlag, output.Example(output.ConsoleOutputType))
}
out, err := parseOutputFlags(outputFlag)
if err != nil {
return err
}
opts.Output = *out
opts.Output = out
filterFlag, _ := cmd.Flags().GetStringArray("filter")
@ -117,10 +121,10 @@ func NewScanCmd() *cobra.Command {
" - Type =='aws_s3_bucket && Id != 'my_bucket' (excludes s3 bucket 'my_bucket')\n"+
" - Attr.Tags.Terraform == 'true' (include only resources that have Tag Terraform equal to 'true')\n",
)
fl.StringP(
fl.StringSliceP(
"output",
"o",
output.Example(output.ConsoleOutputType),
[]string{},
"Output format, by default it will write to the console\n"+
"Accepted formats are: "+strings.Join(output.SupportedOutputsExample(), ",")+"\n",
)
@ -190,7 +194,6 @@ func NewScanCmd() *cobra.Command {
func scanRun(opts *pkg.ScanOptions) error {
store := memstore.New()
selectedOutput := output.GetOutput(opts.Output, opts.Quiet)
c := make(chan os.Signal)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
@ -256,9 +259,12 @@ func scanRun(opts *pkg.ScanOptions) error {
analysis.ProviderVersion = resourceSchemaRepository.ProviderVersion.String()
analysis.ProviderName = resourceSchemaRepository.ProviderName
err = selectedOutput.Write(analysis)
if err != nil {
return err
for _, o := range opts.Output {
selectedOutput := output.GetOutput(o, opts.Quiet)
err = selectedOutput.Write(analysis)
if err != nil {
return err
}
}
globaloutput.Printf(color.WhiteString("Scan duration: %s\n", analysis.Duration.Round(time.Second)))
@ -352,6 +358,18 @@ func parseFromFlag(from []string) ([]config.SupplierConfig, error) {
return configs, nil
}
func parseOutputFlags(out []string) ([]output.OutputConfig, error) {
result := make([]output.OutputConfig, 0, len(out))
for _, v := range out {
o, err := parseOutputFlag(v)
if err != nil {
return result, err
}
result = append(result, *o)
}
return result, nil
}
func parseOutputFlag(out string) (*output.OutputConfig, error) {
schemeOpts := strings.Split(out, "://")
if len(schemeOpts) < 2 || schemeOpts[0] == "" {

View File

@ -48,6 +48,7 @@ func TestScanCmd_Valid(t *testing.T) {
{args: []string{"scan", "--tf-provider-version", "3.30.2"}},
{args: []string{"scan", "--driftignore", "./path/to/driftignore.s3"}},
{args: []string{"scan", "--driftignore", ".driftignore"}},
{args: []string{"scan", "-o", "html://result.html", "-o", "json://result.json"}},
}
for _, tt := range cases {
@ -164,98 +165,140 @@ func Test_parseFromFlag(t *testing.T) {
func Test_parseOutputFlag(t *testing.T) {
type args struct {
out string
out []string
}
tests := []struct {
name string
args args
want *output.OutputConfig
want []output.OutputConfig
err error
}{
{
name: "test empty",
name: "test empty output",
args: args{
out: "",
out: []string{""},
},
want: nil,
want: []output.OutputConfig{},
err: fmt.Errorf("Unable to parse output flag '': \nAccepted formats are: console://,html://PATH/TO/FILE.html,json://PATH/TO/FILE.json,plan://PATH/TO/FILE.json"),
},
{
name: "test empty array",
args: args{
out: []string{},
},
want: []output.OutputConfig{},
err: nil,
},
{
name: "test invalid",
args: args{
out: "sdgjsdgjsdg",
out: []string{"sdgjsdgjsdg"},
},
want: nil,
want: []output.OutputConfig{},
err: fmt.Errorf("Unable to parse output flag 'sdgjsdgjsdg': \nAccepted formats are: console://,html://PATH/TO/FILE.html,json://PATH/TO/FILE.json,plan://PATH/TO/FILE.json"),
},
{
name: "test invalid",
args: args{
out: "://",
out: []string{"://"},
},
want: nil,
want: []output.OutputConfig{},
err: fmt.Errorf("Unable to parse output flag '://': \nAccepted formats are: console://,html://PATH/TO/FILE.html,json://PATH/TO/FILE.json,plan://PATH/TO/FILE.json"),
},
{
name: "test unsupported",
args: args{
out: "foobar://",
out: []string{"foobar://"},
},
want: nil,
want: []output.OutputConfig{},
err: fmt.Errorf("Unsupported output 'foobar': \nValid formats are: console://,html://PATH/TO/FILE.html,json://PATH/TO/FILE.json,plan://PATH/TO/FILE.json"),
},
{
name: "test empty json",
args: args{
out: "json://",
out: []string{"json://"},
},
want: nil,
want: []output.OutputConfig{},
err: fmt.Errorf("Invalid json output 'json://': \nMust be of kind: json://PATH/TO/FILE.json"),
},
{
name: "test valid console",
args: args{
out: "console://",
out: []string{"console://"},
},
want: &output.OutputConfig{
Key: "console",
want: []output.OutputConfig{
{
Key: "console",
},
},
err: nil,
},
{
name: "test valid json",
args: args{
out: "json:///tmp/foobar.json",
out: []string{"json:///tmp/foobar.json"},
},
want: &output.OutputConfig{
Key: "json",
Path: "/tmp/foobar.json",
want: []output.OutputConfig{
{
Key: "json",
Path: "/tmp/foobar.json",
},
},
err: nil,
},
{
name: "test empty jsonplan",
args: args{
out: "plan://",
out: []string{"plan://"},
},
want: nil,
want: []output.OutputConfig{},
err: fmt.Errorf("Invalid plan output 'plan://': \nMust be of kind: plan://PATH/TO/FILE.json"),
},
{
name: "test valid jsonplan",
args: args{
out: "plan:///tmp/foobar.json",
out: []string{"plan:///tmp/foobar.json"},
},
want: &output.OutputConfig{
Key: "plan",
Path: "/tmp/foobar.json",
want: []output.OutputConfig{
{
Key: "plan",
Path: "/tmp/foobar.json",
},
},
err: nil,
},
{
name: "test multiple output values",
args: args{
out: []string{"console:///dev/stdout", "json://result.json"},
},
want: []output.OutputConfig{
{
Key: "console",
},
{
Key: "json",
Path: "result.json",
},
},
err: nil,
},
{
name: "test multiple output values with invalid value",
args: args{
out: []string{"console:///dev/stdout", "invalid://result.json"},
},
want: []output.OutputConfig{
{
Key: "console",
},
},
err: fmt.Errorf("Unsupported output 'invalid': \nValid formats are: console://,html://PATH/TO/FILE.html,json://PATH/TO/FILE.json,plan://PATH/TO/FILE.json"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := parseOutputFlag(tt.args.out)
got, err := parseOutputFlags(tt.args.out)
if err != nil && err.Error() != tt.err.Error() {
t.Fatalf("got error = '%v', expected '%v'", err, tt.err)
}

View File

@ -24,7 +24,7 @@ type ScanOptions struct {
Detect bool
From []config.SupplierConfig
To string
Output output.OutputConfig
Output []output.OutputConfig
Filter *jmespath.JMESPath
Quiet bool
BackendOptions *backend.Options