Example #1
0
def enable_swarm_mode(advertise_addr: str):
    log.info("Enabling Docker Swarm...")
    if swarmadmin.swarm_is_active():
        log.info("Docker Swarm is enabled")
    else:
        res = swarmadmin.init_docker_swarm(advertise_addr)
        fish.handle(res)
Example #2
0
def check_secrets(docker_compose_path: str, no_prompt: bool = False):
    log.info("Checking Coral secrets...")
    required_secrets = swarmadmin.get_objs_from_compose(
        "secret", docker_compose_path)
    missing_secrets = list(
        filter(lambda x: not swarmadmin.obj_exists("secret", x),
               required_secrets))

    if len(missing_secrets) > 0:
        log.info("Some required secrets are missing")

        if no_prompt:
            raise Exception(
                "When running in test mode, all secrets must be manually created beforehand."
            )
        else:
            prompt_secrets(missing_secrets)
    else:
        if not no_prompt:
            question = "All required secrets are defined. Do you wish to redefine them? (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):
                manage.remove_coral_objects("secret",
                                            silent=True,
                                            list_found_objs=False,
                                            prompt=False)
                prompt_secrets(required_secrets)
Example #3
0
def start(quiet: bool = False):
    # TODO: check if all Coral services exist
    if not quiet: log.info("Starting Coral...")
    status = scale_all_services(1)
    if not quiet and status == 0: log.info("Coral stack successfully started!")

    return status
Example #4
0
def config_depl_params(domain: str,
                       email: str,
                       cert_type: str,
                       stack_name: str,
                       advertise_addr: str = None,
                       http_proxy: str = None,
                       https_proxy: str = None,
                       no_port_binding: bool = False,
                       dev: bool = False,
                       central: bool = False):
    log.info("Configuring deployment parameters...")

    depl_conf_path = settings.get_conf_file_path("deployment")
    depl_conf_template = settings.get("deployment_template")

    # set basic configurations
    basic_depl_conf = depl_conf_template["basic"]
    basic_depl_conf["domain"] = domain
    basic_depl_conf["email"] = email
    basic_depl_conf["cert_type"] = cert_type
    basic_depl_conf["stack_name"] = stack_name

    # set advanced configurations
    adv_depl_conf = depl_conf_template["advanced"]
    adv_depl_conf["advertise_addr"] = advertise_addr
    adv_depl_conf["http_proxy"] = http_proxy
    adv_depl_conf["https_proxy"] = https_proxy
    adv_depl_conf["no_port_binding"] = no_port_binding
    adv_depl_conf["dev_images"] = dev
    adv_depl_conf["central"] = central

    depl_conf = {"basic": basic_depl_conf, "advanced": adv_depl_conf}

    fish.write_json(depl_conf, depl_conf_path, indent=2)
Example #5
0
def exec_cmd(cmd, suppress_stdout=False, suppress_stderr=True):
  process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  stdout = stderr = prev_line_stdout = ""

  while True:
    nextline_stdout = process.stdout.readline()
    nextline_stderr = process.stderr.readline()

    if nextline_stdout == b'' and nextline_stderr == b'' and process.poll() is not None: break

    line_stdout = nextline_stdout.decode('utf-8')
    line_stderr = nextline_stderr.decode('utf-8')
    stdout += line_stdout
    stderr += line_stderr

    if not suppress_stdout and line_stdout != prev_line_stdout and line_stdout != "\n":
      prev_line_stdout = line_stdout
      log.info(line_stdout.rstrip('\n'))
      # sys.stdout.write(line_stdout)
      # sys.stdout.flush()

  exit_code = process.returncode

  return {
    "cmd": cmd,
    "exit_code": exit_code,
    "stdout": stdout.rstrip('\n'),
    "stderr": stderr.rstrip('\n')
  }
Example #6
0
def restart():
    log.info("Restarting Coral...")
    status = stop(quiet=True)
    if status != 0: return status
    status = start(quiet=True)
    if status == 0: log.info("Coral stack successfully restarted!")

    return status
