Skip to content

Commit

Permalink
Merge f95ca38 into 1b0d187
Browse files Browse the repository at this point in the history
  • Loading branch information
Michael Beer committed Dec 14, 2017
2 parents 1b0d187 + f95ca38 commit f43129a
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 9 deletions.
2 changes: 2 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Unreleased
Changes
-------

- Added support for password authentication.

- Added support for pasting and executing multiple statements at once.

- The `\r` (read) command auto-completion now also shows directories, instead
Expand Down
27 changes: 26 additions & 1 deletion docs/cli.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ The ``crash`` binary supports several command line arguments.
+---------------------------------------+--------------------------------------------------+
| ``-U``, ``--username`` | The username to authenticate in the database |
+---------------------------------------+--------------------------------------------------+
| ``-W``, ``--password`` | Force prompt for password. Otherwise it prompts |
| | for password automatically in case the server |
| | requires user authorization. |
+---------------------------------------+--------------------------------------------------+
| ``-c COMMAND``, ``--command COMMAND`` | Execute the SQL statement and exit. |
+---------------------------------------+--------------------------------------------------+
| ``--hosts HOSTS`` | The CrateDB hosts to connect to. |
Expand Down Expand Up @@ -75,10 +79,31 @@ Example Usage with SSL
$ crash --hosts st1.crate.io:4200 st2.crate.io st3.crate.io \
--format json \
--verify-ssl true --cert-file /home/.certs/crateClient.crt --key-file /home/.certs/crateClient.key \
--ca-cert-file /home/.certs/rootCA.crt -U sslUser
--ca-cert-file /home/.certs/rootCA.crt -U sslUser \
-c 'select * from sys.nodes' > output.json


=====================
Environment Variables
=====================

Crash takes the following environment variables into account to select default
connection parameters. These can be helpful to avoid user-input prompts or any
other hard-coded cli parameter.

:CRATEPW: Password to be used if CrateDB demands password authentication.
Keep in mind that for security reasons the usage of this variable is
not safe.


For example::

$ export CRASHPW=dustins-password
$ crash --hosts localhost:4200 \
--username dustin \
--format json \
-c 'select * from sys.nodes' > output.json

.. _commands:

==============
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
requirements = [
'colorama',
'Pygments>=2.0',
'crate>=0.11.2',
'crate>=0.21.0',
'appdirs>=1.2,<2.0',
'prompt-toolkit>=1.0,<1.1'
]
Expand Down
54 changes: 48 additions & 6 deletions src/crate/crash/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import re
import sys
import urllib3
from getpass import getpass, getuser
from appdirs import user_data_dir, user_config_dir
from argparse import ArgumentParser
from collections import namedtuple
Expand Down Expand Up @@ -136,6 +137,9 @@ def _conf_or_default(key, value):
help='use -a to enable experimental auto-capitalization of SQL keywords')
parser.add_argument('-U', '--username', type=str,
help='the username to authenticate in the database')
parser.add_argument('-W', '--password', action='store_true',
dest='password', default=_conf_or_default('force_passwd_prompt', False),
help='force password prompt')
parser.add_argument('--history', type=str,
help='the history file to use', default=HISTORY_PATH)
parser.add_argument('--config', type=str,
Expand Down Expand Up @@ -216,7 +220,8 @@ def __init__(self,
cert_file=None,
key_file=None,
ca_cert_file=None,
username=None):
username=None,
password=None):
self.error_trace = error_trace
self.connection = connection or connect(error_trace=error_trace)
self.cursor = self.connection.cursor()
Expand All @@ -237,6 +242,7 @@ def __init__(self,
self._autocomplete = autocomplete
self._autocapitalize = autocapitalize
self.username = username
self.password = password
self.verify_ssl = verify_ssl
self.cert_file = cert_file
self.key_file = key_file
Expand Down Expand Up @@ -310,7 +316,8 @@ def _do_connect(self):
cert_file=self.cert_file,
key_file=self.key_file,
ca_cert=self.ca_cert_file,
username=self.username)
username=self.username,
password=self.password)
self.cursor = self.connection.cursor()

def _connect(self, server):
Expand Down Expand Up @@ -360,6 +367,8 @@ def _try_exec_cmd(self, line):
message = cmd(self, *words[1:])
else:
message = cmd(*words[1:])
except ProgrammingError as e:
raise e
except TypeError as e:
self.logger.critical(getattr(e, 'message', None) or repr(e))
doc = cmd.__doc__
Expand Down Expand Up @@ -494,9 +503,40 @@ def main():

crate_hosts = [host_and_port(h) for h in args.hosts]
error_trace = args.verbose > 0

force_passwd_prompt = args.password
password = None

# If password prompt is not forced try to get it from env. variable.
if not force_passwd_prompt:
password = os.environ.get('CRATEPW', None)

