Skip to content

Commit df91143

Browse files
authored
Merge pull request #158 from britive/develop
v1.8.0rc3
2 parents aec995c + 2584452 commit df91143

File tree

15 files changed

+250
-62
lines changed

15 files changed

+250
-62
lines changed

CHANGELOG.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,28 @@
33
* As of v1.4.0 release candidates will be published in an effort to get new features out faster while still allowing
44
time for full QA testing before moving the release candidate to a full release.
55

6+
## v1.8.0rc3 [2024-06-07]
7+
8+
__What's New:__
9+
10+
* Cloud PAM Anywhere - list, checkout, and checkin resources.
11+
12+
__Enhancements:__
13+
14+
* Added additional `clear kubeconfig` option to clear just the `pybritive` cached `kubeconfig` file.
15+
16+
__Bug Fixes:__
17+
18+
* None
19+
20+
__Dependencies:__
21+
22+
* `britive>=2.25.0rc5`
23+
24+
__Other:__
25+
26+
* A `ca_bundle` being configured will override, or ignore, `REQUESTS_CA_BUNDLE` and `CURL_CA_BUNDLE`
27+
628
## v1.8.0rc2 [2024-06-07]
729

830
__What's New:__
@@ -1398,7 +1420,7 @@ __What's New:__
13981420

13991421
* None
14001422

1401-
### Enhancements
1423+
__Enhancements:__
14021424

