Example #1
0
def guided_install(config, dns_provider, dns_auto_provision, disable_prompt=False):
    # 01 Verify configuration file exists
    verify_configuration_file_exists()

    # 02 Check terraform
    check_terraform()

    # 03 Check Environment Variables
    check_cloud_credentials(config)

    # 04 Check that oauth settings are set
    if not disable_prompt:
        input(
            'Ensure that oauth settings are in configuration [Press "Enter" to continue]'
        )

    # 05 Create terraform backend remote state bucket
    with change_directory("terraform-state"):
        run(["terraform", "init"])
        run(["terraform", "apply", "-auto-approve"])

    # 06 Create qhub initial state (up to nginx-ingress)
    with change_directory("infrastructure"):
        run(["terraform", "init"])
        run(
            [
                "terraform",
                "apply",
                "-auto-approve",
                "-target=module.kubernetes",
                "-target=module.kubernetes-initialization",
                "-target=module.kubernetes-ingress",
            ]
        )
        cmd_output = check_output(["terraform", "output", "--json"])
        # This is a bit ugly, but the issue we have at the moment is being unable
        # to parse cmd_output as json on Github Actions.
        ip_matches = re.findall(rb'"ip": "(?!string)(.*)"', cmd_output)
        if ip_matches:
            ip = ip_matches[0].decode()
        else:
            raise ValueError(f"IP Address not found in: {cmd_output}")
    # 07 Update DNS to point to qhub deployment
    if dns_auto_provision and dns_provider == "cloudflare":
        record_name, zone_name = (
            config["domain"].split(".")[:-2],
            config["domain"].split(".")[-2:],
        )
        record_name = f'jupyter.{".".join(record_name)}'
        zone_name = ".".join(zone_name)
        if config["provider"] in {"do", "gcp"}:
            update_record(zone_name, record_name, "A", ip)
    else:
        input(
            f'Take IP Address {ip} and update DNS to point to "jupyter.{config["domain"]}" [Press Enter when Complete]'
        )

    # 08 Full deploy QHub
    with change_directory("infrastructure"):
        run(["terraform", "apply", "-auto-approve"])
Example #2
0
def add_clearml_dns(zone_name, record_name, record_type, ip_or_hostname):
    logger.info(f"Setting DNS record for ClearML for record: {record_name}")
    dns_records = [
        f"app.clearml.{record_name}",
        f"api.clearml.{record_name}",
        f"files.clearml.{record_name}",
    ]

    for dns_record in dns_records:
        update_record(zone_name, dns_record, record_type, ip_or_hostname)
Example #3
0
def deploy_configuration(config):
    logger.info(f'All qhub endpoints will be under *.{config["domain"]}')

    jupyterhub_endpoint = f'jupyter.{config["domain"]}'
    if ("client_id" not in config["authentication"]["config"]
            or "client_secret" not in config["authentication"]["config"]):
        logger.info(
            "client_id and client_secret were not specified - dynamically creating oauth client"
        )
        with timer(logger, "creating oauth client"):
            config["authentication"]["config"] = auth0.create_client(
                jupyterhub_endpoint)

    with timer(logger, "rendering template"):
        tmp_config = pathlib.Path("./config.yaml")
        with tmp_config.open("w") as f:
            yaml.dump(config, f)

        render_default_template(".", tmp_config)

    infrastructure_dir = pathlib.Path(
        config["project_name"]) / "infrastructure"

    terraform.init(str(infrastructure_dir))

    # ========= boostrap infrastructure ========
    terraform.apply(
        str(infrastructure_dir),
        targets=[
            "module.kubernetes",
            "module.kubernetes-initialization",
            "module.kubernetes-ingress",
        ],
    )

    # ============= update dns ================
    output = terraform.output(str(infrastructure_dir))
    for key in output:
        if key.startswith("ingress"):
            endpoint = f'{key.split("_")[1]}.{config["domain"]}'
            address = output[key]["value"]
            if re.fullmatch(r"\d+\.\d+\.\d+\.\d+", address):
                cloudflare.update_record("qhub.dev", endpoint, "A", address)
            else:
                cloudflare.update_record("qhub.dev", endpoint, "CNAME",
                                         address)

    # ======= apply entire infrastructure ========
    terraform.apply(str(infrastructure_dir))
