2929from kubernetes .client import Configuration
3030
3131from .config_exception import ConfigException
32- from .dateutil import parse_rfc3339
32+ from .dateutil import format_rfc3339 , parse_rfc3339
3333from .kube_config import (ENV_KUBECONFIG_PATH_SEPARATOR , CommandTokenSource ,
3434 ConfigNode , FileOrData , KubeConfigLoader ,
3535 KubeConfigMerger , _cleanup_temp_files ,
@@ -346,9 +346,12 @@ def test_get_with_name_on_duplicate_name(self):
346346class FakeConfig :
347347
348348 FILE_KEYS = ["ssl_ca_cert" , "key_file" , "cert_file" ]
349+ IGNORE_KEYS = ["refresh_api_key_hook" ]
349350
350351 def __init__ (self , token = None , ** kwargs ):
351352 self .api_key = {}
353+ # Provided by the OpenAPI-generated Configuration class
354+ self .refresh_api_key_hook = None
352355 if token :
353356 self .api_key ['authorization' ] = token
354357
@@ -358,6 +361,8 @@ def __eq__(self, other):
358361 if len (self .__dict__ ) != len (other .__dict__ ):
359362 return
360363 for k , v in self .__dict__ .items ():
364+ if k in self .IGNORE_KEYS :
365+ continue
361366 if k not in other .__dict__ :
362367 return
363368 if k in self .FILE_KEYS :
@@ -956,17 +961,15 @@ def test_load_user_token(self):
956961
957962 def test_gcp_no_refresh (self ):
958963 fake_config = FakeConfig ()
959- # swagger-generated config has this, but FakeConfig does not.
960- self .assertFalse (hasattr (fake_config , 'get_api_key_with_prefix' ))
964+ self .assertIsNone (fake_config .refresh_api_key_hook )
961965 KubeConfigLoader (
962966 config_dict = self .TEST_KUBE_CONFIG ,
963967 active_context = "gcp" ,
964968 get_google_credentials = lambda : _raise_exception (
965969 "SHOULD NOT BE CALLED" )).load_and_set (fake_config )
966970 # Should now be populated with a gcp token fetcher.
967- self .assertIsNotNone (fake_config .get_api_key_with_prefix )
971+ self .assertIsNotNone (fake_config .refresh_api_key_hook )
968972 self .assertEqual (TEST_HOST , fake_config .host )
969- # For backwards compatibility, authorization field should still be set.
970973 self .assertEqual (BEARER_TOKEN_FORMAT % TEST_DATA_BASE64 ,
971974 fake_config .api_key ['authorization' ])
972975
@@ -997,7 +1000,7 @@ def cred(): return None
9971000 self .assertEqual (BEARER_TOKEN_FORMAT % TEST_ANOTHER_DATA_BASE64 ,
9981001 loader .token )
9991002
1000- def test_gcp_get_api_key_with_prefix (self ):
1003+ def test_gcp_refresh_api_key_hook (self ):
10011004 class cred_old :
10021005 token = TEST_DATA_BASE64
10031006 expiry = DATETIME_EXPIRY_PAST
@@ -1015,15 +1018,13 @@ class cred_new:
10151018 get_google_credentials = _get_google_credentials )
10161019 loader .load_and_set (fake_config )
10171020 original_expiry = _get_expiry (loader , "expired_gcp_refresh" )
1018- # Call GCP token fetcher .
1019- token = fake_config .get_api_key_with_prefix ( )
1021+ # Refresh the GCP token.
1022+ fake_config .refresh_api_key_hook ( fake_config )
10201023 new_expiry = _get_expiry (loader , "expired_gcp_refresh" )
10211024
10221025 self .assertTrue (new_expiry > original_expiry )
10231026 self .assertEqual (BEARER_TOKEN_FORMAT % TEST_ANOTHER_DATA_BASE64 ,
10241027 loader .token )
1025- self .assertEqual (BEARER_TOKEN_FORMAT % TEST_ANOTHER_DATA_BASE64 ,
1026- token )
10271028
10281029 def test_oidc_no_refresh (self ):
10291030 loader = KubeConfigLoader (
@@ -1383,6 +1384,38 @@ def test_user_exec_auth(self, mock):
13831384 active_context = "exec_cred_user" ).load_and_set (actual )
13841385 self .assertEqual (expected , actual )
13851386
1387+ @mock .patch ('kubernetes.config.kube_config.ExecProvider.run' )
1388+ def test_user_exec_auth_with_expiry (self , mock ):
1389+ expired_token = "expired"
1390+ current_token = "current"
1391+ mock .side_effect = [
1392+ {
1393+ "token" : expired_token ,
1394+ "expirationTimestamp" : format_rfc3339 (DATETIME_EXPIRY_PAST )
1395+ },
1396+ {
1397+ "token" : current_token ,
1398+ "expirationTimestamp" : format_rfc3339 (DATETIME_EXPIRY_FUTURE )
1399+ }
1400+ ]
1401+
1402+ fake_config = FakeConfig ()
1403+ self .assertIsNone (fake_config .refresh_api_key_hook )
1404+
1405+ KubeConfigLoader (
1406+ config_dict = self .TEST_KUBE_CONFIG ,
1407+ active_context = "exec_cred_user" ).load_and_set (fake_config )
1408+ # The kube config should use the first token returned from the
1409+ # exec provider.
1410+ self .assertEqual (fake_config .api_key ["authorization" ],
1411+ BEARER_TOKEN_FORMAT % expired_token )
1412+ # Should now be populated with a method to refresh expired tokens.
1413+ self .assertIsNotNone (fake_config .refresh_api_key_hook )
1414+ # Refresh the token; the kube config should be updated.
1415+ fake_config .refresh_api_key_hook (fake_config )
1416+ self .assertEqual (fake_config .api_key ["authorization" ],
1417+ BEARER_TOKEN_FORMAT % current_token )
1418+
13861419 @mock .patch ('kubernetes.config.kube_config.ExecProvider.run' )
13871420 def test_user_exec_auth_certificates (self , mock ):
13881421 mock .return_value = {
@@ -1412,7 +1445,6 @@ def test_user_cmd_path(self):
14121445 KubeConfigLoader (
14131446 config_dict = self .TEST_KUBE_CONFIG ,
14141447 active_context = "contexttestcmdpath" ).load_and_set (actual )
1415- del actual .get_api_key_with_prefix
14161448 self .assertEqual (expected , actual )
14171449
14181450 def test_user_cmd_path_empty (self ):
@@ -1490,31 +1522,28 @@ def test__get_kube_config_loader_dict_no_persist(self):
14901522class TestKubernetesClientConfiguration (BaseTestCase ):
14911523 # Verifies properties of kubernetes.client.Configuration.
14921524 # These tests guard against changes to the upstream configuration class,
1493- # since GCP authorization overrides get_api_key_with_prefix to refresh its
1494- # token regularly.
1525+ # since GCP and Exec authorization use refresh_api_key_hook to refresh
1526+ # their tokens regularly.
14951527
1496- def test_get_api_key_with_prefix_exists (self ):
1497- self .assertTrue (hasattr (Configuration , 'get_api_key_with_prefix ' ))
1528+ def test_refresh_api_key_hook_exists (self ):
1529+ self .assertTrue (hasattr (Configuration () , 'refresh_api_key_hook ' ))
14981530
1499- def test_get_api_key_with_prefix_returns_token (self ):
1500- expected_token = 'expected_token'
1501- config = Configuration ()
1502- config .api_key ['authorization' ] = expected_token
1503- self .assertEqual (expected_token ,
1504- config .get_api_key_with_prefix ('authorization' ))
1505-
1506- def test_auth_settings_calls_get_api_key_with_prefix (self ):
1531+ def test_get_api_key_calls_refresh_api_key_hook (self ):
1532+ identifier = 'authorization'
15071533 expected_token = 'expected_token'
15081534 old_token = 'old_token'
1535+ config = Configuration (
1536+ api_key = {identifier : old_token },
1537+ api_key_prefix = {identifier : 'Bearer' }
1538+ )
1539+
1540+ def refresh_api_key_hook (client_config ):
1541+ self .assertEqual (client_config , config )
1542+ client_config .api_key [identifier ] = expected_token
1543+ config .refresh_api_key_hook = refresh_api_key_hook
15091544
1510- def fake_get_api_key_with_prefix (identifier ):
1511- self .assertEqual ('authorization' , identifier )
1512- return expected_token
1513- config = Configuration ()
1514- config .api_key ['authorization' ] = old_token
1515- config .get_api_key_with_prefix = fake_get_api_key_with_prefix
1516- self .assertEqual (expected_token ,
1517- config .auth_settings ()['BearerToken' ]['value' ])
1545+ self .assertEqual ('Bearer ' + expected_token ,
1546+ config .get_api_key_with_prefix (identifier ))
15181547
15191548
15201549class TestKubeConfigMerger (BaseTestCase ):
0 commit comments