from prompt_toolkit import prompt
import keyring
from issho.config import (
write_issho_conf,
read_issho_conf,
read_ssh_profile,
write_issho_env,
read_issho_env,
)
from issho.helpers import issho_pw_name, issho_ssh_pw_name, absolute_path, get_user
from collections import OrderedDict
from issho.issho import Issho
import fire
import re
OPENSSH_PASSWORD_ERROR = """
Paramiko v2.4.0 does not allow OpenSSH RSA key format (common on new Macs);
see: https://github.com/paramiko/paramiko/issues/1313#issuecomment-492448807
Create your ssh key using:
$ ssh-keygen -t rsa -b 4096 -C "email@email.com" -m PEM
"""
ENV_PROMPTS = OrderedDict(
(
("HIVE_OPTS", "Hive Options: "),
("HIVE_JDBC", "Hive JDBC connection string: "),
("SPARK_CONF", "Spark Shell Configuration String: "),
)
)
[docs]class IsshoCLI:
"""
CLI for Issho; right now only used for configuration
"""
[docs] def config(
self,
profile,
env=None,
ssh_profile="",
ssh_config="~/.ssh/config",
rsa_id="~/.ssh/id_rsa",
):
"""
Configures a single issho profile. Saves non-private variables
to ``~/.issho/conf.toml`` and passwords to the local keyring.
:param profile: name of the profile to configure
:param env: Optional environment variable profile to draw from.
:param ssh_profile: The name of the associated ssh config profile;
defaults to the profile name if not supplied.
:param ssh_config: the path to the ssh_config to be used for this profile
:param rsa_id: the path to the id_rsa file to be used for this profile
"""
local_user = get_user()
ssh_profile = profile if not ssh_profile else profile
rsa_id = absolute_path(rsa_id)
ssh_config = absolute_path(ssh_config)
env = read_issho_env(env) if env else {}
ssh_conf = read_ssh_profile(ssh_config, ssh_profile)
if not all(x in ssh_conf for x in ("hostname", "user")):
raise KeyError()
if not keyring.get_password(issho_ssh_pw_name(rsa_id), rsa_id):
_set_up_ssh_password(rsa_id=rsa_id)
kinit_was_setup = _set_up_password(
pw_type="kinit", profile=profile, pw_user=local_user
)
new_conf = {
"SSH_CONFIG_PATH": ssh_config,
"RSA_ID_PATH": rsa_id,
**_get_env_vars(env),
}
write_issho_conf({profile: new_conf})
self.test_connection(profile, kinit=kinit_was_setup)
@staticmethod
def env(env_name):
"""
Saves a set of variables to ~/.issho/envs.toml
:param env_name: name of the environment to set up or update
"""
env_conf = {env_name: _get_env_vars({})}
write_issho_env(env_conf)
return env_conf
[docs] @staticmethod
def env(env_name):
"""
Saves a set of variables to ~/.issho/envs.toml
:param env_name: name of the environment to set up or update
"""
env_conf = {env_name: _get_env_vars({})}
write_issho_env(env_conf)
return env_conf
[docs] @staticmethod
def update_variable(profile, variable, value):
"""
Updates or add a single profile variable.
"""
old_conf = read_issho_conf(profile)
old_conf[variable] = value
write_issho_conf({profile: old_conf})
[docs] @staticmethod
def test_connection(profile, kinit=True):
"""
Tests the connection to the specified
:param profile: The name of the profile
:param kinit: if True, will try to kinit using the stored password
:return:
"""
try:
test_issho = Issho(profile, kinit)
except Exception as e:
return "Test Connection failed with error: {}".format(str(e))
return "Test Connection Successful!"
def _get_env_vars(env):
"""
Gets the set of parameters described in ENV_PROMPTS
using a pre-existing environment env if it exists
:param env: a dictionary containing previously-set environment variables
:return:
"""
return {
var_name: env.get(var_name) if var_name in env else prompt(prompt_str)
for var_name, prompt_str in ENV_PROMPTS.items()
}
def _get_pw(pw_type):
"""
Gets a password using the prompt toolkit
"""
while True:
pw = prompt("Enter the {} password: ".format(pw_type), is_password=True)
if not pw:
break
pw2 = prompt("Enter the {} password again: ".format(pw_type), is_password=True)
if pw != pw2:
print("The passwords do not match; try again")
else:
return pw
def _keep_old_password(pw_type, pw_name, pw_user):
"""
:return True to keep the old password, False to reset
"""
if keyring.get_password(pw_name, pw_user):
new_pw_prompt = (
"A {} password exists for {}--do you want to set a new password (y/N)? "
)
reset_password = prompt(new_pw_prompt.format(pw_type, pw_user))
return (not reset_password) or (
not reset_password.strip().lower().startswith("y")
)
return False
def _set_up_password(pw_type, profile, pw_user):
"""
Gets an issho password for this profile and
saves it to the local keyring.
"""
pw_name = issho_pw_name(pw_type=pw_type, profile=profile)
if _keep_old_password(pw_type, pw_name, pw_user):
return True if keyring.get_password(pw_name, pw_user) else False
else:
pw = _get_pw(pw_type=pw_type)
keyring.set_password(pw_name, pw_user, pw)
return True if pw else False
def _set_up_ssh_password(rsa_id):
"""
Adds the ssh password to the local keyring.
"""
_check_not_openssh_pkey(rsa_id)
pw = _get_pw(pw_type="ssh")
pw_name = issho_ssh_pw_name(rsa_id=rsa_id)
keyring.set_password(pw_name, rsa_id, pw)
return True if pw else False
def _check_not_openssh_pkey(rsa_id):
with open(rsa_id) as f:
if re.search("OPENSSH", f.readline()):
raise ValueError(OPENSSH_PASSWORD_ERROR)
[docs]def main():
"""
Inititates the CLI using python-fire
"""
fire.Fire(IsshoCLI)
if __name__ == "__main__":
main()