diff --git a/cmd/main.go b/cmd/main.go index 9f9be2fcd..f9c128bf7 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -91,6 +91,7 @@ func main() { accessManagementWrapper := wrappers.NewAccessManagementHTTPWrapper(accessManagementPath) byorWrapper := wrappers.NewByorHTTPWrapper(byorPath) containerResolverWrapper := wrappers.NewContainerResolverWrapper() + enginesWrapper := wrappers.NewHttpEnginesWrapper() astCli := commands.NewAstCLI( applicationsWrapper, @@ -127,6 +128,7 @@ func main() { accessManagementWrapper, byorWrapper, containerResolverWrapper, + enginesWrapper, ) exitListener() err = astCli.Execute() diff --git a/internal/commands/engines.go b/internal/commands/engines.go new file mode 100644 index 000000000..986ae6542 --- /dev/null +++ b/internal/commands/engines.go @@ -0,0 +1,98 @@ +package commands + +import ( + "github.com/MakeNowJust/heredoc" + commonParams "github.com/checkmarx/ast-cli/internal/params" + "github.com/checkmarx/ast-cli/internal/wrappers" + "github.com/spf13/cobra" +) + +type EngineAPIDetails struct { + EngineId string `format:"name:Engine ID"` + EngineName string `format:"name:Engine Name"` + ApiName string `format:"name:Api Name"` + ApiURL string `format:"name:Api URL"` + Description string `format:"name:Description"` +} + +func NewEnginesCommand( + enginesWrapper wrappers.EnginesWrapper, +) *cobra.Command { + enginesCmd := &cobra.Command{ + Use: "engines", + Short: "Manage engines", + Long: "The engines command enables the ability to manage engines in Checkmarx One.", + Annotations: map[string]string{ + "command:doc": heredoc.Doc( + ``, + ), + }, + } + + getEngineListCmd := getEnginesListSubCommand( + enginesWrapper, + ) + enginesCmd.AddCommand( + getEngineListCmd, + ) + return enginesCmd +} + +func getEnginesListSubCommand( + enginesWrapper wrappers.EnginesWrapper, +) *cobra.Command { + getEngineListCmd := &cobra.Command{ + Use: "list-api", + Short: "Get list of all engines", + Long: "The list-api command is used to get list of all engine apis in Checkmarx One.", + Example: heredoc.Doc( + ` + $ cx engines list-api --engine-name --output-format + `, + ), + Annotations: map[string]string{ + "command:doc": heredoc.Doc( + ``, + ), + }, + RunE: runEngineGetListCommand( + enginesWrapper, + ), + } + getEngineListCmd.PersistentFlags().String(commonParams.EngineName, "", "Filters Engines by EngineName") + getEngineListCmd.PersistentFlags().String(commonParams.OutputFormat, "table", "Show Engine Details based on Output Format") + return getEngineListCmd +} + +func runEngineGetListCommand( + enginesWrapper wrappers.EnginesWrapper, +) func(cmd *cobra.Command, args []string) error { + return func(cmd *cobra.Command, args []string) error { + var engineApiResponse *wrappers.EngineListResponseModel + engineName, _ := cmd.Flags().GetString(commonParams.EngineName) + engineApiResponse = enginesWrapper.Get(engineName) + + if engineApiResponse != nil { + views := ShowEngineList(engineApiResponse.Engines) + return printByOutputFormat(cmd, views) + } + return nil + } +} + +func ShowEngineList(engines []wrappers.EngineList) []*EngineAPIDetails { + + views := make([]*EngineAPIDetails, 0) + for i := 0; i < len(engines); i++ { + for j := 0; j < len(engines[i].APIs); j++ { + views = append(views, &EngineAPIDetails{ + EngineId: engines[i].EngineId, + EngineName: engines[i].EngineName, + ApiName: engines[i].APIs[j].ApiName, + ApiURL: engines[i].APIs[j].ApiURL, + Description: engines[i].APIs[j].Description, + }) + } + } + return views +} diff --git a/internal/commands/engines_test.go b/internal/commands/engines_test.go new file mode 100644 index 000000000..f1ed066e8 --- /dev/null +++ b/internal/commands/engines_test.go @@ -0,0 +1,31 @@ +//go:build !integration + +package commands + +import ( + "testing" +) + +func TestEnginesHelp(t *testing.T) { + execCmdNilAssertion(t, "help", "engines") +} + +func TestEnginesSub(t *testing.T) { + execCmdNilAssertion(t, "engines") +} + +func TestGetAllEngineAPIs(t *testing.T) { + execCmdNilAssertion(t, "engines", "list-api") +} + +func TestGetSASTEngineAPIs(t *testing.T) { + execCmdNilAssertion(t, "engines", "list-api", "--engine-name", "sast") +} + +func TestGetSCAEngineAPIs(t *testing.T) { + execCmdNilAssertion(t, "engines", "list-api", "--engine-name", "sast") +} + +func TestGetEngineAPIsWithNonExistFlag(t *testing.T) { + execCmdNilAssertion(t, "engines", "list-api", "--engine-name", "abc") +} diff --git a/internal/commands/root.go b/internal/commands/root.go index cf5bc0462..492c6ad85 100644 --- a/internal/commands/root.go +++ b/internal/commands/root.go @@ -55,6 +55,7 @@ func NewAstCLI( accessManagementWrapper wrappers.AccessManagementWrapper, byorWrapper wrappers.ByorWrapper, containerResolverWrapper wrappers.ContainerResolverWrapper, + enginesWrapper wrappers.EnginesWrapper, ) *cobra.Command { // Create the root rootCmd := &cobra.Command{ @@ -202,7 +203,7 @@ func NewAstCLI( chatCmd := NewChatCommand(chatWrapper, tenantWrapper) hooksCmd := NewHooksCommand(jwtWrapper) - + enginesCmd := NewEnginesCommand(enginesWrapper) rootCmd.AddCommand( scanCmd, projectCmd, @@ -214,6 +215,7 @@ func NewAstCLI( configCmd, chatCmd, hooksCmd, + enginesCmd, ) rootCmd.SilenceUsage = true @@ -323,3 +325,8 @@ func printByScanInfoFormat(cmd *cobra.Command, view interface{}) error { f, _ := cmd.Flags().GetString(params.ScanInfoFormatFlag) return printer.Print(cmd.OutOrStdout(), view, f) } + +func printByOutputFormat(cmd *cobra.Command, view interface{}) error { + f, _ := cmd.Flags().GetString(params.OutputFormat) + return printer.Print(cmd.OutOrStdout(), view, f) +} diff --git a/internal/commands/root_test.go b/internal/commands/root_test.go index 7006b0e06..39c340e0c 100644 --- a/internal/commands/root_test.go +++ b/internal/commands/root_test.go @@ -68,6 +68,7 @@ func createASTTestCommand() *cobra.Command { byorWrapper := &mock.ByorMockWrapper{} containerResolverMockWrapper := &mock.ContainerResolverMockWrapper{} customStatesMockWrapper := &mock.CustomStatesMockWrapper{} + enginesWrapper := &wrappers.EngineHTTPWrapper{} return NewAstCLI( applicationWrapper, scansMockWrapper, @@ -103,6 +104,7 @@ func createASTTestCommand() *cobra.Command { accessManagementWrapper, byorWrapper, containerResolverMockWrapper, + enginesWrapper, ) } diff --git a/internal/params/flags.go b/internal/params/flags.go index a6b6a6c9e..4543339d6 100644 --- a/internal/params/flags.go +++ b/internal/params/flags.go @@ -217,6 +217,8 @@ const ( ContainersImageTagFilterFlag = "containers-image-tag-filter" ContainersPackageFilterFlag = "containers-package-filter" ContainersExcludeNonFinalStagesFlag = "containers-exclude-non-final-stages" + EngineName = "engine-name" + OutputFormat = "output-format" ) // Parameter values diff --git a/internal/wrappers/engine.go b/internal/wrappers/engine.go new file mode 100644 index 000000000..f28101f67 --- /dev/null +++ b/internal/wrappers/engine.go @@ -0,0 +1,20 @@ +package wrappers + +type EngineListResponseModel struct { + Engines []EngineList `json:"engines"` +} +type EngineList struct { + EngineId string `json:"engine_id"` // [git|upload] + EngineName string `json:"engine_name"` // One of [GitProjectHandler|ScanHandler] + APIs []EngineAPIs `json:"apis"` +} + +type EngineAPIs struct { + ApiURL string `json:"api-url"` // [git|upload] + ApiName string `json:"api-name"` // One of [GitProjectHandler|ScanHandler] + Description string `json:"api-description"` +} + +type EnginesWrapper interface { + Get(engineName string) *EngineListResponseModel +} diff --git a/internal/wrappers/engine_http.go b/internal/wrappers/engine_http.go new file mode 100644 index 000000000..580cb0240 --- /dev/null +++ b/internal/wrappers/engine_http.go @@ -0,0 +1,74 @@ +package wrappers + +import ( + "strings" +) + +type EngineHTTPWrapper struct { +} + +func (e *EngineHTTPWrapper) Get(engineName string) *EngineListResponseModel { + return e.getEngineList(engineName) +} + +func NewHttpEnginesWrapper() *EngineHTTPWrapper { + return &EngineHTTPWrapper{} +} + +func (e *EngineHTTPWrapper) getEngineList(engineName string) *EngineListResponseModel { + allEngines := &EngineListResponseModel{ + Engines: []EngineList{ + { + EngineId: "1", + EngineName: "SAST", + APIs: []EngineAPIs{ + { + ApiURL: "https://{HostName}/api/v1/scans", + ApiName: "Get -> SAST Current Scans", + Description: "Gets List of current Scans", + }, + { + ApiURL: "https://{HostName}/api/v1/scans/{id}/status", + ApiName: "Get -> SAST Scans status", + Description: "Retrieve that current status of Scan", + }, + { + ApiURL: "https://{HostName}/api/v1/scans/{id}/results", + ApiName: "Get -> SAST Scans results", + Description: "Retrieve scan results", + }, + }, + }, + { + EngineId: "2", + EngineName: "SCA", + APIs: []EngineAPIs{ + { + ApiURL: "https://{HOST_NAME}/api/scans/{scanId}", + ApiName: "Get -> SCA scan details", + Description: "Retriever SCA scan details and status", + }, + { + ApiURL: "https://{HOST_NAME}/api/scans", + ApiName: "Post -> Create a new SCA scan", + Description: "Create new scan and get the vulnerabilities in a packages", + }, + }, + }, + }, + } + + if engineName != "" { + var filteredEngines []EngineList + for _, engine := range allEngines.Engines { + if strings.ToLower(engine.EngineName) == strings.ToLower(engineName) { + filteredEngines = append(filteredEngines, engine) + } + } + + return &EngineListResponseModel{ + Engines: filteredEngines, + } + } + return allEngines +} diff --git a/test/integration/engines_test.go b/test/integration/engines_test.go new file mode 100644 index 000000000..aea04328a --- /dev/null +++ b/test/integration/engines_test.go @@ -0,0 +1,71 @@ +//go:build integration + +package integration + +import ( + "gotest.tools/assert" + "testing" +) + +func TestGetAllEnginesApiList(t *testing.T) { + args := []string{ + "engines", "list-api", + } + + err, _ := executeCommand(t, args...) + assert.NilError(t, err) +} + +func TestEnginesApiList_HelpSuccess(t *testing.T) { + args := []string{ + "engines", + } + + err, _ := executeCommand(t, args...) + assert.NilError(t, err) +} + +func TestGetSASTEnginesApiList_Success(t *testing.T) { + args := []string{ + "engines", "list-api", "--engine-name", "SAST", + } + + err, _ := executeCommand(t, args...) + assert.NilError(t, err) +} + +func TestGetSCAEnginesApiList_Success(t *testing.T) { + args := []string{ + "engines", "list-api", "--engine-name", "SCA", + } + + err, _ := executeCommand(t, args...) + assert.NilError(t, err) +} + +func TestEnginesApiListInvalidFlagDetails_Error(t *testing.T) { + args := []string{ + "engines", "list-api", "--engine-name", "xyz", + } + + err, _ := executeCommand(t, args...) + assert.NilError(t, err) +} + +func TestEnginesApiListInTableFormat_Success(t *testing.T) { + args := []string{ + "engines", "list-api", "--output-format", "table", "--engine-name", "SAST", + } + + err, _ := executeCommand(t, args...) + assert.NilError(t, err) +} + +func TestEnginesApiListInJsonFormat_Success(t *testing.T) { + args := []string{ + "engines", "list-api", "--output-format", "json", "--engine-name", "SAST", + } + + err, _ := executeCommand(t, args...) + assert.NilError(t, err) +} diff --git a/test/integration/util_command.go b/test/integration/util_command.go index 4370c053c..dc3082f12 100644 --- a/test/integration/util_command.go +++ b/test/integration/util_command.go @@ -123,6 +123,7 @@ func createASTIntegrationTestCommand(t *testing.T) *cobra.Command { accessManagementWrapper := wrappers.NewAccessManagementHTTPWrapper(accessManagementPath) ByorWrapper := wrappers.NewByorHTTPWrapper(byorPath) containerResolverWrapper := wrappers.NewContainerResolverWrapper() + enginesWrapper := wrappers.NewHttpEnginesWrapper() astCli := commands.NewAstCLI( applicationsWrapper, @@ -159,6 +160,7 @@ func createASTIntegrationTestCommand(t *testing.T) *cobra.Command { accessManagementWrapper, ByorWrapper, containerResolverWrapper, + enginesWrapper, ) return astCli }