Example #4
0
def guided_install(
    config,
    dns_provider,
    dns_auto_provision,
    disable_prompt=False,
    skip_remote_state_provision=False,
    full_only=False,
):
    # 01 Check Environment Variables
    check_cloud_credentials(config)
    # Check that secrets required for terraform
    # variables are set as required
    check_secrets(config)

    # 02 Create terraform backend remote state bucket
    # backwards compatible with `qhub-config.yaml` which
    # don't have `terraform_state` key
    if (
        (not skip_remote_state_provision)
        and (config.get("terraform_state", {}).get("type", "") == "remote")
        and (config.get("provider") != "local")
    ):
        terraform_state_sync(config)

    # 3 kubernetes-alpha provider requires that kubernetes be
    # provisionioned before any "kubernetes_manifests" resources
    logger.info("Running terraform init")
    terraform.init(directory="infrastructure")

    if not full_only:
        targets = [
            "module.kubernetes",
            "module.kubernetes-initialization",
        ]

        logger.info(f"Running Terraform Stage: {targets}")
        terraform.apply(
            directory="infrastructure",
            targets=targets,
        )

        # 04 Create qhub initial state (up to nginx-ingress)
        targets = ["module.kubernetes-ingress"]
        logger.info(f"Running Terraform Stage: {targets}")
        terraform.apply(
            directory="infrastructure",
            targets=targets,
        )

        cmd_output = terraform.output(directory="infrastructure")
        # This is a bit ugly, but the issue we have at the moment is being unable
        # to parse cmd_output as json on Github Actions.
        ip_matches = re.findall(r'"ip": "(?!string)(.+)"', cmd_output)
        hostname_matches = re.findall(r'"hostname": "(?!string)(.+)"', cmd_output)
        if ip_matches:
            ip_or_hostname = ip_matches[0]
        elif hostname_matches:
            ip_or_hostname = hostname_matches[0]
        else:
            raise ValueError(f"IP Address not found in: {cmd_output}")

        # 05 Update DNS to point to qhub deployment
        if dns_auto_provision and dns_provider == "cloudflare":
            record_name, zone_name = (
                config["domain"].split(".")[:-2],
                config["domain"].split(".")[-2:],
            )
            record_name = ".".join(record_name)
            zone_name = ".".join(zone_name)
            if config["provider"] in {"do", "gcp", "azure"}:
                update_record(zone_name, record_name, "A", ip_or_hostname)
                if config.get("clearml", {}).get("enabled"):
                    add_clearml_dns(zone_name, record_name, "A", ip_or_hostname)
            elif config["provider"] == "aws":
                update_record(zone_name, record_name, "CNAME", ip_or_hostname)
                if config.get("clearml", {}).get("enabled"):
                    add_clearml_dns(zone_name, record_name, "CNAME", ip_or_hostname)
            else:
                logger.info(
                    f"Couldn't update the DNS record for cloud provider: {config['provider']}"
                )
        elif not disable_prompt:
            input(
                f"Take IP Address {ip_or_hostname} and update DNS to point to "
                f'"{config["domain"]}" [Press Enter when Complete]'
            )

        # Now Keycloak Helm chart (External Docker Registry before that if we need one)
        targets = ["module.external-container-reg", "module.kubernetes-keycloak-helm"]
        logger.info(f"Running Terraform Stage: {targets}")
        terraform.apply(
            directory="infrastructure",
            targets=targets,
        )

        # Now Keycloak realm and config
        targets = ["module.kubernetes-keycloak-config"]
        logger.info(f"Running Terraform Stage: {targets}")
        terraform.apply(
            directory="infrastructure",
            targets=targets,
        )

    # Full deploy QHub
    logger.info("Running Terraform Stage: FULL")
    terraform.apply(directory="infrastructure")