Example #7
0
def set_env_vars():
    log.info("Setting environment variables...")

    depl_config = get_curr_depl_config()
    env_file_path = settings.get_conf_file_path("stack")

    fish.replace_regex(env_file_path,
                       r"^(SERVER_SYSTEM_NAME=).*$",
                       fr"\1{settings.get('name')}",
                       multiline=True)
    fish.replace_regex(env_file_path,
                       r"^(SERVER_SYSTEM_VERSION=).*$",
                       fr"\g<1>{settings.get('version')}",
                       multiline=True)
    fish.replace_regex(env_file_path,
                       r"^(DOMAIN=).*$",
                       fr"\1{depl_config['basic']['domain']}",
                       multiline=True)
    fish.replace_regex(env_file_path,
                       r"^(WEBMASTER_MAIL=).*$",
                       fr"\1{depl_config['basic']['email']}",
                       multiline=True)
    fish.replace_regex(env_file_path,
                       r"^(CERT_TYPE=).*$",
                       fr"\1{depl_config['basic']['cert_type']}",
                       multiline=True)
    if depl_config['advanced']['advertise_addr'] is not None:
        fish.replace_regex(
            env_file_path,
            r"^(MICA_DOMAIN=).*$",
            fr"\g<1>{depl_config['advanced']['advertise_addr']}/pub",
            multiline=True)
        fish.replace_regex(
            env_file_path,
            r"^(BASE_URL=).*$",
            fr"\g<1>https://{depl_config['advanced']['advertise_addr']}/cat",
            multiline=True)
    else:
        fish.replace_regex(env_file_path,
                           r"^(MICA_DOMAIN=).*$",
                           fr"\1{depl_config['basic']['domain']}/pub",
                           multiline=True)
        fish.replace_regex(env_file_path,
                           r"^(BASE_URL=).*$",
                           fr"\1https://{depl_config['basic']['domain']}/cat",
                           multiline=True)
    if depl_config['advanced']['http_proxy'] is not None:
        fish.replace_regex(env_file_path,
                           r"^((HTTP_PROXY|http_proxy)=).*$",
                           fr"\1{depl_config['advanced']['http_proxy']}",
                           multiline=True)
    if depl_config['advanced']['https_proxy'] is not None:
        fish.replace_regex(env_file_path,
                           r"^((HTTPS_PROXY|https_proxy)=).*$",
                           fr"\1{depl_config['advanced']['https_proxy']}",
                           multiline=True)
Example #8
0
def registry_login():
    resgistry = settings.get('docker_registry')

    log.info(f"Credentials for {resgistry}:")
    while True:
        try:
            swarmadmin.login(resgistry)
            break
        except Exception as e:
            log_file.warning("{0}: {1}".format(type(e).__name__, e))
Example #9
0
def main(parsed_args, skip_depl_conf=False):
    status = None

    if parsed_args.deploy:
        depl_args = {
            "docker_compose_path":
            settings.get("compose_path"),
            "domain":
            parsed_args.domain,
            "email":
            parsed_args.email,
            "cert_type":
            "letsencrypt" if parsed_args.letsencrypt else "custom",
            "stack_name":
            settings.get("name")
            if parsed_args.stack_name is None else parsed_args.stack_name,
            "addr":
            parsed_args.addr,
            "http_proxy":
            parsed_args.http_proxy,
            "https_proxy":
            parsed_args.https_proxy,
            "no_port_binding":
            parsed_args.no_port_binding,
            "dev":
            parsed_args.dev,
            "no_prompt":
            parsed_args.test,
            "central":
            parsed_args.central
        }

        deploy.prepare(skip_depl_config=skip_depl_conf,
                       is_update=False,
                       **depl_args)
    elif parsed_args.list:
        manage.list_(parsed_args.list)
    elif parsed_args.remove:
        manage.remove(parsed_args.remove)
    elif parsed_args.logs:
        manage.logs(parsed_args.logs)
    elif parsed_args.update:
        manage.update()
    else:
        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)"
        )
Example #10
0
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")
Example #11
0
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...")
Example #12
0
def check_skip_depl_config(no_prompt: bool = False):
    curr_depl_conf = get_curr_depl_config()

    if curr_depl_conf is None: return False

    curr_depl_conf = json.dumps(curr_depl_conf, indent=2)
    log.info(
        "Previous deployment configuration found:\n{0}".format(curr_depl_conf))

    if no_prompt:
        return True
    else:
        question = f"Do you wish to reuse the configuration above? (y/n) "
        answer = fish.valid_answer_from_prompt(
            question, valid_answers=settings.get("prompt_boolean_answers"))
        log_file.info(question + answer)

        return fish.user_prompt_yes(answer)
Example #13
0
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...")
Example #14
0
def remove_coral_obj_dependencies(obj_type: str, silent: bool = False):
    if obj_type == "container":
        # must remove services first, or else containers will be automatically recreated
        if not silent: log.info("Must remove dependent services first")
        remove_coral_objects("service",
                             silent=silent,
                             list_found_objs=False,
                             prompt=False,
                             is_dep=True)

    if obj_type in ["image", "volume", "secret"]:
        # must remove associated containers first
        if not silent: log.info("Must remove dependent containers first")
        remove_coral_objects("container",
                             silent=silent,
                             list_found_objs=False,
                             prompt=False,
                             is_dep=True)
Example #15
0
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
Example #16
0
def check_central_mon(env_file_path: str, no_prompt: bool = False):
    log.info("Checking for central monitoring configuration...")
    central_mon_url = get_central_monitor_url()

    if (len(central_mon_url["value"]) == 0):
        log.warn(
            f"A central monitoring URL has not been defined in {env_file_path}, line {str(central_mon_url['index'] + 1)}.\nThe monitoring features described in the 'Coral Monitor Stack' section of README.md will be disabled."
        )

        if not no_prompt:
            question = "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_no(answer):
                sys.exit(0)

        return False
    else:
        log.info(f"Using {central_mon_url['value']} for central monitoring...")
        return True
