
    _i@p                         d Z ddlZddlZddlZddlZddlZddlZddlZddlm	Z	 ddl
mZmZmZ ddlmZ  ej                  e      Zdee   fdZded	ee   defd
Z G d d      Zy)z@
SonarQube scanner integration for running code analysis scans.
    N)Path)DictListOptional)datetimereturnc                  2   g t               dt        ddffd} t        j                  j	                  dd      j                         }|rt        |      j                         }|j                         r@t        j                  |t        j                        r | t        |j                               n!|j                         r | t        |             t        j                         dk(  rS t        j                         }|dz  d	z  d
z  }|j                         rY|j!                  d      j                         }|r7|dk7  r2|dz  dz  dz  |z  dz  }|j                         r | t        |             |dz  dz  dz  }|j                         rIt#        |j%                         d d      D ])  }|dz  }	|	j                         s | t        |	             + t        d      |dz  dz  fD ]$  }
|
j                         s | t        |
             & S )z
    Directories to prepend to PATH so SonarJS can run `node` (cron/minimal env often omits nvm).
    Override with NODE_BIN_PATH=/path/to/dir/containing/node or explicit path to node binary.
    pr   Nc                     | rB| vr=t        |       j                         r#j                  |        j                  |        y y y y N)r   is_diraddappend)r
   dirsseens    9/home/batmin/projects/sonar_scan/src/sonarqube_scanner.pyr   z+_node_bin_directories_for_path.<locals>.add   s8    $47>>#3HHQKKKN $41    NODE_BIN_PATH Windowsz.nvmaliasdefaultutf-8encodingsystemversionsnodebinc                     | j                   S r   )name)xs    r   <lambda>z0_node_bin_directories_for_path.<locals>.<lambda>5   s
    aff r   T)keyreversez/usr/local/binz.local)setstrosenvirongetstripr   
expanduseris_fileaccessX_OKparentr   platformr   home	read_textsortediterdir)r   overrideopr2   default_aliasverbindirnvm_nodechildbextrar   r   s              @@r   _node_bin_directories_for_pathr?      s   
 D5Ds t 
 zz~~or288:H(^&&(::<BIIb"''2BIIYY[BLI%99;D6MG+i7M%%w%7==?3(?F]Z/&83>FF}}CK f}z)F2HH,,.4DdS 	EAxxzCF	
 '($/E*AB <<>E
O Kr   existing_path	node_binsc                 |    |s| S t         j                  j                  |      }| r|t         j                  z   | z   S |S r   )r(   pathsepjoin)r@   rA   prefixs      r   _path_with_node_binsrF   A   s9    ZZ__Y'F

"]22Mr   c                   0   e Zd ZdZddededefdZdee   defdZd d	ed
ede	de	de
f
dZd	ed
edede
fdZdefdZdeddfdZdedee   fdZdedee   fdZdedee
   fdZdedee   fdZdedefdZ	 	 d!dededededef
dZd	edededefdZy)"SonarQubeScannerz&Manages SonarQube scanning operations.	sonar_urlsonar_tokenscanner_pathc                    |j                  d      | _        || _        |r|j                         sd}n|j                         }t	        j
                         dk7  r0d|v s|j                  d      sd|v rt        j                  d       d}t	        j
                         dk7  rt        j                  |v xs$ t        j                  xr t        j                  |v }t        j                  j                  |      s|st        j                  |      }|r|}t        j                  d|       nt!        t"              j%                         j&                  j&                  }|dz  d	z  dz  t!        d
      t!        d      g}|D ]5  }|j)                         st+        |      }t        j                  d|        n t	        j
                         dk(  r|j                  d      st        j                  |      s|dz   }	t        j                  |	      r|	}t        j                  d|        nt!        t"              j&                  j&                  j-                         }
