Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

merge request decoration in gitlab #600

Merged
merged 16 commits into from
Nov 13, 2023
Merged
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down
3 changes: 2 additions & 1 deletion cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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()
Expand Down
90 changes: 85 additions & 5 deletions internal/commands/util/pr.go
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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 {
Expand All @@ -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
}

Expand All @@ -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
Expand All @@ -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 <scan-id> --token <PAT> --namespace <organization> --repo-name <repository>
--iid <pr iid> --gitlab-project <gitlab project ID>
`,
),
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)
Expand All @@ -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)
Expand Down
8 changes: 8 additions & 0 deletions internal/commands/util/pr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
1 change: 1 addition & 0 deletions internal/params/binds.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
Expand Down
1 change: 1 addition & 0 deletions internal/params/envs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
16 changes: 10 additions & 6 deletions internal/params/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions internal/params/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions internal/wrappers/mock/pr-mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
22 changes: 21 additions & 1 deletion internal/wrappers/pr-http.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}

Expand All @@ -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
Expand Down
10 changes: 10 additions & 0 deletions internal/wrappers/pr.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
58 changes: 55 additions & 3 deletions test/integration/pr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand All @@ -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",
Expand All @@ -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")
}
3 changes: 2 additions & 1 deletion test/integration/util_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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()
Expand Down