88import sys
99import unittest
1010
11- sys .path .insert (0 , os .path .abspath (os .path .join (os .path .dirname (__file__ ), ".." )))
12-
1311
1412def _run_command (cmd ):
1513 """Run shell command, return (stdout, stderr)."""
@@ -26,6 +24,7 @@ def _cluster_available(kubeconfig=""):
2624
2725
2826SCRIPT = "provider-kubeconfig.py"
27+ ROOT = os .path .abspath (os .path .join (os .path .dirname (__file__ ), ".." ))
2928
3029# All CLI elements that must appear in --help
3130HELP_ELEMENTS = [
@@ -43,44 +42,25 @@ def _cluster_available(kubeconfig=""):
4342class TestCli (unittest .TestCase ):
4443 """provider-kubeconfig.py exposes expected CLI (actions and flags)."""
4544
46- def test_help_shows_all_actions (self ):
47- root = os . path . abspath ( os . path . join ( os . path . dirname ( __file__ ), ".." ))
45+ def test_help_shows_all_actions_flags_and_namespace (self ):
46+ """--help must show actions, flags, and namespace argument."""
4847 proc = subprocess .run (
49- [sys .executable , os .path .join (root , SCRIPT ), "--help" ],
50- capture_output = True , text = True , cwd = root ,
48+ [sys .executable , os .path .join (ROOT , SCRIPT ), "--help" ],
49+ capture_output = True , text = True , cwd = ROOT ,
5150 )
5251 self .assertEqual (proc .returncode , 0 , proc .stderr )
5352 out = proc .stdout or ""
5453 for elem in ["create" , "delete" , "update" , "extract" ]:
5554 self .assertIn (elem , out , f"Action { elem } should appear in help" )
56-
57- def test_help_shows_all_flags (self ):
58- root = os .path .abspath (os .path .join (os .path .dirname (__file__ ), ".." ))
59- proc = subprocess .run (
60- [sys .executable , os .path .join (root , SCRIPT ), "--help" ],
61- capture_output = True , text = True , cwd = root ,
62- )
63- self .assertEqual (proc .returncode , 0 )
64- out = proc .stdout or ""
6555 for elem in HELP_ELEMENTS :
6656 self .assertIn (elem , out , f"Help should mention { elem } " )
67-
68- def test_help_shows_namespace_argument (self ):
69- root = os .path .abspath (os .path .join (os .path .dirname (__file__ ), ".." ))
70- proc = subprocess .run (
71- [sys .executable , os .path .join (root , SCRIPT ), "--help" ],
72- capture_output = True , text = True , cwd = root ,
73- )
74- self .assertEqual (proc .returncode , 0 )
75- out = proc .stdout or ""
7657 self .assertIn ("namespace" , out .lower ())
7758
7859 def test_update_without_permissionfile_exits_with_error (self ):
7960 """update action without -p exits with code 1."""
80- root = os .path .abspath (os .path .join (os .path .dirname (__file__ ), ".." ))
8161 proc = subprocess .run (
82- [sys .executable , os .path .join (root , SCRIPT ), "update" , "default" ],
83- capture_output = True , text = True , cwd = root ,
62+ [sys .executable , os .path .join (ROOT , SCRIPT ), "update" , "default" ],
63+ capture_output = True , text = True , cwd = ROOT ,
8464 )
8565 self .assertNotEqual (proc .returncode , 0 )
8666 self .assertIn ("permission" , (proc .stdout or proc .stderr or "" ).lower ())
@@ -101,6 +81,10 @@ def setUpClass(cls):
10181 cls .has_cluster = _cluster_available (cls .kubeconfig )
10282 cls .kubeconfig_flag = " --kubeconfig=" + cls .kubeconfig if cls .kubeconfig else ""
10383
84+ def setUp (self ):
85+ if not self .has_cluster :
86+ self .skipTest ("No cluster reachable (set KUBECONFIG)" )
87+
10488 def _create_and_get_kubeconfig (self , root , ns , sa = "kubeplus-saas-provider" , extra_args = None , output_filename = None ):
10589 """Run create, return (kubeconfig_dict, proc). Caller must delete to cleanup."""
10690 create_args = ["create" , ns ]
@@ -124,10 +108,13 @@ def _create_and_get_kubeconfig(self, root, ns, sa="kubeplus-saas-provider", extr
124108 cfg = json .load (fp )
125109 return cfg , proc
126110
127- def _delete_for_cleanup (self , root , ns , sa = "kubeplus-saas-provider" ):
111+ def _delete_for_cleanup (self , root , ns , sa = "kubeplus-saas-provider" , filename = None ):
112+ """Delete k8s resources and local files. Pass filename when -f was used (e.g. custom-provider-kubeconfig)."""
128113 delete_args = ["delete" , ns ]
129114 if sa != "kubeplus-saas-provider" :
130115 delete_args += ["-c" , sa ]
116+ if filename :
117+ delete_args += ["-f" , filename ]
131118 subprocess .run (
132119 [sys .executable , os .path .join (root , SCRIPT )] + delete_args + self .kubeconfig_arg ,
133120 cwd = root , capture_output = True , timeout = 60 ,
@@ -191,12 +178,9 @@ def _sa_exists(self, sa, ns):
191178
192179 def test_provider_kubeconfig_all_fields_nonempty (self ):
193180 """Provider kubeconfig: every field that should exist is non-empty."""
194- if not self .has_cluster :
195- self .skipTest ("No cluster reachable (set KUBECONFIG)" )
196- root = os .path .abspath (os .path .join (os .path .dirname (__file__ ), ".." ))
197181 ns = "kubeplus-test-prov-" + str (os .getpid ())
198182 try :
199- cfg , proc = self ._create_and_get_kubeconfig (root , ns )
183+ cfg , proc = self ._create_and_get_kubeconfig (ROOT , ns )
200184 self .assertEqual (proc .returncode , 0 , proc .stderr )
201185 self ._assert_kubeconfig_valid (
202186 cfg ,
@@ -205,17 +189,14 @@ def test_provider_kubeconfig_all_fields_nonempty(self):
205189 )
206190 self .assertTrue (self ._sa_exists ("kubeplus-saas-provider" , ns ))
207191 finally :
208- self ._delete_for_cleanup (root , ns )
192+ self ._delete_for_cleanup (ROOT , ns )
209193
210194 def test_consumer_kubeconfig_all_fields_nonempty (self ):
211195 """Consumer kubeconfig: every field non-empty, user name matches SA, namespace in context."""
212- if not self .has_cluster :
213- self .skipTest ("No cluster reachable (set KUBECONFIG)" )
214- root = os .path .abspath (os .path .join (os .path .dirname (__file__ ), ".." ))
215196 ns = "kubeplus-test-cons-" + str (os .getpid ())
216197 consumer_sa = "test-consumer-sa"
217198 try :
218- cfg , proc = self ._create_and_get_kubeconfig (root , ns , sa = consumer_sa )
199+ cfg , proc = self ._create_and_get_kubeconfig (ROOT , ns , sa = consumer_sa )
219200 self .assertEqual (proc .returncode , 0 , proc .stderr )
220201 self ._assert_kubeconfig_valid (
221202 cfg ,
@@ -224,35 +205,29 @@ def test_consumer_kubeconfig_all_fields_nonempty(self):
224205 )
225206 self .assertTrue (self ._sa_exists (consumer_sa , ns ))
226207 finally :
227- self ._delete_for_cleanup (root , ns , sa = consumer_sa )
208+ self ._delete_for_cleanup (ROOT , ns , sa = consumer_sa )
228209
229210 def test_flag_s_apiserverurl_reflected_in_kubeconfig (self ):
230211 """-s/--apiserverurl sets cluster server in kubeconfig."""
231- if not self .has_cluster :
232- self .skipTest ("No cluster reachable (set KUBECONFIG)" )
233- root = os .path .abspath (os .path .join (os .path .dirname (__file__ ), ".." ))
234212 ns = "kubeplus-test-s-" + str (os .getpid ())
235213 test_server = "https://api.example.com:6443"
236214 try :
237215 cfg , proc = self ._create_and_get_kubeconfig (
238- root , ns ,
216+ ROOT , ns ,
239217 extra_args = ["-s" , test_server ],
240218 )
241219 self .assertEqual (proc .returncode , 0 , proc .stderr )
242220 self ._assert_kubeconfig_valid (cfg , expected_server = test_server , expected_namespace = ns )
243221 finally :
244- self ._delete_for_cleanup (root , ns )
222+ self ._delete_for_cleanup (ROOT , ns )
245223
246224 def test_flag_x_clustername_reflected_in_kubeconfig (self ):
247225 """-x/--clustername sets context name and cluster name in kubeconfig."""
248- if not self .has_cluster :
249- self .skipTest ("No cluster reachable (set KUBECONFIG)" )
250- root = os .path .abspath (os .path .join (os .path .dirname (__file__ ), ".." ))
251226 ns = "kubeplus-test-x-" + str (os .getpid ())
252227 test_cluster = "my-test-cluster"
253228 try :
254229 cfg , proc = self ._create_and_get_kubeconfig (
255- root , ns ,
230+ ROOT , ns ,
256231 extra_args = ["-x" , test_cluster ],
257232 )
258233 self .assertEqual (proc .returncode , 0 , proc .stderr )
@@ -262,38 +237,32 @@ def test_flag_x_clustername_reflected_in_kubeconfig(self):
262237 expected_namespace = ns ,
263238 )
264239 finally :
265- self ._delete_for_cleanup (root , ns )
240+ self ._delete_for_cleanup (ROOT , ns )
266241
267242 def test_flag_f_filename_uses_custom_output_file (self ):
268243 """-f/--filename writes kubeconfig to specified file."""
269- if not self .has_cluster :
270- self .skipTest ("No cluster reachable (set KUBECONFIG)" )
271- root = os .path .abspath (os .path .join (os .path .dirname (__file__ ), ".." ))
272244 ns = "kubeplus-test-f-" + str (os .getpid ())
273245 custom_name = "custom-provider-kubeconfig"
274246 try :
275247 cfg , proc = self ._create_and_get_kubeconfig (
276- root , ns ,
248+ ROOT , ns ,
277249 extra_args = ["-f" , custom_name ],
278250 output_filename = custom_name ,
279251 )
280252 self .assertEqual (proc .returncode , 0 , proc .stderr )
281253 self ._assert_kubeconfig_valid (cfg , expected_namespace = ns )
282- self .assertTrue (os .path .exists (os .path .join (root , custom_name + ".json" )))
254+ self .assertTrue (os .path .exists (os .path .join (ROOT , custom_name + ".json" )))
283255 finally :
284- self ._delete_for_cleanup (root , ns )
256+ self ._delete_for_cleanup (ROOT , ns , filename = custom_name )
285257
286258 def test_flags_s_and_x_combined (self ):
287259 """-s and -x together: both server and cluster name in kubeconfig."""
288- if not self .has_cluster :
289- self .skipTest ("No cluster reachable (set KUBECONFIG)" )
290- root = os .path .abspath (os .path .join (os .path .dirname (__file__ ), ".." ))
291260 ns = "kubeplus-test-sx-" + str (os .getpid ())
292261 test_server = "https://api.example.com:6443"
293262 test_cluster = "my-test-cluster"
294263 try :
295264 cfg , proc = self ._create_and_get_kubeconfig (
296- root , ns ,
265+ ROOT , ns ,
297266 extra_args = ["-s" , test_server , "-x" , test_cluster ],
298267 )
299268 self .assertEqual (proc .returncode , 0 , proc .stderr )
@@ -304,22 +273,19 @@ def test_flags_s_and_x_combined(self):
304273 expected_namespace = ns ,
305274 )
306275 finally :
307- self ._delete_for_cleanup (root , ns )
276+ self ._delete_for_cleanup (ROOT , ns )
308277
309- def test_consumer_cannot_create_pod_in_other_namespacte (self ):
278+ def test_consumer_cannot_create_pod_in_other_namespace (self ):
310279 """
311280 Consumer kubeconfig: verify create/delete in other namespaces is forbidden.
312281 Consumer RBAC should restrict operations; creating a pod in another ns should fail.
313282 """
314- if not self .has_cluster :
315- self .skipTest ("No cluster reachable (set KUBECONFIG)" )
316- root = os .path .abspath (os .path .join (os .path .dirname (__file__ ), ".." ))
317283 ns = "kubeplus-test-restrict-" + str (os .getpid ())
318284 other_ns = "kubeplus-test-other-" + str (os .getpid ())
319285 consumer_sa = "test-consumer-restrict"
320- kubeconfig_path = os .path .join (root , consumer_sa + ".json" )
286+ kubeconfig_path = os .path .join (ROOT , consumer_sa + ".json" )
321287 try :
322- cfg , proc = self ._create_and_get_kubeconfig (root , ns , sa = consumer_sa )
288+ cfg , proc = self ._create_and_get_kubeconfig (ROOT , ns , sa = consumer_sa )
323289 self .assertEqual (proc .returncode , 0 , proc .stderr )
324290 self ._assert_kubeconfig_valid (cfg , expected_namespace = ns , expected_user_name = consumer_sa )
325291
@@ -331,15 +297,15 @@ def test_consumer_cannot_create_pod_in_other_namespacte(self):
331297 "kubectl run nginx --image=nginx -n " + other_ns
332298 + " --kubeconfig=" + kubeconfig_path
333299 )
334- # Expect Forbidden or similar
300+ # Expect Forbidden (authorization denial), not generic errors (DNS, image pull, etc.)
335301 self .assertTrue (
336- "Forbidden" in err or " forbidden" in err or "Error" in err ,
302+ "forbidden" in err . lower () ,
337303 "Consumer should not be able to create pod in other namespace; got out=%r err=%r"
338304 % (out , err ),
339305 )
340306 finally :
341307 _run_command ("kubectl delete namespace " + other_ns + self .kubeconfig_flag + " 2>/dev/null" )
342- self ._delete_for_cleanup (root , ns , sa = consumer_sa )
308+ self ._delete_for_cleanup (ROOT , ns , sa = consumer_sa )
343309
344310
345311if __name__ == "__main__" :
0 commit comments