Skip to content

Commit

Permalink
Release KSM CLI v1.1.3 (#587)
Browse files Browse the repository at this point in the history
- KSM-496: Added upload file option
- KSM-495: Added query option to ksm secret list command
- KSM-494: Added folder support to secret list command
- KSM-493: Added CLI options to update title and notes
- KSM-492: Added clone option
- KSM-485: Added sub-folder support to ksm secret add command
  • Loading branch information
maksimu authored Apr 12, 2024
1 parent 77c4bcb commit 6930f6d
Show file tree
Hide file tree
Showing 6 changed files with 219 additions and 47 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.cli.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.7", "3.8", "3.9", "3.10"]
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]

steps:
- uses: actions/checkout@v3
Expand Down
9 changes: 9 additions & 0 deletions integration/keeper_secrets_manager_cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ For more information see our official documentation page https://docs.keeper.io/

# Change History

## 1.1.3

- KSM-496: Added upload file option
- KSM-495: Added query option to ksm secret list command
- KSM-494: Added folder support to secret list command
- KSM-493: Added CLI options to update title and notes
- KSM-492: Added clone option
- KSM-485: Added sub-folder support to ksm secret add command

## 1.1.1

* KSM-429 - Add `--profile-name` to `ksm profile import` command
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ class AliasedGroup(HelpColorsGroup):
"add",
"editor",
"field",
"clone",
"record",
"file",
"cache",
Expand Down Expand Up @@ -249,7 +250,8 @@ def handle_parse_result(self, ctx, opts, args):
for mutex_opt in self.not_required_if:
if mutex_opt and mutex_opt[0] in opts and (len(mutex_opt) == 1 or opts.get(mutex_opt[0], str(mutex_opt[1])+'_') == mutex_opt[1]):
if current_opt:
raise click.UsageError("Illegal usage: '" + str(self.name) + "' is mutually exclusive with " + str(mutex_opt) + ".")
opt = str(mutex_opt) if len(mutex_opt) > 1 else f"'{str(mutex_opt[0])}'"
raise click.UsageError("Illegal usage: '" + str(self.name) + "' is mutually exclusive with " + opt + ".")
else:
self.prompt = None
for mutex_opt in self.required_if:
Expand Down Expand Up @@ -530,10 +532,14 @@ def secret_command(ctx):
cls=HelpColorsCommand,
help_options_color='blue'
)
@click.option('--uid', "-u", type=str, multiple=True)
@click.option('--uid', '-u', type=str, multiple=True, help='List specific records by Record UID', cls=Mutex, not_required_if=[('folder',)])
@click.option('--folder', '-f', type=str, help='List only records in specified folder UID')
@click.option('--recursive', '-r', is_flag=True, help='List recursively all records including subfolders of the folder UID')
@click.option('--query', '-q', type=str, help='List records matching the JSONPath query')
@click.option('--show-value', '-v', is_flag=True, help='Print matching value instead of record title')
@click.option('--json', is_flag=True, help='Return secret as JSON')
@click.pass_context
def secret_list_command(ctx, uid, json):
def secret_list_command(ctx, uid, folder, recursive, query, show_value, json):
"""List all secrets"""

output = "text"
Expand All @@ -542,6 +548,10 @@ def secret_list_command(ctx, uid, json):

ctx.obj["secret"].secret_list(
uids=uid,
folder=folder,
recursive=recursive,
query=query,
show_value=show_value,
output_format=output,
use_color=ctx.obj["cli"].use_color
)
Expand Down Expand Up @@ -624,15 +634,19 @@ def secret_notation_command(ctx, text):
@click.option('--field-json', type=str, multiple=True, help="Update value in field section of vault using JSON")
@click.option('--custom-field-json', type=str, multiple=True,
help="Update value in custom field section of vault using JSON")
@click.option('--title', '-t', type=str, help="Update record title.")
@click.option('--notes', '-n', type=str, help="Update record notes.")
@click.pass_context
def secret_update_command(ctx, uid, field, custom_field, field_json, custom_field_json):
def secret_update_command(ctx, uid, field, custom_field, field_json, custom_field_json, title, notes):
"""Update an existing record"""
ctx.obj["secret"].update(
uid=uid,
fields=field,
custom_fields=custom_field,
fields_json=field_json,
custom_fields_json=custom_field_json
custom_fields_json=custom_field_json,
title=title,
notes=notes
)


