
    _iD                     6   d 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 ddlm	Z	m
Z
mZ 	 ddlmZ ddlmZ ddlmZ dd	lmZ dd
lmZ ddlmZ  ej2                  ej4                  d ej6                  ej8                        g        ej:                  e      Zdede
e	   fdZ dede
e	   de
e	   ddfdZ!	 d#de"de
e	   de
e#   de
e#   dee$   de$fdZ%dede"de
e	   de
e#   de
e#   de$fdZ&de$deddfdZ'd$dZ(ed k(  r		  e(        yy# e$ r' ddlmZ ddlmZ ddlmZ dd	lmZ dd
lmZ ddlmZ Y w xY w# e)$ r& ejU                  d!        ejV                  d       Y ye,$ r0Z-ej]                  d"e-         ejV                  d       Y dZ-[-ydZ-[-ww xY w)%u   
scan_recent.py — Pull & scan repos with commits in the last N days.
Appends a structured run entry to scanned.md in the project root after every run.
    N)Path)datetime)DictListOptional)ConfigManager)BitbucketClient)
GitManager)SonarQubeScanner)ReportGenerator)maybe_send_scan_summary_email   4%(asctime)s - %(name)s - %(levelname)s - %(message)s)levelformathandlersconfigreturnc                    | j                  dg       }|sg S g }|D ]N  }t        |t              rH|j                  d      rf|j	                  dd      j	                  dd      }|j                  d      }t        |      dk(  sh|\  }}|j                  ||d|idd	|d
gid       |j                  d      rk|j	                  dd      j	                  dd      }|j                  d      }t        |      dk(  s|\  }}|j                  ||d|idd	|d
d|d
gid       d|v s|j                  d      }t        |      dk(  s/|\  }}d| d| d}	|j                  ||d|idd	|	d
gid       ]t        |t              so|j                  d      xs |j                  d      }|j                  d      }|j                  d      xs |j                  d      }	|s|	st        |t              rd|i}nJ|sHd|	v rD|	j	                  dd      j	                  dd      }d|v r|j                  d      d   nd}
|
rd|
ini }|j                  |||xs i dd	|	d
gid       Q |S )z@Get additional repositories from config (same logic as main.py).z scanning.additional_repositorieszgit@bitbucket.org: .git/   slugclonessh)namehref)r   r   	workspacelinkszhttps://github.com/httpsr   r   ssh_url	clone_urlr   N)	get
isinstancestr
startswithreplacesplitlenappenddict)r   additional_repos_configreposrepo_configpathpartsr   	repo_nameorgr"   ws_names              3/home/batmin/projects/sonar_scan/src/scan_recent.py_get_additional_repositoriesr6   $   s   $jj)KRP"	E. 8k3'%%&:;"**+?DLLVUWX

3u:?+0(IyLL ) )&,i%8")UK,P+Q!R	"  ''(=>"**+@"EMMfVXY

3u:?%*NCLL ) )&,c]#). D)0+ F&"	
" 
 ##))#.u:?+0(Iy 29+QykNGLL ) )&,i%8")UG,L+M!N	"  T*#/J;??63JI#4I!ooi0PKOOK4PGWi-!' 3I"';w'F"??+?DLLVUWXD474Kdjjoa0TG5< 1"I%%!*b%(H'IJ	 g8r L    	all_repos
to_processc                    | j                  d      xs g }t        |t              r=|j                  d      D cg c]#  }|j	                         s|j	                         % }}|sy|D ch c]H  }|j                  d      s|j                  d      r$|j                  d      xs |j                  d      J }}|D ]  }|r||v r
|D ]g  }|j                  d      xs |j                  d      }	|	|k(  s-|j                  |       |j                  |       t        j                  d|         u t        j                  d|        yc c}w c c}w )zkEnsure listed slugs are pulled/scanned even if commit-window logic skipped them (must appear in all_repos).z#scanning.scan_recent_always_include,Nr   r   z-Including repo (scan_recent_always_include): uc   scan_recent_always_include: slug %r not in repo list — add scanning.additional_repositories entry)
r$   r%   r&   r)   stripr+   addloggerinfowarning)
r   r8   r9   rawsrseenr   	repo_inforslugs
             r5   !_merge_scan_recent_always_includerG   g   s:    **:
;
ArC#s"%))C.>QAGGIqwwy>> 55=AEE&M 
v	'!%%-	'D 
  tt|" 	IMM&)BY]]6-BE}!!),KD6RS	 NNu ?s   E
E
*AEdayspull_resultsscan_resultsrun_tsc                    |#t        j                         j                  d      }t        d |D              }t        d |D              }d}|D ]!  \  }}	}
|	rdnd}|	rdn|
}|d| d| d| d	z  }# d}|D ]>  \  }}	}
}|	rdnd}|	rdn|
}|rt	        |      j
                  nd}|d| d| d| d| d		z  }@ d
| d|  dt        |       d| d| dt        |       d| d| dt        |       dS )zKMarkdown block matching scanned.md run sections (also used for email body).%Y-%m-%d %H:%M:%Sc              3   .   K   | ]  \  }}}|s
d   ywr   N ).0_oks      r5   	<genexpr>z/_format_scan_summary_section.<locals>.<genexpr>   s     72qB!7s   c              3   0   K   | ]  \  }}}}|sd   ywrO   rP   )rQ   rR   rS   _rs       r5   rT   z/_format_scan_summary_section.<locals>.<genexpr>   s     ;2q"!;s   r   u   ✅ OKu   ❌ FAILz| z | z |
u   ## Run — z

**Repos with commits in last z	 days:** z?

### Git Pulls

| Repository | Status | Notes |
|---|---|---|
z
**Pulled: z / zT**

### SonarQube Scans

| Repository | Status | Report | Notes |
|---|---|---|---|
z
**Scanned: z	**

---

)r   nowstrftimesumr   r   r*   )rH   r9   rI   rJ   rK   pull_okscan_ok	pull_rows	repo_slugrS   msgstatusnotes	scan_rowsreport_pathreport_cells                   r5   _format_scan_summary_sectionrd      si    ~(()<=7L77G;;;GI* ?	2sZcr)Cxs5'>>	?
 I+7 O'	2sKZc0;d;',,r)Cxs;-s5'NN		O 6( #"V9S_,= >  )3s<() *  9CL)* +! r7   project_rootc                 T   | dz  }t        j                         j                  d      }t        |||||      }|j	                          }t        |dd      5 }	|r|	j                  d       |	j                  |       ddd       t        j                  d|        |S # 1 sw Y   #xY w)	zJAppend a new run section to scanned.md; return the section text for email.z
scanned.mdrM   autf-8encodingz# Scan History

