1+ from collections import Counter
12import json
23import logging
34import os
5+ import shlex
6+ import shutil
47import subprocess
58
6- from kubernetes import client , config
79from junitparser import JUnitXml
810
911logger = logging .getLogger (__name__ )
1012
1113
12- def setup_k8s_client (kubeconfigfile = None ):
13- if not kubeconfigfile :
14- logger .error ("no kubeconfig file provided" )
15- return # XXX smelly: why not raise an exception here?
16- logger .debug (f"loading kubeconfig file '{ kubeconfigfile } '" )
17- config .load_kube_config (kubeconfigfile )
18- logger .info ("kubeconfigfile loaded successfully" )
19- return client .CoreV1Api ()
20-
21-
2214class SonobuoyHandler :
2315 """
2416 A class that handles both the execution of sonobuoy and
@@ -33,101 +25,59 @@ def __init__(
3325 check_name = "sonobuoy_handler" ,
3426 kubeconfig = None ,
3527 result_dir_name = "sonobuoy_results" ,
36- args = None ,
28+ args = () ,
3729 ):
3830 self .check_name = check_name
39- logger .info (f"Inital { __name__ } for { self .check_name } " )
4031 logger .debug (f"kubeconfig: { kubeconfig } " )
4132 if kubeconfig is None :
4233 raise Exception ("No kubeconfig provided" )
4334 else :
4435 self .kubeconfig_path = kubeconfig
4536 self .working_directory = os .getcwd ()
4637 self .result_dir_name = result_dir_name
47- logger .debug (
48- f"Working from { self .working_directory } placing results at { self .result_dir_name } "
49- )
50- self .args = args
38+ self .sonobuoy = shutil .which ('sonobuoy' )
39+ logger .debug (f"working from { self .working_directory } " )
40+ logger .debug (f"placing results at { self .result_dir_name } " )
41+ logger .debug (f"sonobuoy executable at { self .sonobuoy } " )
42+ self .args = (arg0 for arg in args for arg0 in shlex .split (str (arg )))
5143
52- def _build_command (self , process , * args ):
53- return " " . join (
54- ( "sonobuoy" , "--kubeconfig" , self . kubeconfig_path , process ) + args
55- )
44+ def _invoke_sonobuoy (self , * args , ** kwargs ):
45+ inv_args = ( self . sonobuoy , "--kubeconfig" , self . kubeconfig_path ) + args
46+ logger . debug ( f'invoking { " " . join ( inv_args ) } ' )
47+ return subprocess . run ( args = inv_args , capture_output = True , check = True , ** kwargs )
5648
5749 def _sonobuoy_run (self ):
58- logger .debug ("sonobuoy run" )
59- check_args = ["--wait" ]
60- check_args += [str (arg ) for arg in self .args ]
61- subprocess .run (
62- self ._build_command ("run" , * check_args ),
63- shell = True ,
64- capture_output = True ,
65- check = True ,
66- )
50+ self ._invoke_sonobuoy ("run" , "--wait" , * self .args )
6751
6852 def _sonobuoy_delete (self ):
69- logger .info ("removing sonobuoy resources from cluster" )
70- subprocess .run (
71- self ._build_command ("delete" , "--wait" ),
72- shell = True ,
73- capture_output = True ,
74- check = True ,
75- )
53+ self ._invoke_sonobuoy ("delete" , "--wait" )
7654
7755 def _sonobuoy_status_result (self ):
78- logger .debug ("sonobuoy status" )
79- process = subprocess .run (
80- self ._build_command ("status" , "--json" ),
81- shell = True ,
82- capture_output = True ,
83- check = True ,
84- )
56+ process = self ._invoke_sonobuoy ("status" , "--json" )
8557 json_data = json .loads (process .stdout )
58+ counter = Counter ()
8659 for entry in json_data ["plugins" ]:
87- print (f"plugin:{ entry ['plugin' ]} :{ entry ['result-status' ]} " )
88- failed_test_cases = 0
89- passed_test_cases = 0
90- skipped_test_cases = 0
91- for result , count in json_data ["plugins" ][0 ]["result-counts" ].items ():
92- if result == "passed" :
93- passed_test_cases = count
94- if result == "failed" :
95- failed_test_cases = count
96- logger .error (f"ERROR: failed: { count } " )
97- if result == "skipped" :
98- skipped_test_cases = count
99- logger .error (f"ERROR: skipped: { count } " )
100- result_message = f" { passed_test_cases } passed, { failed_test_cases } failed, { skipped_test_cases } skipped"
101- if failed_test_cases == 0 and skipped_test_cases == 0 :
60+ logger .debug (f"plugin:{ entry ['plugin' ]} :{ entry ['result-status' ]} " )
61+ for result , count in entry ["result-counts" ].items ():
62+ counter [result ] += count
63+ return counter
64+
65+ def _eval_result (self , counter ):
66+ result_str = ', ' .join (f"{ counter [key ]} { key } " for key in ('passed' , 'failed' , 'skipped' ))
67+ result_message = f"sonobuoy reports { result_str } "
68+ if counter ['failed' ]:
69+ logger .error (result_message )
70+ self .return_code = 3
71+ else :
10272 logger .info (result_message )
10373 self .return_code = 0
104- else :
105- logger .error ("ERROR:" + result_message )
106- self .return_code = 3
10774
10875 def _preflight_check (self ):
10976 """
11077 Preflight test to ensure that everything is set up correctly for execution
11178 """
112- logger .info ("check kubeconfig" )
113- self .k8s_api_client = setup_k8s_client (self .kubeconfig_path )
114-
115- for api in client .ApisApi ().get_api_versions ().groups :
116- versions = []
117- for v in api .versions :
118- name = ""
119- if v .version == api .preferred_version .version and len (api .versions ) > 1 :
120- name += "*"
121- name += v .version
122- versions .append (name )
123- logger .info (f"[supported api]: { api .name :<40} { ',' .join (versions )} " )
124-
125- logger .debug ("checks if sonobuoy is available" )
126- return_value = os .system (
127- f"sonobuoy version --kubeconfig='{ self .kubeconfig_path } '"
128- )
129- if return_value != 0 :
130- raise Exception ("sonobuoy is not installed" )
79+ if not self .sonobuoy :
80+ raise RuntimeError ("sonobuoy executable not found; is it in PATH?" )
13181
13282 def _sonobuoy_retrieve_result (self ):
13383 """
@@ -141,6 +91,7 @@ def _sonobuoy_retrieve_result(self):
14191 raise Exception ("result directory already existing" )
14292 os .mkdir (result_dir )
14393
94+ # XXX use self._invoke_sonobuoy
14495 os .system (
14596 # ~ f"sonobuoy retrieve {result_dir} -x --filename='{result_dir}' --kubeconfig='{self.kubeconfig_path}'"
14697 f"sonobuoy retrieve { result_dir } --kubeconfig='{ self .kubeconfig_path } '"
@@ -149,35 +100,27 @@ def _sonobuoy_retrieve_result(self):
149100 f"parsing JUnit result from { result_dir + '/plugins/e2e/results/global/junit_01.xml' } "
150101 )
151102 xml = JUnitXml .fromfile (result_dir + "/plugins/e2e/results/global/junit_01.xml" )
152- failed_test_cases = 0
153- passed_test_cases = 0
154- skipped_test_cases = 0
103+ counter = Counter ()
155104 for suite in xml :
156105 for case in suite :
157- if case .is_passed is True :
158- passed_test_cases += 1
106+ if case .is_passed is True : # XXX why `is True`???
107+ counter [ 'passed' ] += 1
159108 elif case .is_skipped is True :
160- skipped_test_cases += 1
109+ counter [ 'skipped' ] += 1
161110 else :
162- failed_test_cases += 1
111+ counter [ 'failed' ] += 1
163112 logger .error (f"{ case .name } " )
164-
165- result_message = f" { passed_test_cases } passed, { failed_test_cases } failed, { skipped_test_cases } skipped"
166- if failed_test_cases : # TODO: add `or skipped_test_cases`?
167- logger .error (result_message )
168- self .return_code = 3
169- else :
170- logger .info (result_message )
171- self .return_code = 0
113+ return counter
172114
173115 def run (self ):
174116 """
175117 This method is to be called to run the plugin
176118 """
119+ logger .info (f"running sonobuoy for testcase { self .check_name } " )
177120 self .return_code = 11
178121 self ._preflight_check ()
179122 self ._sonobuoy_run ()
180- self ._sonobuoy_status_result ()
123+ self ._eval_result ( self . _sonobuoy_status_result () )
181124
182125 # ERROR: currently disabled do to: "error retrieving results: unexpected EOF"
183126 # might be related to following bug: https://github.com/vmware-tanzu/sonobuoy/issues/1633
0 commit comments