From d98a870fae7ce6b5ffdee68203863e4cedba6635 Mon Sep 17 00:00:00 2001 From: Allen Reese Date: Mon, 8 Jul 2019 09:08:37 -0700 Subject: [PATCH 1/9] The password provided to configure shouldn't be echo'd. Add a --stdin option to configure, so that the password will always be read from stdin instead of stored in a file. --- databricks_cli/configure/cli.py | 18 +++++++++++++++--- databricks_cli/configure/provider.py | 13 +++++++++---- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/databricks_cli/configure/cli.py b/databricks_cli/configure/cli.py index 5242b63b..6f9cdd02 100644 --- a/databricks_cli/configure/cli.py +++ b/databricks_cli/configure/cli.py @@ -32,18 +32,27 @@ PROMPT_HOST = 'Databricks Host (should begin with https://)' PROMPT_USERNAME = 'Username' -PROMPT_PASSWORD = 'Password' # NOQA +PROMPT_PASSWORD = 'Password [enter stdin to read from stdin every time]' # NOQA PROMPT_TOKEN = 'Token' # NOQA def _configure_cli_token(profile, insecure): config = ProfileConfigProvider(profile).get_config() or DatabricksConfig.empty() host = click.prompt(PROMPT_HOST, default=config.host, type=_DbfsHost()) - token = click.prompt(PROMPT_TOKEN, default=config.token) + token = click.prompt(PROMPT_TOKEN, default=config.token, hide_input=True) new_config = DatabricksConfig.from_token(host, token, insecure) update_and_persist_config(profile, new_config) +def _configure_cli_stdin(profile, insecure): + config = ProfileConfigProvider(profile).get_config() or DatabricksConfig.empty() + host = click.prompt(PROMPT_HOST, default=config.host, type=_DbfsHost()) + username = click.prompt(PROMPT_USERNAME, default=config.username) + password = 'stdin' + new_config = DatabricksConfig.from_password(host, username, password, insecure) + update_and_persist_config(profile, new_config) + + def _configure_cli_password(profile, insecure): config = ProfileConfigProvider(profile).get_config() or DatabricksConfig.empty() if config.password: @@ -64,9 +73,10 @@ def _configure_cli_password(profile, insecure): short_help='Configures host and authentication info for the CLI.') @click.option('--token', show_default=True, is_flag=True, default=False) @click.option('--insecure', show_default=True, is_flag=True, default=None) +@click.option('--stdin', show_default=True, is_flag=True, default=False) @debug_option @profile_option -def configure_cli(token, insecure): +def configure_cli(token, insecure, stdin): """ Configures host and authentication info for the CLI. """ @@ -74,6 +84,8 @@ def configure_cli(token, insecure): insecure_str = str(insecure) if insecure is not None else None if token: _configure_cli_token(profile, insecure_str) + elif stdin: + _configure_cli_stdin() else: _configure_cli_password(profile, insecure_str) diff --git a/databricks_cli/configure/provider.py b/databricks_cli/configure/provider.py index d8b96aae..a10d4423 100644 --- a/databricks_cli/configure/provider.py +++ b/databricks_cli/configure/provider.py @@ -20,7 +20,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - +import getpass from abc import abstractmethod, ABCMeta from configparser import ConfigParser import os @@ -33,7 +33,7 @@ CONFIG_FILE_ENV_VAR = "DATABRICKS_CONFIG_FILE" HOST = 'host' USERNAME = 'username' -PASSWORD = 'password' # NOQA +PASSWORD = 'password' # NOQA TOKEN = 'token' INSECURE = 'insecure' DEFAULT_SECTION = 'DEFAULT' @@ -263,10 +263,15 @@ def get_config(self): class DatabricksConfig(object): - def __init__(self, host, username, password, token, insecure): # noqa + def __init__(self, host, username, password, token, insecure): # noqa self.host = host self.username = username - self.password = password + + if self.password == 'stdin': + self.password = getpass.getpass("Password to connect to databricks at [" + self.host + "]: ") + else: + self.password = password + self.token = token self.insecure = insecure From 9c2042f4d7e6ecc9df2303e2a0873a086db3d356 Mon Sep 17 00:00:00 2001 From: Allen Reese Date: Mon, 8 Jul 2019 09:08:32 -0700 Subject: [PATCH 2/9] Add a debugging flag If --debug is used or if DATABRICKS_DEBUGGING is set in the environment the entire contents of the http requests will be printed to stdout. --- databricks_cli/configure/config.py | 4 ++++ databricks_cli/configure/provider.py | 21 ++++++++++++++++++--- databricks_cli/sdk/api_client.py | 22 ++++++++++++++++++++++ 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/databricks_cli/configure/config.py b/databricks_cli/configure/config.py index f6ca04e4..eacb3607 100644 --- a/databricks_cli/configure/config.py +++ b/databricks_cli/configure/config.py @@ -83,6 +83,10 @@ def callback(ctx, param, value): # NOQA def _get_api_client(config, command_name=""): verify = config.insecure is None + + if config.is_debugging and config.debugging == "1": + ApiClient.enable_debug_logging() + if config.is_valid_with_token: return ApiClient(host=config.host, token=config.token, verify=verify, command_name=command_name) diff --git a/databricks_cli/configure/provider.py b/databricks_cli/configure/provider.py index a10d4423..6d288973 100644 --- a/databricks_cli/configure/provider.py +++ b/databricks_cli/configure/provider.py @@ -37,6 +37,7 @@ TOKEN = 'token' INSECURE = 'insecure' DEFAULT_SECTION = 'DEFAULT' +DEBUG = 'debug' # User-provided override for the DatabricksConfigProvider _config_provider = None @@ -238,7 +239,11 @@ def get_config(self): password = os.environ.get('DATABRICKS_PASSWORD') token = os.environ.get('DATABRICKS_TOKEN') insecure = os.environ.get('DATABRICKS_INSECURE') - config = DatabricksConfig(host, username, password, token, insecure) + debugging = os.environ.get('DATABRICKS_DEBUGGING') + if debugging is None: + debugging = 0 + + config = DatabricksConfig(host, username, password, token, insecure, debugging=debugging) if config.is_valid: return config return None @@ -256,14 +261,18 @@ def get_config(self): password = _get_option_if_exists(raw_config, self.profile, PASSWORD) token = _get_option_if_exists(raw_config, self.profile, TOKEN) insecure = _get_option_if_exists(raw_config, self.profile, INSECURE) - config = DatabricksConfig(host, username, password, token, insecure) + debugging = _get_option_if_exists(raw_config, self.profile, DEBUG) + if debugging is None: + debugging = 0 + + config = DatabricksConfig(host, username, password, token, insecure, debugging=debugging) if config.is_valid: return config return None class DatabricksConfig(object): - def __init__(self, host, username, password, token, insecure): # noqa + def __init__(self, host, username, password, token, insecure, debugging=0): # noqa self.host = host self.username = username @@ -274,6 +283,8 @@ def __init__(self, host, username, password, token, insecure): # noqa self.token = token self.insecure = insecure + self.debugging = debugging + @classmethod def from_token(cls, host, token, insecure=None): @@ -298,3 +309,7 @@ def is_valid_with_password(self): @property def is_valid(self): return self.is_valid_with_token or self.is_valid_with_password + + @property + def is_debugging(self): + return self.debugging diff --git a/databricks_cli/sdk/api_client.py b/databricks_cli/sdk/api_client.py index d5870a6a..6f20d758 100644 --- a/databricks_cli/sdk/api_client.py +++ b/databricks_cli/sdk/api_client.py @@ -30,6 +30,7 @@ import base64 import json +import logging import warnings import requests import ssl @@ -129,6 +130,27 @@ def perform_query(self, method, path, data = {}, headers = None): raise requests.exceptions.HTTPError(message, response=e.response) return resp.json() + @classmethod + def enable_debug_logging(cls): + # These two lines enable debugging at httplib level (requests->urllib3->http.client) + # You will see the REQUEST, including HEADERS and DATA, and RESPONSE with HEADERS but without DATA. + # The only thing missing will be the response.body which is not logged. + try: + import http.client as http_client + except ImportError: + # Python 2 + import httplib as http_client + + print ("HTTP debugging enabled") + http_client.HTTPConnection.debuglevel = 1 + + # You must initialize logging, otherwise you'll not see debug output. + logging.basicConfig() + logging.getLogger().setLevel(logging.DEBUG) + requests_log = logging.getLogger("requests.packages.urllib3") + requests_log.setLevel(logging.DEBUG) + requests_log.propagate = True + def _translate_boolean_to_query_param(value): assert not isinstance(value, list), 'GET parameters cannot pass list of objects' From deb18441927db0746c7704e90155bcbedd0717d4 Mon Sep 17 00:00:00 2001 From: Allen Reese Date: Mon, 8 Jul 2019 12:33:09 -0700 Subject: [PATCH 3/9] Fix odd whitespace issue --- databricks_cli/sdk/api_client.py | 40 ++++++++++++++++---------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/databricks_cli/sdk/api_client.py b/databricks_cli/sdk/api_client.py index 6f20d758..2115304b 100644 --- a/databricks_cli/sdk/api_client.py +++ b/databricks_cli/sdk/api_client.py @@ -130,26 +130,26 @@ def perform_query(self, method, path, data = {}, headers = None): raise requests.exceptions.HTTPError(message, response=e.response) return resp.json() - @classmethod - def enable_debug_logging(cls): - # These two lines enable debugging at httplib level (requests->urllib3->http.client) - # You will see the REQUEST, including HEADERS and DATA, and RESPONSE with HEADERS but without DATA. - # The only thing missing will be the response.body which is not logged. - try: - import http.client as http_client - except ImportError: - # Python 2 - import httplib as http_client - - print ("HTTP debugging enabled") - http_client.HTTPConnection.debuglevel = 1 - - # You must initialize logging, otherwise you'll not see debug output. - logging.basicConfig() - logging.getLogger().setLevel(logging.DEBUG) - requests_log = logging.getLogger("requests.packages.urllib3") - requests_log.setLevel(logging.DEBUG) - requests_log.propagate = True + @classmethod + def enable_debug_logging(cls): + # These two lines enable debugging at httplib level (requests->urllib3->http.client) + # You will see the REQUEST, including HEADERS and DATA, and RESPONSE with HEADERS but without DATA. + # The only thing missing will be the response.body which is not logged. + try: + import http.client as http_client + except ImportError: + # Python 2 + import httplib as http_client + + print ("HTTP debugging enabled") + http_client.HTTPConnection.debuglevel = 1 + + # You must initialize logging, otherwise you'll not see debug output. + logging.basicConfig() + logging.getLogger().setLevel(logging.DEBUG) + requests_log = logging.getLogger("requests.packages.urllib3") + requests_log.setLevel(logging.DEBUG) + requests_log.propagate = True def _translate_boolean_to_query_param(value): From 18079888adc07d0348410c191c1bb915cb9bcb3a Mon Sep 17 00:00:00 2001 From: Allen Reese Date: Mon, 8 Jul 2019 12:39:38 -0700 Subject: [PATCH 4/9] Fix test failures caused by missing field --- databricks_cli/configure/cli.py | 2 +- databricks_cli/configure/provider.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/databricks_cli/configure/cli.py b/databricks_cli/configure/cli.py index 6f9cdd02..bfb41dd4 100644 --- a/databricks_cli/configure/cli.py +++ b/databricks_cli/configure/cli.py @@ -85,7 +85,7 @@ def configure_cli(token, insecure, stdin): if token: _configure_cli_token(profile, insecure_str) elif stdin: - _configure_cli_stdin() + _configure_cli_stdin(profile, insecure_str) else: _configure_cli_password(profile, insecure_str) diff --git a/databricks_cli/configure/provider.py b/databricks_cli/configure/provider.py index 6d288973..b109ec32 100644 --- a/databricks_cli/configure/provider.py +++ b/databricks_cli/configure/provider.py @@ -276,8 +276,10 @@ def __init__(self, host, username, password, token, insecure, debugging=0): # n self.host = host self.username = username + self.password = None if self.password == 'stdin': - self.password = getpass.getpass("Password to connect to databricks at [" + self.host + "]: ") + message = 'Password to connect to databricks at [{}]: '.format(self.host) + self.password = getpass.getpass(message) else: self.password = password From 9834e6db672995512636272e63f1999647f462e1 Mon Sep 17 00:00:00 2001 From: Allen Reese Date: Mon, 8 Jul 2019 12:41:39 -0700 Subject: [PATCH 5/9] Fix whitespace error --- databricks_cli/configure/provider.py | 1 - 1 file changed, 1 deletion(-) diff --git a/databricks_cli/configure/provider.py b/databricks_cli/configure/provider.py index b109ec32..cdffc196 100644 --- a/databricks_cli/configure/provider.py +++ b/databricks_cli/configure/provider.py @@ -287,7 +287,6 @@ def __init__(self, host, username, password, token, insecure, debugging=0): # n self.insecure = insecure self.debugging = debugging - @classmethod def from_token(cls, host, token, insecure=None): return DatabricksConfig(host, None, None, token, insecure) From d2476959a6b9550f29f36387f25d914026770151 Mon Sep 17 00:00:00 2001 From: Allen Reese Date: Thu, 11 Jul 2019 07:57:44 -0700 Subject: [PATCH 6/9] Remove old style of setting up debug flag. Move all debug logic into ContextObject.set_debug which is called when --debug is sent to any command --- databricks_cli/click_types.py | 24 ++++++++++++++++++++ databricks_cli/configure/cli.py | 18 +++------------ databricks_cli/configure/config.py | 4 ---- databricks_cli/configure/provider.py | 33 +++++----------------------- databricks_cli/sdk/api_client.py | 22 ------------------- 5 files changed, 33 insertions(+), 68 deletions(-) diff --git a/databricks_cli/click_types.py b/databricks_cli/click_types.py index 797fef59..6caa8ff2 100644 --- a/databricks_cli/click_types.py +++ b/databricks_cli/click_types.py @@ -20,6 +20,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +import logging from click import ParamType, Option, MissingParameter, UsageError @@ -104,6 +105,29 @@ def __init__(self): def set_debug(self, debug=False): self._debug = debug + if not self._debug: + return + + # These two lines enable debugging at httplib level (requests->urllib3->http.client) + # You will see the REQUEST, including HEADERS and DATA, + # and RESPONSE with HEADERS but without DATA. + # The only thing missing will be the response.body which is not logged. + try: + import http.client as http_client + except ImportError: + # Python 2 + import httplib as http_client + + print ("HTTP debugging enabled") + http_client.HTTPConnection.debuglevel = 1 + + # You must initialize logging, otherwise you'll not see debug output. + logging.basicConfig() + logging.getLogger().setLevel(logging.DEBUG) + requests_log = logging.getLogger("requests.packages.urllib3") + requests_log.setLevel(logging.DEBUG) + requests_log.propagate = True + @property def debug_mode(self): return self._debug diff --git a/databricks_cli/configure/cli.py b/databricks_cli/configure/cli.py index bfb41dd4..5242b63b 100644 --- a/databricks_cli/configure/cli.py +++ b/databricks_cli/configure/cli.py @@ -32,27 +32,18 @@ PROMPT_HOST = 'Databricks Host (should begin with https://)' PROMPT_USERNAME = 'Username' -PROMPT_PASSWORD = 'Password [enter stdin to read from stdin every time]' # NOQA +PROMPT_PASSWORD = 'Password' # NOQA PROMPT_TOKEN = 'Token' # NOQA def _configure_cli_token(profile, insecure): config = ProfileConfigProvider(profile).get_config() or DatabricksConfig.empty() host = click.prompt(PROMPT_HOST, default=config.host, type=_DbfsHost()) - token = click.prompt(PROMPT_TOKEN, default=config.token, hide_input=True) + token = click.prompt(PROMPT_TOKEN, default=config.token) new_config = DatabricksConfig.from_token(host, token, insecure) update_and_persist_config(profile, new_config) -def _configure_cli_stdin(profile, insecure): - config = ProfileConfigProvider(profile).get_config() or DatabricksConfig.empty() - host = click.prompt(PROMPT_HOST, default=config.host, type=_DbfsHost()) - username = click.prompt(PROMPT_USERNAME, default=config.username) - password = 'stdin' - new_config = DatabricksConfig.from_password(host, username, password, insecure) - update_and_persist_config(profile, new_config) - - def _configure_cli_password(profile, insecure): config = ProfileConfigProvider(profile).get_config() or DatabricksConfig.empty() if config.password: @@ -73,10 +64,9 @@ def _configure_cli_password(profile, insecure): short_help='Configures host and authentication info for the CLI.') @click.option('--token', show_default=True, is_flag=True, default=False) @click.option('--insecure', show_default=True, is_flag=True, default=None) -@click.option('--stdin', show_default=True, is_flag=True, default=False) @debug_option @profile_option -def configure_cli(token, insecure, stdin): +def configure_cli(token, insecure): """ Configures host and authentication info for the CLI. """ @@ -84,8 +74,6 @@ def configure_cli(token, insecure, stdin): insecure_str = str(insecure) if insecure is not None else None if token: _configure_cli_token(profile, insecure_str) - elif stdin: - _configure_cli_stdin(profile, insecure_str) else: _configure_cli_password(profile, insecure_str) diff --git a/databricks_cli/configure/config.py b/databricks_cli/configure/config.py index eacb3607..f6ca04e4 100644 --- a/databricks_cli/configure/config.py +++ b/databricks_cli/configure/config.py @@ -83,10 +83,6 @@ def callback(ctx, param, value): # NOQA def _get_api_client(config, command_name=""): verify = config.insecure is None - - if config.is_debugging and config.debugging == "1": - ApiClient.enable_debug_logging() - if config.is_valid_with_token: return ApiClient(host=config.host, token=config.token, verify=verify, command_name=command_name) diff --git a/databricks_cli/configure/provider.py b/databricks_cli/configure/provider.py index cdffc196..d8b96aae 100644 --- a/databricks_cli/configure/provider.py +++ b/databricks_cli/configure/provider.py @@ -20,7 +20,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -import getpass + from abc import abstractmethod, ABCMeta from configparser import ConfigParser import os @@ -33,11 +33,10 @@ CONFIG_FILE_ENV_VAR = "DATABRICKS_CONFIG_FILE" HOST = 'host' USERNAME = 'username' -PASSWORD = 'password' # NOQA +PASSWORD = 'password' # NOQA TOKEN = 'token' INSECURE = 'insecure' DEFAULT_SECTION = 'DEFAULT' -DEBUG = 'debug' # User-provided override for the DatabricksConfigProvider _config_provider = None @@ -239,11 +238,7 @@ def get_config(self): password = os.environ.get('DATABRICKS_PASSWORD') token = os.environ.get('DATABRICKS_TOKEN') insecure = os.environ.get('DATABRICKS_INSECURE') - debugging = os.environ.get('DATABRICKS_DEBUGGING') - if debugging is None: - debugging = 0 - - config = DatabricksConfig(host, username, password, token, insecure, debugging=debugging) + config = DatabricksConfig(host, username, password, token, insecure) if config.is_valid: return config return None @@ -261,31 +256,19 @@ def get_config(self): password = _get_option_if_exists(raw_config, self.profile, PASSWORD) token = _get_option_if_exists(raw_config, self.profile, TOKEN) insecure = _get_option_if_exists(raw_config, self.profile, INSECURE) - debugging = _get_option_if_exists(raw_config, self.profile, DEBUG) - if debugging is None: - debugging = 0 - - config = DatabricksConfig(host, username, password, token, insecure, debugging=debugging) + config = DatabricksConfig(host, username, password, token, insecure) if config.is_valid: return config return None class DatabricksConfig(object): - def __init__(self, host, username, password, token, insecure, debugging=0): # noqa + def __init__(self, host, username, password, token, insecure): # noqa self.host = host self.username = username - - self.password = None - if self.password == 'stdin': - message = 'Password to connect to databricks at [{}]: '.format(self.host) - self.password = getpass.getpass(message) - else: - self.password = password - + self.password = password self.token = token self.insecure = insecure - self.debugging = debugging @classmethod def from_token(cls, host, token, insecure=None): @@ -310,7 +293,3 @@ def is_valid_with_password(self): @property def is_valid(self): return self.is_valid_with_token or self.is_valid_with_password - - @property - def is_debugging(self): - return self.debugging diff --git a/databricks_cli/sdk/api_client.py b/databricks_cli/sdk/api_client.py index 2115304b..d5870a6a 100644 --- a/databricks_cli/sdk/api_client.py +++ b/databricks_cli/sdk/api_client.py @@ -30,7 +30,6 @@ import base64 import json -import logging import warnings import requests import ssl @@ -130,27 +129,6 @@ def perform_query(self, method, path, data = {}, headers = None): raise requests.exceptions.HTTPError(message, response=e.response) return resp.json() - @classmethod - def enable_debug_logging(cls): - # These two lines enable debugging at httplib level (requests->urllib3->http.client) - # You will see the REQUEST, including HEADERS and DATA, and RESPONSE with HEADERS but without DATA. - # The only thing missing will be the response.body which is not logged. - try: - import http.client as http_client - except ImportError: - # Python 2 - import httplib as http_client - - print ("HTTP debugging enabled") - http_client.HTTPConnection.debuglevel = 1 - - # You must initialize logging, otherwise you'll not see debug output. - logging.basicConfig() - logging.getLogger().setLevel(logging.DEBUG) - requests_log = logging.getLogger("requests.packages.urllib3") - requests_log.setLevel(logging.DEBUG) - requests_log.propagate = True - def _translate_boolean_to_query_param(value): assert not isinstance(value, list), 'GET parameters cannot pass list of objects' From 91461a1b972f002430caaa3dc6cc8bdb7c2cba67 Mon Sep 17 00:00:00 2001 From: Allen Reese Date: Thu, 11 Jul 2019 09:12:53 -0700 Subject: [PATCH 7/9] Use click.echo instead of print --- databricks_cli/click_types.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/databricks_cli/click_types.py b/databricks_cli/click_types.py index 6caa8ff2..a04e0a97 100644 --- a/databricks_cli/click_types.py +++ b/databricks_cli/click_types.py @@ -22,6 +22,7 @@ # limitations under the License. import logging +import click from click import ParamType, Option, MissingParameter, UsageError @@ -118,7 +119,7 @@ def set_debug(self, debug=False): # Python 2 import httplib as http_client - print ("HTTP debugging enabled") + click.echo("HTTP debugging enabled") http_client.HTTPConnection.debuglevel = 1 # You must initialize logging, otherwise you'll not see debug output. From b789ce1063c454c1b0773f89f8d5f45bd94cf146 Mon Sep 17 00:00:00 2001 From: Allen Reese Date: Mon, 15 Jul 2019 16:55:22 -0700 Subject: [PATCH 8/9] Remove excess logging configuration that isn't needed --- databricks_cli/click_types.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/databricks_cli/click_types.py b/databricks_cli/click_types.py index a04e0a97..887fafb4 100644 --- a/databricks_cli/click_types.py +++ b/databricks_cli/click_types.py @@ -122,13 +122,6 @@ def set_debug(self, debug=False): click.echo("HTTP debugging enabled") http_client.HTTPConnection.debuglevel = 1 - # You must initialize logging, otherwise you'll not see debug output. - logging.basicConfig() - logging.getLogger().setLevel(logging.DEBUG) - requests_log = logging.getLogger("requests.packages.urllib3") - requests_log.setLevel(logging.DEBUG) - requests_log.propagate = True - @property def debug_mode(self): return self._debug From ba77943f5dc6d509f9938043811a7e1e98e2e41d Mon Sep 17 00:00:00 2001 From: Allen Reese Date: Mon, 15 Jul 2019 16:58:21 -0700 Subject: [PATCH 9/9] Remove extra import --- databricks_cli/click_types.py | 1 - 1 file changed, 1 deletion(-) diff --git a/databricks_cli/click_types.py b/databricks_cli/click_types.py index 887fafb4..d87b3da6 100644 --- a/databricks_cli/click_types.py +++ b/databricks_cli/click_types.py @@ -20,7 +20,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -import logging import click from click import ParamType, Option, MissingParameter, UsageError