Commit 3a9efa6a authored by Dobli's avatar Dobli
Browse files

Replaced PyInquirer by questionary for cleaner prompt code

parent ef903cc2
...@@ -84,16 +84,14 @@ The setup also expects a working docker-machine environment. To connect all inst ...@@ -84,16 +84,14 @@ The setup also expects a working docker-machine environment. To connect all inst
**Python:** **Python:**
```sh ```sh
docker docker # Docker client library
PyInquirer questionary # Prompt library
pyyaml ruamel.yaml # Yaml library that preserves structure
bcrypt bcrypt # generate bcrypt hashes
pip-tools pip-tools # manage requirements (Optional)
``` ```
All python requirements managed using `pip-tool` in the `requirements.in` file. The command `pip-compile` generates a `requirements.txt` file that can be used with with `pip install--user -r requirements.txt` to install all needed python dependencies. E.g. on Ubuntu you can execute the following: All python requirements managed using `pip-tool` in the `requirements.in` file. The command `pip-compile` generates a `requirements.txt` file that can be used with with `pip ` to install all needed python dependencies. E.g. on Ubuntu you can execute the following:
``` ```
pip3 install --user -r requirements.txt pip3 install --user -r requirements.txt
......
...@@ -9,8 +9,9 @@ from subprocess import PIPE, run ...@@ -9,8 +9,9 @@ from subprocess import PIPE, run
import bcrypt import bcrypt
import docker import docker
from PyInquirer import prompt import questionary as qust
from ruamel.yaml import YAML from ruamel.yaml import YAML
from prompt_toolkit.styles import Style
# Configure YAML # Configure YAML
yaml = YAML() yaml = YAML()
...@@ -19,6 +20,21 @@ yaml.indent(mapping=4, sequence=4, offset=2) ...@@ -19,6 +20,21 @@ yaml.indent(mapping=4, sequence=4, offset=2)
# Log level during development is info # Log level during development is info
logging.basicConfig(level=logging.WARNING) logging.basicConfig(level=logging.WARNING)
# Prompt style
st = Style([
('qmark', 'fg:#00c4b4 bold'), # token in front of question
('question', 'bold'), # question text
('answer', 'fg:#00c4b4 bold'), # submitted answer question
('pointer', 'fg:#00c4b4 bold'), # pointer for select and checkbox
('selected', 'fg:#00c4b4'), # selected item checkbox
('separator', 'fg:#00c4b4'), # separator in lists
('instruction', '') # user instructions for selections
])
# ******************************
# Constants <<<
# ******************************
# Directories for config generation # Directories for config generation
CUSTOM_DIR = 'custom_configs' CUSTOM_DIR = 'custom_configs'
TEMPLATE_DIR = 'template_configs' TEMPLATE_DIR = 'template_configs'
...@@ -57,12 +73,12 @@ SWARM_PORT = 2377 ...@@ -57,12 +73,12 @@ SWARM_PORT = 2377
UID = 9001 UID = 9001
# Username for admin # Username for admin
ADMIN_USER = 'ohadmin' ADMIN_USER = 'ohadmin'
# >>>
# ****************************** # ******************************
# Compose file functions {{{ # Compose file functions <<<
# ****************************** # ******************************
def generate_initial_compose(base_dir): def generate_initial_compose(base_dir):
"""Creates the initial compose using the skeleton """Creates the initial compose using the skeleton
...@@ -296,11 +312,11 @@ def add_or_update_compose_service(compose_path, service_name, service_content): ...@@ -296,11 +312,11 @@ def add_or_update_compose_service(compose_path, service_name, service_content):
yaml.dump(compose, compose_f) yaml.dump(compose, compose_f)
# reduce file to new size # reduce file to new size
compose_f.truncate() compose_f.truncate()
# }}} # >>>
# ****************************** # ******************************
# Config file functions {{{ # Config file functions <<<
# ****************************** # ******************************
def generate_config_folders(base_dir): def generate_config_folders(base_dir):
"""Generate folders for configuration files """Generate folders for configuration files
...@@ -531,11 +547,11 @@ def create_or_replace_config_file(base_dir, config_path, content, json=False): ...@@ -531,11 +547,11 @@ def create_or_replace_config_file(base_dir, config_path, content, json=False):
file.write(content) file.write(content)
# }}} # >>>
# ****************************** # ******************************
# Docker machine functions {{{ # Docker machine functions <<<
# ****************************** # ******************************
def get_machine_list(): def get_machine_list():
"""Get a list of docker machine names using the docker-machine system command """Get a list of docker machine names using the docker-machine system command
...@@ -650,11 +666,11 @@ def generate_swarm(machines): ...@@ -650,11 +666,11 @@ def generate_swarm(machines):
machine, manager=leader) machine, manager=leader)
# }}} # >>>
# ****************************** # ******************************
# Docker client commands {{{ # Docker client commands <<<
# ****************************** # ******************************
def resolve_service_nodes(service): def resolve_service_nodes(service):
"""Returnes nodes running on a specified service """Returnes nodes running on a specified service
...@@ -752,11 +768,11 @@ def get_docker_client(manager=None): ...@@ -752,11 +768,11 @@ def get_docker_client(manager=None):
else: else:
client = docker.from_env() client = docker.from_env()
return client return client
# }}} # >>>
# ****************************** # ******************************
# CLI base commands {{{ # CLI base commands <<<
# ****************************** # ******************************
def init_config_dirs_command(args): def init_config_dirs_command(args):
"""Initialize config directories """Initialize config directories
...@@ -822,11 +838,11 @@ def interactive_command(args): ...@@ -822,11 +838,11 @@ def interactive_command(args):
main_menu(args) main_menu(args)
# }}} # >>>
# ****************************** # ******************************
# Interactive menu entries {{{ # Interactive menu entries <<<
# ****************************** # ******************************
def main_menu(args): def main_menu(args):
""" Display main menu """ Display main menu
...@@ -838,21 +854,15 @@ def main_menu(args): ...@@ -838,21 +854,15 @@ def main_menu(args):
base_dir = os.getcwd() base_dir = os.getcwd()
# Main menu prompts # Main menu prompts
questions = [{ choice = qust.select('Public Building Manager - Main Menu',
'type': 'list', choices=load_main_entires(base_dir), style=st).ask()
'name': 'main',
'message': 'Public Building Manager - Main Menu',
'choices': load_main_entires(base_dir)
}]
answers = prompt(questions)
choice = answers['main']
if 'Create' in choice: if 'Create' in choice:
init_menu(args) init_menu(args)
elif 'Execute' in choice: elif 'Execute' in choice:
exec_menu(args) exec_menu(args)
return answers return choice
def load_main_entires(base_dir): def load_main_entires(base_dir):
...@@ -887,42 +897,18 @@ def init_menu(args): ...@@ -887,42 +897,18 @@ def init_menu(args):
base_dir = os.getcwd() base_dir = os.getcwd()
# Prompts # Prompts
questions = [ stack_name = qust.text('Choose a name for your setup', style=st).ask()
{ hosts = qust.checkbox('What docker machines will be used?',
'type': 'input', choices=generate_cb_choices(
'name': 'stack_name', get_machine_list()), style=st).ask()
'message': 'Choose a name for your setup'
},
{
'type': 'checkbox',
'name': 'machines',
'message': 'What docker machines will be used?',
'choices': generate_checkbox_choices(get_machine_list())
}
]
answers = prompt(questions)
# Ensure passwords match # Ensure passwords match
password_match = False password_match = False
while not password_match: while not password_match:
password_questions = [{ password = qust.password(
'type': 'Choose a password for the ohadmin user:', style=st).ask()
'password', confirm = qust.password(
'name': 'Repeat password for the ohadmin user:', style=st).ask()
'password', if password == confirm:
'message':
'Choose a password for the ohadmin user:',
},
{
'type':
'password',
'name':
'confirm',
'message':
'Repeat password for the ohadmin user',
}]
password_answers = prompt(password_questions)
if password_answers['password'] == password_answers['confirm']:
password_match = True password_match = True
else: else:
print("Passwords did not match, try again") print("Passwords did not match, try again")
...@@ -932,8 +918,6 @@ def init_menu(args): ...@@ -932,8 +918,6 @@ def init_menu(args):
generate_initial_compose(base_dir) generate_initial_compose(base_dir)
# Generate config files based on input # Generate config files based on input
username = ADMIN_USER username = ADMIN_USER
password = password_answers['password']
hosts = answers['machines']
generate_sftp_file(base_dir, username, password) generate_sftp_file(base_dir, username, password)
generate_postgres_files(base_dir, username, password) generate_postgres_files(base_dir, username, password)
generate_mosquitto_file(base_dir, username, password) generate_mosquitto_file(base_dir, username, password)
...@@ -946,19 +930,14 @@ def init_menu(args): ...@@ -946,19 +930,14 @@ def init_menu(args):
init_machine_menu(base_dir, host, i) init_machine_menu(base_dir, host, i)
# print(answers) # print(answers)
print(f"Configuration files generated in {base_dir}") print(f"Configuration files for {stack_name} generated in {base_dir}")
# Check if changes shall be applied to docker environment # Check if changes shall be applied to docker environment
generate_questions = [{ generate = qust.confirm(
'type': 'confirm', 'Apply changes to docker environment?', default=True, style=st).ask()
'name': 'generate',
'message': 'Apply changes to docker environment?',
'default': True
}]
generate_answers = prompt(generate_questions)
if generate_answers['generate']: if generate:
generate_swarm(answers['machines']) generate_swarm(hosts)
def exec_menu(args): def exec_menu(args):
...@@ -967,23 +946,12 @@ def exec_menu(args): ...@@ -967,23 +946,12 @@ def exec_menu(args):
:args: Passed commandline arguments :args: Passed commandline arguments
""" """
machine = docker_client_prompt(" to execute command at") machine = docker_client_prompt(" to execute command at")
questions = [ service_name = qust.select(
{ 'Which service container shall execute the command?',
'type': 'list', choices=get_container_list(machine), style=st).ask()
'name': 'service_name', command = qust.text('What command should be executed?', style=st).ask()
'message': 'Which service container shall execute the command?',
'choices': get_container_list(machine) run_command_in_service(service_name, command, machine)
},
{
'type': 'input',
'name': 'command',
'message': 'What command should be executed?'
}
]
answers = prompt(questions)
run_command_in_service(
answers['service_name'], answers['command'], machine)
print(answers)
# *** Sub Menu Entries *** # *** Sub Menu Entries ***
...@@ -995,22 +963,12 @@ def init_machine_menu(base_dir, host, increment): ...@@ -995,22 +963,12 @@ def init_machine_menu(base_dir, host, increment):
:increment: incrementing number to ensure ports are unique :increment: incrementing number to ensure ports are unique
""" """
# Prompt for services # Prompt for services
questions = [ building = qust.text(f'Choose a name for building on server {host}',
{ default=f'{host}', style=st).ask()
'type': 'input', services = qust.checkbox(f'What services shall {host} provide?',
'name': 'buildingid', choices=generate_cb_choices(SERVICES.keys(),
'message': f'Choose a name for building on server {host}', checked=True),
'default': f'{host}' style=st).ask()
},
{
'type': 'checkbox',
'name': 'services',
'message': f'What services shall {host} provide?',
'choices': generate_checkbox_choices(SERVICES.keys(), checked=True)
}
]
answers = prompt(questions)
services = answers['services']
if 'sftp' in services: if 'sftp' in services:
add_sftp_service(base_dir, host, increment) add_sftp_service(base_dir, host, increment)
if 'openhab' in services: if 'openhab' in services:
...@@ -1021,11 +979,11 @@ def init_machine_menu(base_dir, host, increment): ...@@ -1021,11 +979,11 @@ def init_machine_menu(base_dir, host, increment):
add_mqtt_service(base_dir, host, increment) add_mqtt_service(base_dir, host, increment)
if 'postgres' in services: if 'postgres' in services:
add_postgres_service(base_dir, host) add_postgres_service(base_dir, host)
print(answers) print(building)
# *** Menu Helper Functions *** # *** Menu Helper Functions ***
def generate_checkbox_choices(list, checked=False): def generate_cb_choices(list, checked=False):
"""Generates checkbox entries for lists of strings """Generates checkbox entries for lists of strings
:list: pyhton list that shall be converted :list: pyhton list that shall be converted
...@@ -1041,21 +999,14 @@ def docker_client_prompt(message_details=''): ...@@ -1041,21 +999,14 @@ def docker_client_prompt(message_details=''):
:manager: Optional machine to use, prompt otherwise :manager: Optional machine to use, prompt otherwise
:returns: Docker client instance :returns: Docker client instance
""" """
questions = [ machine = qust.select(f'Choose manager machine{message_details}',
{ choices=get_machine_list(), style=st).ask()
'type': 'list', return machine
'name': 'machine', # >>>
'message': f'Choose manager machine{message_details}',
'choices': get_machine_list()
}
]
answers = prompt(questions)
return answers['machine']
# }}}
# ****************************** # ******************************
# Script main ( entry) {{{ # Script main (entry) <<<
# ****************************** # ******************************
if __name__ == '__main__': if __name__ == '__main__':
import argparse import argparse
...@@ -1123,7 +1074,7 @@ if __name__ == '__main__': ...@@ -1123,7 +1074,7 @@ if __name__ == '__main__':
args.func(args) args.func(args)
except AttributeError: except AttributeError:
interactive_command(args) interactive_command(args)
# }}} # >>>
# --- vim settings --- # --- vim settings ---
# vim:foldmethod=marker:foldlevel=0 # vim:foldmethod=marker:foldlevel=0:foldmarker=<<<,>>>
bcrypt bcrypt
docker docker
PyInquirer questionary
pyyaml
ruamel.yaml ruamel.yaml
...@@ -11,12 +11,9 @@ chardet==3.0.4 # via requests ...@@ -11,12 +11,9 @@ chardet==3.0.4 # via requests
docker-pycreds==0.4.0 # via docker docker-pycreds==0.4.0 # via docker
docker==3.6.0 docker==3.6.0
idna==2.7 # via requests idna==2.7 # via requests
prompt_toolkit==1.0.14 # via pyinquirer prompt-toolkit==2.0.8 # via questionary
pycparser==2.19 # via cffi pycparser==2.19 # via cffi
pygments==2.3.1 # via pyinquirer questionary==1.0.2
pyinquirer==1.0.3
pyyaml==3.13
regex==2018.11.22 # via pyinquirer
requests==2.20.1 # via docker requests==2.20.1 # via docker
ruamel.yaml==0.15.86 ruamel.yaml==0.15.86
six==1.11.0 # via bcrypt, docker, docker-pycreds, prompt-toolkit, websocket-client six==1.11.0 # via bcrypt, docker, docker-pycreds, prompt-toolkit, websocket-client
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment