11# Copyright SUSE LLC 
22# SPDX-License-Identifier: MIT 
33from  argparse  import  Namespace 
4- from  typing  import  Any , Dict , List , Tuple , Optional 
4+ from  typing  import  Any , Dict , List , Tuple , Optional , Set , NamedTuple 
5+ import  re 
56from  logging  import  getLogger 
67from  pprint  import  pformat 
78
1011
1112from  openqabot .openqa  import  openQAInterface 
1213
13- from  . import  OBS_GROUP , OBS_URL 
14+ from  .errors  import  PostOpenQAError 
15+ from  .utils  import  retry10  as  requests 
16+ from  . import  OBS_GROUP , OBS_URL , OBS_DOWNLOAD_URL 
1417
1518log  =  getLogger ("bot.increment_approver" )
1619ok_results  =  set (("passed" , "softfailed" ))
1720final_states  =  set (("done" , "cancelled" ))
1821
1922
23+ class  BuildInfo (NamedTuple ):
24+     distri : str 
25+     product : str 
26+     version : str 
27+     flavor : str 
28+     arch : str 
29+     build : str 
30+ 
31+     def  __str__ (self ):
32+         return  f"{ self .product } { self .version } { self .build } { self .arch } { self .flavor }  
33+ 
34+ 
2035class  IncrementApprover :
2136    def  __init__ (self , args : Namespace ) ->  None :
2237        self .args  =  args 
@@ -35,11 +50,11 @@ def _find_request_on_obs(self) -> Optional[osc.core.Request]:
3550                OBS_GROUP ,
3651                args .obs_project ,
3752            )
38-             requests  =  osc .core .get_request_list (
53+             obs_requests  =  osc .core .get_request_list (
3954                OBS_URL , project = args .obs_project , req_state = relevant_states 
4055            )
4156            relevant_request  =  None 
42-             for  request  in  sorted (requests , reverse = True ):
57+             for  request  in  sorted (obs_requests , reverse = True ):
4358                for  review  in  request .reviews :
4459                    if  review .by_group  ==  OBS_GROUP  and  review .state  in  relevant_states :
4560                        relevant_request  =  request 
@@ -54,31 +69,40 @@ def _find_request_on_obs(self) -> Optional[osc.core.Request]:
5469            )
5570        else :
5671            log .debug ("Found request %s" , relevant_request .id )
72+             if  hasattr (relevant_request .state , "to_xml" ):
73+                 log .debug (relevant_request .to_str ())
5774        return  relevant_request 
5875
59-     def  _request_openqa_job_results (self ) ->  Dict [str , Dict [str , Dict [str , Any ]]]:
60-         log .debug ("Checking openQA job results" )
61-         args  =  self .args 
62-         params  =  {"distri" : args .distri , "version" : args .version , "flavor" : args .flavor }
76+     def  _request_openqa_job_results (
77+         self , build_info : BuildInfo 
78+     ) ->  Dict [str , Dict [str , Dict [str , Any ]]]:
79+         log .debug ("Checking openQA job results for %s" , build_info )
80+         params  =  {
81+             "distri" : build_info .distri ,
82+             "version" : build_info .version ,
83+             "flavor" : build_info .flavor ,
84+             "arch" : build_info .arch ,
85+             "build" : build_info .build ,
86+         }
6387        res  =  self .client .get_scheduled_product_stats (params )
6488        log .debug ("Job statistics:\n %s" , pformat (res ))
6589        return  res 
6690
67-     def  _are_openqa_jobs_ready (self , res : Dict [str , Dict [str , Dict [str , Any ]]]) ->  bool :
68-         args  =  self .args 
91+     def  _check_openqa_jobs (
92+         self , res : Dict [str , Dict [str , Dict [str , Any ]]], build_info : BuildInfo 
93+     ) ->  Optional [bool ]:
6994        actual_states  =  set (res .keys ())
7095        pending_states  =  actual_states  -  final_states 
7196        if  len (actual_states ) ==  0 :
7297            log .info (
73-                 "Skipping approval, there are no relevant jobs on openQA for %s-%s-%s" ,
74-                 args .distri ,
75-                 args .version ,
76-                 args .flavor ,
98+                 "Skipping approval, there are no relevant jobs on openQA for %s" ,
99+                 build_info ,
77100            )
78-             return  False 
101+             return  None 
79102        if  len (pending_states ):
80103            log .info (
81-                 "Skipping approval, some jobs on openQA are in pending states (%s)" ,
104+                 "Skipping approval, some jobs on openQA for %s are in pending states (%s)" ,
105+                 build_info ,
82106                ", " .join (sorted (pending_states )),
83107            )
84108            return  False 
@@ -126,11 +150,72 @@ def _handle_approval(
126150        log .info (message )
127151        return  0 
128152
153+     def  _determine_build_info (self ) ->  Set [BuildInfo ]:
154+         # deduce DISTRI, VERSION, FLAVOR, ARCH and BUILD from the spdx files in the repo listing similar to the sync plugin, 
155+         # e.g. https://download.suse.de/download/ibs/SUSE:/SLFO:/Products:/SLES:/16.0:/TEST/product/?jsontable=1 
156+         path  =  self .args .obs_project .replace (":" , ":/" )
157+         url  =  f"{ OBS_DOWNLOAD_URL } { path }  
158+         rows  =  requests .get (url ).json ().get ("data" , [])
159+         res  =  set ()
160+         args  =  self .args 
161+         for  row  in  rows :
162+             m  =  re .search (
163+                 "(?P<product>.*)-(?P<version>[^\\ -]*?)-(?P<flavor>\\ D+[^\\ -]*?)-(?P<arch>[^\\ -]*?)-Build(?P<build>.*?)\\ .spdx.json" ,
164+                 row .get ("name" , "" ),
165+             )
166+             if  m :
167+                 product  =  m .group ("product" )
168+                 version  =  m .group ("version" )
169+                 flavor  =  m .group ("flavor" ) +  "-Increments" 
170+                 arch  =  m .group ("arch" )
171+                 build  =  m .group ("build" )
172+                 if  product .startswith ("SLE" ):
173+                     distri  =  "sle" 
174+                 else :
175+                     continue   # skip unknown products 
176+                 if  (
177+                     args .distri  in  ("any" , distri )
178+                     and  args .flavor  in  ("any" , flavor )
179+                     and  args .version  in  ("any" , version )
180+                 ):
181+                     res .add (BuildInfo (distri , product , version , flavor , arch , build ))
182+         return  res 
183+ 
184+     def  _schedule_openqa_jobs (self , build_info : BuildInfo ) ->  int :
185+         log .info ("Scheduling jobs for %s" , build_info )
186+         if  self .args .dry :
187+             return  0 
188+         try :
189+             self .client .post_job (  # create a scheduled product with build info from spdx file 
190+                 {
191+                     "DISTRI" : build_info .distri ,
192+                     "VERSION" : build_info .version ,
193+                     "FLAVOR" : build_info .flavor ,
194+                     "ARCH" : build_info .arch ,
195+                     "BUILD" : build_info .build ,
196+                 }
197+             )
198+             return  0 
199+         except  PostOpenQAError :
200+             return  1 
201+ 
129202    def  __call__ (self ) ->  int :
203+         error_count  =  0 
130204        request  =  self ._find_request_on_obs ()
131205        if  request  is  None :
132-             return  0 
133-         res  =  self ._request_openqa_job_results ()
134-         if  not  self ._are_openqa_jobs_ready (res ):
135-             return  0 
136-         return  self ._handle_approval (request , * (self ._evaluate_openqa_job_results (res )))
206+             return  error_count 
207+         for  build_info  in  self ._determine_build_info ():
208+             res  =  self ._request_openqa_job_results (build_info )
209+             if  self .args .reschedule :
210+                 error_count  +=  self ._schedule_openqa_jobs (build_info )
211+                 continue 
212+             openqa_jobs_ready  =  self ._check_openqa_jobs (res , build_info )
213+             if  openqa_jobs_ready  is  None  and  self .args .schedule :
214+                 error_count  +=  self ._schedule_openqa_jobs (build_info )
215+                 continue 
216+             if  not  openqa_jobs_ready :
217+                 continue 
218+             error_count  +=  self ._handle_approval (
219+                 request , * (self ._evaluate_openqa_job_results (res ))
220+             )
221+         return  error_count 
0 commit comments