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
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@

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

## v0.10.2 [2023-02-06]
#### What's New
* None

#### Enhancements
* None

#### Bug Fixes
* Fix issue with `checkout` and related commands that use the `PROFILE` positional argument when the one or more of the `PROFILE` components (application, environment, profile) have a `/` in the name. Caller must now properly escape any `/` with a `\` (e.g. `AWS/Dev\/Test/Admin`).

#### Dependencies
* None

#### Other
* None

## v0.10.1 [2023-01-19]
#### What's New
* None
Expand Down
12 changes: 12 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,18 @@ This can be overwritten by specifying environment variable `PYBRITIVE_HOME_DIR`.
the end user wants to persist the `.britive` directory. Note that `.britive` will still be created so do not specify
that as part of the path.


## Escaping
If the name of an application, environment, or profile contains a `/` then that character must be properly escaped with a `\`.

Example:
* Application: AWS
* Environment: Dev/Test
* Profile: Admin

As we construct the checkout command it would generally be `AWS/Dev/Test/Admin` but since the environment has a `/`
in it, we need to escape that to be `AWS/Dev\/Test/Admin` so the CLI can properly parse out the 3 required parts of the string .

## Shell Completion

TODO: Provide more automated scripts here to automatically add the required configs to the profiles. For now the below works just fine though.
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ certifi==2022.6.15
charset-normalizer==2.1.0
click==8.1.3
idna==3.3
merge-args==0.1.4
merge-args==0.1.5
PyYAML==6.0
requests==2.28.1
six==1.16.0
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 = 0.10.1
version = 0.10.2
author = Britive Inc.
author_email = support@britive.com
description = A pure Python CLI for Britive
Expand Down
16 changes: 13 additions & 3 deletions src/pybritive/britive_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from britive.britive import Britive
from .helpers.config import ConfigManager
from .helpers.credentials import FileCredentialManager, EncryptedFileCredentialManager
from .helpers.split import profile_split
import json
import click
import csv
Expand Down Expand Up @@ -143,7 +144,7 @@ def print(self, data: object, ignore_silent: bool = False):
click.echo(json.dumps(data, indent=2, default=str))
elif self.output_format == 'list':
for row in data:
click.echo(self.list_separator.join(row.values()))
click.echo(self.list_separator.join([self.escape_profile_element(x) for x in row.values()]))
elif self.output_format == 'csv':
fields = list(data[0].keys())
output = io.StringIO()
Expand Down Expand Up @@ -359,7 +360,7 @@ def _should_check_force_renew(app, force_renew, console):

def _split_profile_into_parts(self, profile):
profile_real = self.config.profile_aliases.get(profile, profile)
parts = profile_real.split('/')
parts = profile_split(profile_real)
if len(parts) != 3:
raise click.ClickException('Provided profile string does not have the required 3 parts.')
parts_dict = {
Expand Down Expand Up @@ -551,9 +552,18 @@ def cache_profiles(self, load=True):
self._set_available_profiles()
profiles = []
for p in self.available_profiles:
profiles.append(f"{p['app_name']}/{p['env_name']}/{p['profile_name']}")
profile = self.escape_profile_element(p['app_name'])
profile += '/'
profile += self.escape_profile_element(p['env_name'])
profile += '/'
profile += self.escape_profile_element(p['profile_name'])
profiles.append(profile)
Cache().save_profiles(profiles)

@staticmethod
def escape_profile_element(element):
return element.replace('/', '\\/')

@staticmethod
def cache_clear():
Cache().clear()
Expand Down
3 changes: 2 additions & 1 deletion src/pybritive/helpers/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from ..choices.output_format import output_format_choices
from ..choices.backend import backend_choices
from britive.britive import Britive
from ..helpers.split import profile_split


def extract_tenant(tenant_key):
Expand Down Expand Up @@ -256,7 +257,7 @@ def validate_global(self, section, fields):

def validate_profile_aliases(self, section, fields):
for field, value in fields.items():
if len(value.split('/')) != 3:
if len(profile_split(value)) != 3:
error = f'Invalid {section} field {field} value {value} provided. Value must be 3 parts ' \
'separated by a /'
self.validation_error_messages.append(error)
Expand Down
28 changes: 28 additions & 0 deletions src/pybritive/helpers/split.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
def profile_split(profile):
def str_escape_split(str_to_escape, delimiter=',', escape='\\'):
if len(delimiter) > 1 or len(escape) > 1:
raise ValueError("Either delimiter or escape must be an one char value")
token = ''
escaped = False
for c in str_to_escape:
if c == escape:
if escaped:
token += escape
escaped = False
else:
escaped = True
continue
if c == delimiter:
if not escaped:
yield token
token = ''
else:
token += c
escaped = False
else:
if escaped:
token += escape
escaped = False
token += c
yield token
return list(str_escape_split(profile, delimiter='/', escape='\\'))