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
4 changes: 2 additions & 2 deletions tools/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ classifiers = [
dependencies = [
"attrs >= 25.3, < 26.2",
"certifi >= 2024, < 2027",
"click == 7.1.2",
"click == 8.5.0",
"click-configfile == 0.2.3",
"configparser >= 7.2, < 7.3",
"dvp-libs == 5.2.0",
Expand All @@ -52,7 +52,7 @@ dev = [
"entrypoints == 0.4",
"flake8 == 7.3.0",
"httpretty == 1.1.4",
"isort == 8.0.1",
"isort == 9.0.2",
"mccabe == 0.7.0",
"mock == 5.2.0",
"more-itertools == 11.1.0",
Expand Down
37 changes: 24 additions & 13 deletions tools/src/main/python/dlpx/virtualization/_internal/cli.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#
# Copyright (c) 2019, 2021 by Delphix. All rights reserved.
# Copyright (c) 2019, 2026 by Delphix. All rights reserved.
#

import logging
Expand Down Expand Up @@ -64,7 +64,7 @@ def command_error_handler():
exit(2)


@click.group(context_settings=CONTEXT_SETTINGS)
@click.group(context_settings=CONTEXT_SETTINGS, invoke_without_command=True)
@click.version_option(__version__)
@click.option(
'-v',
Expand All @@ -84,7 +84,8 @@ def command_error_handler():
type=click.IntRange(0, 3),
help=('Enable quiet mode. '
'Can be repeated up to three times for increased suppression.'))
def delphix_sdk(verbose, quiet):
@click.pass_context
def delphix_sdk(ctx, verbose, quiet):
"""
The tools of the Delphix Virtualization SDK that help develop, build, and
upload a plugin.
Expand All @@ -100,6 +101,16 @@ def delphix_sdk(verbose, quiet):
'Python version check failed.'
'Supported version is 3.11.x, found {}'
.format(sys.version_info))
#
# Click >= 8.1 treats a group invoked with no subcommand as a usage
# error (exit code 2) instead of printing help and exiting 0 as Click
# 7.x did. invoke_without_command=True skips that check, so replicate
# the old exit-0/help behavior explicitly here to keep it unchanged
# for anything scripting around a bare `dvp` invocation.
#
if ctx.invoked_subcommand is None:
click.echo(ctx.get_help())
ctx.exit()


@delphix_sdk.command(context_settings=CONTEXT_SETTINGS_INIT)
Expand All @@ -108,11 +119,11 @@ def delphix_sdk(verbose, quiet):
'root',
default=os.getcwd(),
show_default=True,
type=click.Path(exists=True,
file_okay=False,
dir_okay=True,
writable=True,
resolve_path=True),
type=click_util.NonEmptyPath(exists=True,
Comment thread
delphix-devops-bot marked this conversation as resolved.
file_okay=False,
dir_okay=True,
writable=True,
resolve_path=True),
callback=click_util.validate_option_exists,
help='Set the plugin root directory.')
@click.option('-n',
Expand Down Expand Up @@ -297,11 +308,11 @@ def upload(engine, user, upload_artifact, password, wait):
'--directory',
default=os.getcwd(),
show_default=True,
type=click.Path(exists=True,
file_okay=False,
dir_okay=True,
writable=True,
resolve_path=True),
type=click_util.NonEmptyPath(exists=True,
file_okay=False,
dir_okay=True,
writable=True,
resolve_path=True),
callback=click_util.validate_option_exists,
help='Specify the directory of where to download the plugin logs.')
@click.password_option(cls=click_util.PasswordPromptIf,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#
# Copyright (c) 2019 by Delphix. All rights reserved.
# Copyright (c) 2019, 2026 by Delphix. All rights reserved.
#

import os
Expand Down Expand Up @@ -72,6 +72,22 @@ def validate_option_exists(ctx, param, value):
return value


class NonEmptyPath(click.Path):
"""
A click.Path that rejects an empty string outright. Click >= 8.0
resolves an empty string to the current working directory before
running its exists/writable checks (via os.path.realpath('')), so an
empty value for a Path option with resolve_path=True silently passes
instead of failing as it did on Click 7.x.
Comment thread
delphix-devops-bot marked this conversation as resolved.
"""
def convert(self, value, param, ctx):
Comment thread
SumoSourabh marked this conversation as resolved.
if value == '':
self.fail(
'{} {!r} does not exist.'.format(self.name.title(), value),
param, ctx)
return super(NonEmptyPath, self).convert(value, param, ctx)


class MutuallyExclusiveOption(click.Option):
"""
A Click Option type that is mutually exclusive with another option. Click
Expand Down
32 changes: 27 additions & 5 deletions tools/src/test/python/dlpx/virtualization/_internal/test_cli.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#
# Copyright (c) 2019, 2021 by Delphix. All rights reserved.
# Copyright (c) 2019, 2026 by Delphix. All rights reserved.
#

import os
Expand All @@ -18,6 +18,9 @@ class TestCli:
def test_default_verbosity():
runner = click_testing.CliRunner()

# Click >= 8.1 would otherwise exit 2 (usage error) here instead of
# 0 as in Click 7.x; delphix_sdk explicitly restores the old
# help/exit-0 behavior for a bare invocation with no subcommand.
result = runner.invoke(cli.delphix_sdk)

assert result.exit_code == 0, 'Output: {}'.format(result.output)
Expand Down Expand Up @@ -337,7 +340,7 @@ def test_file_not_exist(plugin_config_file):

assert result.exit_code == 2
assert result.output == (u"Usage: delphix-sdk build [OPTIONS]"
u"\nTry 'delphix-sdk build -h' for help."
u"\nTry 'delphix-sdk build --help' for help."
u"\n"
u"\nError: Invalid value for '-c' /"
u" '--plugin-config': File"
Expand Down Expand Up @@ -517,7 +520,7 @@ def test_file_not_exist(artifact_file):

assert result.exit_code == 2
assert result.output == (u"Usage: delphix-sdk upload [OPTIONS]"
u"\nTry 'delphix-sdk upload -h' for help."
u"\nTry 'delphix-sdk upload --help' for help."
u"\n"
u"\nError: Invalid value for '-a' /"
u" '--upload-artifact': File"
Expand Down Expand Up @@ -698,14 +701,33 @@ def test_directory_not_exist(directory):
assert result.exit_code == 2
assert result.output == (
u"Usage: delphix-sdk download-logs [OPTIONS]"
u"\nTry 'delphix-sdk download-logs -h' for help."
u"\nTry 'delphix-sdk download-logs --help' for help."
u"\n"
u"\nError: Invalid value for '-d' /"
u" '--directory': Directory"
u" '/not/a/real/directory'"
u" does not exist."
u"\n")

@staticmethod
def test_empty_directory():
# Click >= 8.0 resolves an empty string to the current working
# directory before running its exists/writable checks, so this
# would otherwise silently pass instead of erroring as it did on
# Click 7.x; NonEmptyPath (click_util.py) restores that rejection.
engine = 'engine'
user = 'admin'
password = 'delphix'

runner = click_testing.CliRunner()
result = runner.invoke(cli.delphix_sdk, [
'download-logs', '-e', engine, '-u', user, '--password', password,
'-d', ''
])

assert result.exit_code != 0
assert "Invalid value for '-d'" in result.output

@staticmethod
@pytest.mark.parametrize('plugin_config_file',
['/not/a/real/file/plugin_config.yml'])
Expand All @@ -724,7 +746,7 @@ def test_file_not_exist(plugin_config_file):
assert result.exit_code == 2
assert result.output == (
u"Usage: delphix-sdk download-logs [OPTIONS]"
u"\nTry 'delphix-sdk download-logs -h' for help."
u"\nTry 'delphix-sdk download-logs --help' for help."
u"\n"
u"\nError: Invalid value for '-c' /"
u" '--plugin-config': File"
Expand Down
Loading