def start(docker_compose_path: str, docker_compose_override_path: str, stack_name: str, enable_mon: bool): log.info("Deploying Coral...") swarmadmin.deploy_stack(docker_compose_path, docker_compose_override_path, stack_name) if (enable_mon): log_stdout.info("") log.info("Deploying Monitor stack...") swarmadmin.deploy_stack("./monitor/docker-compose.monitor.yml", None, f"{stack_name}_monitor")
def remove_coral_objects(obj_type: str, silent: bool = False, list_found_objs: bool = True, prompt: bool = True, is_dep: bool = False): if not silent: log.info( f"Removing{' dependent ' if is_dep else ' '}Coral {obj_type}s...") cleanup_params = settings.get("cleanup_params")[obj_type] rm_confirmation_msg, all_, quiet, format_, filters, force = [ value for value in cleanup_params.values() ] obj_list = get_coral_objs(obj_type, all_=all_, quiet=quiet, format_=f"\"{format_}\"", filters=filters) if obj_list: bullets_obj_list = "\n".join( [f"- {line}" for line in obj_list.split("\n")]) if list_found_objs and not silent: log.info( f"The following Coral {obj_type}s were found:\n{bullets_obj_list}" ) log_stdout.info("") if prompt: question = f"{rm_confirmation_msg}\nAre you sure you want to remove these {obj_type}s? (y/n) " answer = fish.valid_answer_from_prompt( question, valid_answers=settings.get("prompt_boolean_answers")) log_file.info(question.replace("\n", " ") + answer) if not prompt or fish.user_prompt_yes(answer): remove_coral_obj_dependencies(obj_type, silent=silent) obj_ids = [ i for i, j in (obj.split("\t") for obj in obj_list.split("\n")) ] if not silent: log.info(f"Removing {obj_type}s...") res = swarmadmin.rm(obj_type, obj_ids, force=force, suppress_stdout=silent) fish.handle(res) if not silent: log.info(f"Done!") else: if not silent: log.info(f"No Coral {obj_type}s were found. Skipping...")
def update(): question = "Coral will be stopped in order to perform the update. Do you wish to continue? (y/n) " answer = fish.valid_answer_from_prompt( question, valid_answers=settings.get("prompt_boolean_answers")) log_file.info(question + answer) if fish.user_prompt_yes(answer): docker_compose_path = settings.get("compose_path") original_compose_file = None log.info("Updating Coral Docker images...") # check if a current deployment is using dev images curr_depl_conf = deploy.get_curr_depl_config() if curr_depl_conf is not None: original_compose_file = fish.read_file(docker_compose_path) dev = curr_depl_conf["advanced"]["dev_images"] if dev: deploy.switch_to_dev_images( docker_compose_path) # switch to dev images try: registry_login() pull_coral_images(docker_compose_path) finally: if original_compose_file is not None: fish.write_file(original_compose_file, docker_compose_path) log_stdout.info("") log.info("Coral Docker images successfully updated!") curr_depl_conf = deploy.get_curr_depl_config() if (curr_depl_conf is None): log.info("Cannot start Coral. Please deploy first.") else: domain = curr_depl_conf["basic"][ "domain"] # needed for logs after deployment deploy.prepare(skip_depl_config=True, is_update=True, docker_compose_path=docker_compose_path, domain=domain, email=None, cert_type=None, stack_name=None, no_prompt=True)
def prompt_secrets(secrets: list): log_stdout.info("Please provide the secrets below:") for secret_name in secrets: secret_content = getpass.getpass(prompt=f" {secret_name}: ") min_secret_len = settings.get("min_secret_len") while len(secret_content) < min_secret_len: log.info( f"Secrets must use at least {min_secret_len} characters. Try again:" ) secret_content = getpass.getpass(prompt=f" {secret_name}: ") swarmadmin.create_secret(secret_content, secret_name, "system=Coral") log_stdout.warn( "\n\033[1;31mIf you forget your passwords, you will loose access to your data.\nIt is highly recommended that you also manually store the passwords somewhere else safe.\033[0m" ) input("Press Enter to continue...")
def scale_all_services(n_replicas: int, quiet: bool = False): coral_services = get_coral_objs("service", quiet=True, format_="{{.Name}}") if coral_services: coral_services_list = coral_services.split('\n') n_services = len(coral_services_list) # scale services one by one so progress can be displayed in stdout for i, service in enumerate(coral_services_list): # skip if service is already stopped/started res = swarmadmin.get_service_replicas(service) fish.handle(res) curr_replicas = int(res["stdout"]) if n_replicas == 0 and curr_replicas == 0 or n_replicas == 1 and curr_replicas == 1: state = "stopped" if n_replicas == 0 else "running" if not quiet: log.info(f"{service} is already {state}. Skipping...") fish.print_progress_bar(i + 1, n_services, prefix=f"Progress:", suffix="Complete\n", length=50) continue # TODO: check if service image needs to be pulled and warn user res = swarmadmin.scale([service], n_replicas, suppress_stdout=quiet, suppress_stderr=quiet, timeout=settings.get("scaling_timeout")) if not quiet: fish.print_progress_bar(i + 1, n_services, prefix=f"Progress:", suffix="Complete\n", length=50) fish.handle(res) if not quiet: log_stdout.info("") return 0 else: return 1
def signal_handler(sig, frame): msg = "Cancelled by user. Exiting..." log_stdout.info("\n" + msg) log_file.info(msg) sys.exit(0)
def parse_args(parser, skip_depl_config=False): parsed_args = parser.parse_args() help_msg = "Run with the '-h' flag for additional help." if len(sys.argv) == 1: # print help when no args are provided parser.print_help() else: try: # if the version arg is present, print version and finish if parsed_args.version: log.info(settings.get("version")) return parsed_args used_depl_args = get_used_args_from_group(parsed_args, "deployment") used_mgmt_args = get_used_args_from_group(parsed_args, "management") # do not allow deployment args without using '--deploy' if len(used_depl_args) > 0 and "deploy" not in used_depl_args: first_offending_arg = get_first_used_arg_from_group( parsed_args, "deployment") parser.error( "Deployment arguments, such as '{0}', require '--deploy'.". format(first_offending_arg)) # do not allow management args if '--deploy' is used if parsed_args.deploy and len(used_mgmt_args) > 0: first_offending_arg = get_first_used_arg_from_group( parsed_args, "management") parser.error( "Management arguments, such as '{0}', are not allowed when using '--deploy'." .format(first_offending_arg)) str_central_conf = settings.get("custom_apache")["central_rule"] original_custom_file = fish.read_file( settings.get_custom_apache_conf_file_path()) # check for presence of the central arg if parsed_args.deploy and parsed_args.central: if str_central_conf not in original_custom_file: log.info( "Including apache rule for central monitoring in " + settings.get_custom_apache_conf_file_path()) original_custom_file = original_custom_file + str_central_conf fish.write_file( original_custom_file, settings.get_custom_apache_conf_file_path()) else: original_custom_file = original_custom_file.replace( str_central_conf, "") fish.write_file(original_custom_file, settings.get_custom_apache_conf_file_path()) if parsed_args.deploy and not skip_depl_config: # check for presence of required deployment args and validate them required_depl_args = get_required_depl_args() missing = list( filter(lambda x: x not in used_depl_args, required_depl_args)) if len(missing) > 0: parser.error( "Missing required deployment arguments: --{0}".format( ", --".join(missing))) else: if not fish.is_valid("domain", parsed_args.domain): parser.error("Invalid domain: '{0}'".format( parsed_args.domain)) if not fish.is_valid("email", parsed_args.email): parser.error("Invalid email: '{0}'".format( parsed_args.email)) # make '--letsencrypt' and '--no-port-binding' incompatible if parsed_args.letsencrypt and parsed_args.no_port_binding: parser.error( "Incompatible arguments: '--letsencrypt' and '--no-port-binding'. Cannot issue a Let's Encrypt certificate if port binding to host is disabled." ) # do not allow dots in the stack name if parsed_args.stack_name is not None and "." in parsed_args.stack_name: parser.error("The stack name cannot contain dots.") # validate advertise and proxy addresses, if present if parsed_args.addr and not fish.is_valid( "ip", parsed_args.addr): parser.error("Invalid IP address: '{0}'".format( parsed_args.addr)) if parsed_args.http_proxy and not fish.is_valid( "address", parsed_args.http_proxy): parser.error("Invalid proxy address: '{0}'".format( parsed_args.http_proxy)) if parsed_args.https_proxy and not fish.is_valid( "address", parsed_args.https_proxy): parser.error("Invalid proxy address: '{0}'".format( parsed_args.https_proxy)) else: # only allow one management argument at a time if len(used_mgmt_args) > 1: parser.error( "Only one management argument at a time is allowed.") except SystemExit as e: log_stdout.info("\n{0}".format(help_msg)) sys.exit(e) return parsed_args
if parsed_args.start: status = manage.start() elif parsed_args.stop: status = manage.stop() elif parsed_args.restart: status = manage.restart() if status == 1: log.info( "No Coral services were found (Coral stack is not currently deployed)" ) if __name__ == "__main__": try: log_stdout.info(settings.get_intro()) log_file.info("EXEC: [ {0} ]".format(", ".join(sys.argv))) signal.signal(signal.SIGINT, signal_handler) skip_depl_config = False if "--deploy" in sys.argv: skip_depl_config = deploy.check_skip_depl_config( "--test" in sys.argv) parser = config_arg_parser() parsed_args = parse_args(parser, skip_depl_config) main(parsed_args, skip_depl_config) except (Exception, SystemExit) as e: e_name = type(e).__name__ if e_name == "SystemExit": if e == 1: log_file.exception(e_name)
def prepare(skip_depl_config: bool, is_update: bool, docker_compose_path: str, domain: str, email: str, cert_type: str, stack_name: str, addr: str = None, http_proxy: str = None, https_proxy: str = None, no_port_binding: bool = False, dev: bool = False, no_prompt: bool = False, central: bool = False): env_file_path = settings.get_conf_file_path("stack") original_env_file = fish.read_file(env_file_path) original_compose_file = fish.read_file(docker_compose_path) deploy_monitor_stack = True try: if skip_depl_config: log.info("Skipping configuration of deployment parameters...") log_stdout.info("") curr_depl_conf = get_curr_depl_config() domain = curr_depl_conf["basic"]["domain"] stack_name = curr_depl_conf["basic"]["stack_name"] addr = curr_depl_conf["advanced"]["advertise_addr"] dev = curr_depl_conf["advanced"]["dev_images"] no_port_binding = curr_depl_conf["advanced"]["no_port_binding"] central = curr_depl_conf["advanced"]["central"] enable_swarm_mode(addr) log_stdout.info("") # remove any current Coral services if not is_update: log.info("Cleaning up any previously deployed Coral services...") manage.remove_coral_objects("service", list_found_objs=False, prompt=False) log_stdout.info("") # save deployment parameters if not skip_depl_config: config_depl_params(domain, email, cert_type, stack_name, addr, http_proxy, https_proxy, no_port_binding, dev, central) log_stdout.info("") # set environment variables using deployment perameters set_env_vars() log_stdout.info("") # check central monitoring deploy_monitor_stack = check_central_mon(env_file_path, no_prompt) log_stdout.info("") # make sure all required secrets exist (if not, prompt) check_secrets(docker_compose_path, no_prompt) log_stdout.info("") # temporarilly edit compose file to set dev images or unbind ports to host if dev: switch_to_dev_images(docker_compose_path) if no_port_binding: unbind_ports(docker_compose_path) # pull Coral images if not is_update: log.info("Pulling Coral Docker images...") manage.registry_login() manage.pull_coral_images(docker_compose_path) log_stdout.info("") # check_volumes() # deploy Coral docker_compose_override_path = get_compose_override_path( docker_compose_path) start(docker_compose_path, docker_compose_override_path, stack_name, deploy_monitor_stack) log_stdout.info("") # track progress #track_progress(stack_name, docker_compose_path) log.info("\033[1;32mCoral has been deployed!\033[0m") log_stdout.info( "After a few minutes, you should have access to the following services:\n" f" \033[1;37mAgate\033[0m\t\t https://{domain}/auth\n" f" \033[1;37mOpal\033[0m\t\t https://{domain}/repo\n" f" \033[1;37mMica\033[0m\t\t https://{domain}/pub\n" f" \033[1;37mMica Drupal\033[0m\t https://{domain}/cat (or just https://{domain})\n" ) finally: # restore original env and docker-compose files fish.write_file(original_env_file, env_file_path) fish.write_file(original_compose_file, docker_compose_path) return 0