Skip to content

Commit 908181d

Browse files
authored
Merge pull request #58 from britive/develop
v0.11.0
2 parents 5192b18 + c6705e9 commit 908181d

File tree

9 files changed

+187
-4
lines changed

9 files changed

+187
-4
lines changed

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,22 @@
22

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

5+
## v0.11.0 [2023-02-07]
6+
#### What's New
7+
* The `api` command is now available which exposes all the methods available in the Britive Python SDK.
8+
9+
#### Enhancements
10+
* None
11+
12+
#### Bug Fixes
13+
* None
14+
*
15+
#### Dependencies
16+
* None
17+
18+
#### Other
19+
* Updated documentation with examples of how to use the new `api` command.
20+
521
## v0.10.2 [2023-02-06]
622
#### What's New
723
* None

docs/index.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,37 @@ Example:
159159
As we construct the checkout command it would generally be `AWS/Dev/Test/Admin` but since the environment has a `/`
160160
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 .
161161

162+
## `api` Command - Use the Britive Python SDK via the CLI
163+
164+
As of v0.11.0 a new command called `api` has been introduced. This command exposes the full capability of the Britive Python SDK
165+
to users of the `pybritive` CLI.
166+
167+
Documentation on each SDK method can be found inside the Python SDK itself and on Github
168+
(https://github.com/britive/python-sdk). The Python package `britive` is a dependency of the CLI
169+
already so the SDK is available without installing any extra packages.
170+
171+
It is left up to the caller to provide the proper `method` and `parameters` based on the documentation
172+
of the API call being performed. Examples will follow which explain how to do this.
173+
174+
The authenticated identity must have the appropriate permissions to perform the actions being requested.
175+
General end users of Britive will not have these permissions. This call (and the larger SDK) is generally
176+
meant for administrative functionality.
177+
178+
Example of use: (`pybritive api method --parameter1 value1 --parameter2 value2 [--parameterX valueX]`)
179+
180+
* `pybritive api users.list` | will list all users in the britive tenant
181+
* `pybritive api tags.create --name testtag --description "test tag"` | will create a tag
182+
* `pybritive api users.list --query '[].email'` | will list all users and output just the email address of each user via jmespath query
183+
* `pybritive api profiles.create --application-id <id> --name testprofile` | will create a profile
184+
* `pybritive api secrets_manager.secrets.create --name test --vault-id <id> --value '{"Note": {"secret1": "abc"}}'` | creates a secret
185+
* `pybritive api secrets_manager.secrets.create --name test-file --vault-id <id> --file fileb://test.json --static-secret-template-id <id> --value None` | creates a file secret
186+
187+
188+
* The `method` is the same pattern as what would be used when referencing an instance of the `Britive` class.
189+
* The `parameters` are dynamic based on the method being invoked. Review the documentation for the method in question to understand
190+
which parameters are expected and which are optional. Parameters with `_` in the name should be translated to `-` when referencing them
191+
via the CLI.
192+
162193
## Shell Completion
163194

164195
TODO: Provide more automated scripts here to automatically add the required configs to the profiles. For now the below works just fine though.

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ mkdocs==1.3.1
1616
mkdocs-click==0.8.0
1717
twine~=4.0.1
1818
python-dateutil~=2.8.2
19-
boto3
19+
boto3
20+
jmespath

setup.cfg

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[metadata]
22
name = pybritive
3-
version = 0.10.2
3+
version = 0.11.0
44
author = Britive Inc.
55
author_email = support@britive.com
66
description = A pure Python CLI for Britive
@@ -27,6 +27,7 @@ install_requires =
2727
cryptography
2828
python-dateutil
2929
britive>=2.14.0
30+
jmespath
3031

3132
[options.packages.find]
3233
where = src

src/pybritive/britive_cli.py

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import io
2+
import pathlib
23
from britive.britive import Britive
34
from .helpers.config import ConfigManager
45
from .helpers.credentials import FileCredentialManager, EncryptedFileCredentialManager
@@ -15,6 +16,7 @@
1516
from datetime import datetime
1617
import os
1718
import sys
19+
import jmespath
1820

1921

2022
default_table_format = 'fancy_grid'
@@ -142,9 +144,17 @@ def print(self, data: object, ignore_silent: bool = False):
142144

143145
if self.output_format == 'json':
144146
click.echo(json.dumps(data, indent=2, default=str))
145-
elif self.output_format == 'list':
147+
elif self.output_format == 'list-profiles':
146148
for row in data:
147149
click.echo(self.list_separator.join([self.escape_profile_element(x) for x in row.values()]))
150+
elif self.output_format == 'list':
151+
for row in data:
152+
if isinstance(row, dict):
153+
click.echo(self.list_separator.join([json.dumps(x, default=str) for x in row.values()]))
154+
elif isinstance(row, list):
155+
click.echo(self.list_separator.join([json.dumps(x, default=str) for x in row]))
156+
else:
157+
click.echo(row)
148158
elif self.output_format == 'csv':
149159
fields = list(data[0].keys())
150160
output = io.StringIO()
@@ -202,8 +212,18 @@ def list_profiles(self, checked_out: bool = False):
202212
row.pop('Description')
203213
row.pop('Type')
204214
data.append(row)
215+
216+
# set special list output if needed
217+
if self.output_format == 'list':
218+
self.output_format = 'list-profiles'
219+
205220
self.print(data)
206221

222+
# and set it back
223+
if self.output_format == 'list-profiles':
224+
self.output_format = 'list'
225+
226+
207227
def list_applications(self):
208228
self.login()
209229
self._set_available_profiles()
@@ -596,6 +616,61 @@ def request_withdraw(self, profile):
596616
def clear_gcloud_auth_key_files(self):
597617
self.config.clear_gcloud_auth_key_files()
598618

619+
def api(self, method, parameters={}, query=None):
620+
self.login()
621+
622+
# clean up parameters - need to load json as dict if json string is provided and handle file inputs
623+
computed_parameters = {}
624+
open_file_keys = []
625+
for key, value in parameters.items():
626+
computed_key = key.replace('-', '_')
627+
computed_value = value
628+
629+
if value.lower() == 'none':
630+
computed_value = None
631+
632+
if value.startswith('file://'):
633+
filepath = value.replace('file://', '')
634+
path = pathlib.Path(filepath)
635+
with open(str(path), 'r') as f:
636+
computed_value = f.read().strip()
637+
638+
if value.startswith('fileb://'):
639+
filepath = value.replace('fileb://', '')
640+
path = pathlib.Path(filepath)
641+
computed_value = open(str(path), 'rb')
642+
open_file_keys.append(computed_key)
643+
644+
try:
645+
computed_parameters[computed_key] = json.loads(computed_value)
646+
except json.JSONDecodeError:
647+
computed_parameters[computed_key] = computed_value
648+
except Exception: # not sure what else we would do so just default to the value provided
649+
computed_parameters[computed_key] = computed_value
650+
651+
# determine the sdk method we need to execute, starting at the base Britive class
652+
func = self.b
653+
try:
654+
for m in method.split('.'):
655+
func = getattr(func, m)
656+
except Exception as e:
657+
raise click.ClickException(f'invalid method {method} provided.')
658+
659+
# execute the method with the computed parameters
660+
response = func(**computed_parameters)
661+
662+
# close any files we opened due to fileb:// prefix
663+
for key in open_file_keys:
664+
try:
665+
computed_parameters[key].close()
666+
except Exception:
667+
pass
668+
669+
# output the response, optionally filtering based on provided jmespath query/search
670+
self.print(jmespath.search(query, response) if query else response)
671+
672+
673+
599674

600675

601676

src/pybritive/cli_interface.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from .commands.cache import cache as group_cache
1212
from .commands.request import request as group_request
1313
from .commands.clear import clear as group_clear
14+
from .commands.api import api as command_api
1415
import sys
1516
import os
1617

@@ -48,6 +49,7 @@ def cli(version):
4849
cli.add_command(group_cache)
4950
cli.add_command(group_request)
5051
cli.add_command(group_clear)
52+
cli.add_command(command_api)
5153

5254

5355
if __name__ == "__main__":

src/pybritive/commands/api.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import click
2+
from ..helpers.build_britive import build_britive
3+
from ..options.britive_options import britive_options
4+
5+
6+
@click.command(
7+
context_settings=dict(
8+
ignore_unknown_options=True,
9+
allow_extra_args=True
10+
)
11+
)
12+
@build_britive
13+
@britive_options(names='query,output_format,tenant,token,passphrase,federation_provider')
14+
@click.argument('method')
15+
def api(ctx, query, output_format, tenant, token, passphrase, federation_provider, method):
16+
"""Exposes the Britive Python SDK methods to the CLI.
17+
18+
Documentation on each SDK method can be found inside the Python SDK itself and on Github
19+
(https://github.com/britive/python-sdk). The Python package `britive` is a dependency of the CLI
20+
already so the SDK is available without installing any extra packages.
21+
22+
It is left up to the caller to provide the proper `method` and `parameters` based on the documentation
23+
of the API call being performed.
24+
25+
The authenticated identity must have the appropriate permissions to perform the actions being requested.
26+
General end users of Britive will not have these permissions. This call (and the larger SDK) is generally
27+
meant for administrative functionality.
28+
29+
Example of use:
30+
31+
* generic: pybritive api method --parameter1 value1 --parameter2 value2 [--parameterX valueX]
32+
33+
* pybritive api users.list
34+
35+
* pybritive api tags.create --name testtag --description "test tag"
36+
37+
* pybritive api users.list --query '[].email'
38+
39+
* pybritive api profiles.create --application-id <id> --name testprofile
40+
41+
"""
42+
parameters = {ctx.args[i][2:]: ctx.args[i + 1] for i in range(0, len(ctx.args), 2)}
43+
ctx.obj.britive.api(
44+
method=method,
45+
parameters=parameters,
46+
query=query
47+
)

src/pybritive/options/britive_options.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from ..options.federation_provider import option as federation_provider
2323
from ..options.gcloud_key_file import option as gcloud_key_file
2424
from ..options.verbose import option as verbose
25+
from ..options.query import option as query
2526

2627
options_map = {
2728
'tenant': tenant,
@@ -47,7 +48,8 @@
4748
'aws_credentials_file': aws_credentials_file,
4849
'federation_provider': federation_provider,
4950
'gcloud_key_file': gcloud_key_file,
50-
'verbose': verbose
51+
'verbose': verbose,
52+
'query': query
5153
}
5254

5355

src/pybritive/options/query.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import click
2+
3+
4+
option = click.option(
5+
'--query',
6+
default=None,
7+
help='JMESPath query to apply to the response.'
8+
)

0 commit comments

Comments
 (0)