NzAppended run entry to )	r   rW   rX   rd   existsopenwriter>   r?   )
re   rH   r9   rI   rJ   
scanned_mdrK   section	first_runfs
             r5   _append_to_scanned_mdrr      s     ,J\\^$$%89F*4\<Y_`G%%''I	j#	0 AGG()	
 KK(56N s   %BB'ro   log_filec                 z    d|  d| d}t        j                         j                  d      }t        d| |       y)z3Notify recipients when SMTP_REPORT_* is configured.zSonar scan_recent summary

z
Full log: 
rM   z[Sonar] scan_recent finished N)r   rW   rX   r   )ro   rs   bodytss       r5   _send_summary_emailrx      s?    *7)<zLD		 	 !4	5B!$A""FMr7   c            	         t        t              j                         j                  j                  } t	        |       t
        j                  vr)t
        j                  j                  dt	        |              t        j                  |        | dz  }|j                  dd       t        j                         j                  d      }|d| dz  }t        j                  |d	      }|j!                  t        j"                  d
             t        j$                         j'                  |       t(        j+                  d       	 t-               }j5                  d      s*t(        j1                  d       t        j2                  d       t7        |j5                  d            }t7        |j5                  d            }|j5                  d      s*t(        j1                  d       t        j2                  d       |s,|s*t(        j1                  d       t        j2                  d       |j5                  d      s*t(        j1                  d       t        j2                  d       ddl}	t        |j5                  d      xs d      }
i }|
j;                         r"	 |	j=                  |
j?                  d	            }|j5                  d      xs d}|j5                  d      }t        |j5                  d            }tA        ||j5                  d      |s|j5                  d      nd|j5                  d            }tC        |j5                  d            }tE        |j5                  d      |j5                  d      |j5                  d       !      }tG        |j5                  d"      #      }|jI                         xs g }tK        |      }|r3t(        j+                  d$tM        |       d%       |jO                  |       |s1t(        jQ                  d&       tS        | |g g g       }tU        ||       yg }|D ]  }|j5                  d'      xs |j5                  d(      }|s*|j5                  d)      xs i j5                  d(      }|r/||k7  r*|jW                  |       t(        j+                  d*|        |jY                  ||+      s|jW                  |       t(        j+                  d,| d-|         t[        |||       t(        j+                  d.tM        |              g }|D ]  }|j5                  d'      xs |j5                  d(      }|j]                  |d/0      }|s.t(        jQ                  d1| d2       |jW                  |d3d4f       j	 |j_                  ||5      }|xr ta        |tb              xr |d   }|jW                  |||rd6nd7f       |rt(        j+                  d8|        nt(        jQ                  d9|         g }|D ]  }|j5                  d'      xs |j5                  d(      }||z  }|j;                         r|d<z  j;                         s|jW                  |d3d=df       f|jg                  |      } | s|j5                  d)i       j5                  d(      xs |}!|! d>| ji                         jk                  d?d>      jk                  d@d>      }"tm        jn                  dAd>|"      }"|j5                  d'      xs |}#	 |jq                  ||"|#B      } |js                  |       }"|"s|jW                  |d3dDdf       >|ju                  |"      s?|j5                  d'      xs |}#|j5                  dEdF      xs dF}$|jw                  |"|#|$G       	 |jy                  ||       }|j5                  dHd3      }%|%s|j5                  dIdF      ndF}&d}'|%r&ddl=}(|(j}                  dJ       |j                  |"      }'d})	 |j                  |||'|j5                  dK      L      })t(        j+                  dM|)        t        j                         j                         |%|)rt	        |)      nddO||<   	 |
j                  |	j                  |dPQ      d	       |jW                  ||%|&xs |%rd6ndS|)rt	        |)      ndf       |%rt(        j+                  dT|        nt(        jQ                  dU| dV|&         tS        | ||||      }tU        ||       t(        j+                  dX|        y# t.        $ r8}t(        j1                  d|        t        j2                  d       Y d}~"d}~ww xY w# t.        $ r Y w xY w# t.        $ rC}t(        je                  d:| d;|        |jW                  |d3t	        |      f       Y d}~d}~ww xY w# t.        $ rD}t(        j1                  dC| d;|        |jW                  |d3t	        |      df       Y d}~d}~ww xY w# t.        $ r&}t(        j1                  dN| d;|        Y d}~d}~ww xY w# t.        $ r#}t(        jQ                  dR|        Y d}~d}~ww xY w# t.        $ rD}t(        je                  dW| d;|        |jW                  |d3t	        |      df       Y d}~d}~ww xY w)YzNPull and scan repos with commits in last N days; append results to scanned.md.r   logsT)parentsexist_okz%Y%m%d_%H%M%Sscan_recent_z.logrh   ri   r   z8Starting scan_recent (repos with commits in last N days)zFailed to load config: r   Nzbitbucket.workspacez"Bitbucket workspace not configuredzbitbucket.api_tokenzbitbucket.app_passwordzbitbucket.usernamez'Bitbucket username/email not configuredzHBitbucket authentication not configured (need API token or app password)zsonarqube.tokenzSonarQube token not configuredzscanning.history_filezscan_history.jsonzscanning.weekly_scan_days   zscanning.repos_dir)r   usernameapp_password	api_token)	repos_dirzsonarqube.urlzscanning.sonar_scanner_path)	sonar_urlsonar_tokenscanner_pathzscanning.reports_dir)reports_dirzFound z$ additional repositories from configzNo repositories foundr   r   r   z-Including additional repo (no commit check): )rH   z Including repo (commits in last z days): zRepos to pull and scan: r   )protocolzNo SSH clone URL for z
, skippingFzNo SSH clone URL)r]   r#   OKzPull failedz	Pull OK: zPull failed: zPull error for z: r   zRepo missing or not a git reporR   - z
[^a-z0-9_])	repo_pathproject_keyproject_namez0Failed to generate sonar-project.properties for z*No project key in sonar-project.propertiesdescriptionr   )r   r   r   successerror   output)r2   rJ   sonar_resultsscan_outputzReport generated: zFailed to generate report for )	last_scanr   rb   r   )indentzFailed to save scan history: zScan failedz	Scan OK: zScan failed: z - zScan error for zscan_recent finished. Log: )Dr   __file__resolveparentr&   sysr0   insertoschdirmkdirr   rW   rX   loggingFileHandlersetFormatter	Formatter	getLogger
addHandlerr>   r?   r   	Exceptionr   exitr$   booljsonrk   loads	read_textr	   r
   r   r   get_all_repositoriesr6   r*   extendr@   rr   rx   r+   has_commits_in_last_n_daysrG   get_clone_urlclone_or_updater%   tuple	exceptionget_sonar_properties_pathlowerr(   resubgenerate_sonar_propertiesget_project_keycheck_project_existscreate_projectrun_scantimesleepget_scan_resultsgenerate_report	isoformat
write_textdumps)*re   log_dirlog_tsrs   file_handlerr   ehas_api_tokenhas_app_passwordr   history_filescan_historyrH   main_workspacer   	bitbucketgit_managersonar_scannerreport_generatorr.   
additionalro   r9   rE   r]   ws_slugrI   r#   resultrS   rJ   r   sonar_props_pathr   r   r   r   r   errr   r   rb   s*                                             r5   run_scan_recentr      s	   >))+2299L
<(3|,-HH\ V#GMM$M.\\^$$_5F<xt44H&&x'BLg//0fgh""<0
KKJK ::+,9:$9:;MFJJ'?@A::*+>?!1_`::'(56

