Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,33 @@

All changes to the package starting with v0.3.1 will be logged here.

## v1.2.2 [2023-03-17]
#### What's New
* None

#### Enhancements
* None

#### Bug Fixes
* Fix bug with `logout` command when no active credentials were available
* Expand `--silent/-s` flag to the following commands
* `api`
* `cache profiles`
* `checkin`
* `login`
* `logout`
* `ls [profiles|environments|applications|secrets]`
* `request [submit|withdraw]`
* `secret view`
* `user`
* Fix bug when saving profile alias when the `PROFILE` is only 2 parts instead of 3

#### Dependencies
* None

#### Other
* None

## v1.2.1 [2023-03-14]
#### What's New
* None
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[metadata]
name = pybritive
version = 1.2.1
version = 1.2.2
author = Britive Inc.
author_email = support@britive.com
description = A pure Python CLI for Britive
Expand Down
37 changes: 26 additions & 11 deletions src/pybritive/britive_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,26 @@ def _cleanup_credentials(self):
self.credential_manager.delete()

def logout(self):
# if dealing with a token there is no concept of logout
if self.token:
raise click.ClickException('Logout not available when using an API token.')
self.login()
self.b.delete(f'https://{Britive.parse_tenant(self.tenant_name)}/api/auth')
self._cleanup_credentials()

self.tenant_name = self.config.get_tenant()['name']
self.tenant_alias = self.config.alias
self.set_credential_manager()

# let's see if we have credentials for this tenant already
# if we do we need to invalidate them at the tenant and clean them up on the client side
# if we don't have valid credentials for the tenant then there is no need to logout
if self.credential_manager.has_valid_credentials():

self.b = Britive(
tenant=self.tenant_name,
token=self.credential_manager.get_token(),
query_features=False
)
self.b.delete(f'https://{Britive.parse_tenant(self.tenant_name)}/api/auth')
self._cleanup_credentials()

def debug(self, data: object, ignore_silent: bool = False):
if debug_enabled:
Expand Down Expand Up @@ -183,11 +198,11 @@ def user(self):
output = f'{username} @ {self.tenant_name}'
if alias != self.tenant_name:
output += f' (alias: {alias})'
self.print(output)
self.print(output, ignore_silent=True)

def list_secrets(self):
self.login()
self.print(self.b.my_secrets.list())
self.print(self.b.my_secrets.list(), ignore_silent=True)

def list_profiles(self, checked_out: bool = False):
self.login()
Expand Down Expand Up @@ -219,7 +234,7 @@ def list_profiles(self, checked_out: bool = False):
if self.output_format == 'list':
self.output_format = 'list-profiles'

self.print(data)
self.print(data, ignore_silent=True)

# and set it back
if self.output_format == 'list-profiles':
Expand All @@ -242,7 +257,7 @@ def list_applications(self):

}
data.append(row)
self.print(data)
self.print(data, ignore_silent=True)

def list_environments(self):
self.login()
Expand All @@ -262,7 +277,7 @@ def list_environments(self):
'Type': env['app_type']
}
data.append(row)
self.print(data)
self.print(data, ignore_silent=True)

def _set_available_profiles(self):
if not self.available_profiles:
Expand Down Expand Up @@ -555,7 +570,7 @@ def viewsecret(self, path, blocktime, justification, maxpolltime):
pass

# and finally print the secret data
self.print(value)
self.print(value, ignore_silent=True)

def downloadsecret(self, path, blocktime, justification, maxpolltime, file):
self._validate_justification(justification)
Expand All @@ -578,7 +593,7 @@ def downloadsecret(self, path, blocktime, justification, maxpolltime, file):