t+        |
dz  d	z  dz        ddt        j                  j/                  d      g}|D ]=  }t        j                  j1                  |      s#|}t        j                  d|         n || _        t	        j
                         dk(  | _        y)ak  
        Initialize SonarQube scanner.
        
        Args:
            sonar_url: SonarQube server URL
            sonar_token: SonarQube authentication token
            scanner_path: Invoke this command to run the scanner (e.g. "sonar-scanner" when in PATH).
                         Set via SONAR_SCANNER_PATH or config scanning.sonar_scanner_path.
        /sonar-scannerr   \.bat.cmdzC:z<Ignoring Windows scanner path on Linux; using sonar-scanner.z#Resolved sonar-scanner via PATH: %sr   z$/opt/sonar-scanner/bin/sonar-scannerz./usr/local/opt/sonar-scanner/bin/sonar-scannerzUsing sonar-scanner at: %s)rQ   z.exerR   rQ   zUsing Windows batch file: zsonar-scanner.batz0C:\Program Files\SonarQube\bin\sonar-scanner.batz&C:\sonar-scanner\bin\sonar-scanner.batz%~\sonar-scanner\bin\sonar-scanner.batzFound sonar-scanner at: N)rstriprI   rJ   r+   r1   r   endswithloggerinfor(   sepaltseppathisabsshutilwhichr   __file__resolver0   r-   r'   absoluter,   existsrK   
is_windows)selfrI   rJ   rK   has_sepfoundproject_root
candidatescandbat_path
script_dircommon_pathsrY   s                r   __init__zSonarQubeScanner.__init__M   s{    #))#.& !3!3!5*L'--/L ??	)|#|'<'<=M'NRVZfRfZ[. ??	)ff,Y1XryyL?XG77==.w\2#(LKK E|T#'>#9#9#;#B#B#I#IL$6>PCDMN"J
 !+ "<<>+.t9L"KK(DlS!	" ??	)(()AB||L1+f4H||H-'/&@$OP &*(^%:%:%A%A%J%J%L

_ <u DGZ Z[OEGG../WX	( %1 &D!ww~~d3/3 &.F|n,U V %	& )"//+y8r   	repo_slugr   c                     |s| j                   S |j                  dd      j                         }t        j                  j                  d| d      j                         }|r|S | j                   S )u,  
        Token for sonar-scanner only (SONAR_TOKEN).
        Per-repo SONARQUBE_TOKEN_<SLUG> is for SonarQube *project analysis* tokens (sqp_...).
        Server REST calls use self.sonar_token (user/global SONARQUBE_TOKEN) — project tokens cannot manage projects or read all metrics APIs.
        -_SONARQUBE_TOKEN_r   )rJ   replaceupperr(   r)   r*   r+   )rb   rl   suffixr6   s       r   _token_for_slugz SonarQubeScanner._token_for_slug   sf     ###""3,224::>>$4VH"=rBHHJ#x9)9)99r   	repo_pathsonar_properties_pathmax_retriesretry_delayc           
         t         j                  d|j                          d}t        |dz         D ]  }|dkD  r?|d|dz
  z  z  }t         j                  d| d| d| d	       t	        j
                  |       | j                  |||j                        }|j                  d
      r|c S |j                  dd      }d|v sd|v r%||k  r t         j                  d|d|z  z   d	       |c S  |r|S dddS )a  
        Run SonarQube scan on a repository with retry logic for concurrent scan errors.
        
        Args:
            repo_path: Path to repository directory
            sonar_properties_path: Path to sonar-project.properties file
            max_retries: Maximum number of retries for concurrent scan errors (default: 3)
            retry_delay: Initial delay in seconds between retries (default: 60, doubles each retry)
            
        Returns:
            Dictionary with scan results including:
            - success: bool
            - task_id: str (if available)
            - analysis_id: str (if available)
            - error: str (if failed)
        zStarting SonarQube scan for N   r      zRetry attempt rM   z after waiting z seconds...successerrorr   1Another SonarQube analysis is already in progressalready in progressz/Another analysis is in progress. Will retry in FzScan failed after all retriesr|   r}   )	rU   rV   r!   rangetimesleep_execute_scanr*   warning)	rb   ru   rv   rw   rx   resultattempt	wait_time	error_msgs	            r   run_scanzSonarQubeScanner.run_scan   s   " 	29>>2BCD[1_- 	G{'11+=>	nWIQ{m?S\R]]hij

9%''	3H)..YF zz)$ 