Example #5
0
def guided_install(config,
                   dns_provider,
                   dns_auto_provision,
                   disable_prompt=False):
    # 01 Verify configuration file exists
    if not path.exists("qhub-config.yaml"):
        raise Exception('Configuration file "qhub-config.yaml" does not exist')

    # 02 Check if Terraform works
    if which("terraform") is None:
        raise Exception(
            f"Please install Terraform with one of the following minor releases: {SUPPORTED_TERRAFORM_MINOR_RELEASES}"
        )

    # 03 Check version of Terraform
    version_out = check_output(["terraform", "--version"]).decode("utf-8")
    minor_release = re.search(r"(\d+)\.(\d+)", version_out).group(0)

    if minor_release not in SUPPORTED_TERRAFORM_MINOR_RELEASES:
        raise Exception(
            f"Unsupported Terraform version. Supported minor releases: {SUPPORTED_TERRAFORM_MINOR_RELEASES}"
        )

    # 04 Check Environment Variables
    if config["provider"] == "gcp":
        for variable in {"GOOGLE_CREDENTIALS"}:
            if variable not in os.environ:
                raise Exception(
                    f"""Missing the following required environment variable: {variable}\n
                    Please see the documentation for more information: {GCP_ENV_DOCS}"""
                )
    elif config["provider"] == "aws":
        for variable in {
                "AWS_ACCESS_KEY_ID",
                "AWS_SECRET_ACCESS_KEY",
                "AWS_DEFAULT_REGION",
        }:
            if variable not in os.environ:
                raise Exception(
                    f"""Missing the following required environment variable: {variable}\n
                    Please see the documentation for more information: {AWS_ENV_DOCS}"""
                )
    elif config["provider"] == "do":
        for variable in {
                "AWS_ACCESS_KEY_ID",
                "AWS_SECRET_ACCESS_KEY",
                "SPACES_ACCESS_KEY_ID",
                "SPACES_SECRET_ACCESS_KEY",
                "DIGITALOCEAN_TOKEN",
        }:
            if variable not in os.environ:
                raise Exception(
                    f"""Missing the following required environment variable: {variable}\n
                    Please see the documentation for more information: {DO_ENV_DOCS}"""
                )

        if os.environ["AWS_ACCESS_KEY_ID"] != os.environ[
                "SPACES_ACCESS_KEY_ID"]:
            raise Exception(
                f"""The environment variables AWS_ACCESS_KEY_ID and SPACES_ACCESS_KEY_ID must be equal\n
                See {DO_ENV_DOCS} for more information""")

        if (os.environ["AWS_SECRET_ACCESS_KEY"] !=
                os.environ["SPACES_SECRET_ACCESS_KEY"]):
            raise Exception(
                f"""The environment variables AWS_SECRET_ACCESS_KEY and SPACES_SECRET_ACCESS_KEY must be equal\n
                See {DO_ENV_DOCS} for more information""")
    else:
        raise Exception("Cloud Provider configuration not supported")

    # 05 Check that oauth settings are set
    if not disable_prompt:
        input(
            'Ensure that oauth settings are in configuration [Press "Enter" to continue]'
        )

    # 06 Create terraform backend remote state bucket
    with change_directory("terraform-state"):
        run(["terraform", "init"])
        run(["terraform", "apply", "-auto-approve"])

    # 07 Create qhub initial state (up to nginx-ingress)
    with change_directory("infrastructure"):
        run(["terraform", "init"])
        run([
            "terraform",
            "apply",
            "-auto-approve",
            "-target=module.kubernetes",
            "-target=module.kubernetes-initialization",
            "-target=module.kubernetes-ingress",
        ])
        cmd_output = check_output(["terraform", "output", "--json"])
        try:
            output = json.loads(cmd_output)
        except json.decoder.JSONDecodeError as e:
            print(f"Failed to parse terraform output: {cmd_output}")
            raise e

    # 08 Update DNS to point to qhub deployment
    if dns_auto_provision and dns_provider == "cloudflare":
        record_name, zone_name = (
            config["domain"].split(".")[:-2],
            config["domain"].split(".")[-2:],
        )
        record_name = f'jupyter.{".".join(record_name)}'
        zone_name = ".".join(zone_name)
        ip = output["ingress_jupyter"]["value"]["ip"]
        if config["provider"] in {"do", "gcp"}:
            update_record(zone_name, record_name, "A", ip)
    else:
        input(
            f'Take IP Address {output["ingress_jupyter"]["value"]["ip"]} and update DNS to point to "jupyter.{config["domain"]}" [Press Enter when Complete]'
        )

    # 09 Full deploy QHub
    with change_directory("infrastructure"):
        run(["terraform", "apply", "-auto-approve"])