From e146ff09af80f14d32a49aedf8f1ed60d72ff66d Mon Sep 17 00:00:00 2001 From: Tiago Baptista <92083272+tiagobcx@users.noreply.github.com> Date: Mon, 13 Nov 2023 11:42:45 +0000 Subject: [PATCH] merge request decoration in gitlab (#600) --- .github/workflows/ci.yml | 5 ++ cmd/main.go | 3 +- internal/commands/util/pr.go | 90 +++++++++++++++++++++++++++++-- internal/commands/util/pr_test.go | 8 +++ internal/params/binds.go | 1 + internal/params/envs.go | 1 + internal/params/flags.go | 16 +++--- internal/params/keys.go | 1 + internal/wrappers/mock/pr-mock.go | 4 ++ internal/wrappers/pr-http.go | 22 +++++++- internal/wrappers/pr.go | 10 ++++ test/integration/pr_test.go | 58 ++++++++++++++++++-- test/integration/util_command.go | 3 +- 13 files changed, 205 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 34798a489..e911e4a35 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,6 +75,11 @@ jobs: PR_GITHUB_NAMESPACE: "checkmarx" PR_GITHUB_REPO_NAME: "ast-cli" PR_GITHUB_NUMBER: 418 + PR_GITLAB_TOKEN : ${{ secrets.PR_GITLAB_TOKEN }} + PR_GITLAB_NAMESPACE: "tiagobcx" + PR_GITLAB_REPO_NAME: "testProject" + PR_GITLAB_PROJECT_ID: 40227565 + PR_GITLAB_IID: 19 AZURE_ORG: ${{ secrets.AZURE_ORG }} AZURE_PROJECT: ${{ secrets.AZURE_PROJECT }} AZURE_REPOS: ${{ secrets.AZURE_REPOS }} diff --git a/cmd/main.go b/cmd/main.go index 756a2902d..72ad6852d 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -40,6 +40,7 @@ func main() { codebashing := viper.GetString(params.CodeBashingPathKey) bfl := viper.GetString(params.BflPathKey) prDecorationGithubPath := viper.GetString(params.PRDecorationGithubPathKey) + prDecorationGitlabPath := viper.GetString(params.PRDecorationGitlabPathKey) descriptionsPath := viper.GetString(params.DescriptionsPathKey) tenantConfigurationPath := viper.GetString(params.TenantConfigurationPathKey) resultsPdfPath := viper.GetString(params.ResultsPdfReportPathKey) @@ -66,7 +67,7 @@ func main() { bitBucketServerWrapper := bitbucketserver.NewBitbucketServerWrapper() gitLabWrapper := wrappers.NewGitLabWrapper() bflWrapper := wrappers.NewBflHTTPWrapper(bfl) - prWrapper := wrappers.NewHTTPPRWrapper(prDecorationGithubPath) + prWrapper := wrappers.NewHTTPPRWrapper(prDecorationGithubPath, prDecorationGitlabPath) learnMoreWrapper := wrappers.NewHTTPLearnMoreWrapper(descriptionsPath) tenantConfigurationWrapper := wrappers.NewHTTPTenantConfigurationWrapper(tenantConfigurationPath) jwtWrapper := wrappers.NewJwtWrapper() diff --git a/internal/commands/util/pr.go b/internal/commands/util/pr.go index 68d49b1ed..aeaf1bc7b 100644 --- a/internal/commands/util/pr.go +++ b/internal/commands/util/pr.go @@ -1,6 +1,8 @@ package util import ( + "fmt" + "github.com/MakeNowJust/heredoc" "github.com/checkmarx/ast-cli/internal/logger" "github.com/checkmarx/ast-cli/internal/params" @@ -11,8 +13,9 @@ import ( ) const ( - failedCreatingPrDecoration = "Failed creating PR Decoration" - errorCodeFormat = "%s: CODE: %d, %s\n" + failedCreatingGithubPrDecoration = "Failed creating github PR Decoration" + failedCreatingGitlabPrDecoration = "Failed creating gitlab MR Decoration" + errorCodeFormat = "%s: CODE: %d, %s\n" ) func NewPRDecorationCommand(prWrapper wrappers.PRWrapper) *cobra.Command { @@ -27,8 +30,10 @@ func NewPRDecorationCommand(prWrapper wrappers.PRWrapper) *cobra.Command { } prDecorationGithub := PRDecorationGithub(prWrapper) + prDecorationGitlab := PRDecorationGitlab(prWrapper) cmd.AddCommand(prDecorationGithub) + cmd.AddCommand(prDecorationGitlab) return cmd } @@ -54,8 +59,8 @@ func PRDecorationGithub(prWrapper wrappers.PRWrapper) *cobra.Command { prDecorationGithub.Flags().String(params.ScanIDFlag, "", "Scan ID to retrieve results from") prDecorationGithub.Flags().String(params.SCMTokenFlag, "", params.GithubTokenUsage) - prDecorationGithub.Flags().String(params.NamespaceFlag, "", params.NamespaceFlagUsage) - prDecorationGithub.Flags().String(params.RepoNameFlag, "", params.RepoNameFlagUsage) + prDecorationGithub.Flags().String(params.NamespaceFlag, "", fmt.Sprintf(params.NamespaceFlagUsage, "Github")) + prDecorationGithub.Flags().String(params.RepoNameFlag, "", fmt.Sprintf(params.RepoNameFlagUsage, "Github")) prDecorationGithub.Flags().Int(params.PRNumberFlag, 0, params.PRNumberFlagUsage) // Set the value for token to mask the scm token @@ -71,6 +76,47 @@ func PRDecorationGithub(prWrapper wrappers.PRWrapper) *cobra.Command { return prDecorationGithub } +func PRDecorationGitlab(prWrapper wrappers.PRWrapper) *cobra.Command { + prDecorationGitlab := &cobra.Command{ + Use: "gitlab", + Short: "Decorate gitlab PR with vulnerabilities", + Long: "Decorate gitlab PR with vulnerabilities", + Example: heredoc.Doc( + ` + $ cx utils pr gitlab --scan-id --token --namespace --repo-name + --iid --gitlab-project + `, + ), + Annotations: map[string]string{ + "command:doc": heredoc.Doc( + ` + `, + ), + }, + RunE: runPRDecorationGitlab(prWrapper), + } + + prDecorationGitlab.Flags().String(params.ScanIDFlag, "", "Scan ID to retrieve results from") + prDecorationGitlab.Flags().String(params.SCMTokenFlag, "", params.GitLabTokenUsage) + prDecorationGitlab.Flags().String(params.NamespaceFlag, "", fmt.Sprintf(params.NamespaceFlagUsage, "Gitlab")) + prDecorationGitlab.Flags().String(params.RepoNameFlag, "", fmt.Sprintf(params.RepoNameFlagUsage, "Gitlab")) + prDecorationGitlab.Flags().Int(params.PRIidFlag, 0, params.PRIidFlagUsage) + prDecorationGitlab.Flags().Int(params.PRGitlabProjectFlag, 0, params.PRGitlabProjectFlagUsage) + + // Set the value for token to mask the scm token + _ = viper.BindPFlag(params.SCMTokenFlag, prDecorationGitlab.Flags().Lookup(params.SCMTokenFlag)) + + // mark all fields as required\ + _ = prDecorationGitlab.MarkFlagRequired(params.ScanIDFlag) + _ = prDecorationGitlab.MarkFlagRequired(params.SCMTokenFlag) + _ = prDecorationGitlab.MarkFlagRequired(params.NamespaceFlag) + _ = prDecorationGitlab.MarkFlagRequired(params.RepoNameFlag) + _ = prDecorationGitlab.MarkFlagRequired(params.PRIidFlag) + _ = prDecorationGitlab.MarkFlagRequired(params.PRGitlabProjectFlag) + + return prDecorationGitlab +} + func runPRDecoration(prWrapper wrappers.PRWrapper) func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error { scanID, _ := cmd.Flags().GetString(params.ScanIDFlag) @@ -94,7 +140,41 @@ func runPRDecoration(prWrapper wrappers.PRWrapper) func(cmd *cobra.Command, args } if errorModel != nil { - return errors.Errorf(errorCodeFormat, failedCreatingPrDecoration, errorModel.Code, errorModel.Message) + return errors.Errorf(errorCodeFormat, failedCreatingGithubPrDecoration, errorModel.Code, errorModel.Message) + } + + logger.Print(prResponse) + + return nil + } +} + +func runPRDecorationGitlab(prWrapper wrappers.PRWrapper) func(cmd *cobra.Command, args []string) error { + return func(cmd *cobra.Command, args []string) error { + scanID, _ := cmd.Flags().GetString(params.ScanIDFlag) + scmTokenFlag, _ := cmd.Flags().GetString(params.SCMTokenFlag) + namespaceFlag, _ := cmd.Flags().GetString(params.NamespaceFlag) + repoNameFlag, _ := cmd.Flags().GetString(params.RepoNameFlag) + iIDFlag, _ := cmd.Flags().GetInt(params.PRIidFlag) + gitlabProjectIDFlag, _ := cmd.Flags().GetInt(params.PRGitlabProjectFlag) + + prModel := &wrappers.GitlabPRModel{ + ScanID: scanID, + ScmToken: scmTokenFlag, + Namespace: namespaceFlag, + RepoName: repoNameFlag, + IiD: iIDFlag, + GitlabProjectID: gitlabProjectIDFlag, + } + + prResponse, errorModel, err := prWrapper.PostGitlabPRDecoration(prModel) + + if err != nil { + return err + } + + if errorModel != nil { + return errors.Errorf(errorCodeFormat, failedCreatingGitlabPrDecoration, errorModel.Code, errorModel.Message) } logger.Print(prResponse) diff --git a/internal/commands/util/pr_test.go b/internal/commands/util/pr_test.go index 2a68ebc1e..d731dd53c 100644 --- a/internal/commands/util/pr_test.go +++ b/internal/commands/util/pr_test.go @@ -13,3 +13,11 @@ func TestNewPRDecorationCommandMustExist(t *testing.T) { err := cmd.Execute() assert.ErrorContains(t, err, "scan-id") } + +func TestNewMRDecorationCommandMustExist(t *testing.T) { + cmd := PRDecorationGitlab(nil) + assert.Assert(t, cmd != nil, "MR decoration command must exist") + + err := cmd.Execute() + assert.ErrorContains(t, err, "scan-id") +} diff --git a/internal/params/binds.go b/internal/params/binds.go index 902ece957..6a790baff 100644 --- a/internal/params/binds.go +++ b/internal/params/binds.go @@ -26,6 +26,7 @@ var EnvVarsBinds = []struct { {KicsResultsPredicatesPathKey, KicsResultsPredicatesPathEnv, "api/kics-results-predicates"}, {BflPathKey, BflPathEnv, "api/bfl"}, {PRDecorationGithubPathKey, PRDecorationGithubPathEnv, "api/flow-publisher/pr/github"}, + {PRDecorationGitlabPathKey, PRDecorationGitlabPathEnv, "api/flow-publisher/pr/gitlab"}, {DescriptionsPathKey, DescriptionsPathEnv, "api/queries/descriptions"}, {TenantConfigurationPathKey, TenantConfigurationPathEnv, "api/configuration/tenant"}, {UploadsPathKey, UploadsPathEnv, "api/uploads"}, diff --git a/internal/params/envs.go b/internal/params/envs.go index 1a82d4fa8..2c55459fe 100644 --- a/internal/params/envs.go +++ b/internal/params/envs.go @@ -28,6 +28,7 @@ const ( KicsResultsPredicatesPathEnv = "CX_KICS_RESULTS_PREDICATES_PATH" BflPathEnv = "CX_BFL_PATH" PRDecorationGithubPathEnv = "CX_PR_DECORATION_GITHUB_PATH" + PRDecorationGitlabPathEnv = "CX_PR_DECORATION_GITLAB_PATH" SastRmPathEnv = "CX_SAST_RM_PATH" UploadsPathEnv = "CX_UPLOADS_PATH" TokenExpirySecondsEnv = "CX_TOKEN_EXPIRY_SECONDS" diff --git a/internal/params/flags.go b/internal/params/flags.go index f12785d8e..a40b0adb5 100644 --- a/internal/params/flags.go +++ b/internal/params/flags.go @@ -148,12 +148,16 @@ const ( ScaFilterUsage = "SCA filter" // PR decoration flags - NamespaceFlag = "namespace" - NamespaceFlagUsage = "Github namespace is required to post the comments" - RepoNameFlag = "repo-name" - RepoNameFlagUsage = "Github repository details" - PRNumberFlag = "pr-number" - PRNumberFlagUsage = "Pull Request number for posting notifications and comments" + NamespaceFlag = "namespace" + NamespaceFlagUsage = "%s namespace is required to post the comments" + RepoNameFlag = "repo-name" + RepoNameFlagUsage = "%s repository details" + PRNumberFlag = "pr-number" + PRNumberFlagUsage = "Pull Request number for posting notifications and comments" + PRIidFlag = "mr-iid" + PRIidFlagUsage = "Gitlab IID (internal ID) of the merge request" + PRGitlabProjectFlag = "gitlab-project-id" + PRGitlabProjectFlagUsage = "Gitlab project ID" // Chat ChatAPIKey = "chat-apikey" diff --git a/internal/params/keys.go b/internal/params/keys.go index cd3fe39aa..28c448a33 100644 --- a/internal/params/keys.go +++ b/internal/params/keys.go @@ -25,6 +25,7 @@ var ( KicsResultsPathKey = strings.ToLower(KicsResultsPathEnv) BflPathKey = strings.ToLower(BflPathEnv) PRDecorationGithubPathKey = strings.ToLower(PRDecorationGithubPathEnv) + PRDecorationGitlabPathKey = strings.ToLower(PRDecorationGitlabPathEnv) UploadsPathKey = strings.ToLower(UploadsPathEnv) SastRmPathKey = strings.ToLower(SastRmPathEnv) AccessKeyIDConfigKey = strings.ToLower(AccessKeyIDEnv) diff --git a/internal/wrappers/mock/pr-mock.go b/internal/wrappers/mock/pr-mock.go index b41d3cdec..4d5a464aa 100644 --- a/internal/wrappers/mock/pr-mock.go +++ b/internal/wrappers/mock/pr-mock.go @@ -14,3 +14,7 @@ func (pr *PRMockWrapper) PostPRDecoration(model *wrappers.PRModel) ( ) { return "PR comment created successfully.", nil, nil } + +func (pr *PRMockWrapper) PostGitlabPRDecoration(model *wrappers.GitlabPRModel) (string, *wrappers.WebError, error) { + return "MR comment created successfully.", nil, nil +} diff --git a/internal/wrappers/pr-http.go b/internal/wrappers/pr-http.go index 2dbd0d512..fa70f1bd4 100644 --- a/internal/wrappers/pr-http.go +++ b/internal/wrappers/pr-http.go @@ -17,11 +17,13 @@ const ( type PRHTTPWrapper struct { githubPath string + gitlabPath string } -func NewHTTPPRWrapper(githubPath string) PRWrapper { +func NewHTTPPRWrapper(githubPath, gitlabPath string) PRWrapper { return &PRHTTPWrapper{ githubPath: githubPath, + gitlabPath: gitlabPath, } } @@ -43,6 +45,24 @@ func (r *PRHTTPWrapper) PostPRDecoration(model *PRModel) ( return handlePRResponseWithBody(resp, err) } +func (r *PRHTTPWrapper) PostGitlabPRDecoration(model *GitlabPRModel) ( + string, + *WebError, + error, +) { + clientTimeout := viper.GetUint(commonParams.ClientTimeoutKey) + jsonBytes, err := json.Marshal(model) + if err != nil { + return "", nil, err + } + resp, err := SendHTTPRequestWithJSONContentType(http.MethodPost, r.gitlabPath, bytes.NewBuffer(jsonBytes), true, clientTimeout) + if err != nil { + return "", nil, err + } + defer resp.Body.Close() + return handlePRResponseWithBody(resp, err) +} + func handlePRResponseWithBody(resp *http.Response, err error) (string, *WebError, error) { if err != nil { return "", nil, err diff --git a/internal/wrappers/pr.go b/internal/wrappers/pr.go index 8b8d1ea38..7158fd4da 100644 --- a/internal/wrappers/pr.go +++ b/internal/wrappers/pr.go @@ -12,6 +12,16 @@ type PRModel struct { PrNumber int `json:"prNumber"` } +type GitlabPRModel struct { + ScanID string `json:"scanId"` + ScmToken string `json:"scmToken"` + Namespace string `json:"namespace"` + RepoName string `json:"repoName"` + IiD int `json:"iid"` + GitlabProjectID int `json:"gitlabProjectID"` +} + type PRWrapper interface { PostPRDecoration(model *PRModel) (string, *WebError, error) + PostGitlabPRDecoration(model *GitlabPRModel) (string, *WebError, error) } diff --git a/test/integration/pr_test.go b/test/integration/pr_test.go index a06d5e59a..c742d4a84 100644 --- a/test/integration/pr_test.go +++ b/test/integration/pr_test.go @@ -15,9 +15,14 @@ const ( prGithubNamespace = "PR_GITHUB_NAMESPACE" prGithubNumber = "PR_GITHUB_NUMBER" prGithubRepoName = "PR_GITHUB_REPO_NAME" + prGitlabRepoName = "PR_GITLAB_REPO_NAME" + prGitlabToken = "PR_GITLAB_TOKEN" + prGitlabNamespace = "PR_GITLAB_NAMESPACE" + prGitlabProjectId = "PR_GITLAB_PROJECT_ID" + prGitlabIid = "PR_GITLAB_IID" ) -func TestPRDecorationSuccessCase(t *testing.T) { +func TestPRGithubDecorationSuccessCase(t *testing.T) { scanID, _ := getRootScan(t) args := []string{ @@ -39,7 +44,7 @@ func TestPRDecorationSuccessCase(t *testing.T) { assert.NilError(t, err, "Error should be nil") } -func TestPRDecorationFailure(t *testing.T) { +func TestPRGithubDecorationFailure(t *testing.T) { args := []string{ "utils", "pr", @@ -56,5 +61,52 @@ func TestPRDecorationFailure(t *testing.T) { os.Getenv(prGithubRepoName), } err, _ := executeCommand(t, args...) - assert.ErrorContains(t, err, "Failed creating PR Decoration") + assert.ErrorContains(t, err, "Failed creating github PR Decoration") +} + +func TestPRGitlabDecorationSuccessCase(t *testing.T) { + scanID, _ := getRootScan(t) + + args := []string{ + "utils", + "pr", + "gitlab", + flag(params.ScanIDFlag), + scanID, + flag(params.SCMTokenFlag), + os.Getenv(prGitlabToken), + flag(params.NamespaceFlag), + os.Getenv(prGitlabNamespace), + flag(params.RepoNameFlag), + os.Getenv(prGitlabRepoName), + flag(params.PRGitlabProjectFlag), + os.Getenv(prGitlabProjectId), + flag(params.PRIidFlag), + os.Getenv(prGitlabIid), + } + err, _ := executeCommand(t, args...) + assert.NilError(t, err, "Error should be nil") +} + +func TestPRGitlabDecorationFailure(t *testing.T) { + + args := []string{ + "utils", + "pr", + "gitlab", + flag(params.ScanIDFlag), + "", + flag(params.SCMTokenFlag), + os.Getenv(prGitlabToken), + flag(params.NamespaceFlag), + os.Getenv(prGitlabNamespace), + flag(params.RepoNameFlag), + os.Getenv(prGitlabRepoName), + flag(params.PRGitlabProjectFlag), + os.Getenv(prGitlabProjectId), + flag(params.PRIidFlag), + os.Getenv(prGitlabIid), + } + err, _ := executeCommand(t, args...) + assert.ErrorContains(t, err, "Failed creating gitlab MR Decoration") } diff --git a/test/integration/util_command.go b/test/integration/util_command.go index 423b096e4..375ed3ff3 100644 --- a/test/integration/util_command.go +++ b/test/integration/util_command.go @@ -72,6 +72,7 @@ func createASTIntegrationTestCommand(t *testing.T) *cobra.Command { bfl := viper.GetString(params.BflPathKey) learnMore := viper.GetString(params.DescriptionsPathKey) prDecorationGithubPath := viper.GetString(params.PRDecorationGithubPathKey) + prDecorationGitlabPath := viper.GetString(params.PRDecorationGitlabPathKey) tenantConfigurationPath := viper.GetString(params.TenantConfigurationPathKey) resultsPdfPath := viper.GetString(params.ResultsPdfReportPathKey) resultsSbomPath := viper.GetString(params.ResultsSbomReportPathKey) @@ -98,7 +99,7 @@ func createASTIntegrationTestCommand(t *testing.T) *cobra.Command { bitBucketWrapper := wrappers.NewBitbucketWrapper() bflWrapper := wrappers.NewBflHTTPWrapper(bfl) learnMoreWrapper := wrappers.NewHTTPLearnMoreWrapper(learnMore) - prWrapper := wrappers.NewHTTPPRWrapper(prDecorationGithubPath) + prWrapper := wrappers.NewHTTPPRWrapper(prDecorationGithubPath,prDecorationGitlabPath) tenantConfigurationWrapper := wrappers.NewHTTPTenantConfigurationWrapper(tenantConfigurationPath) jwtWrapper := wrappers.NewJwtWrapper() scaRealtimeWrapper := wrappers.NewHTTPScaRealTimeWrapper()