Example #17
0
def list_(obj_type: str):
    log.info(f"Listing Coral {obj_type}:")
    objs_found = get_coral_objs(obj_type.rstrip("s"))

    if objs_found.count("\n") == 0:
        log.info(f"No Coral {obj_type} found.")
    else:
        log.info("\n{0}\n".format(objs_found))
Example #18
0
def get_coral_logs(obj_type: str):
    log.info(f"Getting {obj_type} logs...")

    volumes_dir = os.path.join(swarmadmin.get_data_root(), "volumes")
    stack_name = get_stack_name()
    if stack_name == None:
        raise Exception(
            "Unable to retrieve logs, No Coral deployment configuration found."
        )

    if obj_type in {"agate", "mica"}:
        fish.copy_folder(
            os.path.join(volumes_dir, f"{stack_name}_{obj_type}", "_data",
                         "logs"), settings.get_logs_tmp_folder(obj_type))
        save_docker_service_log(f"{stack_name}_mongo",
                                settings.get_logs_tmp_folder("mongodb"))
    elif obj_type == "apache":
        fish.copy_folder(
            os.path.join(volumes_dir, f"{stack_name}_{obj_type}-logs",
                         "_data"), settings.get_logs_tmp_folder(obj_type))
    elif obj_type == "opal":
        fish.copy_folder(
            os.path.join(volumes_dir, f"{stack_name}_{obj_type}", "_data",
                         "logs"), settings.get_logs_tmp_folder(obj_type))
        fish.copy_folder(
            os.path.join(volumes_dir, f"{stack_name}_rserver", "_data",
                         "logs"), settings.get_logs_tmp_folder("rserver"))
        save_docker_service_log(f"{stack_name}_{obj_type}-data",
                                settings.get_logs_tmp_folder("opal-data"))
        save_docker_service_log(f"{stack_name}_{obj_type}-ids",
                                settings.get_logs_tmp_folder("opal-ids"))
    elif obj_type == "drupal":
        save_docker_service_log(f"{stack_name}_mica-{obj_type}",
                                settings.get_logs_tmp_folder(obj_type))
        save_docker_service_log(
            f"{stack_name}_mica-{obj_type}-data",
            settings.get_logs_tmp_folder("mica-drupal-data"))
Example #19
0
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)
Example #20
0
def logs(obj_types: list, is_all: bool = False, zip_name: str = ""):
    obj_types = list(set(obj_types))

    if not is_all:
        shutil.rmtree(settings.get_logs_tmp_folder("coral"), True)
        pathlib.Path(settings.get_logs_tmp_folder("coral")).mkdir(
            parents=True, exist_ok=True)
        log.info(f"Getting coral.log...")
        shutil.copyfile(settings.get_log_file_path("coral"),
                        settings.get_log_tmp_file_path())
        log.info(f"Getting docker.log...")
        save_mixed_docker_command_logs(settings.get_logs_tmp_folder("docker"))

    if "all" in obj_types:
        zip_name = datetime.today().strftime("%Y%m%d") + "_" + time.strftime(
            '%H%M%S', time.gmtime()) + "_all_logs"
        logs(["agate", "opal", "mica", "drupal", "apache"], True, zip_name)
    else:
        if "agate" in obj_types:
            get_coral_logs("agate")
            if not is_all: zip_name += "_agate"
        if "opal" in obj_types:
            get_coral_logs("opal")
            if not is_all: zip_name += "_opal"
        if "mica" in obj_types:
            get_coral_logs("mica")
            if not is_all: zip_name += "_mica"
        if "drupal" in obj_types:
            get_coral_logs("drupal")
            if not is_all: zip_name += "_drupal"
        if "apache" in obj_types:
            get_coral_logs("apache")
            if not is_all: zip_name += "_apache"
        if not is_all:
            zip_name = datetime.today().strftime(
                "%Y%m%d") + "_" + time.strftime(
                    '%H%M%S', time.gmtime()) + zip_name + "_logs"

        file_name = shutil.make_archive(f"./logs/{zip_name}", "zip",
                                        settings.get_logs_tmp_folder("coral"))
        log.info(
            f"A zip file containing the requested services logs was saved in {file_name}"
        )
        shutil.rmtree(settings.get_logs_tmp_folder("coral"), True)
Example #21
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
Example #22
0
def stop(quiet: bool = False):
    if not quiet: log.info("Stopping Coral...")
    status = scale_all_services(0, quiet=quiet)
    if not quiet and status == 0: log.info("Coral stack successfully stopped!")

    return status
Example #23
0
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