7B/ICyP%2+8M!PQ\`ael`lQmPnnyz{ M)	.  vaA`%aar   c                    	 |dz  }|j                  dd       |dz  }|j                  dd       t        j                  d|        | j                  |       t        j
                  j                         }| j                  |      |d<   t               }|r8t        |j                  dd      |      |d<   t        j                  d	|d
d        t        t              j                  j                  dz  }|j                  dd       t        |j                               |d<   t        j                  d|        t	        j                          }	t	        j"                  |       	 | j$                  rA| j&                  j)                  d      r&t+        j,                  | j&                  dd|dd      }
n%t+        j,                  | j&                  gdd|d      }
|
j.                  dk(  r~t        j1                  d|j2                          | j5                  |
j6                        }| j9                  |
j6                        }d|||
j6                  dt	        j"                  |	       S |
j:                  xs |
j6                  }t        j=                  d|j2                   d|        d|v sd|v r/t        j?                  d       t        j1                  d       |dz  }d|v sd|v sd|v r_t        j?                  d       |dz  }|j                  dd       |dz  }|j                  dd       t        j1                  d        |d!z  }d"|v sd#|v rt        j?                  d$       | jA                         }|rt        j1                  d%       |d&z  }nOt        j?                  d'       t        j?                  d(t        jB                  jE                  d)              |d*z  }d+||
j6                  d,t	        j"                  |	       S # t	        j"                  |	       w xY w# t*        jF                  $ r* t        j=                  d-|j2                          d+d.d/cY S tH        $ r}d0| j&                   }t        j=                  |       t        j=                  d1       t        j=                  d2       t        j=                  d3       t        j=                  d4       d+|d/cY d
}~S d
}~wtJ        $ r}t        |      }t        j=                  d5|        d"|v sd#|v rt        j?                  d$       | jA                         }|rt        j1                  d%       |d&z  }nOt        j?                  d'       t        j?                  d(t        jB                  jE                  d)              |d*z  }d+|d/cY d
}~S d
}~ww xY w)6aa  
        Execute a single SonarQube scan attempt.
        
        Args:
            repo_path: Path to repository directory
            sonar_properties_path: Path to sonar-project.properties file
            repo_slug: Directory name / repo slug (for per-repo SONARQUBE_TOKEN_<SLUG>)

        Returns:
            Dictionary with scan results
        z.scannerworkT)parentsexist_okz	.sonartmpz(Ensured scanner work directories exist: SONAR_TOKENPATHr   z/Prepended Node bin dirs to PATH for SonarJS: %sN   z.sonar_cacheSONAR_USER_HOMEz!Using SonarQube cache directory: rP   i  )capture_outputtextenvtimeoutshell)r   r   r   r   r   z*SonarQube scan completed successfully for )r|   task_idanalysis_idoutputzSonarQube scan failed for : r~   r   zBAnother SonarQube analysis is already in progress for this projectzKThis usually means a previous scan is still running on the SonarQube serverz<

Another analysis is in progress. Will retry automatically.zFailed to create temp fileNoSuchFileExceptionzHDetected temp file creation error, ensuring scanner directories exist...z8Scanner work directories created. Please retry the scan.z5

Scanner directories created. Please retry the scan.AccessDeniedExceptionzFailed to extract archivezGDetected cache/permission error, attempting to clear SonarQube cache...z2Cache cleared successfully. Please retry the scan.z'

Cache cleared. Please retry the scan.z;Could not clear cache automatically. Please manually clear:z  ~/.sonar/cachez7

Please clear the SonarQube cache directory and retry.F)r|   r}   r   zSonarQube scan timed out for zScan timed out after 30 minutesr   zSonarQube scanner not found: z4Please ensure sonar-scanner is installed and either:z"  1. Added to your system PATH, orzH  2. Set the 'scanning.sonar_scanner_path' config value to the full pathz<   Example: C:\Program Files\SonarQube\bin\sonar-scanner.batz(Unexpected error during SonarQube scan: )&mkdirrU   debug_update_sonar_propertiesr(   r)   copyrt   r?   rF   r*   r   r]   r0   r'   r_   getcwdchdirra   rK   rT   
subprocessrun
returncoderV   r!   _extract_task_idstdout_extract_analysis_idstderrr}   r   _clear_sonar_cacherY   r,   TimeoutExpiredFileNotFoundError	Exception)rb   ru   rv   rl   scannerwork_dirsonartmp_dirr   rA   project_cache_diroriginal_cwdr   r   r   r   cache_clearedes                   r   r   zSonarQubeScanner._execute_scan   sB   Q	'.8O!!$!> +[8Ltd;LLCOCTUV ))*?@ **//#C!%!5!5i!@C68I2377623F	RFNPYZ\[\P]^ !%X 5 5 < <~ M##D4#@%():)C)C)E%FC!"LL<=N<OPQ 99;LHHYK' ??t'8'8'A'ABR'S'^^))'+! $"F (^^**+'+! $F $$)KK"LY^^L\ ]^ #33FMMBG"&";";FMM"JK $(#*'2"(--	X &K !' >ILL#=inn=MRPY{![\ KiW[pt}[}'kl$qr!%ee	 4y@DY]fDfju  zC  kC'qr*3n*D'--dT-J'6'D$**4$*G$^_!%^^	 /);?Z^g?g'pq(,(?(?(A("KK(\]%)TTI"NN+hi"NNR0B0BCS0T/U+VW%)ddI $)!*"(-- &&(( 	LL88HIJ :  ! 
	78I8I7JKILL#LLOPLL=>LLcdLL[] "   	AILLCA3GH ')37RV_7_hi $ 7 7 9 KK TU!LLINN#`aNNR(:(:;K(L'M#NO!\\I !" !	s^   E%P  (C)P P  'F	P 0P  PP   :V=V=$A<S& V=&V=2C V82V=8V=c                 
   	 t         j                  j                  d      }t         j                  j                  |      rCt        j                  d|        t        j                  |       t        j                  d       yt        j                  d       y# t        $ r7}t        j                  d|        t        j                  d       Y d}~y	d}~wt        $ r"}t        j                  d
|        Y d}~y	d}~ww xY w)z
        Clear SonarQube cache directory to fix permission/corruption issues.
        
        Returns:
            True if cache was cleared successfully, False otherwise
        r   z$Clearing SonarQube cache directory: z$SonarQube cache cleared successfullyTz(SonarQube cache directory does not existz.Permission denied when trying to clear cache: zJPlease manually clear the cache directory or run with elevated permissionsNFz!Failed to clear SonarQube cache: )r(   rY   r,   r`   rU   rV   r[   rmtreer   PermissionErrorr}   r   )rb   	cache_dirr   s      r   r   z#SonarQubeScanner._clear_sonar_cacheq  s    	**+;<Iww~~i(B9+NOi(BCGH 	LLI!MNLLef 	LL<QC@A	s*   B B B 	D"-CD C==Dproperties_pathNc                 |   	 t        |d      5 }|j                         }ddd       g }j                  d      D ]F  }|j                         }|j	                  d      s|j	                  d      r6|j                  |       H dj                  |      }d|v r%d	dl}|j                  d
d| j                   |      }n|d| j                   dz  }t        |d      5 }|j                  |       ddd       y# 1 sw Y   xY w# 1 sw Y   yxY w# t        $ r"}t        j                  d|        Y d}~yd}~ww xY w)z
        Update sonar-project.properties with server URL and token.
        
        Args:
            properties_path: Path to sonar-project.properties file
        rNT)keependszsonar.tokenzsonar.loginr   zsonar.host.urlr   zsonar\.host\.url\s*=.*zsonar.host.url=z
sonar.host.url=
wz+Failed to update sonar-project.properties: )openread
splitlinesr+   
startswithr   rD   resubrI   writer   rU   r   )	rb   r   fcontentfilteredlinesr   r   s	            r   r   z)SonarQubeScanner._update_sonar_properties  sB   #	Nos+ #q&&(# H**D*9 &JJL<<.!,,}2M%	&
 ggh'G  7*&&-%dnn%56 .t~~.>bAA os+ !q ! !;# #:! !  	NNNHLMM	NsL   D C8B>D D/D 8D=D D	D D 	D;D66D;r   c                     ddl }|j                  d||j                        }|r|j                  d      S |j                  d|      }|r|j                  d      S y)z
        Extract task ID from scanner output.
        
        Args:
            output: Scanner output text
            
        Returns:
            Task ID string or None
        r   Nz#EXECUTION SUCCESS.*?taskId=([^\s]+)rz   ztaskId[=:]\s*([^\s\n]+))r   searchDOTALLgrouprb   r   r   matchs       r   r   z!SonarQubeScanner._extract_task_id  sV     			@&"))T;;q>! 		4f=;;q>!r   c                 V    ddl }|j                  d|      }|r|j                  d      S y)z
        Extract analysis ID from scanner output.
        
        Args:
            output: Scanner output text
            
        Returns:
            Analysis ID string or None
        r   NzanalysisId[=:]\s*([^\s\n]+)rz   )r   r   r   r   s       r   r   z%SonarQubeScanner._extract_analysis_id  s,     			8&A;;q>!r   project_keyc                    	 dd| j                    i}| j                   d}d|i}t        j                  |||d      }|j	                          |j                         }| j                   d}|dd	}t        j                  |||d      }	|	j                  d
k(  r|	j                         ni }
|j                  di       j                  dd      |
j                  di       j                  dg       t        j                         j                         dS # t        $ r"}t        j                  d|        Y d}~yd}~ww xY w)a  
        Get scan results from SonarQube API (uses global SONARQUBE_TOKEN, not project analysis tokens).

        Args:
            project_key: SonarQube project key

        Returns:
            Dictionary with scan metrics and quality gate status
        AuthorizationBearer z /api/qualitygates/project_status
projectKey   paramsheadersr   z/api/measures/componentzHcoverage,bugs,vulnerabilities,code_smells,duplicated_lines_density,ncloc)	component
metricKeys   projectStatusstatusUNKNOWNr   measures)quality_gatemetrics	timestampz/Failed to get scan results from SonarQube API: N)rJ   rI   requestsr*   raise_for_statusjsonstatus_coder   now	isoformatr   rU   r}   )rb   r   r   
status_urlr   responsestatus_datametrics_urlmetrics_paramsmetrics_responsemetrics_datar   s               r   get_scan_resultsz!SonarQubeScanner.get_scan_results  sH   	&'$2B2B1C(DEG NN++KLJ"K0F||JvwXZ[H%%'"--/K "^^,,CDK(hN
  (||%	  7G6R6RVY6Y+002_aL !, D H HS\ ]'++K<@@RP%\\^557 
  	LLJ1#NO	s   DD 	D>D99D>c                 N   	 t        |d      5 }|D ]O  }|j                         j                  d      s#|j                  dd      d   j                         c cddd       S  	 ddd       y# 1 sw Y   yxY w# t        $ r"}t
        j                  d|        Y d}~yd}~ww xY w)z
        Extract project key from sonar-project.properties.
        
        Args:
            properties_path: Path to sonar-project.properties file
            
        Returns:
            Project key string or None
        r   zsonar.projectKey=rz   NzFailed to read project key: )r   r+   r   splitr   rU   r   )rb   r   r   r   r   s        r   get_project_keyz SonarQubeScanner.get_project_key  s    	?os+ =q =Dzz|../AB#zz#q1!4::<<= === =   	?NN9!=>>	?sE   A9 %A-$A-	A9 "A-$A9 -A62A9 6A9 9	B$BB$c                    	 | j                    d}d|i}dd| j                   i}t        j                  |||d      }|j	                          |j                         }|j                  dg       }|D ]1  }|j                  d      |k(  st        j                  d	| d
        y t        j                  d	| d       y# t        j                  $ r@}	|	j                  j                  dk(  rY d}	~	yt        j                  d|	        Y d}	~	yd}	~	wt        $ r"}	t        j                  d|	        Y d}	~	yd}	~	ww xY w)z
        Check if a SonarQube project exists (uses global SONARQUBE_TOKEN).

        Args:
            project_key: SonarQube project key

        Returns:
            True if project exists, False otherwise
        z/api/projects/searchprojectsr   r   r   r   
componentsr$   	Project 'z' exists in SonarQubeTz' not found in SonarQubeFi  Nz"Error checking project existence: z#Failed to check if project exists: )rI   rJ   r   r*   r   r   rU   r   	HTTPErrorr   r   r   r   )
rb   r   urlr   r   r   datar   r   r   s
             r   check_project_existsz%SonarQubeScanner.check_project_exists  s/   	^^$$89C +.F&'$2B2B1C(DEG||CQSTH%%'==?D,3J (  	==';6LL9[M9N!OP 
 LL9[M1IJK!! 	zz%%,NN?sCD 	NN@DE	s6   BC C &C D=D2DD=D88D=project_namedescription
visibilityc                 "   	 | j                    d}dd| j                   i}|||d}|r||d<   t        j                  |||d      }|j                  dk(  rt
        j                  d	| d
| d       y|j                  dk(  rT|j                  }	d|	j                         v rt
        j                  d| d       yt
        j                  d| d|	        y|j                          y# t        j                  $ rg}
t
        j                  d| d|
        t        |
j                  d      r,t
        j                  d|
j                  j                          Y d}
~
yd}
~
wt         $ r%}
t
        j                  d| d|
        Y d}
~
yd}
~
ww xY w)a  
        Create a new project in SonarQube (uses global SONARQUBE_TOKEN).

        Args:
            project_key: SonarQube project key (must be unique)
            project_name: Display name for the project
            description: Project description (optional)
            visibility: Project visibility - "private" or "public" (default: private)

        Returns:
            True if project was created successfully, False otherwise
        z/api/projects/creater   r   )projectr!   r  r   r   )r   r   r   r   z(Successfully created SonarQube project: z ()Ti  zalready existsr   z' already exists in SonarQubezFailed to create project 'z': Fz'HTTP error creating SonarQube project 'r   z
Response: Nz$Failed to create SonarQube project ')rI   rJ   r   postr   rU   rV   r   lowerr   r   r   r}   hasattrr   r   r   )rb   r   r   r   r  r   r   r   r   
error_textr   s              r   create_projectzSonarQubeScanner.create_project@  s   &$	^^$$89C&'$2B2B1C(DEG '$(D &1]#}}StWbQH##s*F{mSUVbUccdef%%,%]]
#z'7'7'99KK)K=8U VW!;K=J<XY))+!! 	LLB;-sSTRUVWqzz6*z!**//):;< 	LL?}CPQsST	s8   A1C( 4AC( ;C( C( (F;AEF)F		Fc                 D   |dz  }	 d| d| d| j                    d}t        |dd      5 }|j                  |       d	d	d	       t        j	                  d
|j
                   d|        |S # 1 sw Y   0xY w# t        $ r}t        j                  d|         d	}~ww xY w)aN  
        Generate sonar-project.properties file with default values.
        
        Args:
            repo_path: Path to repository directory
            project_key: SonarQube project key
            project_name: Project display name
            
        Returns:
            Path to created sonar-project.properties file
        zsonar-project.propertiesz{# SonarQube Project Properties
# Auto-generated by SonarQube Scanner Automation

# Project identification
sonar.projectKey=z
sonar.projectName=zL

# Source code location
sonar.sources=.

# SonarQube server
sonar.host.url=z(

# Encoding
sonar.sourceEncoding=UTF-8
r   r   r   Nz'Generated sonar-project.properties for r   z-Failed to generate sonar-project.properties: )rI   r   r   rU   rV   r!   r   r}   )rb   ru   r   r   r   properties_contentr   r   s           r   generate_sonar_propertiesz*SonarQubeScanner.generate_sonar_propertiesy  s     $&@@	&  . !   "$ osW= ,*+, KKA)..AQQSTcSdef""	, ,
  	LLHLM	s-   $A8 A,.A8 ,A51A8 8	BBB)rN   )r   <   )r   private)__name__
__module____qualname____doc__r'   rk   r   rt   r   intr   r   r   boolr   r   r   r   r   r   r   r	  r   r   r   rH   rH   J   s   0J9# J9C J9s J9X
:# 
:3 
:+b$ +bt +bRU +bhk +buy +bZ]t ]D ]UX ]]a ]~D 2*N *N *NXs x} ,3 8C=  )C )HTN )Vt  &$ $ $T #77 7 	7
 7 
7r)4 )c )Y\ )ae )r   rH   )r  r(   r   loggingr   r   r[   r1   pathlibr   typingr   r   r   r   	getLoggerr  rU   r'   r?   rF   rH   r  r   r   <module>r     sz    
        ' ' 			8	$,S	 ,^ S	 c X	 X	r   