#:;R?RSLL	::l&<&<g&<&NOL ::127aDZZ 56NVZZ 456I   01ANVZZ 89TX**23	I vzz2F'GHK$**_-JJ01ZZ =>M
 '6::>T3UV **,2E-f5JfS_--QRSZ ./'dBBGGX. J V	MM&)BY]]6-B	==-388@w.0i(KKG	{ST11)$1Gi(KK:4&TUV &feZ@
KK*3z?*;<= L <	MM&)BY]]6-B	++I+F	NN29+ZHIE3E FG
	< 009PY0ZFEZ6D6!9BB NOi	{34yk:;<( L PB	MM&)BY]]6-B		)	!)f*<)D)D)FE3SUY Z[&@@K!k26::6BTnI&Kq4::<DDS#NVVWZ\_`K&&[AK$==0=IL	#0#J#J' +!- $K $  $334DEE3_ae fg11+>$==0=IL#--r:@bK((')' ) *	B"++I7GHFjjE2G18&**Wb)bC !M

1 - > >{ KK	P.>>'!'"/ &

8 4	 ?  0>? &\\^557"3>s;/D'L#
D''

<
(JU\'] GS5`WTR_  wBbefqbr  HL  !M  Ni	{34ykSEBC[PBd $L$
LR^_G*
KK-hZ89s  .qc234  		J  	<ykA3?@E3q6 :;;	<2  OPY{Z\]^\_`a##Ys1vt$DEP  P=i[1#NOOP  D!>qcBCCD  	BykA3?@E3q64 @AA	Bs   
d3 !e7 A9fgA&j<h&6j8$iA j3	e4<-e//e47	ff	g8gg	h#9hh#&	i/i
jij	j!i?9j?jj	k9kk__main__zInterrupted by userzFatal error: )N)r   N)/__doc__r   r   r   r   pathlibr   r   typingr   r   r   config_managerr   bitbucket_clientr	   r   r
   sonarqube_scannerr   r   r   scan_report_emailr   ImportErrorbasicConfigINFOStreamHandlerstdoutr   __name__r>   r6   rG   intr   r&   rd   rr   rx   r   KeyboardInterruptr?   r   r   r   r   rP   r7   r5   <module>r      s)   
 	 
    ' 'A,0&20?   
,,A#g##CJJ/0
 
		8	$@ @4: @FDz T
 
	L !/
/T
/ u+/ u+	/
 SM/ 	/d
 T
 u+	
 u+ 	,N N N NN:b z i  A-1'31@An  )* =,-s/   $D D: )D76D7:(F$F(&FF