# Prompt for password immediately to avoid that the first time trying to
# connect to the server runs into an `Unauthorized` excpetion
# is_tty = False
if force_passwd_prompt and not password and is_tty:
password = getpass()

# Tries to create a connection to the server.
# Prompts for the password automatically if the server only accepts
# password authentication.
cmd = None
try:
cmd = _create_cmd(crate_hosts, error_trace, output_writer, is_tty, args)
cmd = _create_cmd(crate_hosts, error_trace, output_writer, is_tty,
args, password=password)
except (ProgrammingError, LocationParseError) as e:
if '401' in e.message and not force_passwd_prompt:
if is_tty:
password = getpass()
try:
cmd = _create_cmd(crate_hosts, error_trace, output_writer,
is_tty, args, password=password)
except (ProgrammingError, LocationParseError) as ex:
printer.warn(str(ex))
sys.exit(1)
else:
raise e
except Exception as e:
printer.warn(str(e))
sys.exit(1)

Expand All @@ -522,14 +562,15 @@ def main():
conf.save()
sys.exit(cmd.exit())

def _create_cmd(crate_hosts, error_trace, output_writer, is_tty, args, timeout=None):
def _create_cmd(crate_hosts, error_trace, output_writer, is_tty, args, timeout=None, password=None):
conn = connect(crate_hosts,
verify_ssl_cert=args.verify_ssl,
cert_file=args.cert_file,
key_file=args.key_file,
ca_cert=args.ca_cert_file,
username=args.username,
timeout=timeout)
timeout=timeout,
password=password)
return CrateCmd(connection=conn,
error_trace=error_trace,
output_writer=output_writer,
Expand All @@ -540,7 +581,8 @@ def _create_cmd(crate_hosts, error_trace, output_writer, is_tty, args, timeout=N
cert_file=args.cert_file,
key_file=args.key_file,
ca_cert_file=args.ca_cert_file,
username=args.username)
username=args.username,
password=password)

def file_with_permissions(path):
open(path, 'r').close()
Expand Down
17 changes: 17 additions & 0 deletions src/crate/crash/repl.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
from prompt_toolkit.key_binding.manager import KeyBindingManager
from prompt_toolkit.shortcuts import (create_output,
create_eventloop)
from crate.client.exceptions import ProgrammingError
from getpass import getpass

from .commands import Command
from .layout import create_layout
Expand Down Expand Up @@ -339,6 +341,21 @@ def get_num_columns_override():
doc = cli.run(reset_current_buffer=True)
if doc:
cmd.process(doc.text)
except ProgrammingError as e:
if '401' in e.message:
username = cmd.username
password = cmd.password
cmd.username = input('Username: ')
cmd.password = getpass()
try:
cmd.process(doc.text)
except ProgrammingError as ex:
# fallback to existing user/pw
cmd.username = username
cmd.password = password
cmd.logger.warn(str(ex))
else:
cmd.logger.warn(str(e))
except KeyboardInterrupt:
cmd.logger.warn("Query not cancelled. Run KILL <jobId> to cancel it")
except EOFError:
Expand Down
3 changes: 3 additions & 0 deletions src/crate/crash/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ def test_get_and_set(self):
conf.get_or_set('verbosity', 3)
conf.get_or_set('autocomplete', False)
conf.get_or_set('hosts', ['c1', 'c2'])
conf.get_or_set('force_passwd_prompt', 0)
conf.save()

# Reading raw values
Expand All @@ -113,13 +114,15 @@ def test_get_and_set(self):
self.assertEqual(config.get('crash', 'verbosity'), '3')
self.assertEqual(config.get('crash', 'autocomplete'), '0')
self.assertEqual(config.get('crash', 'hosts'), 'c1\nc2')
self.assertEqual(config.get('crash', 'force_passwd_prompt'), '0')

# Reading transformed values
conf = Configuration(path)
self.assertEqual(conf.get_or_set('format', 'mixed'), 'json')
self.assertEqual(conf.get_or_set('verbosity', 0), 3)
self.assertEqual(conf.get_or_set('autocomplete', True), False)
self.assertEqual(conf.get_or_set('hosts', ['localhost']), ['c1', 'c2'])
self.assertEqual(conf.get_or_set('force_passwd_prompt', True), False)

# Reading section header
with open(path) as fp:
Expand Down
2 changes: 1 addition & 1 deletion versions.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ chardet = 3.0.4
codecov = 2.0.9
collective.recipe.omelette = 0.16
coverage = 4.4.1
crate = 0.20.1
crate = 0.21.0
crate-docs-theme = 0.5.41
docutils = 0.13.1
gp.recipe.tox = 0.4
Expand Down

0 comments on commit f43129a

Please sign in to comment.