def get_container_options(self, defaults): # Default options options = self.default_container_options() # User environmental variables if not defaults: write_pretty_output("Provide passwords for the three Tethys database users or press enter to accept the " "default passwords shown in square brackets:") default_password = '******' # tethys_default prompt = 'Password for "tethys_default" database user' tethys_default_pass = UserInputHelper.get_verified_password(prompt, default_password) # tethys_db_manager prompt = 'Password for "tethys_db_manager" database user' tethys_db_manager_pass = UserInputHelper.get_verified_password(prompt, default_password) # tethys_super prompt = 'Password for "tethys_super" database user' tethys_super_pass = UserInputHelper.get_verified_password(prompt, default_password) options['environment'].update( TETHYS_DEFAULT_PASS=tethys_default_pass, TETHYS_DB_MANAGER_PASS=tethys_db_manager_pass, TETHYS_SUPER_PASS=tethys_super_pass ) return options
def get_container_options(self, defaults): # Default options options = self.default_container_options() # User environmental variables if not defaults: write_pretty_output("Provide passwords for the three Tethys database users or press enter to accept the " "default passwords shown in square brackets:") default_password = '******' # tethys_default prompt = 'Password for "tethys_default" database user' tethys_default_pass = UserInputHelper.get_verified_password(prompt, default_password) # tethys_db_manager prompt = 'Password for "tethys_db_manager" database user' tethys_db_manager_pass = UserInputHelper.get_verified_password(prompt, default_password) # tethys_super prompt = 'Password for "tethys_super" database user' tethys_super_pass = UserInputHelper.get_verified_password(prompt, default_password) options['environment'].update( TETHYS_DEFAULT_PASS=tethys_default_pass, TETHYS_DB_MANAGER_PASS=tethys_db_manager_pass, TETHYS_SUPER_PASS=tethys_super_pass ) return options
def start(self): msg = 'Starting {} container...' write_pretty_output(msg.format(self.display_name)) msg = None try: self.container.start() except Exception as e: msg = 'There was an error while attempting to start container {}: {}'.format( self.display_name, str(e) ) return msg
def start(self): msg = 'Starting {} container...' write_pretty_output(msg.format(self.display_name)) msg = None try: self.container.start() except Exception as e: msg = 'There was an error while attempting to start container {}: {}'.format( self.display_name, str(e) ) return msg
def stop(self, silent=False): msg = 'Stopping {} container...' if not silent: write_pretty_output(msg.format(self.display_name)) try: self.container.stop() msg = None except Exception as e: msg = 'There was an error while attempting to stop container {}: {}'.format( self.display_name, str(e) ) return msg
def stop(self, silent=False): msg = 'Stopping {} container...' if not silent: write_pretty_output(msg.format(self.display_name)) try: self.container.stop() msg = None except Exception as e: msg = 'There was an error while attempting to stop container {}: {}'.format( self.display_name, str(e) ) return msg
def install_docker_containers(containers_to_install, defaults=False): """ Install all Docker containers Args: containers_to_install(list[ContainerMetadata], required): list of containers to install defaults(bool, optional, default=False): if True use all of the default options instead of prompting user to specify options """ for container_metadata in containers_to_install: container_metadata.create(defaults) write_pretty_output("Finished installing Docker containers.")
def install_docker_containers(containers_to_install, defaults=False): """ Install all Docker containers Args: containers_to_install(list[ContainerMetadata], required): list of containers to install defaults(bool, optional, default=False): if True use all of the default options instead of prompting user to specify options """ for container_metadata in containers_to_install: container_metadata.create(defaults) write_pretty_output("Finished installing Docker containers.")
def pull_docker_images(containers_to_install): """ Pull Docker container images Args: containers_to_install(list[ContainerMetadata], required): list of containers to install """ if len(containers_to_install) < 1: write_pretty_output("Docker images already pulled.") else: write_pretty_output("Pulling Docker images...") for container in containers_to_install: container.pull()
def docker_status(containers=None): """ Returns the status of the Docker containers: either Running or Stopped. """ # Get container dicts container_statuses = get_docker_container_statuses(containers=containers) for container, is_running in container_statuses.items(): if is_running is None: msg = '{}: Not Installed' elif is_running: msg = '{}: Running' else: msg = '{}: Not Running' write_pretty_output(msg.format(container.display_name))
def docker_status(containers=None): """ Returns the status of the Docker containers: either Running or Stopped. """ # Get container dicts container_statuses = get_docker_container_statuses(containers=containers) for container, is_running in container_statuses.items(): if is_running is None: msg = '{}: Not Installed' elif is_running: msg = '{}: Running' else: msg = '{}: Not Running' write_pretty_output(msg.format(container.display_name))
def get_verified_password(prompt, default): password_1 = getpass.getpass('{} [{}]: '.format(prompt, default)) if password_1 == '': return default else: password_2 = getpass.getpass('Confirm Password: '******'Passwords do not match, please try again.') password_1 = getpass.getpass('{} [{}]: '.format(prompt, default)) password_2 = getpass.getpass('Confirm Password: ') return password_1
def pull_docker_images(containers_to_install): """ Pull Docker container images Args: containers_to_install(list[ContainerMetadata], required): list of containers to install """ if len(containers_to_install) < 1: write_pretty_output("Docker images already pulled.") else: write_pretty_output("Pulling Docker images...") for container in containers_to_install: container.pull()
def get_verified_password(prompt, default): password_1 = getpass.getpass('{} [{}]: '.format(prompt, default)) if password_1 == '': return default else: password_2 = getpass.getpass('Confirm Password: '******'Passwords do not match, please try again.') password_1 = getpass.getpass('{} [{}]: '.format(prompt, default)) password_2 = getpass.getpass('Confirm Password: ') return password_1
def docker_ip(containers=None): """ Returns the hosts and ports of the Docker containers. """ # Containers container_statuses = get_docker_container_statuses(containers=containers) for container_metadata, is_running in container_statuses.items(): if is_running is None: msg = '{name}: Not Installed' elif not is_running: msg = '{name}: Not Running' else: msg = container_metadata.ip write_pretty_output(msg.format(name=container_metadata.display_name))
def docker_ip(containers=None): """ Returns the hosts and ports of the Docker containers. """ # Containers container_statuses = get_docker_container_statuses(containers=containers) for container_metadata, is_running in container_statuses.items(): if is_running is None: msg = '{name}: Not Installed' elif not is_running: msg = '{name}: Not Running' else: msg = container_metadata.ip write_pretty_output(msg.format(name=container_metadata.display_name))
def docker_stop(containers=None): """ Stop Docker containers """ container_statuses = get_docker_container_statuses(containers=containers) for container_metadata, status in container_statuses.items(): already_stopped = not status if status is None: msg = '{} container not installed.' elif already_stopped: msg = '{} container already stopped.' else: msg = container_metadata.stop() if msg is not None: write_pretty_output(msg.format(container_metadata.display_name))
def docker_stop(containers=None): """ Stop Docker containers """ container_statuses = get_docker_container_statuses(containers=containers) for container_metadata, status in container_statuses.items(): already_stopped = not status if status is None: msg = '{} container not installed.' elif already_stopped: msg = '{} container already stopped.' else: msg = container_metadata.stop() if msg is not None: write_pretty_output(msg.format(container_metadata.display_name))
def docker_start(containers=None): """ Start the docker containers """ container_statuses = get_docker_container_statuses(containers=containers) for container_metadata, status in container_statuses.items(): already_running = status if already_running is None: msg = '{} container not installed.' elif already_running: msg = '{} container already running.' else: msg = container_metadata.start() if msg is not None: write_pretty_output(msg.format(container_metadata.display_name))
def docker_start(containers=None): """ Start the docker containers """ container_statuses = get_docker_container_statuses(containers=containers) for container_metadata, status in container_statuses.items(): already_running = status if already_running is None: msg = '{} container not installed.' elif already_running: msg = '{} container already running.' else: msg = container_metadata.start() if msg is not None: write_pretty_output(msg.format(container_metadata.display_name))
def get_valid_directory_input(prompt, default=None): default = default or '' pre_prompt = '' prompt = '{} [{}]: '.format(prompt, default) while True: value = input('{}{}'.format(pre_prompt, prompt)) or str(default) if len(value) > 0 and value[0] != '/': value = '/' + value if not os.path.isdir(value): try: os.makedirs(value) except OSError as e: write_pretty_output('{0}: {1}'.format(repr(e), value)) pre_prompt = 'Please provide a valid directory\n' continue break return value
def get_valid_directory_input(prompt, default=None): default = default or '' pre_prompt = '' prompt = '{} [{}]: '.format(prompt, default) while True: value = input('{}{}'.format(pre_prompt, prompt)) or str(default) if len(value) > 0 and value[0] != '/': value = '/' + value if not os.path.isdir(value): try: os.makedirs(value) except OSError as e: write_pretty_output('{0}: {1}'.format(repr(e), value)) pre_prompt = 'Please provide a valid directory\n' continue break return value
def get_container_options(self, defaults): # Default environmental vars options = self.default_container_options() if not defaults: write_pretty_output("Provide contact information for the 52 North Web Processing Service or press enter to " "accept the defaults shown in square brackets: ") options['environment'].update( NAME=UserInputHelper.get_input_with_default('Name', 'NONE'), POSITION=UserInputHelper.get_input_with_default('Position', 'NONE'), ADDRESS=UserInputHelper.get_input_with_default('Address', 'NONE'), CITY=UserInputHelper.get_input_with_default('City', 'NONE'), STATE=UserInputHelper.get_input_with_default('State', 'NONE'), COUNTRY=UserInputHelper.get_input_with_default('Country', 'NONE'), POSTAL_CODE=UserInputHelper.get_input_with_default('Postal Code', 'NONE'), EMAIL=UserInputHelper.get_input_with_default('Email', 'NONE'), PHONE=UserInputHelper.get_input_with_default('Phone', 'NONE'), FAX=UserInputHelper.get_input_with_default('Fax', 'NONE'), USERNAME=UserInputHelper.get_input_with_default('Admin Username', 'wps'), PASSWORD=UserInputHelper.get_verified_password('Admin Password', 'wps') ) return options
def get_container_options(self, defaults): # Default environmental vars options = self.default_container_options() if not defaults: write_pretty_output("Provide contact information for the 52 North Web Processing Service or press enter to " "accept the defaults shown in square brackets: ") options['environment'].update( NAME=UserInputHelper.get_input_with_default('Name', 'NONE'), POSITION=UserInputHelper.get_input_with_default('Position', 'NONE'), ADDRESS=UserInputHelper.get_input_with_default('Address', 'NONE'), CITY=UserInputHelper.get_input_with_default('City', 'NONE'), STATE=UserInputHelper.get_input_with_default('State', 'NONE'), COUNTRY=UserInputHelper.get_input_with_default('Country', 'NONE'), POSTAL_CODE=UserInputHelper.get_input_with_default('Postal Code', 'NONE'), EMAIL=UserInputHelper.get_input_with_default('Email', 'NONE'), PHONE=UserInputHelper.get_input_with_default('Phone', 'NONE'), FAX=UserInputHelper.get_input_with_default('Fax', 'NONE'), USERNAME=UserInputHelper.get_input_with_default('Admin Username', 'wps'), PASSWORD=UserInputHelper.get_verified_password('Admin Password', 'wps') ) return options
def remove(self): write_pretty_output('Removing {} container...'.format(self.display_name)) self.container.remove()
def create(self, defaults=False): write_pretty_output("\nInstalling the {} Docker container...".format(self.display_name)) options = self.get_container_options(defaults) options['host_config'] = self.docker_client.api.create_host_config(**options['host_config']) self.docker_client.api.create_container(**options)
def remove(self): write_pretty_output('Removing {} container...'.format(self.display_name)) self.container.remove()
def get_container_options(self, defaults): # default configuration options = self.default_container_options() if not self.is_cluster: # Then all of the other options are irrelevant defaults = True if not defaults: # Environmental variables from user input environment = dict() write_pretty_output( "The GeoServer docker can be configured to run in a clustered mode (multiple instances of " "GeoServer running in the docker container) for better performance.\n") environment['ENABLED_NODES'] = UserInputHelper.get_valid_numeric_input( prompt='Number of GeoServer Instances Enabled', max_val=4, ) environment['REST_NODES'] = UserInputHelper.get_valid_numeric_input( prompt='Number of GeoServer Instances with REST API Enabled', max_val=int(environment['ENABLED_NODES']), ) write_pretty_output( "\nGeoServer can be configured with limits to certain types of requests to prevent it from " "becoming overwhelmed. This can be done automatically based on a number of processors or " "each " "limit can be set explicitly.\n") flow_control_mode = UserInputHelper.get_valid_choice_input( prompt='Would you like to specify number of Processors (c) OR set request limits explicitly (e)', choices=['c', 'e'], default='c', ) if flow_control_mode.lower() == 'c': environment['NUM_CORES'] = UserInputHelper.get_valid_numeric_input( prompt='Number of Processors', max_val=4, # TODO dynamically figure out what the max is ) else: environment['MAX_OWS_GLOBAL'] = UserInputHelper.get_valid_numeric_input( prompt='Maximum number of simultaneous OGC web service requests (e.g.: WMS, WCS, WFS)', default=100 ) environment['MAX_WMS_GETMAP'] = UserInputHelper.get_valid_numeric_input( prompt='Maximum number of simultaneous GetMap requests', default=8 ) environment['MAX_OWS_GWC'] = UserInputHelper.get_valid_numeric_input( prompt='Maximum number of simultaneous GeoWebCache tile renders', default=16 ) environment['MAX_TIMEOUT'] = UserInputHelper.get_valid_numeric_input( prompt='Maximum request timeout in seconds', default=60 ) environment['MAX_MEMORY'] = UserInputHelper.get_valid_numeric_input( prompt='Maximum memory to allocate to each GeoServer instance in MB', max_val=4096, # TODO dynamically figure out what the max is default=1024 ) max_memory = int(environment['MAX_MEMORY']) environment['MIN_MEMORY'] = UserInputHelper.get_valid_numeric_input( prompt='Minimum memory to allocate to each GeoServer instance in MB', max_val=max_memory, default=max_memory ) options.update( environment=environment, ) mount_data_dir = UserInputHelper.get_valid_choice_input( prompt='Bind the GeoServer data directory to the host?', choices=['y', 'n'], default='y', ) if mount_data_dir.lower() == 'y': tethys_home = os.environ.get('TETHYS_HOME', os.path.expanduser('~/tethys/')) default_mount_location = os.path.join(tethys_home, 'geoserver', 'data') gs_data_volume = '/var/geoserver/data' mount_location = UserInputHelper.get_valid_directory_input( prompt='Specify location to bind data directory', default=default_mount_location ) mounts = [Mount(gs_data_volume, mount_location, type='bind')] options['host_config'].update(mounts=mounts) return options
def get_container_options(self, defaults): # default configuration options = self.default_container_options() if not self.is_cluster: # Then all of the other options are irrelevant defaults = True if not defaults: # Environmental variables from user input environment = dict() write_pretty_output( "The GeoServer docker can be configured to run in a clustered mode (multiple instances of " "GeoServer running in the docker container) for better performance.\n") environment['ENABLED_NODES'] = UserInputHelper.get_valid_numeric_input( prompt='Number of GeoServer Instances Enabled', max_val=4, ) environment['REST_NODES'] = UserInputHelper.get_valid_numeric_input( prompt='Number of GeoServer Instances with REST API Enabled', max_val=int(environment['ENABLED_NODES']), ) write_pretty_output( "\nGeoServer can be configured with limits to certain types of requests to prevent it from " "becoming overwhelmed. This can be done automatically based on a number of processors or " "each " "limit can be set explicitly.\n") flow_control_mode = UserInputHelper.get_valid_choice_input( prompt='Would you like to specify number of Processors (c) OR set request limits explicitly (e)', choices=['c', 'e'], default='c', ) if flow_control_mode.lower() == 'c': environment['NUM_CORES'] = UserInputHelper.get_valid_numeric_input( prompt='Number of Processors', max_val=4, # TODO dynamically figure out what the max is ) else: environment['MAX_OWS_GLOBAL'] = UserInputHelper.get_valid_numeric_input( prompt='Maximum number of simultaneous OGC web service requests (e.g.: WMS, WCS, WFS)', default=100 ) environment['MAX_WMS_GETMAP'] = UserInputHelper.get_valid_numeric_input( prompt='Maximum number of simultaneous GetMap requests', default=8 ) environment['MAX_OWS_GWC'] = UserInputHelper.get_valid_numeric_input( prompt='Maximum number of simultaneous GeoWebCache tile renders', default=16 ) environment['MAX_TIMEOUT'] = UserInputHelper.get_valid_numeric_input( prompt='Maximum request timeout in seconds', default=60 ) environment['MAX_MEMORY'] = UserInputHelper.get_valid_numeric_input( prompt='Maximum memory to allocate to each GeoServer instance in MB', max_val=4096, # TODO dynamically figure out what the max is default=1024 ) max_memory = int(environment['MAX_MEMORY']) environment['MIN_MEMORY'] = UserInputHelper.get_valid_numeric_input( prompt='Minimum memory to allocate to each GeoServer instance in MB', max_val=max_memory, default=max_memory ) options.update( environment=environment, ) mount_data_dir = UserInputHelper.get_valid_choice_input( prompt='Bind the GeoServer data directory to the host?', choices=['y', 'n'], default='y', ) if mount_data_dir.lower() == 'y': tethys_home = os.environ.get('TETHYS_HOME', os.path.expanduser('~/tethys/')) default_mount_location = os.path.join(tethys_home, 'geoserver', 'data') gs_data_volume = '/var/geoserver/data' mount_location = UserInputHelper.get_valid_directory_input( prompt='Specify location to bind data directory', default=default_mount_location ) mounts = [Mount(gs_data_volume, mount_location, type='bind')] options['host_config'].update(mounts=mounts) return options
def log_pull_stream(stream): """ Handle the printing of pull statuses """ if platform.system() == 'Windows': # i.e. can't uses curses for block in stream: lines = [l for l in block.split(b'\r\n') if l] for line in lines: json_line = json.loads(line) current_id = "{}:".format(json_line['id']) if 'id' in json_line else '' current_status = json_line['status'] if 'status' in json_line else '' current_progress = json_line['progress'] if 'progress' in json_line else '' write_pretty_output("{id}{status} {progress}".format( id=current_id, status=current_status, progress=current_progress )) else: TERMINAL_STATUSES = ['Already exists', 'Download complete', 'Pull complete'] PROGRESS_STATUSES = ['Downloading', 'Extracting'] STATUSES = TERMINAL_STATUSES + PROGRESS_STATUSES + ['Pulling fs layer', 'Waiting', 'Verifying Checksum'] NUMBER_OF_HEADER_ROWS = 2 header_rows = list() message_log = list() progress_messages = dict() messages_to_print = list() # prepare console for curses window printing stdscr = curses.initscr() curses.noecho() curses.cbreak() try: for block in stream: lines = [l for l in block.split(b'\r\n') if l] for line in lines: json_line = json.loads(line) current_id = json_line['id'] if 'id' in json_line else None current_status = json_line['status'] if 'status' in json_line else '' current_progress = json_line['progress'] if 'progress' in json_line else '' if current_id is None: # save messages to print after docker images are pulled messages_to_print.append(current_status.strip()) else: # use curses window to properly display progress if current_status not in STATUSES: # Assume this is the header header_rows.append(current_status) header_rows.append('-' * len(current_status)) elif current_status in PROGRESS_STATUSES: # add messages with progress to dictionary to print at the bottom of the screen progress_messages[current_id] = {'id': current_id, 'status': current_status, 'progress': current_progress} else: # add all other messages to list to show above progress messages message_log.append("{id}: {status} {progress}".format( id=current_id, status=current_status, progress=current_progress )) # remove messages from progress that have completed if current_id in progress_messages: del progress_messages[current_id] # update window # row/column calculations for proper display on screen maxy, maxx = stdscr.getmaxyx() number_of_rows, number_of_columns = maxy, maxx current_progress_messages = sorted(progress_messages.values(), key=lambda message: STATUSES.index(message['status'])) # row/column calculations for proper display on screen number_of_progress_rows = len(current_progress_messages) number_of_message_rows = number_of_rows - number_of_progress_rows - NUMBER_OF_HEADER_ROWS # slice messages to only those that will fit on the screen current_messages = [''] * number_of_message_rows + message_log current_messages = current_messages[-number_of_message_rows:] rows = header_rows + current_messages + ['{id}: {status} {progress}'.format(**current_message) for current_message in current_progress_messages] for row, message in enumerate(rows): message += ' ' * number_of_columns message = message[:number_of_columns - 1] stdscr.addstr(row, 0, message) stdscr.refresh() finally: # always reset console to normal regardless of success or failure curses.echo() curses.nocbreak() curses.endwin() write_pretty_output('\n'.join(messages_to_print))
def test_write_pretty_output(self, mock_print): act_msg = 'This is a test in RED' expected_string = '\x1b[31mThis is a test in RED\x1b[0m' write_pretty_output(act_msg, FG_RED) mock_print.assert_called_with(expected_string)
def log_pull_stream(stream): """ Handle the printing of pull statuses """ if platform.system() == 'Windows': # i.e. can't uses curses for block in stream: lines = [l for l in block.split(b'\r\n') if l] for line in lines: json_line = json.loads(line) current_id = "{}:".format(json_line['id']) if 'id' in json_line else '' current_status = json_line['status'] if 'status' in json_line else '' current_progress = json_line['progress'] if 'progress' in json_line else '' write_pretty_output("{id}{status} {progress}".format( id=current_id, status=current_status, progress=current_progress )) else: TERMINAL_STATUSES = ['Already exists', 'Download complete', 'Pull complete'] PROGRESS_STATUSES = ['Downloading', 'Extracting'] STATUSES = TERMINAL_STATUSES + PROGRESS_STATUSES + ['Pulling fs layer', 'Waiting', 'Verifying Checksum'] NUMBER_OF_HEADER_ROWS = 2 header_rows = list() message_log = list() progress_messages = dict() messages_to_print = list() # prepare console for curses window printing stdscr = curses.initscr() curses.noecho() curses.cbreak() try: for block in stream: lines = [l for l in block.split(b'\r\n') if l] for line in lines: json_line = json.loads(line) current_id = json_line['id'] if 'id' in json_line else None current_status = json_line['status'] if 'status' in json_line else '' current_progress = json_line['progress'] if 'progress' in json_line else '' if current_id is None: # save messages to print after docker images are pulled messages_to_print.append(current_status.strip()) else: # use curses window to properly display progress if current_status not in STATUSES: # Assume this is the header header_rows.append(current_status) header_rows.append('-' * len(current_status)) elif current_status in PROGRESS_STATUSES: # add messages with progress to dictionary to print at the bottom of the screen progress_messages[current_id] = { 'id': current_id, 'status': current_status, 'progress': current_progress } else: # add all other messages to list to show above progress messages message_log.append("{id}: {status} {progress}".format( id=current_id, status=current_status, progress=current_progress )) # remove messages from progress that have completed if current_id in progress_messages: del progress_messages[current_id] # update window # row/column calculations for proper display on screen maxy, maxx = stdscr.getmaxyx() number_of_rows, number_of_columns = maxy, maxx current_progress_messages = sorted(progress_messages.values(), key=lambda message: STATUSES.index(message['status'])) # row/column calculations for proper display on screen number_of_progress_rows = len(current_progress_messages) number_of_message_rows = number_of_rows - number_of_progress_rows - NUMBER_OF_HEADER_ROWS # slice messages to only those that will fit on the screen current_messages = [''] * number_of_message_rows + message_log current_messages = current_messages[-number_of_message_rows:] rows = header_rows + current_messages + ['{id}: {status} {progress}'.format(**current_message) for current_message in current_progress_messages] for row, message in enumerate(rows): message += ' ' * number_of_columns message = message[:number_of_columns - 1] stdscr.addstr(row, 0, message) stdscr.refresh() finally: # always reset console to normal regardless of success or failure curses.echo() curses.nocbreak() curses.endwin() write_pretty_output('\n'.join(messages_to_print))
def create(self, defaults=False): write_pretty_output("\nInstalling the {} Docker container...".format(self.display_name)) options = self.get_container_options(defaults) options['host_config'] = self.docker_client.api.create_host_config(**options['host_config']) self.docker_client.api.create_container(**options)