From c71c67d6db6d70d7d65bd586cff36414559de619 Mon Sep 17 00:00:00 2001 From: Samuel Wan Date: Tue, 22 Jul 2025 12:03:10 -0700 Subject: [PATCH 1/4] feat:kube session attributes --- src/pybritive/britive_cli.py | 17 +++++++++++++++++ src/pybritive/helpers/kube_config_builder.py | 15 +++++++++++---- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/pybritive/britive_cli.py b/src/pybritive/britive_cli.py index dce1104..e87f409 100644 --- a/src/pybritive/britive_cli.py +++ b/src/pybritive/britive_cli.py @@ -486,6 +486,18 @@ def _get_missing_env_properties( ) return {} + def _fetch_session_attributes_for_profiles(self, accesses): + session_attributes = {} + unique_profile_ids = {profile_id for _, _, profile_id in accesses} + for profile_id in unique_profile_ids: + try: + session_attrs = self.b.application_management.profiles.session_attributes.list(profile_id=profile_id) + session_attributes[profile_id] = session_attrs + except Exception: + session_attributes[profile_id] = {} + + return session_attributes + def _set_available_profiles(self, from_cache_command=False, profile_type: Optional[str] = None): if not self.available_profiles: data = [] @@ -503,6 +515,7 @@ def _set_available_profiles(self, from_cache_command=False, profile_type: Option accesses = [ ([a['appContainerId'], a['environmentId'], a['papId']]) for a in access_data.get('accesses', []) ] + session_attributes = self._fetch_session_attributes_for_profiles(accesses) access_output = [] for app_id, env_id, profile_id in accesses: app = apps[app_id] @@ -522,6 +535,7 @@ def _set_available_profiles(self, from_cache_command=False, profile_type: Option 'profile_allows_console': app.get('consoleAccess', False), 'profile_allows_programmatic': app.get('programmaticAccess', False), 'profile_description': profile['papDescription'], + 'session_attributes': session_attributes.get(profile_id, []), '2_part_profile_format_allowed': app['requiresHierarchicalModel'], 'env_properties': env['profileEnvironmentProperties'] or self._get_missing_env_properties( @@ -537,6 +551,7 @@ def _set_available_profiles(self, from_cache_command=False, profile_type: Option else: profiles = self.b.my_resources.list(size=resource_limit) profiles = profiles['data'] + session_attributes = self._fetch_session_attributes_for_profiles(accesses) for item in profiles: row = { 'app_name': None, @@ -551,6 +566,7 @@ def _set_available_profiles(self, from_cache_command=False, profile_type: Option 'profile_id': item['profileId'], 'profile_allows_console': False, 'profile_allows_programmatic': True, + 'session_attributes': session_attributes.get(profile_id, []), 'profile_description': None, '2_part_profile_format_allowed': False, 'env_properties': item.get('resourceLabels', {}), @@ -584,6 +600,7 @@ def construct_kube_config(self, from_cache_command=False): 'profile': p['profile_name'], 'url': url, 'cert': cert, + 'session_attributes': p['session_attributes'], } ) diff --git a/src/pybritive/helpers/kube_config_builder.py b/src/pybritive/helpers/kube_config_builder.py index 30c4a0b..c9dc166 100644 --- a/src/pybritive/helpers/kube_config_builder.py +++ b/src/pybritive/helpers/kube_config_builder.py @@ -69,7 +69,7 @@ def parse_profiles(profiles, aliases): cluster_names = {} assigned_aliases = [] for profile in profiles: - env_profile = f"{sanitize(profile['env'])}-{sanitize(profile['profile'].lower())}" + env_profile = f'{sanitize(profile["env"])}-{sanitize(profile["profile"].lower())}' if env_profile not in cluster_names: app = BritiveCli.escape_profile_element(profile['app']) env = BritiveCli.escape_profile_element(profile['env']) @@ -78,14 +78,17 @@ def parse_profiles(profiles, aliases): escaped_profile_str = f'{app}/{env}/{pro}'.lower() alias = aliases.get(escaped_profile_str, None) assigned_aliases.append(alias) + keys_to_remove = ['id', 'attributeSchemaId', 'transitive', 'sessionAttributeType'] + [list(map(attr.pop, keys_to_remove)) for attr in profile['session_attributes']] cluster_names[env_profile] = { 'apps': [], 'url': profile['url'], 'cert': profile['cert'], 'escaped_profile': escaped_profile_str, - 'profile': f"{profile['app']}/{profile['env']}/{profile['profile']}".lower(), + 'profile': f'{profile["app"]}/{profile["env"]}/{profile["profile"]}'.lower(), 'alias': alias, + 'session_attributes': profile['session_attributes'], } cluster_names[env_profile]['apps'].append(sanitize(profile['app'])) return [cluster_names, assigned_aliases] @@ -129,7 +132,6 @@ def build_tenant_config(tenant, cluster_names, username, cli: BritiveCli): ) contexts = [] clusters = [] - for env_profile, details in cluster_names.items(): if len(details['apps']) == 1: names = [env_profile] @@ -138,6 +140,7 @@ def build_tenant_config(tenant, cluster_names, username, cli: BritiveCli): cert = details['cert'] url = details['url'] + session_attributes = details['session_attributes'] if not valid_cert(cert=cert, profile=details['profile'], cli=cli): continue @@ -162,7 +165,11 @@ def build_tenant_config(tenant, cluster_names, username, cli: BritiveCli): contexts.append( { 'name': details.get('alias') or f'{tenant}-{name}', - 'context': {'cluster': f'{tenant}-{name}', 'user': username}, + 'context': { + 'cluster': f'{tenant}-{name}', + 'user': username, + **{attr['mappingName']: attr['attributeValue'] for attr in session_attributes}, + }, } ) return [clusters, contexts, users] From 147491b6f1105b4f87788c16eb721d3e438df593 Mon Sep 17 00:00:00 2001 From: theborch Date: Fri, 25 Jul 2025 15:26:07 -0500 Subject: [PATCH 2/4] refactor:drop _get_missing_env_properites, add _get_missing_session_attributes --- src/pybritive/britive_cli.py | 86 ++++++++------------ src/pybritive/helpers/kube_config_builder.py | 5 +- 2 files changed, 33 insertions(+), 58 deletions(-) diff --git a/src/pybritive/britive_cli.py b/src/pybritive/britive_cli.py index e87f409..7775757 100644 --- a/src/pybritive/britive_cli.py +++ b/src/pybritive/britive_cli.py @@ -465,38 +465,20 @@ def list_environments(self): data.append(row) self.print(data, ignore_silent=True) - # temporary fix till the new API is updated to return `profileEnvironmentProperties` - def _get_missing_env_properties( - self, app_id: str, app_type: str, env_id: str, profile_id: str, from_cache_command: bool - ) -> dict: - if app_type.lower() == 'kubernetes' and (from_cache_command or self.config.auto_refresh_kube_config()): - if not self.listed_profiles: - self.listed_profiles = self.b.my_access.list_profiles() - return next( - ( - env['profileEnvironmentProperties'] - for app in self.listed_profiles - if app['appContainerId'] == app_id - for profile in app.get('profiles', []) - if profile['profileId'] == profile_id - for env in profile.get('environments', []) - if env['environmentId'] == env_id - ), - {}, - ) - return {} - - def _fetch_session_attributes_for_profiles(self, accesses): - session_attributes = {} - unique_profile_ids = {profile_id for _, _, profile_id in accesses} - for profile_id in unique_profile_ids: - try: - session_attrs = self.b.application_management.profiles.session_attributes.list(profile_id=profile_id) - session_attributes[profile_id] = session_attrs - except Exception: - session_attributes[profile_id] = {} - - return session_attributes + # temporary fix till the new API is updated to return `sessionAttributes` + def _get_missing_session_attributes(self, app_id: str, profile_id: str) -> dict: + if not self.listed_profiles: + self.listed_profiles = self.b.my_access.list_profiles() + return next( + ( + profile['sessionAttributes'] + for app in self.listed_profiles + if app['appContainerId'] == app_id + for profile in app.get('profiles', []) + if profile['profileId'] == profile_id + ), + [], + ) def _set_available_profiles(self, from_cache_command=False, profile_type: Optional[str] = None): if not self.available_profiles: @@ -515,31 +497,29 @@ def _set_available_profiles(self, from_cache_command=False, profile_type: Option accesses = [ ([a['appContainerId'], a['environmentId'], a['papId']]) for a in access_data.get('accesses', []) ] - session_attributes = self._fetch_session_attributes_for_profiles(accesses) access_output = [] for app_id, env_id, profile_id in accesses: app = apps[app_id] env = envs[env_id] profile = profiles[profile_id] row = { - 'app_name': app['catalogAppDisplayName'], + '2_part_profile_format_allowed': app['requiresHierarchicalModel'], + 'app_description': app['appDescription'], 'app_id': app_id, + 'app_name': app['catalogAppDisplayName'], 'app_type': app['catalogAppName'], - 'app_description': app['appDescription'], - 'env_name': env['environmentName'], + 'env_description': env['environmentDescription'], 'env_id': env_id, + 'env_name': env['environmentName'], + 'env_properties': env['profileEnvironmentProperties'], 'env_short_name': env['alternateEnvironmentName'], - 'env_description': env['environmentDescription'], - 'profile_name': profile['papName'], - 'profile_id': profile_id, 'profile_allows_console': app.get('consoleAccess', False), 'profile_allows_programmatic': app.get('programmaticAccess', False), 'profile_description': profile['papDescription'], - 'session_attributes': session_attributes.get(profile_id, []), - '2_part_profile_format_allowed': app['requiresHierarchicalModel'], - 'env_properties': env['profileEnvironmentProperties'] - or self._get_missing_env_properties( - app_id, app['catalogAppName'], env_id, profile_id, from_cache_command + 'profile_id': profile_id, + 'profile_name': profile['papName'], + 'session_attributes': profile.get( + 'sessionAttributes', self._get_missing_session_attributes(app_id, profile_id) ), } if row not in access_output: @@ -551,25 +531,23 @@ def _set_available_profiles(self, from_cache_command=False, profile_type: Option else: profiles = self.b.my_resources.list(size=resource_limit) profiles = profiles['data'] - session_attributes = self._fetch_session_attributes_for_profiles(accesses) for item in profiles: row = { - 'app_name': None, + '2_part_profile_format_allowed': False, + 'app_description': None, 'app_id': None, + 'app_name': None, 'app_type': 'Resources', - 'app_description': None, - 'env_name': item['resourceName'], + 'env_description': None, 'env_id': item['resourceId'], + 'env_name': item['resourceName'], + 'env_properties': item.get('resourceLabels', {}), 'env_short_name': item['resourceName'], - 'env_description': None, - 'profile_name': item['profileName'], - 'profile_id': item['profileId'], 'profile_allows_console': False, 'profile_allows_programmatic': True, - 'session_attributes': session_attributes.get(profile_id, []), 'profile_description': None, - '2_part_profile_format_allowed': False, - 'env_properties': item.get('resourceLabels', {}), + 'profile_id': item['profileId'], + 'profile_name': item['profileName'], } data.append(row) self.available_profiles = data diff --git a/src/pybritive/helpers/kube_config_builder.py b/src/pybritive/helpers/kube_config_builder.py index c9dc166..51710ee 100644 --- a/src/pybritive/helpers/kube_config_builder.py +++ b/src/pybritive/helpers/kube_config_builder.py @@ -78,8 +78,6 @@ def parse_profiles(profiles, aliases): escaped_profile_str = f'{app}/{env}/{pro}'.lower() alias = aliases.get(escaped_profile_str, None) assigned_aliases.append(alias) - keys_to_remove = ['id', 'attributeSchemaId', 'transitive', 'sessionAttributeType'] - [list(map(attr.pop, keys_to_remove)) for attr in profile['session_attributes']] cluster_names[env_profile] = { 'apps': [], @@ -140,7 +138,6 @@ def build_tenant_config(tenant, cluster_names, username, cli: BritiveCli): cert = details['cert'] url = details['url'] - session_attributes = details['session_attributes'] if not valid_cert(cert=cert, profile=details['profile'], cli=cli): continue @@ -168,7 +165,7 @@ def build_tenant_config(tenant, cluster_names, username, cli: BritiveCli): 'context': { 'cluster': f'{tenant}-{name}', 'user': username, - **{attr['mappingName']: attr['attributeValue'] for attr in session_attributes}, + **{attr['mappingName']: attr['attributeValue'] for attr in details['session_attributes']}, }, } ) From 4130ed63d7879306256c6852bc83c1ba6fe03586 Mon Sep 17 00:00:00 2001 From: theborch Date: Fri, 25 Jul 2025 15:26:40 -0500 Subject: [PATCH 3/4] fix:bug when resource doesn't have console access --- src/pybritive/britive_cli.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pybritive/britive_cli.py b/src/pybritive/britive_cli.py index 7775757..a96049b 100644 --- a/src/pybritive/britive_cli.py +++ b/src/pybritive/britive_cli.py @@ -973,6 +973,7 @@ def checkout( if self._profile_is_for_resource(profile=profile, profile_type=profile_type): app_type = 'Resources' k8s_processor = None + console_fallback = False credentials = self._resource_checkout( blocktime=blocktime, justification=justification, From 30fa7b7a725bbe5792bb3fa1e1027a560903c274 Mon Sep 17 00:00:00 2001 From: theborch Date: Fri, 25 Jul 2025 15:37:34 -0500 Subject: [PATCH 4/4] v2.2.2 --- CHANGELOG.md | 22 ++++++++++++++++++++++ src/pybritive/__init__.py | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e696ea..d5681d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,28 @@ > As of v1.4.0, release candidates will be published in an effort to get new features out faster while still allowing > time for full QA testing before moving the release candidate to a full release. +## v2.2.2 [2025-07-25] + +__What's New:__ + +* None + +__Enhancements:__ + +* Added profile `sessionAttributes` to `context` when building `britive/kube/config`. + +__Bug Fixes:__ + +* Fixed issue with `console_fallback` when checking out a Resource profile with no console access. + +__Dependencies:__ + +* None + +__Other:__ + +* Dropped `_get_missing_env_properties` workaround. + ## v2.2.1 [2025-06-26] __What's New:__ diff --git a/src/pybritive/__init__.py b/src/pybritive/__init__.py index 36a511e..f1edb19 100644 --- a/src/pybritive/__init__.py +++ b/src/pybritive/__init__.py @@ -1 +1 @@ -__version__ = '2.2.1' +__version__ = '2.2.2'