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 (
@@ -1390,6 +1391,38 @@ def test_user_exec_auth(self, mock):
13901391 active_context = "exec_cred_user" ).load_and_set (actual )
13911392 self .assertEqual (expected , actual )
13921393
1394+ @mock .patch ('kubernetes.config.kube_config.ExecProvider.run' )
1395+ def test_user_exec_auth_with_expiry (self , mock ):
1396+ expired_token = "expired"
1397+ current_token = "current"
1398+ mock .side_effect = [
1399+ {
1400+ "token" : expired_token ,
1401+ "expirationTimestamp" : format_rfc3339 (DATETIME_EXPIRY_PAST )
1402+ },
1403+ {
1404+ "token" : current_token ,
1405+ "expirationTimestamp" : format_rfc3339 (DATETIME_EXPIRY_FUTURE )
1406+ }
1407+ ]
1408+
1409+ fake_config = FakeConfig ()
1410+ self .assertIsNone (fake_config .refresh_api_key_hook )
1411+
1412+ KubeConfigLoader (
1413+ config_dict = self .TEST_KUBE_CONFIG ,
1414+ active_context = "exec_cred_user" ).load_and_set (fake_config )
1415+ # The kube config should use the first token returned from the
1416+ # exec provider.
1417+ self .assertEqual (fake_config .api_key ["authorization" ],
1418+ BEARER_TOKEN_FORMAT % expired_token )
1419+ # Should now be populated with a method to refresh expired tokens.
1420+ self .assertIsNotNone (fake_config .refresh_api_key_hook )
1421+ # Refresh the token; the kube config should be updated.
1422+ fake_config .refresh_api_key_hook (fake_config )
1423+ self .assertEqual (fake_config .api_key ["authorization" ],
1424+ BEARER_TOKEN_FORMAT % current_token )
1425+
13931426 @mock .patch ('kubernetes.config.kube_config.ExecProvider.run' )
13941427 def test_user_exec_auth_certificates (self , mock ):
13951428 mock .return_value = {
@@ -1419,7 +1452,6 @@ def test_user_cmd_path(self):
14191452 KubeConfigLoader (
14201453 config_dict = self .TEST_KUBE_CONFIG ,
14211454 active_context = "contexttestcmdpath" ).load_and_set (actual )
1422- del actual .get_api_key_with_prefix
14231455 self .assertEqual (expected , actual )
14241456
14251457 def test_user_cmd_path_empty (self ):
@@ -1497,31 +1529,28 @@ def test__get_kube_config_loader_dict_no_persist(self):
14971529class TestKubernetesClientConfiguration (BaseTestCase ):
14981530 # Verifies properties of kubernetes.client.Configuration.
14991531 # These tests guard against changes to the upstream configuration class,
1500- # since GCP authorization overrides get_api_key_with_prefix to refresh its
1501- # token regularly.
1532+ # since GCP and Exec authorization use refresh_api_key_hook to refresh
1533+ # their tokens regularly.
15021534
1503- def test_get_api_key_with_prefix_exists (self ):
1504- self .assertTrue (hasattr (Configuration , 'get_api_key_with_prefix ' ))
1535+ def test_refresh_api_key_hook_exists (self ):
1536+ self .assertTrue (hasattr (Configuration () , 'refresh_api_key_hook ' ))
15051537
1506- def test_get_api_key_with_prefix_returns_token (self ):
1507- expected_token = 'expected_token'
1508- config = Configuration ()
1509- config .api_key ['authorization' ] = expected_token
1510- self .assertEqual (expected_token ,
1511- config .get_api_key_with_prefix ('authorization' ))
1512-
1513- def test_auth_settings_calls_get_api_key_with_prefix (self ):
1538+ def test_get_api_key_calls_refresh_api_key_hook (self ):
1539+ identifier = 'authorization'
15141540 expected_token = 'expected_token'
15151541 old_token = 'old_token'
1542+ config = Configuration (
1543+ api_key = {identifier : old_token },
1544+ api_key_prefix = {identifier : 'Bearer' }
1545+ )
1546+
1547+ def refresh_api_key_hook (client_config ):
1548+ self .assertEqual (client_config , config )
1549+ client_config .api_key [identifier ] = expected_token
1550+ config .refresh_api_key_hook = refresh_api_key_hook
15161551
1517- def fake_get_api_key_with_prefix (identifier ):
1518- self .assertEqual ('authorization' , identifier )
1519- return expected_token
1520- config = Configuration ()
1521- config .api_key ['authorization' ] = old_token
1522- config .get_api_key_with_prefix = fake_get_api_key_with_prefix
1523- self .assertEqual (expected_token ,
1524- config .auth_settings ()['BearerToken' ]['value' ])
1552+ self .assertEqual ('Bearer ' + expected_token ,
1553+ config .get_api_key_with_prefix (identifier ))
15251554
15261555
15271556class TestKubeConfigMerger (BaseTestCase ):
0 commit comments