diff --git a/cmd/grype/cli/commands/db_providers.go b/cmd/grype/cli/commands/db_providers.go index 0b4bb8adb11..3895b2fd4bc 100644 --- a/cmd/grype/cli/commands/db_providers.go +++ b/cmd/grype/cli/commands/db_providers.go @@ -40,7 +40,7 @@ func (d *dbProvidersOptions) AddFlags(flags clio.FlagSet) { func DBProviders(app clio.Application) *cobra.Command { opts := &dbProvidersOptions{ - Output: internal.JSONOutputFormat, + Output: internal.TableOutputFormat, } return app.SetupCommand(&cobra.Command{ @@ -54,7 +54,12 @@ func DBProviders(app clio.Application) *cobra.Command { } func runDBProviders(opts *dbProvidersOptions, app clio.Application) error { - providers, err := getDBProviders(app) + + metadataFileLocation, err := getMetadataFileLocation(app) + if err != nil { + return nil + } + providers, err := getDBProviders(*metadataFileLocation) if err != nil { return err } @@ -77,32 +82,38 @@ func runDBProviders(opts *dbProvidersOptions, app clio.Application) error { return nil } -func getDBProviders(app clio.Application) (*dbProviders, error) { +func getMetadataFileLocation(app clio.Application) (*string, error) { + dbCurator, err := distribution.NewCurator(dbOptionsDefault(app.ID()).DB.ToCuratorConfig()) if err != nil { return nil, err } - metadataFileLocation := dbCurator.Status().Location + location := dbCurator.Status().Location + + return &location, nil +} + +func getDBProviders(metadataFileLocation string) (*dbProviders, error) { metadataFile := path.Join(metadataFileLocation, metadataFileName) file, err := os.Open(metadataFile) if err != nil { if os.IsNotExist(err) { - return nil, fmt.Errorf("file not found: %v", err) + return nil, fmt.Errorf("file not found: %w", err) } - return nil, fmt.Errorf("error opening file: %v", err) + return nil, fmt.Errorf("error opening file: %w", err) } defer file.Close() var providers dbProviders fileBytes, err := io.ReadAll(file) if err != nil { - return nil, fmt.Errorf("error reading file: %v", err) + return nil, fmt.Errorf("error reading file: %w", err) } err = json.Unmarshal(fileBytes, &providers) if err != nil { - return nil, fmt.Errorf("cannot unmarshal providers: %v", err) + return nil, fmt.Errorf("cannot unmarshal providers: %w", err) } return &providers, nil @@ -117,6 +128,18 @@ func displayDBProvidersTable(providers []dbProviderMetadata, output io.Writer) { table := tablewriter.NewWriter(output) table.SetHeader([]string{"Name", "Last Successful Run"}) + table.SetHeaderLine(false) + table.SetBorder(false) + table.SetAutoWrapText(false) + table.SetAutoFormatHeaders(true) + table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) + table.SetAlignment(tablewriter.ALIGN_LEFT) + table.SetCenterSeparator("") + table.SetColumnSeparator("") + table.SetRowSeparator("") + table.SetTablePadding(" ") + table.SetNoWhiteSpace(true) + table.AppendBulk(rows) table.Render() } @@ -127,7 +150,7 @@ func displayDBProvidersJSON(providers *dbProviders, output io.Writer) error { encoder.SetIndent("", " ") err := encoder.Encode(providers) if err != nil { - return fmt.Errorf("cannot display json: %v", err) + return fmt.Errorf("cannot display json: %w", err) } return nil } diff --git a/cmd/grype/cli/commands/db_providers_test.go b/cmd/grype/cli/commands/db_providers_test.go new file mode 100644 index 00000000000..1c217a496e2 --- /dev/null +++ b/cmd/grype/cli/commands/db_providers_test.go @@ -0,0 +1,151 @@ +package commands + +import ( + "bytes" + "encoding/json" + "errors" + "os" + "reflect" + "testing" +) + +func TestGetDBProviders(t *testing.T) { + + tests := []struct { + name string + fileLocation string + expectedProviders dbProviders + expectedError error + }{ + { + name: "test provider metadata file", + fileLocation: "./test-fixtures", + expectedProviders: dbProviders{ + Providers: []dbProviderMetadata{ + dbProviderMetadata{ + Name: "provider1", + LastSuccessfulRun: "2024-10-16T01:33:16.844201Z", + }, + dbProviderMetadata{ + Name: "provider2", + LastSuccessfulRun: "2024-10-16T01:32:43.516596Z", + }, + }, + }, + expectedError: nil, + }, + { + name: "no metadata file found", + fileLocation: "./", + expectedProviders: dbProviders{}, + expectedError: os.ErrNotExist, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + providers, err := getDBProviders(test.fileLocation) + if err != nil { + if errors.Is(err, test.expectedError) { + return + } + t.Errorf("getDBProviders() expected list of providers, got error: %v", err) + return + } + if !reflect.DeepEqual(*providers, test.expectedProviders) { + t.Error("getDBProviders() providers comparison failed, got error") + } + }) + } + +} + +func TestDisplayDBProvidersTable(t *testing.T) { + tests := []struct { + name string + providers dbProviders + expectedOutput string + }{ + { + name: "display providers table", + providers: dbProviders{ + Providers: []dbProviderMetadata{ + dbProviderMetadata{ + Name: "provider1", + LastSuccessfulRun: "2024-10-16T01:33:16.844201Z", + }, + dbProviderMetadata{ + Name: "provider2", + LastSuccessfulRun: "2024-10-16T01:32:43.516596Z", + }, + }, + }, + expectedOutput: "NAME LAST SUCCESSFUL RUN \nprovider1 2024-10-16T01:33:16.844201Z \nprovider2 2024-10-16T01:32:43.516596Z \n", + }, + { + name: "empty list of providers", + providers: dbProviders{ + Providers: []dbProviderMetadata{}, + }, + expectedOutput: "NAME LAST SUCCESSFUL RUN \n", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + + var out bytes.Buffer + displayDBProvidersTable(test.providers.Providers, &out) + outputString := out.String() + if outputString != test.expectedOutput { + t.Errorf("displayDBProvidersTable() = %v, want %v", out.String(), test.expectedOutput) + } + }) + } +} + +func TestDisplayDBProvidersJSON(t *testing.T) { + tests := []struct { + name string + providers dbProviders + }{ + + { + name: "display providers table", + providers: dbProviders{ + Providers: []dbProviderMetadata{ + dbProviderMetadata{ + Name: "provider1", + LastSuccessfulRun: "2024-10-16T01:33:16.844201Z", + }, + dbProviderMetadata{ + Name: "provider2", + LastSuccessfulRun: "2024-10-16T01:32:43.516596Z", + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + + var out bytes.Buffer + err := displayDBProvidersJSON(&test.providers, &out) + if err != nil { + t.Error(err) + } + var providers dbProviders + + err = json.Unmarshal(out.Bytes(), &providers) + if err != nil { + t.Error(err) + } + + if !reflect.DeepEqual(providers, test.providers) { + t.Error("DBProvidersJSON() providers comparison failed, got error") + } + + }) + } +} diff --git a/cmd/grype/cli/commands/test-fixtures/provider-metadata.json b/cmd/grype/cli/commands/test-fixtures/provider-metadata.json new file mode 100644 index 00000000000..015f6914e41 --- /dev/null +++ b/cmd/grype/cli/commands/test-fixtures/provider-metadata.json @@ -0,0 +1,12 @@ +{ + "providers": [ + { + "name": "provider1", + "lastSuccessfulRun": "2024-10-16T01:33:16.844201Z" + }, + { + "name": "provider2", + "lastSuccessfulRun": "2024-10-16T01:32:43.516596Z" + } + ] +} \ No newline at end of file diff --git a/test/cli/db_providers_test.go b/test/cli/db_providers_test.go index b1c142a358a..3437aa8860a 100644 --- a/test/cli/db_providers_test.go +++ b/test/cli/db_providers_test.go @@ -16,8 +16,9 @@ func TestDBProviders(t *testing.T) { name: "db providers command", args: []string{"db", "providers"}, assertions: []traitAssertion{ - assertInOutput("providers"), + assertInOutput("LAST SUCCESSFUL RUN"), assertNoStderr, + assertTableReport, }, }, { @@ -34,6 +35,7 @@ func TestDBProviders(t *testing.T) { assertions: []traitAssertion{ assertInOutput("LAST SUCCESSFUL RUN"), assertNoStderr, + assertTableReport, }, }, { @@ -42,6 +44,7 @@ func TestDBProviders(t *testing.T) { assertions: []traitAssertion{ assertInOutput("providers"), assertNoStderr, + assertJsonReport, }, }, } diff --git a/test/cli/trait_assertions_test.go b/test/cli/trait_assertions_test.go index 8aa14b09850..11e30f18ac6 100644 --- a/test/cli/trait_assertions_test.go +++ b/test/cli/trait_assertions_test.go @@ -1,6 +1,7 @@ package cli import ( + "encoding/json" "strings" "testing" @@ -70,3 +71,19 @@ func assertNotInOutput(notWanted string) traitAssertion { } } } + +func assertJsonReport(tb testing.TB, stdout, _ string, _ int) { + tb.Helper() + var data interface{} + + if err := json.Unmarshal([]byte(stdout), &data); err != nil { + tb.Errorf("expected to find a JSON report, but was unmarshalable: %+v", err) + } +} + +func assertTableReport(tb testing.TB, stdout, _ string, _ int) { + tb.Helper() + if !strings.Contains(stdout, "NAME") || !strings.Contains(stdout, "LAST SUCCESSFUL RUN") { + tb.Errorf("expected to find a table report, but did not") + } +}