diff --git a/CHANGELOG.md b/CHANGELOG.md index 2297d2d..568f041 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,23 @@ * 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. +## v1.6.0rc4 [2023-11-03] +#### What's New +* None + +#### Enhancements +* For command `ls profiles -c` show the time remaining for the checkout +* Add new flag `-e/--extend` to command `checkout` which will extend the expiration time of a currently checked out profile (only applicable to specific application types) + +#### Bug Fixes +* Fix bug in `configure import` related to the default AWS checkout mode + +#### Dependencies +* None + +#### Other +* None + ## v1.6.0rc3 [2023-10-31] #### What's New * None diff --git a/requirements.txt b/requirements.txt index 47d79a7..e71b895 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ boto3 -britive>=2.22.0 +britive==2.23.0rc1 certifi>=2022.12.7 charset-normalizer==2.1.0 click~=8.1.3 diff --git a/setup.cfg b/setup.cfg index 9fdc1a6..24051e7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = pybritive -version = 1.6.0rc3 +version = 1.6.0rc4 author = Britive Inc. author_email = support@britive.com description = A pure Python CLI for Britive @@ -27,7 +27,7 @@ install_requires = toml cryptography>=41.0.0 python-dateutil - britive>=2.22.0 + britive==2.23.0rc1 jmespath pyjwt diff --git a/src/pybritive/britive_cli.py b/src/pybritive/britive_cli.py index 57bc731..cf07d16 100644 --- a/src/pybritive/britive_cli.py +++ b/src/pybritive/britive_cli.py @@ -262,13 +262,23 @@ def list_profiles(self, checked_out: bool = False): self.login() self._set_available_profiles() data = [] - checked_out_profiles = [ - f'{p["papId"]}-{p["environmentId"]}' - for p in self.b.my_access.list_checked_out_profiles() - ] if checked_out else [] + checked_out_profiles = {} + if checked_out: # only make this call if we have to + now = datetime.utcnow() + for p in self.b.my_access.list_checked_out_profiles(): + expiration_str = p['expiration'] + expiration_timestamp = datetime.fromisoformat(expiration_str.replace('Z', '')) + seconds_until_expiration = int((expiration_timestamp - now).total_seconds()) + key = f'{p["papId"]}-{p["environmentId"]}' + checked_out_profiles[key] = { + 'expiration': expiration_str, + 'expires_in_seconds': seconds_until_expiration + } for profile in self.available_profiles: - if not checked_out or f'{profile["profile_id"]}-{profile["env_id"]}' in checked_out_profiles: + key = f'{profile["profile_id"]}-{profile["env_id"]}' + profile_is_checked_out = key in checked_out_profiles + if not checked_out or profile_is_checked_out: row = { 'Application': profile['app_name'], 'Environment': profile['env_name'], @@ -276,12 +286,26 @@ def list_profiles(self, checked_out: bool = False): 'Description': profile['profile_description'], 'Type': profile['app_type'] } + + if profile_is_checked_out: + row['Expiration'] = checked_out_profiles[key]['expiration'] + total_seconds = checked_out_profiles[key]['expires_in_seconds'] + + hours, remainder = divmod(total_seconds, 3600) + minutes, seconds = divmod(remainder, 60) + time_format = '{:02d}:{:02d}:{:02d}'.format(hours, minutes, seconds) + row['TimeRemaining'] = time_format + row['TimeRemainingSeconds'] = total_seconds + if self.output_format == 'list': self.list_separator = '/' - row.pop('Description') - row.pop('Type') + row.pop('Description', None) + row.pop('Type', None) + row.pop('TimeRemaining', None) + row.pop('TimeRemainingSeconds', None) + row.pop('Expiration', None) if profile['2_part_profile_format_allowed']: - row.pop('Environment') + row.pop('Environment', None) data.append(row) # set special list output if needed @@ -545,8 +569,24 @@ def _split_profile_into_parts(self, profile): } return parts_dict + def _extend_checkout(self, profile, console): + self.login() + parts = self._split_profile_into_parts(profile) + response = self.b.my_access.extend_checkout_by_name( + profile_name=parts['profile'], + environment_name=parts['env'], + application_name=parts['app'], + programmatic=not console + ) + def checkout(self, alias, blocktime, console, justification, mode, maxpolltime, profile, passphrase, - force_renew, aws_credentials_file, gcloud_key_file, verbose): + force_renew, aws_credentials_file, gcloud_key_file, verbose, extend): + + # handle this special use case and quit + if extend: + self._extend_checkout(profile, console) + return + credentials = None app_type = None cached_credentials_found = False diff --git a/src/pybritive/commands/checkout.py b/src/pybritive/commands/checkout.py index e4cbbe5..26c57ee 100644 --- a/src/pybritive/commands/checkout.py +++ b/src/pybritive/commands/checkout.py @@ -7,10 +7,11 @@ @click.command() @build_britive @britive_options(names='alias,blocktime,console,justification,mode,maxpolltime,silent,force_renew,aws_credentials_file,' - 'gcloud_key_file,verbose,tenant,token,passphrase,federation_provider') + 'gcloud_key_file,verbose,extend,tenant,token,passphrase,federation_provider') @click_smart_profile_argument def checkout(ctx, alias, blocktime, console, justification, mode, maxpolltime, silent, force_renew, - aws_credentials_file, gcloud_key_file, verbose, tenant, token, passphrase, federation_provider, profile): + aws_credentials_file, gcloud_key_file, verbose, extend, tenant, token, passphrase, federation_provider, + profile): """Checkout a profile. This command takes 1 required argument `PROFILE`. This should be a string representation of the profile @@ -30,5 +31,6 @@ def checkout(ctx, alias, blocktime, console, justification, mode, maxpolltime, s force_renew=force_renew, aws_credentials_file=aws_credentials_file, gcloud_key_file=gcloud_key_file, - verbose=verbose + verbose=verbose, + extend=extend ) diff --git a/src/pybritive/helpers/config.py b/src/pybritive/helpers/config.py index 047b8a9..6ec054e 100644 --- a/src/pybritive/helpers/config.py +++ b/src/pybritive/helpers/config.py @@ -228,8 +228,9 @@ def import_global_npm_config(self): if aws_section: checkout_mode = aws_section.get('checkoutMode') if checkout_mode: + checkout_mode = checkout_mode.lower().replace('display', '') self.cli.print(f'Found aws default checkout mode of {checkout_mode}.') - self.config['aws'] = {'default_checkout_mode': checkout_mode.lower()} + self.config['aws'] = {'default_checkout_mode': checkout_mode} self.save() self.load(force=True) diff --git a/src/pybritive/options/britive_options.py b/src/pybritive/options/britive_options.py index 6d06186..29da45d 100644 --- a/src/pybritive/options/britive_options.py +++ b/src/pybritive/options/britive_options.py @@ -31,6 +31,7 @@ from ..options.aws_profile import option as aws_profile from ..options.aws_console_duration import option as aws_console_duration from ..options.browser import option as browser +from ..options.extend import option as extend options_map = { 'tenant': tenant, @@ -65,7 +66,8 @@ 'ssh_key_source': ssh_key_source, 'aws_profile': aws_profile, 'aws_console_duration': aws_console_duration, - 'browser': browser + 'browser': browser, + 'extend': extend } diff --git a/src/pybritive/options/extend.py b/src/pybritive/options/extend.py new file mode 100644 index 0000000..9edc440 --- /dev/null +++ b/src/pybritive/options/extend.py @@ -0,0 +1,10 @@ +import click + + +option = click.option( + '--extend', '-e', + default=False, + is_flag=True, + show_default=True, + help='Extend the expiration time for a currently checked out profile.' +)