@@ -16,6 +16,7 @@ import (
1616
1717const (
1818 failedCreatingGithubPrDecoration = "Failed creating github PR Decoration"
19+ failedCreatingAzurePrDecoration = "Failed creating azure PR Decoration"
1920 failedCreatingGitlabPrDecoration = "Failed creating gitlab MR Decoration"
2021 errorCodeFormat = "%s: CODE: %d, %s\n "
2122 policyErrorFormat = "%s: Failed to get scanID policy information"
@@ -27,6 +28,8 @@ const (
2728 gitlabOnPremURLSuffix = "/api/v4/"
2829 githubCloudURL = "https://api.github.com/repos/"
2930 gitlabCloudURL = "https://gitlab.com" + gitlabOnPremURLSuffix
31+ azureCloudURL = "https://dev.azure.com/"
32+ errorAzureOnPremParams = "code-repository-url must be set when code-repository-username is set"
3033)
3134
3235func NewPRDecorationCommand (prWrapper wrappers.PRWrapper , policyWrapper wrappers.PolicyWrapper , scansWrapper wrappers.ScansWrapper ) * cobra.Command {
@@ -42,9 +45,11 @@ func NewPRDecorationCommand(prWrapper wrappers.PRWrapper, policyWrapper wrappers
4245
4346 prDecorationGithub := PRDecorationGithub (prWrapper , policyWrapper , scansWrapper )
4447 prDecorationGitlab := PRDecorationGitlab (prWrapper , policyWrapper , scansWrapper )
48+ prDecorationAzure := PRDecorationAzure (prWrapper , policyWrapper , scansWrapper )
4549
4650 cmd .AddCommand (prDecorationGithub )
4751 cmd .AddCommand (prDecorationGitlab )
52+ cmd .AddCommand (prDecorationAzure )
4853 return cmd
4954}
5055
@@ -159,6 +164,47 @@ func PRDecorationGitlab(prWrapper wrappers.PRWrapper, policyWrapper wrappers.Pol
159164 return prDecorationGitlab
160165}
161166
167+ func PRDecorationAzure (prWrapper wrappers.PRWrapper , policyWrapper wrappers.PolicyWrapper , scansWrapper wrappers.ScansWrapper ) * cobra.Command {
168+ prDecorationAzure := & cobra.Command {
169+ Use : "azure" ,
170+ Short : "Decorate azure PR with vulnerabilities" ,
171+ Long : "Decorate azure PR with vulnerabilities" ,
172+ Example : heredoc .Doc (
173+ `
174+ $ cx utils pr azure --scan-id <scan-id> --token <AAD> --namespace <organization> --project <project-name or project-id>
175+ --pr-number <pr number> --code-repository-url <code-repository-url>
176+ ` ,
177+ ),
178+ Annotations : map [string ]string {
179+ "command:doc" : heredoc .Doc (
180+ `https://checkmarx.com/resource/documents/en/34965-68653-utils.html
181+ ` ,
182+ ),
183+ },
184+ RunE : runPRDecorationAzure (prWrapper , policyWrapper , scansWrapper ),
185+ }
186+
187+ prDecorationAzure .Flags ().String (params .ScanIDFlag , "" , "Scan ID to retrieve results from" )
188+ prDecorationAzure .Flags ().String (params .SCMTokenFlag , "" , params .AzureTokenUsage )
189+ prDecorationAzure .Flags ().String (params .NamespaceFlag , "" , fmt .Sprintf (params .NamespaceFlagUsage , "Azure" ))
190+ prDecorationAzure .Flags ().String (params .AzureProjectFlag , "" , fmt .Sprintf (params .AzureProjectFlagUsage ))
191+ prDecorationAzure .Flags ().Int (params .PRNumberFlag , 0 , params .PRNumberFlagUsage )
192+ prDecorationAzure .Flags ().String (params .CodeRepositoryFlag , "" , params .CodeRepositoryFlagUsage )
193+ prDecorationAzure .Flags ().String (params .CodeRespositoryUsernameFlag , "" , fmt .Sprintf (params .CodeRespositoryUsernameFlagUsage ))
194+
195+ // Set the value for token to mask the scm token
196+ _ = viper .BindPFlag (params .SCMTokenFlag , prDecorationAzure .Flags ().Lookup (params .SCMTokenFlag ))
197+
198+ // mark some fields as required\
199+ _ = prDecorationAzure .MarkFlagRequired (params .ScanIDFlag )
200+ _ = prDecorationAzure .MarkFlagRequired (params .SCMTokenFlag )
201+ _ = prDecorationAzure .MarkFlagRequired (params .NamespaceFlag )
202+ _ = prDecorationAzure .MarkFlagRequired (params .AzureProjectFlag )
203+ _ = prDecorationAzure .MarkFlagRequired (params .PRNumberFlag )
204+
205+ return prDecorationAzure
206+ }
207+
162208func runPRDecoration (prWrapper wrappers.PRWrapper , policyWrapper wrappers.PolicyWrapper , scansWrapper wrappers.ScansWrapper ) func (cmd * cobra.Command , args []string ) error {
163209 return func (cmd * cobra.Command , args []string ) error {
164210 scanID , _ := cmd .Flags ().GetString (params .ScanIDFlag )
@@ -226,6 +272,13 @@ func updateAPIURLForGitlabOnPrem(apiURL string) string {
226272 return gitlabCloudURL
227273}
228274
275+ func getAzureAPIURL (apiURL string ) string {
276+ if apiURL != "" {
277+ return apiURL
278+ }
279+ return azureCloudURL
280+ }
281+
229282func runPRDecorationGitlab (prWrapper wrappers.PRWrapper , policyWrapper wrappers.PolicyWrapper , scansWrapper wrappers.ScansWrapper ) func (cmd * cobra.Command , args []string ) error {
230283 return func (cmd * cobra.Command , args []string ) error {
231284 scanID , _ := cmd .Flags ().GetString (params .ScanIDFlag )
@@ -283,6 +336,85 @@ func runPRDecorationGitlab(prWrapper wrappers.PRWrapper, policyWrapper wrappers.
283336 }
284337}
285338
339+ func runPRDecorationAzure (prWrapper wrappers.PRWrapper , policyWrapper wrappers.PolicyWrapper , scansWrapper wrappers.ScansWrapper ) func (cmd * cobra.Command , args []string ) error {
340+ return func (cmd * cobra.Command , args []string ) error {
341+ scanID , _ := cmd .Flags ().GetString (params .ScanIDFlag )
342+ scmTokenFlag , _ := cmd .Flags ().GetString (params .SCMTokenFlag )
343+ namespaceFlag , _ := cmd .Flags ().GetString (params .NamespaceFlag )
344+ projectNameFlag , _ := cmd .Flags ().GetString (params .AzureProjectFlag )
345+ prNumberFlag , _ := cmd .Flags ().GetInt (params .PRNumberFlag )
346+ apiURL , _ := cmd .Flags ().GetString (params .CodeRepositoryFlag )
347+ codeRepositoryUserName , _ := cmd .Flags ().GetString (params .CodeRespositoryUsernameFlag )
348+
349+ errParams := validateAzureOnPremParameters (apiURL , codeRepositoryUserName )
350+ if errParams != nil {
351+ return errParams
352+ }
353+
354+ scanRunningOrQueued , err := IsScanRunningOrQueued (scansWrapper , scanID )
355+
356+ if err != nil {
357+ return err
358+ }
359+
360+ if scanRunningOrQueued {
361+ log .Println (noPRDecorationCreated )
362+ return nil
363+ }
364+
365+ // Retrieve policies related to the scan and project to include in the PR decoration
366+ policies , policyError := getScanViolatedPolicies (scansWrapper , policyWrapper , scanID , cmd )
367+ if policyError != nil {
368+ return errors .Errorf (policyErrorFormat , failedCreatingAzurePrDecoration )
369+ }
370+
371+ // Build and post the pr decoration
372+ updatedAPIURL := getAzureAPIURL (apiURL )
373+ updatedScmToken := updateScmTokenForAzure (scmTokenFlag , codeRepositoryUserName )
374+ azureNameSpace := createAzureNameSpace (namespaceFlag , projectNameFlag )
375+
376+ prModel := & wrappers.AzurePRModel {
377+ ScanID : scanID ,
378+ ScmToken : updatedScmToken ,
379+ Namespace : azureNameSpace ,
380+ PrNumber : prNumberFlag ,
381+ Policies : policies ,
382+ APIURL : updatedAPIURL ,
383+ }
384+ prResponse , errorModel , err := prWrapper .PostAzurePRDecoration (prModel )
385+ if err != nil {
386+ return err
387+ }
388+
389+ if errorModel != nil {
390+ return errors .Errorf (errorCodeFormat , failedCreatingAzurePrDecoration , errorModel .Code , errorModel .Message )
391+ }
392+
393+ logger .Print (prResponse )
394+
395+ return nil
396+ }
397+ }
398+
399+ func validateAzureOnPremParameters (apiURL , codeRepositoryUserName string ) error {
400+ if apiURL == "" && codeRepositoryUserName != "" {
401+ log .Println (errorAzureOnPremParams )
402+ return errors .New (errorAzureOnPremParams )
403+ }
404+ return nil
405+ }
406+
407+ func createAzureNameSpace (namespace , projectName string ) string {
408+ return fmt .Sprintf ("%s/%s" , namespace , projectName )
409+ }
410+
411+ func updateScmTokenForAzure (scmTokenFlag , codeRepositoryUserName string ) string {
412+ if codeRepositoryUserName != "" {
413+ return fmt .Sprintf ("%s:%s" , codeRepositoryUserName , scmTokenFlag )
414+ }
415+ return scmTokenFlag
416+ }
417+
286418func getScanViolatedPolicies (scansWrapper wrappers.ScansWrapper , policyWrapper wrappers.PolicyWrapper , scanID string , cmd * cobra.Command ) ([]wrappers.PrPolicy , error ) {
287419 // retrieve scan model to get the projectID
288420 scanResponseModel , errorScanModel , err := scansWrapper .GetByID (scanID )
0 commit comments