14031425
* None
14041426

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ classifiers = [
1818
license = {file = "LICENSE"}
1919
requires-python = ">= 3.7"
2020
dependencies = [
21-
"britive>=2.25.0rc3",
21+
"britive>=2.25.0rc5",
2222
"click",
2323
"cryptography>=41.0.0",
2424
"jmespath",

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
beautifulsoup4
22
boto3
3-
britive>=2.25.0rc4
3+
britive>=2.25.0rc5
44
certifi
55
charset-normalizer
66
click~=8.1.7

src/pybritive/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '1.8.0rc2'
1+
__version__ = '1.8.0rc3'

src/pybritive/britive_cli.py

Lines changed: 162 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ def __init__(
5858
'kube-exec': {'app_type': 'Kubernetes', 'expiration_jmespath': 'expirationTime'},
5959
}
6060
self.browser = default_browser
61+
self.resource_profile_prefix = 'resources/'
6162

6263
def set_output_format(self, output_format: str):
6364
self.output_format = self.config.get_output_format(output_format)
@@ -328,6 +329,23 @@ def list_approvals(self):
328329
approvals.reverse()
329330
self.print(approvals, ignore_silent=True)
330331

332+
def list_resources(self):
333+
self.login()
334+
found_resource_names = []
335+
resources = []
336+
for item in self.b.my_resources.list_profiles():
337+
name = item['resourceName']
338+
if name not in found_resource_names:
339+
resources.append(
340+
{
341+
'resourceId': item['resourceId'],
342+
'resourceName': name,
343+
'resourceLabels': item['resourceLabels']
344+
}
345+
)
346+
found_resource_names.append(name)
347+
self.print(resources, ignore_silent=True)
348+
331349
def list_profiles(self, checked_out: bool = False):
332350
self.login()
333351
self._set_available_profiles()
@@ -429,30 +447,51 @@ def list_environments(self):
429447
data.append(row)
430448
self.print(data, ignore_silent=True)
431449

432-
def _set_available_profiles(self, from_cache_command=False):
450+
def _set_available_profiles(self, from_cache_command=False, profile_type: str = None):
433451
if not self.available_profiles:
434452
data = []
435-
for app in self.b.my_access.list_profiles():
436-
for profile in app.get('profiles', []):
437-
for env in profile.get('environments', []):
438-
row = {
439-
'app_name': app['appName'],
440-
'app_id': app['appContainerId'],
441-
'app_type': app['catalogAppName'],
442-
'app_description': app['appDescription'],
443-
'env_name': env['environmentName'],
444-
'env_id': env['environmentId'],
445-
'env_short_name': env['alternateEnvironmentName'],
446-
'env_description': env['environmentDescription'],
447-
'profile_name': profile['profileName'],
448-
'profile_id': profile['profileId'],
449-
'profile_allows_console': profile['consoleAccess'],
450-
'profile_allows_programmatic': profile['programmaticAccess'],
451-
'profile_description': profile['profileDescription'],
452-
'2_part_profile_format_allowed': app['requiresHierarchicalModel'],
453-
'env_properties': env.get('profileEnvironmentProperties', {}),
454-
}
455-
data.append(row)
453+
if not profile_type or profile_type == 'my-access':
454+
for app in self.b.my_access.list_profiles():
455+
for profile in app.get('profiles', []):
456+
for env in profile.get('environments', []):
457+
row = {
458+
'app_name': app['appName'],
459+
'app_id': app['appContainerId'],
460+
'app_type': app['catalogAppName'],
461+
'app_description': app['appDescription'],
462+
'env_name': env['environmentName'],
463+
'env_id': env['environmentId'],
464+
'env_short_name': env['alternateEnvironmentName'],
465+
'env_description': env['environmentDescription'],
466+
'profile_name': profile['profileName'],
467+
'profile_id': profile['profileId'],
468+
'profile_allows_console': profile['consoleAccess'],
469+
'profile_allows_programmatic': profile['programmaticAccess'],
470+
'profile_description': profile['profileDescription'],
471+
'2_part_profile_format_allowed': app['requiresHierarchicalModel'],
472+
'env_properties': env.get('profileEnvironmentProperties', {})
473+
}
474+
data.append(row)
475+
if not profile_type or profile_type == 'my-resources':
476+
for item in self.b.my_resources.list_profiles():
477+
row = {
478+
'app_name': None,
479+
'app_id': None,
480+
'app_type': 'Resources',
481+
'app_description': None,
482+
'env_name': item['resourceName'],
483+
'env_id': item['resourceId'],
484+
'env_short_name': item['resourceName'],
485+
'env_description': None,
486+
'profile_name': item['profileName'],
487+
'profile_id': item['profileId'],
488+
'profile_allows_console': False,
489+
'profile_allows_programmatic': True,
490+
'profile_description': None,
491+
'2_part_profile_format_allowed': False,
492+
'env_properties': item.get('resourceLabels', {})
493+
}
494+
data.append(row)
456495
self.available_profiles = data
457496
if not from_cache_command and self.config.auto_refresh_profile_cache():
458497
self.cache_profiles()
@@ -548,13 +587,37 @@ def __get_cloud_credential_printer(
548587
)
549588
if app_type in ['OpenShift']:
550589
return printer.OpenShiftCredentialPrinter(
551-
console=console, mode=mode, profile=profile, credentials=credentials, silent=silent, cli=self
590+
console=console,
591+
mode=mode,
592+
profile=profile,
593+
credentials=credentials,
594+
silent=silent,
595+
cli=self
596+
)
597+
if app_type in ['Resources']:
598+
return printer.ResourcesCredentialPrinter(
599+
profile=profile,
600+
credentials=credentials,
601+
silent=silent,
602+
cli=self
552603
)
553604
return printer.GenericCloudCredentialPrinter(
554-
console=console, mode=mode, profile=profile, credentials=credentials, silent=silent, cli=self
605+
console=console,
606+
mode=mode,
607+
profile=profile,
608+
credentials=credentials,
609+
silent=silent,
610+
cli=self,
611+
)
612+
def _resource_checkin(self, profile):
613+
resource_name, profile_name = self._split_resource_profile_into_parts(profile=profile)
614+
self.login()
615+
self.b.my_resources.checkin_by_name(
616+
profile_name=profile_name,
617+
resource_name=resource_name
555618
)
556619

557-
def checkin(self, profile, console):
620+
def _access_checkin(self, profile, console):
558621
self.login()
559622
self._set_available_profiles()
560623
parts = self._split_profile_into_parts(profile)
@@ -591,6 +654,12 @@ def checkin(self, profile, console):
591654
if application_type in ['gcp']:
592655
self.clear_gcloud_auth_key_files(profile=profile)
593656

657+
def checkin(self, profile, console, profile_type: str = 'my-access'):
658+
if self._profile_is_for_resource(profile=profile, profile_type=profile_type):
659+
self._resource_checkin(profile=profile)
660+
else:
661+
self._access_checkin(profile=profile, console=console)
662+
594663
def _checkout(self, profile_name, env_name, app_name, programmatic, blocktime, maxpolltime, justification, otp):
595664
try:
596665
self.login()
@@ -648,23 +717,38 @@ def _extend_checkout(self, profile, console):
648717
programmatic=not console,
649718
)
650719

651-
def checkout(
652-
self,
653-
alias,
654-
blocktime,
655-
console,
656-
justification,
657-
otp,
658-
mode,
659-
maxpolltime,
660-
profile,
661-
passphrase,
662-
force_renew,
663-
aws_credentials_file,
664-
gcloud_key_file,
665-
verbose,
666-
extend,
667-
):
720+
def _save_alias(self, alias, profile):
721+
if alias:
722+
self.config.save_profile_alias(alias=alias, profile=profile.lower())
723+
724+
def _split_resource_profile_into_parts(self, profile):
725+
real_profile_name = self.config.profile_aliases.get(profile.lower(), profile).lower()
726+
if real_profile_name.startswith(self.resource_profile_prefix):
727+
real_profile_name = real_profile_name.replace(self.resource_profile_prefix, '')
728+
return real_profile_name.split('/')
729+
730+
def _profile_is_for_resource(self, profile, profile_type):
731+
if profile_type == 'my-resources':
732+
return True
733+
real_profile_name = self.config.profile_aliases.get(profile.lower(), profile).lower()
734+
return real_profile_name.startswith(f'{self.resource_profile_prefix}')
735+
736+
def _resource_checkout(self, blocktime, justification, maxpolltime, profile):
737+
self.login()
738+
resource_name, profile_name = self._split_resource_profile_into_parts(profile=profile)
739+
response = self.b.my_resources.checkout_by_name(
740+
resource_name=resource_name,
741+
profile_name=profile_name,
742+
include_credentials=True,
743+
justification=justification,
744+
wait_time=blocktime,
745+
max_wait_time=maxpolltime,
746+
progress_func=self.checkout_callback_printer # callback will handle silent, isatty, etc.
747+
)
748+
return response['credentials']
749+
750+
def _access_checkout(self, alias, blocktime, console, justification, otp, mode, maxpolltime, profile, passphrase,
751+
force_renew, verbose, extend):
668752
# handle this special use case and quit
669753
if extend:
670754
self._extend_checkout(profile, console)
@@ -744,13 +828,41 @@ def checkout(
744828
cached_credentials_found = False # need to write new creds to cache
745829
credentials = response['credentials']
746830

747-
if alias: # do this down here, so we know that the profile is valid and a checkout was successful
748-
self.config.save_profile_alias(alias=alias, profile=profile)
749-
750831
if mode in self.cachable_modes and not cached_credentials_found:
751832
Cache(passphrase=passphrase).save_credentials(
752833
profile_name=alias or profile, credentials=credentials, mode=mode
753834
)
835+
return app_type, credentials, k8s_processor
836+
837+
def checkout(self, alias, blocktime, console, justification, otp, mode, maxpolltime, profile, passphrase,
838+
force_renew, aws_credentials_file, gcloud_key_file, verbose, extend, profile_type: str = 'my-access'):
839+
if self._profile_is_for_resource(profile=profile, profile_type=profile_type):
840+
app_type = 'Resources'
841+
k8s_processor = None
842+
credentials = self._resource_checkout(
843+
blocktime=blocktime,
844+
justification=justification,
845+
maxpolltime=maxpolltime,
846+
profile=profile
847+
)
848+
else:
849+
app_type, credentials, k8s_processor = self._access_checkout(
850+
alias=alias,
851+
blocktime=blocktime,
852+
console=console,
853+
justification=justification,
854+
otp=otp,
855+
mode=mode,
856+
maxpolltime=maxpolltime,
857+
profile=profile,
858+
passphrase=passphrase,
859+
force_renew=force_renew,
860+
verbose=verbose,
861+
extend=extend
862+
)
863+
864+
# do this down here, so we know that the profile is valid and a checkout was successful
865+
self._save_alias(alias=alias, profile=profile)
754866

755867
self.__get_cloud_credential_printer(
756868
app_type,
@@ -879,6 +991,11 @@ def escape_profile_element(element):
879991
@staticmethod
880992
def cache_clear():
881993
Cache().clear()
994+
Cache().clear_kubeconfig()
995+
996+
@staticmethod
997+
def clear_kubeconfig():
998+
Cache().clear_kubeconfig()
882999

8831000
def configure_update(self, section, field, value):
8841001
self.config.update(section=section, field=field, value=value)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import click
2+
3+
# eval example: eval $(pybritive checkout test -m env)
4+
5+
profile_type_choices = click.Choice(
6+
[
7+
'my-access',
8+
'my-resources'
9+
],
10+
case_sensitive=False
11+
)

src/pybritive/commands/checkin.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,16 @@
66

77
@click.command()
88
@build_britive
9-
@britive_options(names='console,tenant,token,silent,passphrase,federation_provider')
9+
@britive_options(names='console,profile_type,tenant,token,silent,passphrase,federation_provider')
1010
@click_smart_profile_argument
11-
def checkin(ctx, console, tenant, token, silent, passphrase, federation_provider, profile):
11+
def checkin(ctx, console, profile_type, tenant, token, silent, passphrase, federation_provider, profile):
1212
"""Checkin a profile.
1313
1414
This command takes 1 required argument `PROFILE`. This should be a string representation of the profile
1515
that should be checked in. Format is `application name/environment name/profile name`.
1616
"""
1717
ctx.obj.britive.checkin(
1818
profile=profile,
19-
console=console
19+
console=console,
20+
profile_type=profile_type
2021
)

src/pybritive/commands/checkout.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
@click.command()
88
@build_britive
99
@britive_options(names='alias,blocktime,console,justification,otp,mode,maxpolltime,silent,force_renew,aws_credentials_file,'
10-
'gcloud_key_file,verbose,extend,tenant,token,passphrase,federation_provider')
10+
'gcloud_key_file,verbose,extend,profile_type,tenant,token,passphrase,federation_provider')
1111
@click_smart_profile_argument
1212
def checkout(ctx, alias, blocktime, console, justification, otp, mode, maxpolltime, silent, force_renew,
13-
aws_credentials_file, gcloud_key_file, verbose, extend, tenant, token, passphrase, federation_provider,
14-
profile):
13+
aws_credentials_file, gcloud_key_file, verbose, extend, profile_type, tenant, token, passphrase,
14+
federation_provider, profile):
1515
"""Checkout a profile.
1616
1717
This command takes 1 required argument `PROFILE`. This should be a string representation of the profile
@@ -33,5 +33,6 @@ def checkout(ctx, alias, blocktime, console, justification, otp, mode, maxpollti
3333
aws_credentials_file=aws_credentials_file,
3434
gcloud_key_file=gcloud_key_file,
3535
verbose=verbose,
36-
extend=extend
36+
extend=extend,
37+
profile_type=profile_type
3738
)

src/pybritive/commands/clear.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ def cache(ctx):
1616
ctx.obj.britive.cache_clear()
1717

1818

19+
@clear.command(name='kubeconfig')
20+
@build_britive
21+
def clear_kubeconfig(ctx):
22+
"""Clears the local .britive/kube/config file."""
23+
ctx.obj.britive.clear_kubeconfig()
24+
25+
1926
@clear.command(name='gcloud-auth-key-files')
2027
@build_britive
2128
def gcloud_auth_key_files(ctx):

0 commit comments

Comments
 (0)