99from pathlib import Path
1010import sys
1111import uuid
12+ import pkg_resources
1213import yaml
13-
1414import click
1515import jmespath
1616from britive .britive import Britive
2828
2929
3030class BritiveCli :
31- def __init__ (self , tenant_name : str = None , token : str = None , silent : bool = False ,
32- passphrase : str = None , federation_provider : str = None ):
31+ def __init__ (self , tenant_name : str = None , token : str = None , silent : bool = False ,passphrase : str = None ,
32+ federation_provider : str = None , from_helper_console_script : bool = False ):
3333 self .silent = silent
34+ self .from_helper_console_script = from_helper_console_script
3435 self .output_format = None
3536 self .tenant_name = None
3637 self .tenant_alias = None
@@ -44,6 +45,16 @@ def __init__(self, tenant_name: str = None, token: str = None, silent: bool = Fa
4445 self .credential_manager = None
4546 self .verbose_checkout = False
4647 self .checkout_progress_previous_message = None
48+ self .cachable_modes = {
49+ 'awscredentialprocess' : {
50+ 'app_type' : 'AWS' ,
51+ 'expiration_jmespath' : 'expirationTime'
52+ },
53+ 'kube-exec' : {
54+ 'app_type' : 'Kubernetes' ,
55+ 'expiration_jmespath' : 'expirationTime'
56+ }
57+ }
4758 self .browser = None
4859
4960 def set_output_format (self , output_format : str ):
@@ -102,10 +113,25 @@ def login(self, explicit: bool = False, browser: str = None):
102113 except exceptions .UnauthorizedRequest :
103114 self ._cleanup_credentials ()
104115
105- # if user called `pybritive login` and we should refresh the profile cache...do so
106- if explicit and self .config .auto_refresh_profile_cache ():
107- self ._set_available_profiles ()
108- self .cache_profiles ()
116+ self ._update_sdk_user_agent ()
117+
118+ # if user called `pybritive login` and we should get profiles...do so
119+ should_get_profiles = any ([self .config .auto_refresh_profile_cache (), self .config .auto_refresh_kube_config ()])
120+ if explicit and should_get_profiles :
121+ self ._set_available_profiles () # will handle calling cache_profiles() and construct_kube_config()
122+
123+ def _update_sdk_user_agent (self ):
124+ # update the user agent to include the pybritive cli version
125+ user_agent = self .b .session .headers .get ('User-Agent' )
126+
127+ try :
128+ version = pkg_resources .get_distribution ('pybritive' ).version
129+ except Exception :
130+ version = 'unknown'
131+
132+ self .b .session .headers .update ({
133+ 'User-Agent' : f'pybritive/{ version } { user_agent } '
134+ })
109135
110136 def _cleanup_credentials (self ):
111137 self .set_credential_manager ()
@@ -307,7 +333,7 @@ def list_environments(self):
307333 data .append (row )
308334 self .print (data , ignore_silent = True )
309335
310- def _set_available_profiles (self ):
336+ def _set_available_profiles (self , from_cache_command = False ):
311337 if not self .available_profiles :
312338 data = []
313339 for app in self .b .my_access .list_profiles ():
@@ -327,12 +353,46 @@ def _set_available_profiles(self):
327353 'profile_allows_console' : profile ['consoleAccess' ],
328354 'profile_allows_programmatic' : profile ['programmaticAccess' ],
329355 'profile_description' : profile ['profileDescription' ],
330- '2_part_profile_format_allowed' : app ['requiresHierarchicalModel' ]
356+ '2_part_profile_format_allowed' : app ['requiresHierarchicalModel' ],
357+ 'env_properties' : env .get ('profileEnvironmentProperties' , {})
331358 }
332359 data .append (row )
333360 self .available_profiles = data
334- if self .config .auto_refresh_profile_cache ():
335- self .cache_profiles (load = False )
361+
362+ if not from_cache_command and self .config .auto_refresh_profile_cache ():
363+ self .cache_profiles ()
364+ if not from_cache_command and self .config .auto_refresh_kube_config ():
365+ self .construct_kube_config ()
366+
367+ def construct_kube_config (self , from_cache_command = False ):
368+ if self .from_helper_console_script :
369+ return
370+
371+ if from_cache_command :
372+ self .login ()
373+ self ._set_available_profiles (from_cache_command = from_cache_command )
374+
375+ profiles = []
376+ for p in self .available_profiles :
377+ if p ['app_type' ].lower () == 'kubernetes' :
378+ props = p ['env_properties' ]
379+ url = props .get ('apiServerUrl' )
380+ cert = props .get ('certificateAuthorityData' )
381+ if props and all ([url , cert ]):
382+ profiles .append ({
383+ 'app' : p ['app_name' ],
384+ 'env' : p ['env_name' ],
385+ 'profile' : p ['profile_name' ],
386+ 'url' : url ,
387+ 'cert' : cert ,
388+ })
389+ from .helpers .kube_config_builder import build_kube_config # lazy import as not everyone will want this
390+ build_kube_config (
391+ profiles = profiles ,
392+ config = self .config ,
393+ username = self .b .my_access .whoami ()['username' ],
394+ cli = self
395+ )
336396
337397 def _get_app_type (self , application_id ):
338398 self ._set_available_profiles ()
@@ -342,7 +402,7 @@ def _get_app_type(self, application_id):
342402 raise click .ClickException (f'Application { application_id } not found' )
343403
344404 def __get_cloud_credential_printer (self , app_type , console , mode , profile , silent , credentials ,
345- aws_credentials_file , gcloud_key_file ):
405+ aws_credentials_file , gcloud_key_file , k8s_processor ):
346406 if app_type in ['AWS' , 'AWS Standalone' ]:
347407 return printer .AwsCloudCredentialPrinter (
348408 console = console ,
@@ -372,14 +432,25 @@ def __get_cloud_credential_printer(self, app_type, console, mode, profile, silen
372432 cli = self ,
373433 gcloud_key_file = gcloud_key_file
374434 )
375- return printer .GenericCloudCredentialPrinter (
376- console = console ,
377- mode = mode ,
378- profile = profile ,
379- credentials = credentials ,
380- silent = silent ,
381- cli = self
382- )
435+ elif app_type in ['Kubernetes' ]:
436+ return printer .KubernetesCredentialPrinter (
437+ console = console ,
438+ mode = mode ,
439+ profile = profile ,
440+ credentials = credentials ,
441+ silent = silent ,
442+ cli = self ,
443+ k8s_processor = k8s_processor
444+ )
445+ else :
446+ return printer .GenericCloudCredentialPrinter (
447+ console = console ,
448+ mode = mode ,
449+ profile = profile ,
450+ credentials = credentials ,
451+ silent = silent ,
452+ cli = self
453+ )
383454
384455 def checkin (self , profile , console ):
385456 self .login ()
@@ -474,9 +545,17 @@ def checkout(self, alias, blocktime, console, justification, mode, maxpolltime,
474545 force_renew , aws_credentials_file , gcloud_key_file , verbose ):
475546 credentials = None
476547 app_type = None
477- credential_process_creds_found = False
548+ cached_credentials_found = False
549+ k8s_processor = None
478550 self .verbose_checkout = verbose
479551
552+ # handle kube-exec since the profile is actually going to be passed in via another method
553+ # and perform some basic validation so we don't waste time performing a checkout when we
554+ # will not be able to return a response back to kubectl via the exec command
555+ if mode == 'kube-exec' :
556+ from .helpers .k8s_exec_credential_builder import KubernetesExecCredentialProcessor
557+ k8s_processor = KubernetesExecCredentialProcessor ()
558+
480559 # these 2 modes implicitly say that console access should be checked out without having to provide
481560 # the --console flag
482561 if mode and (mode == 'console' or mode .startswith ('browser' )):
@@ -488,22 +567,23 @@ def checkout(self, alias, blocktime, console, justification, mode, maxpolltime,
488567
489568 self ._validate_justification (justification )
490569
491- if mode == 'awscredentialprocess' :
492- self .silent = True # the aws credential process CANNOT output anything other than the expected JSON
493- # we need to check the credential process cache for the credentials first
494- # then check to see if they are expired
495- # if not simply return those credentials
496- # if they are expired
497- app_type = 'AWS' # just hardcode as we know for sure this is for AWS
498- credentials = Cache (passphrase = passphrase ).get_awscredentialprocess (profile_name = alias or profile )
570+ if mode in self .cachable_modes :
571+ self .silent = True # CANNOT output anything other than the expected JSON
572+ # we need to check the cache for the credentials first and then check to see if they are expired
573+ # if not simply return those credentials, if they are expired, continue to do an actual checkout
574+ app_type = self .cachable_modes [mode ]['app_type' ]
575+ credentials = Cache (passphrase = passphrase ).get_credentials (profile_name = alias or profile , mode = mode )
499576 if credentials :
500- expiration_timestamp_str = credentials ['expirationTime' ].replace ('Z' , '' )
577+ expiration_timestamp_str = jmespath .search (
578+ expression = self .cachable_modes [mode ]['expiration_jmespath' ],
579+ data = credentials
580+ ).replace ('Z' , '' )
501581 expires = datetime .fromisoformat (expiration_timestamp_str )
502582 now = datetime .utcnow ()
503583 if now >= expires : # check to ensure the credentials are still valid, if not, set to None and get new
504584 credentials = None
505585 else :
506- credential_process_creds_found = True
586+ cached_credentials_found = True
507587
508588 parts = self ._split_profile_into_parts (profile )
509589
@@ -518,7 +598,7 @@ def checkout(self, alias, blocktime, console, justification, mode, maxpolltime,
518598 'justification' : justification
519599 }
520600
521- if not credential_process_creds_found : # nothing found via aws cred process or not aws cred process mode
601+ if not cached_credentials_found : # nothing found in cache, cache is expired, or not a cachable mode
522602 response = self ._checkout (** params )
523603 app_type = self ._get_app_type (response ['appContainerId' ])
524604 credentials = response ['credentials' ]
@@ -533,16 +613,17 @@ def checkout(self, alias, blocktime, console, justification, mode, maxpolltime,
533613 self .print ('checking in the profile to get renewed credentials....standby' )
534614 self .checkin (profile = profile )
535615 response = self ._checkout (** params )
536- credential_process_creds_found = False # need to write new creds to cache
616+ cached_credentials_found = False # need to write new creds to cache
537617 credentials = response ['credentials' ]
538618
539619 if alias : # do this down here, so we know that the profile is valid and a checkout was successful
540620 self .config .save_profile_alias (alias = alias , profile = profile )
541621
542- if mode == 'awscredentialprocess' and not credential_process_creds_found :
543- Cache (passphrase = passphrase ).save_awscredentialprocess (
622+ if mode in self . cachable_modes and not cached_credentials_found :
623+ Cache (passphrase = passphrase ).save_credentials (
544624 profile_name = alias or profile ,
545- credentials = credentials
625+ credentials = credentials ,
626+ mode = mode
546627 )
547628
548629 self .__get_cloud_credential_printer (
@@ -553,7 +634,8 @@ def checkout(self, alias, blocktime, console, justification, mode, maxpolltime,
553634 self .silent ,
554635 credentials ,
555636 aws_credentials_file ,
556- gcloud_key_file
637+ gcloud_key_file ,
638+ k8s_processor
557639 ).print ()
558640
559641 def import_existing_npm_config (self ):
@@ -659,11 +741,15 @@ def downloadsecret(self, path, blocktime, justification, maxpolltime, file):
659741 f .write (content )
660742 self .print (f'wrote contents of secret file to { path } ' )
661743
662- def cache_profiles (self , load = True ):
663- if load :
664- self .login ()
665- self ._set_available_profiles ()
744+ def cache_profiles (self , from_cache_command = False ):
745+ if self .from_helper_console_script :
746+ return
666747 profiles = []
748+
749+ if from_cache_command :
750+ self .login ()
751+ self ._set_available_profiles (from_cache_command = from_cache_command )
752+
667753 for p in self .available_profiles :
668754 profile = self .escape_profile_element (p ['app_name' ])
669755 profile += '/'
0 commit comments