if file == '-':
try:
self.print(content.decode('utf-8'))
self.print(content.decode('utf-8'), ignore_silent=True)
except UnicodeDecodeError as e:
raise click.ClickException(
'Secret file contents cannot be decoded to utf-8. '
Expand Down Expand Up @@ -707,7 +722,7 @@ def api(self, method, parameters={}, query=None):
pass

# output the response, optionally filtering based on provided jmespath query/search
self.print(jmespath.search(query, response) if query else response)
self.print(jmespath.search(query, response) if query else response, ignore_silent=True)

# yes - this method exits in b.my_access as _get_profile_and_environment_ids_given_names
# but we are doing additional business logic here to enhance the cli experience so there is
Expand Down
4 changes: 2 additions & 2 deletions src/pybritive/commands/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@
)
)
@build_britive
@britive_options(names='query,output_format,tenant,token,passphrase,federation_provider')
@britive_options(names='query,output_format,tenant,token,silent,passphrase,federation_provider')
@click_smart_api_method_argument # need to gracefully handle older version of click
def api(ctx, query, output_format, tenant, token, passphrase, federation_provider, method):
def api(ctx, query, output_format, tenant, token, silent, passphrase, federation_provider, method):
"""Exposes the Britive Python SDK methods to the CLI.

Documentation on each SDK method can be found inside the Python SDK itself and on Github
Expand Down
4 changes: 2 additions & 2 deletions src/pybritive/commands/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ def cache():

@cache.command()
@build_britive
@britive_options(names='tenant,token,passphrase,federation_provider')
def profiles(ctx, tenant, token, passphrase, federation_provider):
@britive_options(names='tenant,token,silent,passphrase,federation_provider')
def profiles(ctx, tenant, token, silent, passphrase, federation_provider):
"""Cache profiles locally to facilitate auto-completion of profile names on checkin/checkout."""
ctx.obj.britive.cache_profiles()

Expand Down
4 changes: 2 additions & 2 deletions src/pybritive/commands/checkin.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@

@click.command()
@build_britive
@britive_options(names='tenant,token,passphrase,federation_provider')
@britive_options(names='tenant,token,silent,passphrase,federation_provider')
@click_smart_profile_argument
def checkin(ctx, tenant, token, passphrase, federation_provider, profile):
def checkin(ctx, tenant, token, silent, passphrase, federation_provider, profile):
"""Checkin a profile.

This command takes 1 required argument `PROFILE`. This should be a string representation of the profile
Expand Down
4 changes: 2 additions & 2 deletions src/pybritive/commands/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

@click.command()
@build_britive
@britive_options(names='tenant,token,passphrase,federation_provider')
def login(ctx, tenant, token, passphrase, federation_provider):
@britive_options(names='tenant,token,silent,passphrase,federation_provider')
def login(ctx, tenant, token, silent, passphrase, federation_provider):
"""Perform an interactive login to obtain temporary credentials.

This only applies when an API token has not been specified via `--token,-T` or via environment variable
Expand Down
4 changes: 2 additions & 2 deletions src/pybritive/commands/logout.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

@click.command()
@build_britive
@britive_options(names='tenant,passphrase')
def logout(ctx, tenant, passphrase):
@britive_options(names='tenant,silent,passphrase')
def logout(ctx, tenant, silent, passphrase):
"""Logout of an interactive login session.

This only applies when an API token has not been specified via `--token,-T` or via environment variable
Expand Down
16 changes: 8 additions & 8 deletions src/pybritive/commands/ls.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,32 +11,32 @@ def ls():

@ls.command()
@build_britive
@britive_options(names='format,tenant,token,passphrase,federation_provider')
def applications(ctx, output_format, tenant, token, passphrase, federation_provider):
@britive_options(names='format,tenant,token,silent,passphrase,federation_provider')
def applications(ctx, output_format, tenant, token, silent, passphrase, federation_provider):
"""List applications for the currently authenticated identity."""
ctx.obj.britive.list_applications()


@ls.command()
@build_britive
@britive_options(names='format,tenant,token,passphrase,federation_provider')
def environments(ctx, output_format, tenant, token, passphrase, federation_provider):
@britive_options(names='format,tenant,token,silent,passphrase,federation_provider')
def environments(ctx, output_format, tenant, token, silent, passphrase, federation_provider):
"""List environments for the currently authenticated identity."""
ctx.obj.britive.list_environments()


@ls.command()
@build_britive
@britive_options(names='checked_out,output_format,tenant,token,passphrase,federation_provider')
def profiles(ctx, checked_out, output_format, tenant, token, passphrase, federation_provider):
@britive_options(names='checked_out,output_format,tenant,token,silent,passphrase,federation_provider')
def profiles(ctx, checked_out, output_format, tenant, token, silent, passphrase, federation_provider):
"""List profiles for the currently authenticated identity."""
ctx.obj.britive.list_profiles(checked_out=checked_out)


@ls.command()
@build_britive
@britive_options(names='format,tenant,token,passphrase, federation_provider')
def secrets(ctx, output_format, tenant, token, passphrase, federation_provider):
@britive_options(names='format,tenant,token,silent,passphrase,federation_provider')
def secrets(ctx, output_format, tenant, token, silent, passphrase, federation_provider):
"""List secrets for the currently authenticated identity."""
ctx.obj.britive.list_secrets()

