Skip to content

Commit

Permalink
Release KSM CLI v1.1.4 (#605)
Browse files Browse the repository at this point in the history
* KSM-507 `ksm cli` add secret delete functionality (#594)

* KSM-508 `ksm cli` add secret lookup by title (#595)

* KSM-508 Added record lookup by title

* KSM-509 Added folder commands (#596)
  • Loading branch information
maksimu authored Jun 4, 2024
1 parent 9750ff3 commit dcd9631
Show file tree
Hide file tree
Showing 6 changed files with 368 additions and 20 deletions.
6 changes: 6 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,12 @@ For more information see our official documentation page https://docs.keeper.io/

# Change History

## 1.1.4

- KSM-507: Added `ksm secret delete` command
- KSM-508: Added search by title to `ksm secret list` command
- KSM-509: Added `ksm folder ...` commands

## 1.1.3

- KSM-496: Added upload file option
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from . import KeeperCli
from .exception import KsmCliException
from .exec import Exec
from .folder import Folder
from .secret import Secret
from .sync import Sync
from .profile import Profile
Expand Down Expand Up @@ -51,8 +52,10 @@ class AliasedGroup(HelpColorsGroup):
"export",
"import",
"init",
"default",
"setup",
"sync",
"folder",
"secret",
"totp",
"download",
Expand All @@ -61,6 +64,7 @@ class AliasedGroup(HelpColorsGroup):
"list",
"notation",
"update",
"delete",
"version",
"password",
"template",
Expand Down Expand Up @@ -223,6 +227,20 @@ def base_command_help(f):
return f


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")


def validate_non_empty_or_blank_list(ctx, param, value):
"""Validate parameter's value - list doesn't contain empty strings"""
if isinstance(value, tuple) and next((x for x in value if str(x).strip() == ""), None) is None:
return value
raise click.BadParameter("Empty strings are not allowed")


class Mutex(click.Option):
def __init__(self, *args, **kwargs):
# Detect mutually exclusive or required options - search by key only or key and value
Expand Down Expand Up @@ -514,6 +532,103 @@ def profile_import_command(ctx, profile_name, output_file, config_base64):
profile_command.add_command(profile_import_command)


# FOLDER GROUP
@click.group(
name='folder',
cls=AliasedGroup,
help_headers_color='yellow',
help_options_color='green'
)
@click.pass_context
def folder_command(ctx):
"""Commands for folders"""
ctx.obj["folder"] = Folder(cli=ctx.obj["cli"])


@click.command(
name='list',
cls=HelpColorsCommand,
help_options_color='blue'
)
@click.option('--folder', '-f', type=str, help='List only records in specified folder UID')
@click.option('--recursive', '-r', is_flag=True, help='List recursively including subfolders of the folder UID')
@click.option('--list-records', '-l', is_flag=True, help='List folder records too')
@click.option('--json', is_flag=True, help='Format result as JSON')
@click.pass_context
def folder_list_command(ctx, folder, recursive, list_records, json):
"""List folders"""

output = "json" if json is True else "text"
ctx.obj["folder"].list_folders(
folder=folder,
recursive=recursive,
list_records=list_records,
output_format=output,
use_color=ctx.obj["cli"].use_color
)


@click.command(
name='add',
cls=HelpColorsCommand,
help_options_color='blue'
)
@click.option('--parent-folder', '-f', type=str, required=True, callback=validate_non_empty, help='Parent folder UID')
@click.option('--title', '-t', type=str, required=True, callback=validate_non_empty, help='New folder title')
@click.pass_context
def folder_add_command(ctx, parent_folder, title):
"""Create new subfolder in specified parent folder"""

ctx.obj["folder"].add_folder(
parent_folder=parent_folder,
title=title
)


@click.command(
name='update',
cls=HelpColorsCommand,
help_options_color='blue'
)
@click.option('--folder', '-f', type=str, required=True, callback=validate_non_empty, help='Folder UID')
@click.option('--title', '-t', type=str, required=True, help='New folder title')
@click.pass_context
def folder_update_command(ctx, folder, title):
"""Rename folder"""

ctx.obj["folder"].update_folder(
folder_uid=folder,
folder_name=title
)


@click.command(
name='delete',
cls=HelpColorsCommand,
help_options_color='blue'
)
@click.option('--force', '-f', is_flag=True, help='Force deletion of non-empty folders')
@click.option('--json', is_flag=True, help='Format result as JSON')
@click.argument('folder_uid', type=str, required=True, nargs=-1, callback=validate_non_empty_or_blank_list)
@click.pass_context
def folder_delete_command(ctx, force, json, folder_uid):
"""Delete folders"""

output = "json" if json is True else "text"
ctx.obj["folder"].delete_folders(
uids=folder_uid,
force=force,
output_format=output,
use_color=ctx.obj["cli"].use_color
)


folder_command.add_command(folder_list_command)
folder_command.add_command(folder_add_command)
folder_command.add_command(folder_update_command)
folder_command.add_command(folder_delete_command)


# SECRET GROUP
@click.group(
name='secret',
Expand All @@ -537,21 +652,20 @@ def secret_command(ctx):
@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('--title', '-t', type=str, help='List only records with title matching the regex')
@click.option('--json', is_flag=True, help='Return secret as JSON')
@click.pass_context
def secret_list_command(ctx, uid, folder, recursive, query, show_value, json):
def secret_list_command(ctx, uid, folder, recursive, query, show_value, title, json):
"""List all secrets"""

output = "text"
if json is True:
output = "json"

output = "json" if json is True else "text"
ctx.obj["secret"].secret_list(
uids=uid,
folder=folder,
recursive=recursive,
query=query,
show_value=show_value,
title=title,
output_format=output,
use_color=ctx.obj["cli"].use_color
)
Expand Down Expand Up @@ -650,6 +764,20 @@ def secret_update_command(ctx, uid, field, custom_field, field_json, custom_fiel
)


@click.command(
name='delete',
cls=HelpColorsCommand,
help_options_color='blue'
)
@click.option('--uid', '-u', required=True, type=str, callback=validate_non_empty_or_blank_list, multiple=True, help='UIDs of secrets to delete.')
@click.option('--json', is_flag=True, help='Return results as JSON')
@click.pass_context
def secret_delete_command(ctx, uid, json):
"""Delete secret records"""
output = "json" if json else "text"
ctx.obj["secret"].delete(uids=uid, output_format=output, use_color=ctx.obj["cli"].use_color)


@click.command(
name='upload',
cls=HelpColorsCommand,
Expand Down Expand Up @@ -742,8 +870,6 @@ def secret_password_command(ctx, length, lc, uc, d, sc):


# SECRET TEMPLATE COMMAND


@click.group(
name='template',
cls=AliasedGroup,
Expand Down Expand Up @@ -823,8 +949,6 @@ def secret_template_field_command(ctx, show_list, output_format, version, field_


# SECRET ADD COMMAND


@click.group(
name='add',
cls=AliasedGroup,
Expand Down Expand Up @@ -916,13 +1040,6 @@ def secret_add_field_command(ctx, storage_folder_uid, record_type, title, passwo
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,
Expand Down Expand Up @@ -951,6 +1068,7 @@ def secret_add_clone_command(ctx, uid, title):
secret_command.add_command(secret_get_command)
secret_command.add_command(secret_notation_command)
secret_command.add_command(secret_update_command)
secret_command.add_command(secret_delete_command)
secret_command.add_command(secret_add_command)
secret_command.add_command(secret_upload_command)
secret_command.add_command(secret_download_command)
Expand Down Expand Up @@ -1242,6 +1360,7 @@ def sync_command(ctx, credentials, type, dry_run, preserve_missing, map):
# TOP LEVEL COMMANDS
cli.add_command(profile_command)
cli.add_command(sync_command)
cli.add_command(folder_command)
cli.add_command(secret_command)
cli.add_command(exec_command)
cli.add_command(config_command)
Expand Down
Loading

0 comments on commit dcd9631

Please sign in to comment.