Skip to content

Commit

Permalink
feat(cli): Add 'subtype' filter flag to 'lacework report-definitions … (
Browse files Browse the repository at this point in the history
#1163)

* feat(cli): Add 'subtype' filter flag to 'lacework report-definitions list' cmd

Signed-off-by: Darren Murray <[email protected]>

* refactor: address code review comments

Signed-off-by: Darren Murray <[email protected]>

---------

Signed-off-by: Darren Murray <[email protected]>
  • Loading branch information
dmurray-lacework authored Feb 28, 2023
1 parent 30b5144 commit d256376
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 1 deletion.
2 changes: 2 additions & 0 deletions api/reports_definitions.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ func NewReportDefinition(cfg ReportDefinitionConfig) ReportDefinition {
}
}

var ReportDefinitionSubtypes = []string{"AWS", "Azure", "GCP"}

type ReportDefinitionConfig struct {
ReportName string `json:"reportName"`
ReportType string `json:"reportType"`
Expand Down
33 changes: 33 additions & 0 deletions cli/cmd/report_definitions.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,18 @@ import (
"strings"

"github.com/lacework/go-sdk/api"
"github.com/lacework/go-sdk/internal/array"
"github.com/olekukonko/tablewriter"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)

var (
reportDefinitionsCmdState = struct {
// filter report definitions by subtype. 'AWS', 'GCP' or 'Azure'
SubType string
}{}

// report-definitions command is used to manage lacework report definitions
reportDefinitionsCommand = &cobra.Command{
Use: "report-definition",
Expand All @@ -44,6 +50,12 @@ var (
Short: "List all report definitions",
Long: "List all report definitions configured in your Lacework account.",
Args: cobra.NoArgs,
PreRunE: func(_ *cobra.Command, _ []string) error {
if reportDefinitionsCmdState.SubType != "" && !array.ContainsStr(api.ReportDefinitionSubtypes, reportDefinitionsCmdState.SubType) {
return errors.Errorf("'%s' is not valid. Report definitions subtype can be %s", reportDefinitionsCmdState.SubType, api.ReportDefinitionSubtypes)
}
return nil
},
RunE: func(_ *cobra.Command, _ []string) error {
cli.StartProgress(" Fetching report definitions...")
reportDefinitions, err := cli.LwApi.V2.ReportDefinitions.List()
Expand All @@ -56,6 +68,12 @@ var (
cli.OutputHuman("There are no report definitions configured in your account.\n")
return nil
}

// filter definitions by subtype
if reportDefinitionsCmdState.SubType != "" {
filterReportDefinitions(&reportDefinitions)
}

if cli.JSONOutput() {
return cli.OutputJSON(reportDefinitions)
}
Expand Down Expand Up @@ -122,6 +140,16 @@ var (
}
)

func filterReportDefinitions(reportDefinitions *api.ReportDefinitionsResponse) {
var filteredDefinitions []api.ReportDefinition
for _, rd := range reportDefinitions.Data {
if rd.SubReportType == reportDefinitionsCmdState.SubType {
filteredDefinitions = append(filteredDefinitions, rd)
}
}
reportDefinitions.Data = filteredDefinitions
}

func init() {
// add the report-definition command
rootCmd.AddCommand(reportDefinitionsCommand)
Expand All @@ -130,6 +158,11 @@ func init() {
reportDefinitionsCommand.AddCommand(reportDefinitionsListCommand)
reportDefinitionsCommand.AddCommand(reportDefinitionsShowCommand)
reportDefinitionsCommand.AddCommand(reportDefinitionsDeleteCommand)

// add flags to report-definition commands
reportDefinitionsListCommand.Flags().StringVar(&reportDefinitionsCmdState.SubType,
"subtype", "", "filter report definitions by subtype. 'AWS', 'GCP' or 'Azure'",
)
}

func buildReportDefinitionDetailsTable(definition api.ReportDefinition) string {
Expand Down
88 changes: 88 additions & 0 deletions cli/cmd/report_definitions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,48 @@ func TestBuildReportDefinitions(t *testing.T) {
assert.Equal(t, reportDetails, reportDefinitionOutput)
}

func TestFilterReportDefinitionsWithSubType(t *testing.T) {
defer func() { reportDefinitionsCmdState.SubType = "" }()
var reportDefinitionResponse api.ReportDefinitionsResponse
reportDefinitionResponse.Data = []api.ReportDefinition{mockReportDefinition, mockReportDefinitionGCP, mockReportDefinitionAzure}

reportDefinitionsTableTest := []struct {
Name string
Input api.ReportDefinitionsResponse
Expected []api.ReportDefinition
Cloud string
}{
{
Name: "Test Filter AWS report Definitions",
Input: reportDefinitionResponse,
Expected: []api.ReportDefinition{mockReportDefinition},
Cloud: "AWS",
},
{
Name: "Test Filter GCP report Definitions",
Input: reportDefinitionResponse,
Expected: []api.ReportDefinition{mockReportDefinitionGCP},
Cloud: "GCP",
},
{
Name: "Test Filter Azure report Definitions",
Input: reportDefinitionResponse,
Expected: []api.ReportDefinition{mockReportDefinitionAzure},
Cloud: "Azure",
},
}

for _, rdtt := range reportDefinitionsTableTest {
t.Run(rdtt.Name, func(t *testing.T) {
reportDefinitionsCmdState.SubType = rdtt.Cloud
filterReportDefinitions(&rdtt.Input)
assert.Len(t, rdtt.Input.Data, 1)
assert.Equal(t, rdtt.Input.Data, rdtt.Expected)
})
}

}

var created, _ = time.Parse(time.RFC3339, "2022-09-09T10:35:16Z")

var (
Expand All @@ -57,6 +99,52 @@ var (
CreatedTime: &created,
Enabled: 1,
}
mockReportDefinitionGCP = api.ReportDefinition{
ReportDefinitionGuid: "EXAMPLE_GUID",
ReportName: "My Custom Report",
DisplayName: "My Custom Report Display",
ReportType: "Compliance",
SubReportType: "GCP",
ReportDefinitionDetails: api.ReportDefinitionDetails{
Sections: []api.ReportDefinitionSection{{
Category: "1.0.0",
Title: "Example Section",
Policies: []string{"lacework-global-22", "lacework-global-78"},
}},
},
Props: api.ReportDefinitionProps{
Engine: "lpp",
},
DistributionType: "pdf",
Frequency: "daily",
Version: 2,
CreatedBy: "SYSTEM",
CreatedTime: &created,
Enabled: 1,
}
mockReportDefinitionAzure = api.ReportDefinition{
ReportDefinitionGuid: "EXAMPLE_GUID",
ReportName: "My Custom Report",
DisplayName: "My Custom Report Display",
ReportType: "Compliance",
SubReportType: "Azure",
ReportDefinitionDetails: api.ReportDefinitionDetails{
Sections: []api.ReportDefinitionSection{{
Category: "1.0.0",
Title: "Example Section",
Policies: []string{"lacework-global-22", "lacework-global-78"},
}},
},
Props: api.ReportDefinitionProps{
Engine: "lpp",
},
DistributionType: "pdf",
Frequency: "daily",
Version: 2,
CreatedBy: "SYSTEM",
CreatedTime: &created,
Enabled: 1,
}

reportDefinitionOutput = ` REPORT DEFINITION DETAILS
-----------------------------------------------------
Expand Down
34 changes: 34 additions & 0 deletions integration/report_definitions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,22 @@ func TestReportDefintionsList(t *testing.T) {
assert.Equal(t, 0, exitcode, "EXITCODE is not the expected one")
}

func TestReportDefintionsListWithSubtype(t *testing.T) {
out, err, exitcode := LaceworkCLIWithTOMLConfig("report-definitions", "list", "--subtype", "AWS")
// assert response contains table headers
assert.Contains(t, out.String(), "GUID")
assert.Contains(t, out.String(), "NAME")
assert.Contains(t, out.String(), "TYPE")
assert.Contains(t, out.String(), "SUB-TYPE")

assert.Contains(t, out.String(), "AWS")
assert.NotContains(t, out.String(), "GCP")
assert.NotContains(t, out.String(), "AZURE")

assert.Empty(t, err.String(), "STDERR should be empty")
assert.Equal(t, 0, exitcode, "EXITCODE is not the expected one")
}

func TestReportDefintionsListJson(t *testing.T) {
out, err, exitcode := LaceworkCLIWithTOMLConfig("report-definitions", "list", "--json")
// assert response contains json fields
Expand All @@ -35,6 +51,24 @@ func TestReportDefintionsListJson(t *testing.T) {
assert.Equal(t, 0, exitcode, "EXITCODE is not the expected one")
}

func TestReportDefintionsListJsonWithSubtype(t *testing.T) {
out, err, exitcode := LaceworkCLIWithTOMLConfig("report-definitions", "list", "--json", "--subtype", "GCP")
// assert response contains json fields
assert.Contains(t, out.String(), "\"data\"")
assert.Contains(t, out.String(), "\"createdBy\"")
assert.Contains(t, out.String(), "\"displayName\"")
assert.Contains(t, out.String(), "\"reportDefinition\"")
assert.Contains(t, out.String(), "\"category\"")
assert.Contains(t, out.String(), "\"policies\"")

assert.Contains(t, out.String(), "\"GCP\"")
assert.NotContains(t, out.String(), "\"Azure\"")
assert.NotContains(t, out.String(), "\"AWS\"")

assert.Empty(t, err.String(), "STDERR should be empty")
assert.Equal(t, 0, exitcode, "EXITCODE is not the expected one")
}

func TestReportDefintionsShow(t *testing.T) {
if testReportDefinitionID == "" {
t.Skip("skipping test. No report definition found")
Expand Down
3 changes: 2 additions & 1 deletion integration/test_resources/help/report-definition_list
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ Aliases:
list, ls

Flags:
-h, --help help for list
-h, --help help for list
--subtype string filter report definitions by subtype. 'AWS', 'GCP' or 'Azure'

Global Flags:
-a, --account string account subdomain of URL (i.e. <ACCOUNT>.lacework.net)
Expand Down

0 comments on commit d256376

Please sign in to comment.