Expand Down
8 changes: 4 additions & 4 deletions src/pybritive/commands/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ def request():

@request.command()
@build_britive
@britive_options(names='justification,tenant,token,passphrase,federation_provider')
@britive_options(names='justification,tenant,token,silent,passphrase,federation_provider')
@click_smart_profile_argument
def submit(ctx, justification, tenant, token, passphrase, federation_provider, profile):
def submit(ctx, justification, tenant, token, silent, passphrase, federation_provider, profile):
"""Submit a request to checkout a profile.

Only applicable for profiles which require approval. This command will NOT block/wait until the request is
Expand All @@ -33,9 +33,9 @@ def submit(ctx, justification, tenant, token, passphrase, federation_provider, p

@request.command()
@build_britive
@britive_options(names='tenant,token,passphrase,federation_provider')
@britive_options(names='tenant,token,silent,passphrase,federation_provider')
@click_smart_profile_argument
def withdraw(ctx, tenant, token, passphrase, federation_provider, profile):
def withdraw(ctx, tenant, token, silent, passphrase, federation_provider, profile):
"""Withdraw a request to checkout a profile.

Only applicable for profiles which require approval.
Expand Down
4 changes: 2 additions & 2 deletions src/pybritive/commands/secret.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ def secret():

@secret.command()
@build_britive
@britive_options(names='blocktime,justification,maxpolltime,format,tenant,token,passphrase,federation_provider')
@britive_options(names='blocktime,justification,maxpolltime,format,tenant,token,silent,passphrase,federation_provider')
@click.argument('path')
def view(ctx, blocktime, justification, maxpolltime, output_format, tenant, token, passphrase,
def view(ctx, blocktime, justification, maxpolltime, output_format, tenant, token, silent, passphrase,
federation_provider, path):
"""Display the secret value of the provided secret.

Expand Down
4 changes: 2 additions & 2 deletions src/pybritive/commands/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

@click.command()
@build_britive
@britive_options(names='tenant,token,passphrase,federation_provider')
def user(ctx, tenant, token, passphrase, federation_provider):
@britive_options(names='tenant,token,silent,passphrase,federation_provider')
def user(ctx, tenant, token, silent, passphrase, federation_provider):
"""Print details about the authenticated identity."""
ctx.obj.britive.user()

4 changes: 2 additions & 2 deletions src/pybritive/helpers/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,8 +276,8 @@ def validate_global(self, section, fields):

def validate_profile_aliases(self, section, fields):
for field, value in fields.items():
if len(profile_split(value)) != 3:
error = f'Invalid {section} field {field} value {value} provided. Value must be 3 parts ' \
if len(profile_split(value)) not in [2, 3]:
error = f'Invalid {section} field {field} value {value} provided. Value must be 2 or 3 parts ' \
'separated by a /'
self.validation_error_messages.append(error)

Expand Down
10 changes: 6 additions & 4 deletions src/pybritive/helpers/credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,9 @@ def perform_interactive_login(self):
webbrowser.get()
webbrowser.open(url)
except webbrowser.Error:
self.cli.print('No web browser found. Please manually navigate to the link below and authenticate.')
self.cli.print(
'No web browser found. Please manually navigate to the link below and authenticate.'
)
self.cli.print(url)

time.sleep(3)
Expand Down Expand Up @@ -190,15 +192,15 @@ def retrieve_tokens(self):
return self.session.post(url, headers=headers, json=auth_params)

def load(self, full=False):
# we should NEVER get herte but adding here just in case
# we should NEVER get here but adding here just in case
raise click.ClickException('Must use a subclass of CredentialManager')

def save(self, credentials: dict):
# we should NEVER get herte but adding here just in case
# we should NEVER get here but adding here just in case
raise click.ClickException('Must use a subclass of CredentialManager')

def delete(self):
# we should NEVER get herte but adding here just in case
# we should NEVER get here but adding here just in case
raise click.ClickException('Must use a subclass of CredentialManager')

# this helper exists since subclasses may need to override the method due to how the
Expand Down
2 changes: 1 addition & 1 deletion tests/test_0800_checkin.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

def test_checkout_json(runner, cli, profile):
def test_checkin(runner, cli, profile):
result = runner.invoke(cli, ['checkin', profile])
assert result.exit_code == 0
assert result.output == ''
Expand Down