From 53a9df4dcb0e6bb0d6da32225da7e591ad826447 Mon Sep 17 00:00:00 2001 From: miryamfoiferCX Date: Wed, 13 Nov 2024 15:42:16 +0200 Subject: [PATCH 1/9] pr decoration support for ADO --- cmd/main.go | 3 +- internal/commands/util/pr.go | 118 ++++++++++++++++++++++++++++++ internal/commands/util/pr_test.go | 44 ++++++++++- internal/params/binds.go | 1 + internal/params/envs.go | 1 + internal/params/flags.go | 24 +++--- internal/params/keys.go | 1 + internal/wrappers/mock/pr-mock.go | 4 + internal/wrappers/pr-http.go | 26 ++++++- internal/wrappers/pr.go | 10 +++ test/integration/pr_test.go | 85 +++++++++++++++++++++ test/integration/util_command.go | 3 +- 12 files changed, 305 insertions(+), 15 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index 8e6c70cfb..36ee14044 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -42,6 +42,7 @@ func main() { bfl := viper.GetString(params.BflPathKey) prDecorationGithubPath := viper.GetString(params.PRDecorationGithubPathKey) prDecorationGitlabPath := viper.GetString(params.PRDecorationGitlabPathKey) + prDecorationAzurePath := viper.GetString(params.PRDecorationAzurePathKey) descriptionsPath := viper.GetString(params.DescriptionsPathKey) tenantConfigurationPath := viper.GetString(params.TenantConfigurationPathKey) resultsPdfPath := viper.GetString(params.ResultsPdfReportPathKey) @@ -72,7 +73,7 @@ func main() { bitBucketServerWrapper := bitbucketserver.NewBitbucketServerWrapper() gitLabWrapper := wrappers.NewGitLabWrapper() bflWrapper := wrappers.NewBflHTTPWrapper(bfl) - prWrapper := wrappers.NewHTTPPRWrapper(prDecorationGithubPath, prDecorationGitlabPath) + prWrapper := wrappers.NewHTTPPRWrapper(prDecorationGithubPath, prDecorationGitlabPath, prDecorationAzurePath) 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 ab5207d0d..65506ac0a 100644 --- a/internal/commands/util/pr.go +++ b/internal/commands/util/pr.go @@ -16,6 +16,7 @@ import ( const ( failedCreatingGithubPrDecoration = "Failed creating github PR Decoration" + failedCreatingAzurePrDecoration = "Failed creating azure PR Decoration" failedCreatingGitlabPrDecoration = "Failed creating gitlab MR Decoration" errorCodeFormat = "%s: CODE: %d, %s\n" policyErrorFormat = "%s: Failed to get scanID policy information" @@ -27,6 +28,7 @@ const ( gitlabOnPremURLSuffix = "/api/v4/" githubCloudURL = "https://api.github.com/repos/" gitlabCloudURL = "https://gitlab.com" + gitlabOnPremURLSuffix + azureCloudURL = "https://dev.azure.com/" ) func NewPRDecorationCommand(prWrapper wrappers.PRWrapper, policyWrapper wrappers.PolicyWrapper, scansWrapper wrappers.ScansWrapper) *cobra.Command { @@ -42,9 +44,11 @@ func NewPRDecorationCommand(prWrapper wrappers.PRWrapper, policyWrapper wrappers prDecorationGithub := PRDecorationGithub(prWrapper, policyWrapper, scansWrapper) prDecorationGitlab := PRDecorationGitlab(prWrapper, policyWrapper, scansWrapper) + prDecorationAzure := PRDecorationAzure(prWrapper, policyWrapper, scansWrapper) cmd.AddCommand(prDecorationGithub) cmd.AddCommand(prDecorationGitlab) + cmd.AddCommand(prDecorationAzure) return cmd } @@ -159,6 +163,47 @@ func PRDecorationGitlab(prWrapper wrappers.PRWrapper, policyWrapper wrappers.Pol return prDecorationGitlab } +func PRDecorationAzure(prWrapper wrappers.PRWrapper, policyWrapper wrappers.PolicyWrapper, scansWrapper wrappers.ScansWrapper) *cobra.Command { + prDecorationAzure := &cobra.Command{ + Use: "azure", + Short: "Decorate azure PR with vulnerabilities", + Long: "Decorate azure PR with vulnerabilities", + Example: heredoc.Doc( + ` + $ cx utils pr azure --scan-id --token --namespace --project + --pr-number --code-repository-url + `, + ), + Annotations: map[string]string{ + "command:doc": heredoc.Doc( + `https://checkmarx.com/resource/documents/en/34965-68653-utils.html + `, + ), + }, + RunE: runPRDecorationAzure(prWrapper, policyWrapper, scansWrapper), + } + + prDecorationAzure.Flags().String(params.ScanIDFlag, "", "Scan ID to retrieve results from") + prDecorationAzure.Flags().String(params.SCMTokenFlag, "", params.AzureTokenUsage) + prDecorationAzure.Flags().String(params.NamespaceFlag, "", fmt.Sprintf(params.NamespaceFlagUsage, "Azure")) + prDecorationAzure.Flags().String(params.AzureProjectFlag, "", fmt.Sprintf(params.AzureProjectFlagUsage)) + prDecorationAzure.Flags().Int(params.PRNumberFlag, 0, params.PRNumberFlagUsage) + prDecorationAzure.Flags().String(params.CodeRepositoryFlag, "", params.CodeRepositoryFlagUsage) + prDecorationAzure.Flags().String(params.CodeRespositoryUsernameFlag, "", fmt.Sprintf(params.CodeRespositoryUsernameFlagUsage)) + + // Set the value for token to mask the scm token + _ = viper.BindPFlag(params.SCMTokenFlag, prDecorationAzure.Flags().Lookup(params.SCMTokenFlag)) + + // mark all fields as required\ + _ = prDecorationAzure.MarkFlagRequired(params.ScanIDFlag) + _ = prDecorationAzure.MarkFlagRequired(params.SCMTokenFlag) + _ = prDecorationAzure.MarkFlagRequired(params.NamespaceFlag) + _ = prDecorationAzure.MarkFlagRequired(params.AzureProjectFlag) + _ = prDecorationAzure.MarkFlagRequired(params.PRNumberFlag) + + return prDecorationAzure +} + func runPRDecoration(prWrapper wrappers.PRWrapper, policyWrapper wrappers.PolicyWrapper, scansWrapper wrappers.ScansWrapper) func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error { scanID, _ := cmd.Flags().GetString(params.ScanIDFlag) @@ -226,6 +271,13 @@ func updateAPIURLForGitlabOnPrem(apiURL string) string { return gitlabCloudURL } +func updateAPIURLForAzureOnPrem(apiURL string) string { + if apiURL != "" { + return apiURL + } + return azureCloudURL +} + func runPRDecorationGitlab(prWrapper wrappers.PRWrapper, policyWrapper wrappers.PolicyWrapper, scansWrapper wrappers.ScansWrapper) func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error { scanID, _ := cmd.Flags().GetString(params.ScanIDFlag) @@ -283,6 +335,72 @@ func runPRDecorationGitlab(prWrapper wrappers.PRWrapper, policyWrapper wrappers. } } +func runPRDecorationAzure(prWrapper wrappers.PRWrapper, policyWrapper wrappers.PolicyWrapper, scansWrapper wrappers.ScansWrapper) 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) + projectNameFlag, _ := cmd.Flags().GetString(params.AzureProjectFlag) + prNumberFlag, _ := cmd.Flags().GetInt(params.PRNumberFlag) + apiURL, _ := cmd.Flags().GetString(params.CodeRepositoryFlag) + codeRepositoryUserName, _ := cmd.Flags().GetString(params.CodeRespositoryUsernameFlag) + + scanRunningOrQueued, err := IsScanRunningOrQueued(scansWrapper, scanID) + + if err != nil { + return err + } + + if scanRunningOrQueued { + log.Println(noPRDecorationCreated) + return nil + } + + // Retrieve policies related to the scan and project to include in the PR decoration + policies, policyError := getScanViolatedPolicies(scansWrapper, policyWrapper, scanID, cmd) + if policyError != nil { + return errors.Errorf(policyErrorFormat, failedCreatingAzurePrDecoration) + } + + // Build and post the pr decoration + updatedAPIURL := updateAPIURLForAzureOnPrem(apiURL) + updatedScmToken := updateScmTokenForAzure(scmTokenFlag, codeRepositoryUserName) + azureNameSpace := createAzureNameSpace(namespaceFlag, projectNameFlag) + + prModel := &wrappers.AzurePRModel{ + ScanID: scanID, + ScmToken: updatedScmToken, + Namespace: azureNameSpace, + PrNumber: prNumberFlag, + Policies: policies, + APIURL: updatedAPIURL, + } + prResponse, errorModel, err := prWrapper.PostAzurePRDecoration(prModel) + if err != nil { + return err + } + + if errorModel != nil { + return errors.Errorf(errorCodeFormat, failedCreatingAzurePrDecoration, errorModel.Code, errorModel.Message) + } + + logger.Print(prResponse) + + return nil + } +} + +func createAzureNameSpace(namespaceFlag string, projectNameFlag string) string { + return fmt.Sprintf("%s/%s", namespaceFlag, projectNameFlag) +} + +func updateScmTokenForAzure(scmTokenFlag string, codeRepositoryUserName string) string { + if codeRepositoryUserName != "" { + return fmt.Sprintf("%s:%s", codeRepositoryUserName, scmTokenFlag) + } + return scmTokenFlag +} + func getScanViolatedPolicies(scansWrapper wrappers.ScansWrapper, policyWrapper wrappers.PolicyWrapper, scanID string, cmd *cobra.Command) ([]wrappers.PrPolicy, error) { // retrieve scan model to get the projectID scanResponseModel, errorScanModel, err := scansWrapper.GetByID(scanID) diff --git a/internal/commands/util/pr_test.go b/internal/commands/util/pr_test.go index 9e11b8385..81bcb6ab8 100644 --- a/internal/commands/util/pr_test.go +++ b/internal/commands/util/pr_test.go @@ -8,7 +8,7 @@ import ( "gotest.tools/assert" ) -func TestNewPRDecorationCommandMustExist(t *testing.T) { +func TestNewGithubPRDecorationCommandMustExist(t *testing.T) { cmd := PRDecorationGithub(nil, nil, nil) assert.Assert(t, cmd != nil, "PR decoration command must exist") @@ -16,7 +16,7 @@ func TestNewPRDecorationCommandMustExist(t *testing.T) { assert.ErrorContains(t, err, "scan-id") } -func TestNewMRDecorationCommandMustExist(t *testing.T) { +func TestNewGitlabMRDecorationCommandMustExist(t *testing.T) { cmd := PRDecorationGitlab(nil, nil, nil) assert.Assert(t, cmd != nil, "MR decoration command must exist") @@ -24,6 +24,14 @@ func TestNewMRDecorationCommandMustExist(t *testing.T) { assert.ErrorContains(t, err, "scan-id") } +func TestNewAzurePRDecorationCommandMustExist(t *testing.T) { + cmd := PRDecorationAzure(nil, nil, nil) + assert.Assert(t, cmd != nil, "PR decoration command must exist") + + err := cmd.Execute() + assert.ErrorContains(t, err, "scan-id") +} + func TestIsScanRunning_WhenScanRunning_ShouldReturnTrue(t *testing.T) { scansMockWrapper := &mock.ScansMockWrapper{Running: true} @@ -66,3 +74,35 @@ func TestUpdateAPIURLForGitlabOnPrem_whenAPIURLIsNotSet_ShouldReturnCloudAPIURL( cloudAPIURL := updateAPIURLForGitlabOnPrem("") asserts.Equal(t, gitlabCloudURL, cloudAPIURL) } + +func TestUpdateAPIURLForAzureOnPrem_whenAPIURLIsSet_ShouldUpdateAPIURL(t *testing.T) { + selfHostedURL := "https://azure.example.com" + updatedAPIURL := updateAPIURLForAzureOnPrem(selfHostedURL) + asserts.Equal(t, selfHostedURL, updatedAPIURL) +} + +func TestUpdateAPIURLForAzureOnPrem_whenAPIURLIsNotSet_ShouldReturnCloudAPIURL(t *testing.T) { + cloudAPIURL := updateAPIURLForAzureOnPrem("") + asserts.Equal(t, azureCloudURL, cloudAPIURL) +} + +func TestUpdateScmTokenForAzureOnPrem_whenUserNameIsSet_ShouldUpdateToken(t *testing.T) { + token := "token" + username := "username" + expectedToken := username + ":" + token + updatedToken := updateScmTokenForAzure(token, username) + asserts.Equal(t, expectedToken, updatedToken) +} + +func TestUpdateScmTokenForAzureOnPrem_whenUserNameNotSet_ShouldNotUpdateToken(t *testing.T) { + token := "token" + username := "" + expectedToken := token + updatedToken := updateScmTokenForAzure(token, username) + asserts.Equal(t, expectedToken, updatedToken) +} + +func TestCreateAzureNameSpace_ShouldCreateNamespace(t *testing.T) { + azureNamespace := createAzureNameSpace("organization", "project") + asserts.Equal(t, "organization/project", azureNamespace) +} diff --git a/internal/params/binds.go b/internal/params/binds.go index 4a5b10e0b..71b359d1d 100644 --- a/internal/params/binds.go +++ b/internal/params/binds.go @@ -28,6 +28,7 @@ var EnvVarsBinds = []struct { {BflPathKey, BflPathEnv, "api/bfl"}, {PRDecorationGithubPathKey, PRDecorationGithubPathEnv, "api/flow-publisher/pr/github"}, {PRDecorationGitlabPathKey, PRDecorationGitlabPathEnv, "api/flow-publisher/pr/gitlab"}, + {PRDecorationAzurePathKey, PRDecorationAzurePathEnv, "api/flow-publisher/pr/azure"}, {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 9ea114d98..cd29f1081 100644 --- a/internal/params/envs.go +++ b/internal/params/envs.go @@ -30,6 +30,7 @@ const ( BflPathEnv = "CX_BFL_PATH" PRDecorationGithubPathEnv = "CX_PR_DECORATION_GITHUB_PATH" PRDecorationGitlabPathEnv = "CX_PR_DECORATION_GITLAB_PATH" + PRDecorationAzurePathEnv = "CX_PR_DECORATION_AZURE_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 b52903aee..8c364f1f7 100644 --- a/internal/params/flags.go +++ b/internal/params/flags.go @@ -161,16 +161,20 @@ const ( ScaFilterUsage = "SCA filter" // PR decoration flags - 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" + 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" + AzureProjectFlag = "project" + AzureProjectFlagUsage = "Azure project name or project ID" + CodeRespositoryUsernameFlag = "code-repository-username" + CodeRespositoryUsernameFlagUsage = "Azure username for code repository" // Chat (General) ChatAPIKey = "chat-apikey" diff --git a/internal/params/keys.go b/internal/params/keys.go index 1da512e43..ed23c6b9a 100644 --- a/internal/params/keys.go +++ b/internal/params/keys.go @@ -28,6 +28,7 @@ var ( BflPathKey = strings.ToLower(BflPathEnv) PRDecorationGithubPathKey = strings.ToLower(PRDecorationGithubPathEnv) PRDecorationGitlabPathKey = strings.ToLower(PRDecorationGitlabPathEnv) + PRDecorationAzurePathKey = strings.ToLower(PRDecorationAzurePathEnv) 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 4d5a464aa..e608fec9b 100644 --- a/internal/wrappers/mock/pr-mock.go +++ b/internal/wrappers/mock/pr-mock.go @@ -18,3 +18,7 @@ func (pr *PRMockWrapper) PostPRDecoration(model *wrappers.PRModel) ( func (pr *PRMockWrapper) PostGitlabPRDecoration(model *wrappers.GitlabPRModel) (string, *wrappers.WebError, error) { return "MR comment created successfully.", nil, nil } + +func (pr *PRMockWrapper) PostAzurePRDecoration(model *wrappers.AzurePRModel) (string, *wrappers.WebError, error) { + return "PR comment created successfully.", nil, nil +} diff --git a/internal/wrappers/pr-http.go b/internal/wrappers/pr-http.go index 8756d74ac..8a4362770 100644 --- a/internal/wrappers/pr-http.go +++ b/internal/wrappers/pr-http.go @@ -18,12 +18,14 @@ const ( type PRHTTPWrapper struct { githubPath string gitlabPath string + azurePath string } -func NewHTTPPRWrapper(githubPath, gitlabPath string) PRWrapper { +func NewHTTPPRWrapper(githubPath, gitlabPath string, azurePath string) PRWrapper { return &PRHTTPWrapper{ githubPath: githubPath, gitlabPath: gitlabPath, + azurePath: azurePath, } } @@ -71,6 +73,28 @@ func (r *PRHTTPWrapper) PostGitlabPRDecoration(model *GitlabPRModel) ( return handlePRResponseWithBody(resp, err) } +func (r *PRHTTPWrapper) PostAzurePRDecoration(model *AzurePRModel) ( + 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.azurePath, bytes.NewBuffer(jsonBytes), true, clientTimeout) + if err != nil { + return "", nil, err + } + defer func() { + if err == nil { + _ = 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 40ccdb159..376541d33 100644 --- a/internal/wrappers/pr.go +++ b/internal/wrappers/pr.go @@ -25,7 +25,17 @@ type GitlabPRModel struct { APIURL string `json:"apiUrl"` } +type AzurePRModel struct { + ScanID string `json:"scanId"` + ScmToken string `json:"scmToken"` + Namespace string `json:"namespace"` + PrNumber int `json:"prNumber"` + Policies []PrPolicy `json:"violatedPolicyList"` + APIURL string `json:"apiUrl"` +} + type PRWrapper interface { PostPRDecoration(model *PRModel) (string, *WebError, error) PostGitlabPRDecoration(model *GitlabPRModel) (string, *WebError, error) + PostAzurePRDecoration(model *AzurePRModel) (string, *WebError, error) } diff --git a/test/integration/pr_test.go b/test/integration/pr_test.go index a2d624d73..63aa76033 100644 --- a/test/integration/pr_test.go +++ b/test/integration/pr_test.go @@ -27,10 +27,15 @@ const ( prGitlabNamespace = "PR_GITLAB_NAMESPACE" prGitlabProjectId = "PR_GITLAB_PROJECT_ID" prGitlabIid = "PR_GITLAB_IID" + prAzureToken = "PR_AZURE_TOKEN" + prAzureNamespace = "PR_AZURE_NAMESPACE" + prAzureProject = "PR_AZURE_PROJECT" + prAzureNumber = "PR_AZURE_NUMBER" prdDecorationForbiddenMessage = "A PR couldn't be created for this scan because it is still in progress." failedGettingScanError = "Failed showing a scan" githubPRCommentCreated = "github PR comment created successfully." gitlabPRCommentCreated = "gitlab PR comment created successfully." + azurePRCommentCreated = "azure PR comment created successfully." outputFileName = "test_output.log" scans = "api/scans" ) @@ -226,6 +231,86 @@ func TestPRGitlabDecorationFailure(t *testing.T) { assert.ErrorContains(t, err, "scan not found") } +func TestPRAzureDecorationSuccessCase(t *testing.T) { + args := []string{ + "utils", + "pr", + "azure", + flag(params.ScanIDFlag), + getCompletedScanID(t), + flag(params.SCMTokenFlag), + os.Getenv(prAzureToken), + flag(params.NamespaceFlag), + os.Getenv(prAzureNamespace), + flag(params.AzureProjectFlag), + os.Getenv(prAzureProject), + flag(params.PRNumberFlag), + os.Getenv(prAzureNumber), + } + err, _ := executeCommand(t, args...) + assert.NilError(t, err, "Error should be nil") +} + +func TestPRAzureDecoration_WhenUseCodeRepositoryFlag_ShouldSuccess(t *testing.T) { + args := []string{ + "utils", + "pr", + "azure", + flag(params.ScanIDFlag), + getCompletedScanID(t), + flag(params.SCMTokenFlag), + os.Getenv(prAzureToken), + flag(params.NamespaceFlag), + os.Getenv(prAzureNamespace), + flag(params.AzureProjectFlag), + os.Getenv(prAzureProject), + flag(params.PRNumberFlag), + os.Getenv(prAzureNumber), + flag(params.CodeRepositoryFlag), + "https://azure.example.com", + } + + monkey.Patch((*wrappers.PRHTTPWrapper).PostAzurePRDecoration, func(*wrappers.PRHTTPWrapper, *wrappers.AzurePRModel) (string, *wrappers.WebError, error) { + return azurePRCommentCreated, nil, nil + }) + defer monkey.Unpatch((*wrappers.PRHTTPWrapper).PostAzurePRDecoration) + + file := createOutputFile(t, outputFileName) + defer deleteOutputFile(t, file) + defer logger.SetOutput(os.Stdout) + + err, _ := executeCommand(t, args...) + assert.NilError(t, err, "Error should be nil") + + stdoutString, err := util.ReadFileAsString(file.Name()) + if err != nil { + t.Fatalf("Failed to read log file: %v", err) + } + + assert.Equal(t, strings.Contains(stdoutString, azurePRCommentCreated), true, "Expected output: %s", azurePRCommentCreated) +} + +func TestPRAzureDecorationFailure(t *testing.T) { + + args := []string{ + "utils", + "pr", + "azure", + flag(params.ScanIDFlag), + "fakeScanID", + flag(params.SCMTokenFlag), + os.Getenv(prAzureToken), + flag(params.NamespaceFlag), + os.Getenv(prAzureNamespace), + flag(params.AzureProjectFlag), + os.Getenv(prAzureProject), + flag(params.PRNumberFlag), + os.Getenv(prAzureNumber), + } + err, _ := executeCommand(t, args...) + assert.ErrorContains(t, err, "scan not found") +} + func TestPRGithubDecoration_WhenScanIsRunning_ShouldAvoidPRDecorationCommand(t *testing.T) { scanID, _ := createScanNoWait(t, Zip, Tags, getProjectNameForScanTests()) args := []string{ diff --git a/test/integration/util_command.go b/test/integration/util_command.go index 8c91f45ec..3655a0ee1 100644 --- a/test/integration/util_command.go +++ b/test/integration/util_command.go @@ -74,6 +74,7 @@ func createASTIntegrationTestCommand(t *testing.T) *cobra.Command { learnMore := viper.GetString(params.DescriptionsPathKey) prDecorationGithubPath := viper.GetString(params.PRDecorationGithubPathKey) prDecorationGitlabPath := viper.GetString(params.PRDecorationGitlabPathKey) + prDecorationAzurePath := viper.GetString(params.PRDecorationAzurePathKey) tenantConfigurationPath := viper.GetString(params.TenantConfigurationPathKey) resultsPdfPath := viper.GetString(params.ResultsPdfReportPathKey) exportPath := viper.GetString(params.ExportPathKey) @@ -104,7 +105,7 @@ func createASTIntegrationTestCommand(t *testing.T) *cobra.Command { bitBucketWrapper := wrappers.NewBitbucketWrapper() bflWrapper := wrappers.NewBflHTTPWrapper(bfl) learnMoreWrapper := wrappers.NewHTTPLearnMoreWrapper(learnMore) - prWrapper := wrappers.NewHTTPPRWrapper(prDecorationGithubPath, prDecorationGitlabPath) + prWrapper := wrappers.NewHTTPPRWrapper(prDecorationGithubPath, prDecorationGitlabPath, prDecorationAzurePath) tenantConfigurationWrapper := wrappers.NewHTTPTenantConfigurationWrapper(tenantConfigurationPath) jwtWrapper := wrappers.NewJwtWrapper() scaRealtimeWrapper := wrappers.NewHTTPScaRealTimeWrapper() From dcfdddf06ad6aba6f34c293bd4d7bc1835a49636 Mon Sep 17 00:00:00 2001 From: miryamfoiferCX Date: Wed, 13 Nov 2024 16:02:37 +0200 Subject: [PATCH 2/9] fix for lint --- internal/commands/util/pr.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/commands/util/pr.go b/internal/commands/util/pr.go index 65506ac0a..083d47862 100644 --- a/internal/commands/util/pr.go +++ b/internal/commands/util/pr.go @@ -390,11 +390,11 @@ func runPRDecorationAzure(prWrapper wrappers.PRWrapper, policyWrapper wrappers.P } } -func createAzureNameSpace(namespaceFlag string, projectNameFlag string) string { +func createAzureNameSpace(namespaceFlag, projectNameFlag string) string { return fmt.Sprintf("%s/%s", namespaceFlag, projectNameFlag) } -func updateScmTokenForAzure(scmTokenFlag string, codeRepositoryUserName string) string { +func updateScmTokenForAzure(scmTokenFlag, codeRepositoryUserName string) string { if codeRepositoryUserName != "" { return fmt.Sprintf("%s:%s", codeRepositoryUserName, scmTokenFlag) } From 2c639e549cc174f4530e48e639a249988686258e Mon Sep 17 00:00:00 2001 From: miryamfoiferCX Date: Thu, 14 Nov 2024 08:47:45 +0200 Subject: [PATCH 3/9] fixes for linter --- internal/commands/util/pr_test.go | 6 ++++-- internal/wrappers/pr-http.go | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/internal/commands/util/pr_test.go b/internal/commands/util/pr_test.go index 81bcb6ab8..98f5bc7ce 100644 --- a/internal/commands/util/pr_test.go +++ b/internal/commands/util/pr_test.go @@ -8,6 +8,10 @@ import ( "gotest.tools/assert" ) +const ( + token = "token" +) + func TestNewGithubPRDecorationCommandMustExist(t *testing.T) { cmd := PRDecorationGithub(nil, nil, nil) assert.Assert(t, cmd != nil, "PR decoration command must exist") @@ -87,7 +91,6 @@ func TestUpdateAPIURLForAzureOnPrem_whenAPIURLIsNotSet_ShouldReturnCloudAPIURL(t } func TestUpdateScmTokenForAzureOnPrem_whenUserNameIsSet_ShouldUpdateToken(t *testing.T) { - token := "token" username := "username" expectedToken := username + ":" + token updatedToken := updateScmTokenForAzure(token, username) @@ -95,7 +98,6 @@ func TestUpdateScmTokenForAzureOnPrem_whenUserNameIsSet_ShouldUpdateToken(t *tes } func TestUpdateScmTokenForAzureOnPrem_whenUserNameNotSet_ShouldNotUpdateToken(t *testing.T) { - token := "token" username := "" expectedToken := token updatedToken := updateScmTokenForAzure(token, username) diff --git a/internal/wrappers/pr-http.go b/internal/wrappers/pr-http.go index 8a4362770..e114cea51 100644 --- a/internal/wrappers/pr-http.go +++ b/internal/wrappers/pr-http.go @@ -21,7 +21,7 @@ type PRHTTPWrapper struct { azurePath string } -func NewHTTPPRWrapper(githubPath, gitlabPath string, azurePath string) PRWrapper { +func NewHTTPPRWrapper(githubPath, gitlabPath, azurePath string) PRWrapper { return &PRHTTPWrapper{ githubPath: githubPath, gitlabPath: gitlabPath, From fd5d63fdfd30cf1f18f95dce3b6870e67e761572 Mon Sep 17 00:00:00 2001 From: miryamfoiferCX Date: Thu, 14 Nov 2024 08:55:34 +0200 Subject: [PATCH 4/9] add validation for azure params --- internal/commands/util/pr.go | 14 ++++++++++++++ internal/commands/util/pr_test.go | 10 ++++++++++ 2 files changed, 24 insertions(+) diff --git a/internal/commands/util/pr.go b/internal/commands/util/pr.go index 083d47862..ca176305d 100644 --- a/internal/commands/util/pr.go +++ b/internal/commands/util/pr.go @@ -29,6 +29,7 @@ const ( githubCloudURL = "https://api.github.com/repos/" gitlabCloudURL = "https://gitlab.com" + gitlabOnPremURLSuffix azureCloudURL = "https://dev.azure.com/" + errorAzureOnPremParams = "code-repository-url must be set when code-repository-username is set" ) func NewPRDecorationCommand(prWrapper wrappers.PRWrapper, policyWrapper wrappers.PolicyWrapper, scansWrapper wrappers.ScansWrapper) *cobra.Command { @@ -345,6 +346,11 @@ func runPRDecorationAzure(prWrapper wrappers.PRWrapper, policyWrapper wrappers.P apiURL, _ := cmd.Flags().GetString(params.CodeRepositoryFlag) codeRepositoryUserName, _ := cmd.Flags().GetString(params.CodeRespositoryUsernameFlag) + errParams := validateAzureOnPremParameters(apiURL, codeRepositoryUserName) + if errParams != nil { + return errParams + } + scanRunningOrQueued, err := IsScanRunningOrQueued(scansWrapper, scanID) if err != nil { @@ -390,6 +396,14 @@ func runPRDecorationAzure(prWrapper wrappers.PRWrapper, policyWrapper wrappers.P } } +func validateAzureOnPremParameters(apiURL, codeRepositoryUserName string) error { + if apiURL == "" && codeRepositoryUserName != "" { + log.Println(errorAzureOnPremParams) + return errors.New(errorAzureOnPremParams) + } + return nil +} + func createAzureNameSpace(namespaceFlag, projectNameFlag string) string { return fmt.Sprintf("%s/%s", namespaceFlag, projectNameFlag) } diff --git a/internal/commands/util/pr_test.go b/internal/commands/util/pr_test.go index 98f5bc7ce..91d7c819f 100644 --- a/internal/commands/util/pr_test.go +++ b/internal/commands/util/pr_test.go @@ -108,3 +108,13 @@ func TestCreateAzureNameSpace_ShouldCreateNamespace(t *testing.T) { azureNamespace := createAzureNameSpace("organization", "project") asserts.Equal(t, "organization/project", azureNamespace) } + +func TestValidateAzureOnPremParameters_WhenParametersAreValid_ShouldReturnNil(t *testing.T) { + err := validateAzureOnPremParameters("https://azure.example.com", "username") + asserts.Nil(t, err) +} + +func TestValidateAzureOnPremParameters_WhenParametersAreNotValid_ShouldReturnError(t *testing.T) { + err := validateAzureOnPremParameters("", "username") + asserts.NotNil(t, err) +} From b4ad17ec2d0c483abb3e2c68435c1a67daabd07e Mon Sep 17 00:00:00 2001 From: miryamfoiferCX Date: Thu, 14 Nov 2024 09:24:42 +0200 Subject: [PATCH 5/9] refactor PR tests for running scan GH/GL/ADO --- test/integration/pr_test.go | 72 +++++++++++++++++++++++++------------ 1 file changed, 49 insertions(+), 23 deletions(-) diff --git a/test/integration/pr_test.go b/test/integration/pr_test.go index 63aa76033..b64569fec 100644 --- a/test/integration/pr_test.go +++ b/test/integration/pr_test.go @@ -41,6 +41,7 @@ const ( ) var completedScanId = "" +var runningScanId = "" func getCompletedScanID(t *testing.T) string { if completedScanId != "" { @@ -63,6 +64,19 @@ func getCompletedScanID(t *testing.T) string { return scanID } +func getRunningScanId(t *testing.T) string { + scanWrapper := wrappers.NewHTTPScansWrapper(scans) + if runningScanId != "" { + isRunning, _ := util.IsScanRunningOrQueued(scanWrapper, runningScanId) + if isRunning { + return runningScanId + } + } + scanID, _ := createScanNoWait(t, Zip, Tags, getProjectNameForScanTests()) + runningScanId = scanID + return scanID +} + func TestPRGithubDecorationSuccessCase(t *testing.T) { args := []string{ "utils", @@ -312,13 +326,12 @@ func TestPRAzureDecorationFailure(t *testing.T) { } func TestPRGithubDecoration_WhenScanIsRunning_ShouldAvoidPRDecorationCommand(t *testing.T) { - scanID, _ := createScanNoWait(t, Zip, Tags, getProjectNameForScanTests()) args := []string{ "utils", "pr", "github", flag(params.ScanIDFlag), - scanID, + getRunningScanId(t), flag(params.SCMTokenFlag), os.Getenv(prGithubToken), flag(params.NamespaceFlag), @@ -327,29 +340,18 @@ func TestPRGithubDecoration_WhenScanIsRunning_ShouldAvoidPRDecorationCommand(t * os.Getenv(prGithubNumber), flag(params.RepoNameFlag), os.Getenv(prGithubRepoName), - "--debug", - } - - file := createOutputFile(t, outputFileName) - _, _ = executeCommand(t, args...) - stdoutString, err := util.ReadFileAsString(file.Name()) - if err != nil { - t.Fatalf("Failed to read log file: %v", err) } - assert.Equal(t, strings.Contains(stdoutString, prdDecorationForbiddenMessage), true, "Expected output: %s", prdDecorationForbiddenMessage) - defer deleteOutputFile(t, file) - defer logger.SetOutput(os.Stdout) + runPRTestForRunningScan(t, args) } func TestPRGitlabDecoration_WhenScanIsRunning_ShouldAvoidPRDecorationCommand(t *testing.T) { - scanID, _ := createScanNoWait(t, Zip, Tags, getProjectNameForScanTests()) args := []string{ "utils", "pr", "gitlab", flag(params.ScanIDFlag), - scanID, + getRunningScanId(t), flag(params.SCMTokenFlag), os.Getenv(prGitlabToken), flag(params.NamespaceFlag), @@ -362,16 +364,27 @@ func TestPRGitlabDecoration_WhenScanIsRunning_ShouldAvoidPRDecorationCommand(t * os.Getenv(prGitlabIid), } - file := createOutputFile(t, outputFileName) - _, _ = executeCommand(t, args...) - stdoutString, err := util.ReadFileAsString(file.Name()) - if err != nil { - t.Fatalf("Failed to read log file: %v", err) + runPRTestForRunningScan(t, args) +} + +func TestPRAzureDecoration_WhenScanIsRunning_ShouldAvoidPRDecorationCommand(t *testing.T) { + args := []string{ + "utils", + "pr", + "azure", + flag(params.ScanIDFlag), + getRunningScanId(t), + flag(params.SCMTokenFlag), + os.Getenv(prAzureToken), + flag(params.NamespaceFlag), + os.Getenv(prAzureNamespace), + flag(params.AzureProjectFlag), + os.Getenv(prAzureProject), + flag(params.PRNumberFlag), + os.Getenv(prAzureNumber), } - assert.Equal(t, strings.Contains(stdoutString, prdDecorationForbiddenMessage), true, "Expected output: %s", prdDecorationForbiddenMessage) - defer deleteOutputFile(t, file) - defer logger.SetOutput(os.Stdout) + runPRTestForRunningScan(t, args) } func createOutputFile(t *testing.T, fileName string) *os.File { @@ -390,3 +403,16 @@ func deleteOutputFile(t *testing.T, file *os.File) { logger.Printf("Failed to remove log file: %v", err) } } + +func runPRTestForRunningScan(t *testing.T, args []string) { + file := createOutputFile(t, outputFileName) + _, _ = executeCommand(t, args...) + stdoutString, err := util.ReadFileAsString(file.Name()) + if err != nil { + t.Fatalf("Failed to read log file: %v", err) + } + assert.Equal(t, strings.Contains(stdoutString, prdDecorationForbiddenMessage), true, "Expected output: %s", prdDecorationForbiddenMessage) + + defer deleteOutputFile(t, file) + defer logger.SetOutput(os.Stdout) +} From c854892473e65c1ca5957521bd5a54ad1689c6e3 Mon Sep 17 00:00:00 2001 From: miryamfoiferCX Date: Thu, 14 Nov 2024 09:53:11 +0200 Subject: [PATCH 6/9] change comment about mark some fields as required --- internal/commands/util/pr.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/commands/util/pr.go b/internal/commands/util/pr.go index ca176305d..030f42e20 100644 --- a/internal/commands/util/pr.go +++ b/internal/commands/util/pr.go @@ -195,7 +195,7 @@ func PRDecorationAzure(prWrapper wrappers.PRWrapper, policyWrapper wrappers.Poli // Set the value for token to mask the scm token _ = viper.BindPFlag(params.SCMTokenFlag, prDecorationAzure.Flags().Lookup(params.SCMTokenFlag)) - // mark all fields as required\ + // mark some fields as required\ _ = prDecorationAzure.MarkFlagRequired(params.ScanIDFlag) _ = prDecorationAzure.MarkFlagRequired(params.SCMTokenFlag) _ = prDecorationAzure.MarkFlagRequired(params.NamespaceFlag) From 2286c7eabed13ec870c596aefc1eeebf0e029010 Mon Sep 17 00:00:00 2001 From: miryamfoiferCX Date: Thu, 14 Nov 2024 10:26:55 +0200 Subject: [PATCH 7/9] update --project flag to get project name or project id --- internal/commands/util/pr.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/commands/util/pr.go b/internal/commands/util/pr.go index 030f42e20..5e0ea126d 100644 --- a/internal/commands/util/pr.go +++ b/internal/commands/util/pr.go @@ -171,7 +171,7 @@ func PRDecorationAzure(prWrapper wrappers.PRWrapper, policyWrapper wrappers.Poli Long: "Decorate azure PR with vulnerabilities", Example: heredoc.Doc( ` - $ cx utils pr azure --scan-id --token --namespace --project + $ cx utils pr azure --scan-id --token --namespace --project --pr-number --code-repository-url `, ), From 808aa4aa92d031009d6a7130e205cfee7d9bcaeb Mon Sep 17 00:00:00 2001 From: miryamfoiferCX Date: Thu, 14 Nov 2024 10:42:19 +0200 Subject: [PATCH 8/9] CR fixes --- internal/commands/util/pr.go | 8 ++++---- internal/commands/util/pr_test.go | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/internal/commands/util/pr.go b/internal/commands/util/pr.go index 5e0ea126d..4060e3217 100644 --- a/internal/commands/util/pr.go +++ b/internal/commands/util/pr.go @@ -272,7 +272,7 @@ func updateAPIURLForGitlabOnPrem(apiURL string) string { return gitlabCloudURL } -func updateAPIURLForAzureOnPrem(apiURL string) string { +func getAzureAPIURL(apiURL string) string { if apiURL != "" { return apiURL } @@ -369,7 +369,7 @@ func runPRDecorationAzure(prWrapper wrappers.PRWrapper, policyWrapper wrappers.P } // Build and post the pr decoration - updatedAPIURL := updateAPIURLForAzureOnPrem(apiURL) + updatedAPIURL := getAzureAPIURL(apiURL) updatedScmToken := updateScmTokenForAzure(scmTokenFlag, codeRepositoryUserName) azureNameSpace := createAzureNameSpace(namespaceFlag, projectNameFlag) @@ -404,8 +404,8 @@ func validateAzureOnPremParameters(apiURL, codeRepositoryUserName string) error return nil } -func createAzureNameSpace(namespaceFlag, projectNameFlag string) string { - return fmt.Sprintf("%s/%s", namespaceFlag, projectNameFlag) +func createAzureNameSpace(namespace, projectName string) string { + return fmt.Sprintf("%s/%s", namespace, projectName) } func updateScmTokenForAzure(scmTokenFlag, codeRepositoryUserName string) string { diff --git a/internal/commands/util/pr_test.go b/internal/commands/util/pr_test.go index 91d7c819f..96add7129 100644 --- a/internal/commands/util/pr_test.go +++ b/internal/commands/util/pr_test.go @@ -79,14 +79,14 @@ func TestUpdateAPIURLForGitlabOnPrem_whenAPIURLIsNotSet_ShouldReturnCloudAPIURL( asserts.Equal(t, gitlabCloudURL, cloudAPIURL) } -func TestUpdateAPIURLForAzureOnPrem_whenAPIURLIsSet_ShouldUpdateAPIURL(t *testing.T) { +func TestGetAzureAPIURL_whenAPIURLIsSet_ShouldUpdateAPIURL(t *testing.T) { selfHostedURL := "https://azure.example.com" - updatedAPIURL := updateAPIURLForAzureOnPrem(selfHostedURL) + updatedAPIURL := getAzureAPIURL(selfHostedURL) asserts.Equal(t, selfHostedURL, updatedAPIURL) } -func TestUpdateAPIURLForAzureOnPrem_whenAPIURLIsNotSet_ShouldReturnCloudAPIURL(t *testing.T) { - cloudAPIURL := updateAPIURLForAzureOnPrem("") +func TestGetAzureAPIURL_whenAPIURLIsNotSet_ShouldReturnCloudAPIURL(t *testing.T) { + cloudAPIURL := getAzureAPIURL("") asserts.Equal(t, azureCloudURL, cloudAPIURL) } From 2e8d1004c59d8c17d99f193e0521bd345020eb8b Mon Sep 17 00:00:00 2001 From: miryamfoiferCX Date: Thu, 14 Nov 2024 12:24:06 +0200 Subject: [PATCH 9/9] use new ENV vars --- .github/workflows/ci.yml | 4 ++++ .github/workflows/manual-integration-test.yml | 4 ++++ test/integration/pr_test.go | 16 ++++++++-------- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1d9484f42..bdb0d87ee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,6 +75,10 @@ jobs: AZURE_PROJECT: ${{ secrets.AZURE_PROJECT }} AZURE_REPOS: ${{ secrets.AZURE_REPOS }} AZURE_TOKEN: ${{ secrets.AZURE_TOKEN }} + AZURE_NEW_ORG: "azureAccountTests" + AZURE_PROJECT_NAME: "testsProject" + AZURE_PR_NUMBER: 1 + AZURE_NEW_TOKEN: ${{ secrets.AZURE_NEW_TOKEN }} BITBUCKET_WORKSPACE: ${{ secrets.BITBUCKET_WORKSPACE }} BITBUCKET_REPOS: ${{ secrets.BITBUCKET_REPOS }} BITBUCKET_USERNAME: ${{ secrets.BITBUCKET_USERNAME }} diff --git a/.github/workflows/manual-integration-test.yml b/.github/workflows/manual-integration-test.yml index 81219f263..59a30b05d 100644 --- a/.github/workflows/manual-integration-test.yml +++ b/.github/workflows/manual-integration-test.yml @@ -78,6 +78,10 @@ jobs: AZURE_PROJECT: ${{ secrets.AZURE_PROJECT }} AZURE_REPOS: ${{ secrets.AZURE_REPOS }} AZURE_TOKEN: ${{ secrets.AZURE_TOKEN }} + AZURE_NEW_ORG: "azureAccountTests" + AZURE_PROJECT_NAME: "testsProject" + AZURE_PR_NUMBER: 1 + AZURE_NEW_TOKEN: ${{ secrets.AZURE_NEW_TOKEN }} BITBUCKET_WORKSPACE: ${{ secrets.BITBUCKET_WORKSPACE }} BITBUCKET_REPOS: ${{ secrets.BITBUCKET_REPOS }} BITBUCKET_USERNAME: ${{ secrets.BITBUCKET_USERNAME }} diff --git a/test/integration/pr_test.go b/test/integration/pr_test.go index b64569fec..55560cb8e 100644 --- a/test/integration/pr_test.go +++ b/test/integration/pr_test.go @@ -27,10 +27,10 @@ const ( prGitlabNamespace = "PR_GITLAB_NAMESPACE" prGitlabProjectId = "PR_GITLAB_PROJECT_ID" prGitlabIid = "PR_GITLAB_IID" - prAzureToken = "PR_AZURE_TOKEN" - prAzureNamespace = "PR_AZURE_NAMESPACE" - prAzureProject = "PR_AZURE_PROJECT" - prAzureNumber = "PR_AZURE_NUMBER" + prAzureToken = "AZURE_NEW_TOKEN" + prAzureOrganization = "AZURE_NEW_ORG" + prAzureProject = "AZURE_PROJECT_NAME" + prAzureNumber = "AZURE_PR_NUMBER" prdDecorationForbiddenMessage = "A PR couldn't be created for this scan because it is still in progress." failedGettingScanError = "Failed showing a scan" githubPRCommentCreated = "github PR comment created successfully." @@ -255,7 +255,7 @@ func TestPRAzureDecorationSuccessCase(t *testing.T) { flag(params.SCMTokenFlag), os.Getenv(prAzureToken), flag(params.NamespaceFlag), - os.Getenv(prAzureNamespace), + os.Getenv(prAzureOrganization), flag(params.AzureProjectFlag), os.Getenv(prAzureProject), flag(params.PRNumberFlag), @@ -275,7 +275,7 @@ func TestPRAzureDecoration_WhenUseCodeRepositoryFlag_ShouldSuccess(t *testing.T) flag(params.SCMTokenFlag), os.Getenv(prAzureToken), flag(params.NamespaceFlag), - os.Getenv(prAzureNamespace), + os.Getenv(prAzureOrganization), flag(params.AzureProjectFlag), os.Getenv(prAzureProject), flag(params.PRNumberFlag), @@ -315,7 +315,7 @@ func TestPRAzureDecorationFailure(t *testing.T) { flag(params.SCMTokenFlag), os.Getenv(prAzureToken), flag(params.NamespaceFlag), - os.Getenv(prAzureNamespace), + os.Getenv(prAzureOrganization), flag(params.AzureProjectFlag), os.Getenv(prAzureProject), flag(params.PRNumberFlag), @@ -377,7 +377,7 @@ func TestPRAzureDecoration_WhenScanIsRunning_ShouldAvoidPRDecorationCommand(t *t flag(params.SCMTokenFlag), os.Getenv(prAzureToken), flag(params.NamespaceFlag), - os.Getenv(prAzureNamespace), + os.Getenv(prAzureOrganization), flag(params.AzureProjectFlag), os.Getenv(prAzureProject), flag(params.PRNumberFlag),