Expand Down Expand Up @@ -827,7 +841,7 @@ def secret_add_command():
help_options_color='blue'
)
@click.pass_context
@click.option('--shared-folder-uid', '--sf', required=True, type=str, help="Place record in folder with UID.")
@click.option('--storage-folder-uid', '--sf', required=True, type=str, help="Place record in folder with UID.")
@click.option('--record-type', '--rt', required=True, type=str, help="Record type")
@click.option('--password-generate', '-p', is_flag=True, help='Generate passwords for empty password fields.')
@click.option('--title', '-t', type=str, help="Record title")
Expand All @@ -836,13 +850,13 @@ def secret_add_command():
help='File format to display in editor.')
@click.option('--editor', '-e', type=str, help='Application to use to edit record data.')
@click.option('--version', type=click.Choice(['v3'], case_sensitive=False), default='v3', help='Record version.')
def secret_add_editor_command(ctx, shared_folder_uid, record_type, password_generate, title, notes,
def secret_add_editor_command(ctx, storage_folder_uid, record_type, password_generate, title, notes,
output_format, editor, version):
"""Add a secret record via a text editor"""

ctx.obj["secret"].add_record_interactive(
version=version,
folder_uid=shared_folder_uid,
folder_uid=storage_folder_uid,
record_type=record_type,
output_format=output_format,
password_generate_flag=password_generate,
Expand All @@ -859,14 +873,14 @@ def secret_add_editor_command(ctx, shared_folder_uid, record_type, password_gene
help_options_color='blue'
)
@click.pass_context
@click.option('--shared-folder-uid', '--sf', required=True, type=str, help="Place record in folder with UID.")
@click.option('--storage-folder-uid', '--sf', required=True, type=str, help="Place record in folder with UID.")
@click.option('--file', '-f', required=True, type=str, help='Add records from record script file.')
@click.option('--password-generate', '-p', is_flag=True, help='Generate passwords for empty password fields.')
def secret_add_file_command(ctx, shared_folder_uid, file, password_generate):
def secret_add_file_command(ctx, storage_folder_uid, file, password_generate):
"""Add a secret record(s) from a file"""

ctx.obj["secret"].add_record_from_file(
folder_uid=shared_folder_uid,
folder_uid=storage_folder_uid,
file=file,
password_generate_flag=password_generate,
)
Expand All @@ -879,20 +893,20 @@ def secret_add_file_command(ctx, shared_folder_uid, file, password_generate):
help_options_color='blue'
)
@click.pass_context
@click.option('--shared-folder-uid', '--sf', required=True, type=str, help="Place record in folder with UID.")
@click.option('--storage-folder-uid', '--sf', required=True, type=str, help="Place record in folder with UID.")
@click.option('--record-type', '--rt', required=True, type=str, help="Record type")
@click.option('--title', '-t', required=True, type=str, help="Record title")
@click.option('--password-generate', '-p', is_flag=True, help='Generate passwords for empty password fields.')
@click.option('--notes', '-n', type=str, help="Record simple note")
@click.option('--version', type=click.Choice(['v3'], case_sensitive=False), default='v3', help='Record version.')
@click.argument('field_args', type=str, nargs=-1)
def secret_add_field_command(ctx, shared_folder_uid, record_type, title, password_generate, notes, version,
def secret_add_field_command(ctx, storage_folder_uid, record_type, title, password_generate, notes, version,
field_args):
"""Add a secret record from a command line field arguments"""

ctx.obj["secret"].add_record_from_field_args(
version=version,
folder_uid=shared_folder_uid,
folder_uid=storage_folder_uid,
password_generate_flag=password_generate,
record_type=record_type,
title=title,
Expand All @@ -902,6 +916,32 @@ def secret_add_field_command(ctx, shared_folder_uid, record_type, title, passwor
print("", file=sys.stderr)


def validate_non_empty(ctx, param, value):
"""Validate that parameter's value is not an empty string"""
if isinstance(value, str) and value != "":
return value
raise click.BadParameter("Empty strings are not allowed")


@click.command(
name='clone',
cls=HelpColorsCommand,
help_options_color='blue'
)
@click.pass_context
@click.option('--uid', '-u', required=True, type=str, callback=validate_non_empty, help="Record UID to clone")
@click.option('--title', '-t', type=str, help="New record title")
def secret_add_clone_command(ctx, uid, title):
"""Add new record by duplicating existing record"""

ctx.obj["secret"].add_record_from_clone(
uid=uid,
title=title
)
print("", file=sys.stderr)


secret_add_command.add_command(secret_add_clone_command)
secret_add_command.add_command(secret_add_field_command)
secret_add_command.add_command(secret_add_file_command)
secret_add_command.add_command(secret_add_editor_command)
Expand Down Expand Up @@ -1186,7 +1226,7 @@ def quit_command():
@click.option('--credentials', '-c', type=str, metavar="UID", help="Keeper record with credentials to access destination key/value store.",
cls=Mutex,
# not_required_if=[('type','json')],
required_if=[('type','azure'), ('type','aws'), ('type','gcp')]
required_if=[('type', 'azure'), ('type', 'aws'), ('type', 'gcp')]
)
@click.option('--type', '-t', type=click.Choice(['aws', 'azure', 'gcp', 'json']), default='json', help="Type of the target key/value storage (aws, azure, gcp, json).", show_default=True)
@click.option('--dry-run', '-n', is_flag=True, help='Perform a trial run with no changes made.')
Expand Down
Loading

0 comments on commit 6930f6d

Please sign in to comment.