From cbd4d1feb0738204cef15da590328a1b071d493c Mon Sep 17 00:00:00 2001 From: iameskild Date: Tue, 19 Oct 2021 15:20:02 -0700 Subject: [PATCH 01/46] Add k8s version checker for AWS --- qhub/provider/cloud/amazon_web_services.py | 23 ++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/qhub/provider/cloud/amazon_web_services.py b/qhub/provider/cloud/amazon_web_services.py index 330e8bbda5..b40538fa98 100644 --- a/qhub/provider/cloud/amazon_web_services.py +++ b/qhub/provider/cloud/amazon_web_services.py @@ -1,6 +1,7 @@ import json import subprocess import functools +import boto3 @functools.lru_cache() @@ -20,8 +21,26 @@ def zones(region): @functools.lru_cache() -def kubernetes_versions(): - return {_: _ for _ in ["1.13", "1.14", "1.15"]} +def kubernetes_versions(grab_latest_version=True): + # AWS SDK (boto3) currently doesn't offer an intuitive way to list available kubernetes version. This implementation grabs kubernetes versions for specific EKS addons. It will therefore always be (at the very least) a subset of all kubernetes versions still supported by AWS. + client = boto3.client("eks") + supported_kubernetes_versions = list() + available_addons = client.describe_addon_versions() + for addon in available_addons.get("addons", None): + for eksbuild in addon.get("addonVersions", None): + for k8sversion in eksbuild.get("compatibilities", None): + supported_kubernetes_versions.append( + k8sversion.get("clusterVersion", None) + ) + + # remove duplicate values + # then sort from oldest to newest available versions + supported_kubernetes_versions = sorted(list(set(supported_kubernetes_versions))) + + if grab_latest_version: + return supported_kubernetes_versions[-1] + + return supported_kubernetes_versions @functools.lru_cache() From c3bd8b77eb0a92135183560e6be84df83f167899 Mon Sep 17 00:00:00 2001 From: iameskild Date: Tue, 19 Oct 2021 15:20:35 -0700 Subject: [PATCH 02/46] Grab latest azure k8s version available by default --- qhub/provider/cloud/azure_cloud.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/qhub/provider/cloud/azure_cloud.py b/qhub/provider/cloud/azure_cloud.py index ae3f0ab753..a6e0df3a13 100644 --- a/qhub/provider/cloud/azure_cloud.py +++ b/qhub/provider/cloud/azure_cloud.py @@ -13,7 +13,7 @@ def initiate_container_service_client(): @functools.lru_cache() -def kubernetes_versions(azure_location="Central US"): +def kubernetes_versions(azure_location="Central US", grap_latest_version=True): client = initiate_container_service_client() azure_location = azure_location.replace(" ", "").lower() @@ -27,4 +27,8 @@ def kubernetes_versions(azure_location="Central US"): if key["orchestrator_type"] == "Kubernetes": supported_kubernetes_versions.append(key["orchestrator_version"]) + supported_kubernetes_versions = sorted(supported_kubernetes_versions) + + if grap_latest_version: + return supported_kubernetes_versions[-1] return supported_kubernetes_versions From 99feba7a799fc82fba2ab7a9c7cf2a7f060f2424 Mon Sep 17 00:00:00 2001 From: iameskild Date: Tue, 19 Oct 2021 15:21:19 -0700 Subject: [PATCH 03/46] Update initialize with k8s versioning --- qhub/initialize.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/qhub/initialize.py b/qhub/initialize.py index 3e98799a47..cb30891956 100644 --- a/qhub/initialize.py +++ b/qhub/initialize.py @@ -378,13 +378,9 @@ def render_config( if kubernetes_version: config["azure"]["kubernetes_version"] = kubernetes_version else: - # first kubernetes version returned by azure sdk is - # the newest version of kubernetes supported this field needs - # to be dynamically filled since azure updates the - # versions so frequently config["azure"]["kubernetes_version"] = azure_cloud.kubernetes_versions( config["azure"]["region"] - )[0] + ) elif cloud_provider == "aws": config["theme"]["jupyterhub"][ @@ -392,7 +388,7 @@ def render_config( ] = "Autoscaling Compute Environment on Amazon Web Services" config["amazon_web_services"] = AMAZON_WEB_SERVICES if kubernetes_version: - config["amazon_web_services"]["kubernetes_version"] = kubernetes_version + config["amazon_web_services"]["kubernetes_version"] = kubernetes_version() elif cloud_provider == "local": config["theme"]["jupyterhub"][ "hub_subtitle" From 9c24d93d5939d08588452d21ccecfe1b77e94d10 Mon Sep 17 00:00:00 2001 From: iameskild Date: Tue, 19 Oct 2021 15:22:06 -0700 Subject: [PATCH 04/46] Add boto3 to setup.py --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index d5213c8284..e01d560989 100644 --- a/setup.py +++ b/setup.py @@ -42,6 +42,7 @@ "bcrypt", "azure-identity==1.6.1", "azure-mgmt-containerservice==16.2.0", + "boto3", ], extras_require={ "dev": [ From 8fd4665f2856b476e61aedbc5c86f563d721f326 Mon Sep 17 00:00:00 2001 From: iameskild Date: Tue, 19 Oct 2021 17:09:59 -0700 Subject: [PATCH 05/46] Create _set_kubernetes_version function for init --- qhub/initialize.py | 84 ++++++++++++++++------ qhub/provider/cloud/amazon_web_services.py | 2 +- qhub/provider/cloud/azure_cloud.py | 5 +- 3 files changed, 65 insertions(+), 26 deletions(-) diff --git a/qhub/initialize.py b/qhub/initialize.py index cb30891956..1608a34272 100644 --- a/qhub/initialize.py +++ b/qhub/initialize.py @@ -12,7 +12,7 @@ from qhub.provider.oauth.auth0 import create_client from qhub.provider.cicd import github from qhub.provider import git -from qhub.provider.cloud import digital_ocean, azure_cloud +from qhub.provider.cloud import digital_ocean, azure_cloud, amazon_web_services from qhub.utils import namestr_regex, qhub_image_tag, check_cloud_credentials logger = logging.getLogger(__name__) @@ -156,7 +156,7 @@ AMAZON_WEB_SERVICES = { "region": "us-west-2", - "kubernetes_version": "1.18", + "kubernetes_version": "PLACEHOLDER", "node_groups": { "general": {"instance": "m5.xlarge", "min_nodes": 1, "max_nodes": 1}, "user": {"instance": "m5.large", "min_nodes": 1, "max_nodes": 5}, @@ -345,23 +345,14 @@ def render_config( "hub_subtitle" ] = "Autoscaling Compute Environment on Digital Ocean" config["digital_ocean"] = DIGITAL_OCEAN - if kubernetes_version: - config["digital_ocean"]["kubernetes_version"] = kubernetes_version - else: - # first kubernetes version returned by Digital Ocean api is - # the newest version of kubernetes supported this field needs - # to be dynamically filled since digital ocean updates the - # versions so frequently - config["digital_ocean"][ - "kubernetes_version" - ] = digital_ocean.kubernetes_versions()[0]["slug"] + _set_kubernetes_version(config, kubernetes_version) + elif cloud_provider == "gcp": config["theme"]["jupyterhub"][ "hub_subtitle" ] = "Autoscaling Compute Environment on Google Cloud Platform" config["google_cloud_platform"] = GOOGLE_PLATFORM - if kubernetes_version: - config["google_cloud_platform"]["kubernetes_version"] = kubernetes_version + _set_kubernetes_version(config, kubernetes_version) if "PROJECT_ID" in os.environ: config["google_cloud_platform"]["project"] = os.environ["PROJECT_ID"] @@ -369,26 +360,21 @@ def render_config( config["google_cloud_platform"]["project"] = input( "Enter Google Cloud Platform Project ID: " ) + elif cloud_provider == "azure": config["theme"]["jupyterhub"][ "hub_subtitle" ] = "Autoscaling Compute Environment on Azure" config["azure"] = AZURE - - if kubernetes_version: - config["azure"]["kubernetes_version"] = kubernetes_version - else: - config["azure"]["kubernetes_version"] = azure_cloud.kubernetes_versions( - config["azure"]["region"] - ) + _set_kubernetes_version(config, kubernetes_version) elif cloud_provider == "aws": config["theme"]["jupyterhub"][ "hub_subtitle" ] = "Autoscaling Compute Environment on Amazon Web Services" config["amazon_web_services"] = AMAZON_WEB_SERVICES - if kubernetes_version: - config["amazon_web_services"]["kubernetes_version"] = kubernetes_version() + _set_kubernetes_version(config, kubernetes_version) + elif cloud_provider == "local": config["theme"]["jupyterhub"][ "hub_subtitle" @@ -502,3 +488,55 @@ def auth0_auto_provision(config): config["security"]["authentication"]["config"]["auth0_subdomain"] = auth0_config[ "auth0_subdomain" ] + + +def _set_kubernetes_version(config, kubernetes_version): + cloud_provider = config["provider"] + + def _raise_value_error(cloud_provider, k8s_versions): + raise ValueError( + f"\nInvalid `kubernetes-version` provided: {kubernetes_version}.\nPlease select from one of the following {cloud_provider.upper()} supported Kubernetes versions: {k8s_versions} or omit flag to use latest Kubernetes version available." + ) + + if cloud_provider == "aws": + if kubernetes_version: + aws_k8s_versions = amazon_web_services.kubernetes_versions() + if kubernetes_version in aws_k8s_versions: + config["amazon_web_services"]["kubernetes_version"] = kubernetes_version + else: + _raise_value_error(cloud_provider, aws_k8s_versions) + else: + config["amazon_web_services"][ + "kubernetes_version" + ] = amazon_web_services.kubernetes_versions(grab_latest_version=True) + + elif cloud_provider == "azure": + if kubernetes_version: + azure_k8s_versions = azure_cloud.kubernetes_versions( + config["azure"]["region"] + ) + if kubernetes_version in azure_k8s_versions: + config["azure"]["kubernetes_version"] = kubernetes_version + else: + _raise_value_error(cloud_provider, azure_k8s_versions) + + else: + config["azure"]["kubernetes_version"] = azure_cloud.kubernetes_versions( + config["azure"]["region"], grab_latest_version=True + ) + + elif cloud_provider == "do": + if kubernetes_version: + config["digital_ocean"]["kubernetes_version"] = kubernetes_version + else: + # first kubernetes version returned by Digital Ocean api is + # the newest version of kubernetes supported this field needs + # to be dynamically filled since digital ocean updates the + # versions so frequently + config["digital_ocean"][ + "kubernetes_version" + ] = digital_ocean.kubernetes_versions()[0]["slug"] + + elif cloud_provider == "gcp": + if kubernetes_version: + config["google_cloud_platform"]["kubernetes_version"] = kubernetes_version diff --git a/qhub/provider/cloud/amazon_web_services.py b/qhub/provider/cloud/amazon_web_services.py index b40538fa98..e4bb8609dc 100644 --- a/qhub/provider/cloud/amazon_web_services.py +++ b/qhub/provider/cloud/amazon_web_services.py @@ -21,7 +21,7 @@ def zones(region): @functools.lru_cache() -def kubernetes_versions(grab_latest_version=True): +def kubernetes_versions(grab_latest_version=False): # AWS SDK (boto3) currently doesn't offer an intuitive way to list available kubernetes version. This implementation grabs kubernetes versions for specific EKS addons. It will therefore always be (at the very least) a subset of all kubernetes versions still supported by AWS. client = boto3.client("eks") supported_kubernetes_versions = list() diff --git a/qhub/provider/cloud/azure_cloud.py b/qhub/provider/cloud/azure_cloud.py index a6e0df3a13..ed0417720b 100644 --- a/qhub/provider/cloud/azure_cloud.py +++ b/qhub/provider/cloud/azure_cloud.py @@ -13,7 +13,7 @@ def initiate_container_service_client(): @functools.lru_cache() -def kubernetes_versions(azure_location="Central US", grap_latest_version=True): +def kubernetes_versions(azure_location="Central US", grab_latest_version=False): client = initiate_container_service_client() azure_location = azure_location.replace(" ", "").lower() @@ -29,6 +29,7 @@ def kubernetes_versions(azure_location="Central US", grap_latest_version=True): supported_kubernetes_versions = sorted(supported_kubernetes_versions) - if grap_latest_version: + if grab_latest_version: return supported_kubernetes_versions[-1] + return supported_kubernetes_versions From 64a08dce088a967385b738e9328e7734abec6a03 Mon Sep 17 00:00:00 2001 From: iameskild Date: Wed, 20 Oct 2021 10:07:11 -0700 Subject: [PATCH 06/46] Minor update to tests --- tests/test_init.py | 2 +- tests/test_render.py | 2 +- tests/test_schema.py | 6 ++---- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/test_init.py b/tests/test_init.py index c3f06d3039..cec43e7fc6 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -24,6 +24,6 @@ def test_init(project, namespace, domain, cloud_provider, ci_provider, auth_prov repository_auto_provision=False, auth_auto_provision=False, terraform_state="remote", - kubernetes_version="1.18.0", + kubernetes_version=None, disable_prompt=True, ) diff --git a/tests/test_render.py b/tests/test_render.py index 5ee1d2872d..1fee4bc958 100644 --- a/tests/test_render.py +++ b/tests/test_render.py @@ -48,7 +48,7 @@ def init_render(request, tmp_path): repository_auto_provision=False, auth_auto_provision=False, terraform_state="remote", - kubernetes_version="1.18.0", + kubernetes_version=None, disable_prompt=True, ) yaml = YAML(typ="unsafe", pure=True) diff --git a/tests/test_schema.py b/tests/test_schema.py index 1545b79064..8aa7c10ced 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -13,9 +13,7 @@ ("azure-pytest", "dev", "azure.qhub.dev", "azure", "github-actions", "github"), ], ) -def test_schema( - project, namespace, domain, cloud_provider, ci_provider, auth_provider -): +def test_schema(project, namespace, domain, cloud_provider, ci_provider, auth_provider): config = render_config( project_name=project, namespace=namespace, @@ -27,7 +25,7 @@ def test_schema( repository_auto_provision=False, auth_auto_provision=False, terraform_state="remote", - kubernetes_version="1.18.0", + kubernetes_version=None, disable_prompt=True, ) assert qhub.schema.verify(config) is None From 8afb52cfa3f12305514c32b27e9266cf4d6ebadb Mon Sep 17 00:00:00 2001 From: iameskild Date: Wed, 20 Oct 2021 11:57:09 -0700 Subject: [PATCH 07/46] Standardize k8s version check for DO and GCP --- qhub/initialize.py | 34 ++++++++++++++++++++-------- qhub/provider/cloud/digital_ocean.py | 9 ++++++-- qhub/provider/cloud/google_cloud.py | 9 ++++++-- 3 files changed, 39 insertions(+), 13 deletions(-) diff --git a/qhub/initialize.py b/qhub/initialize.py index 5a797afd27..ec132fe1c2 100644 --- a/qhub/initialize.py +++ b/qhub/initialize.py @@ -12,7 +12,12 @@ from qhub.provider.oauth.auth0 import create_client from qhub.provider.cicd import github from qhub.provider import git -from qhub.provider.cloud import digital_ocean, azure_cloud, amazon_web_services +from qhub.provider.cloud import ( + digital_ocean, + azure_cloud, + amazon_web_services, + google_cloud, +) from qhub.utils import namestr_regex, qhub_image_tag, check_cloud_credentials from .version import __version__ @@ -125,7 +130,7 @@ GOOGLE_PLATFORM = { "project": "PLACEHOLDER", "region": "us-central1", - "kubernetes_version": "1.18.16-gke.502", + "kubernetes_version": "PLACEHOLDER", "node_groups": { "general": {"instance": "n1-standard-4", "min_nodes": 1, "max_nodes": 1}, "user": {"instance": "n1-standard-2", "min_nodes": 1, "max_nodes": 5}, @@ -531,16 +536,27 @@ def _raise_value_error(cloud_provider, k8s_versions): elif cloud_provider == "do": if kubernetes_version: - config["digital_ocean"]["kubernetes_version"] = kubernetes_version + do_k8s_versions = digital_ocean.kubernetes_versions() + if kubernetes_version in do_k8s_versions: + config["digital_ocean"]["kubernetes_version"] = kubernetes_version + else: + _raise_value_error(cloud_provider, do_k8s_versions) else: - # first kubernetes version returned by Digital Ocean api is - # the newest version of kubernetes supported this field needs - # to be dynamically filled since digital ocean updates the - # versions so frequently config["digital_ocean"][ "kubernetes_version" - ] = digital_ocean.kubernetes_versions()[0]["slug"] + ] = digital_ocean.kubernetes_versions(grab_latest_version=True) elif cloud_provider == "gcp": + region = config["google_cloud_platform"]["region"] if kubernetes_version: - config["google_cloud_platform"]["kubernetes_version"] = kubernetes_version + gcp_k8s_versions = google_cloud.kubernetes_versions(region) + if kubernetes_version in gcp_k8s_versions: + config["google_cloud_platform"][ + "kubernetes_version" + ] = kubernetes_version + else: + _raise_value_error(cloud_provider, gcp_k8s_versions) + else: + config["google_cloud_platform"][ + "kubernetes_version" + ] = google_cloud.kubernetes_versions(region, grab_latest_version=True) diff --git a/qhub/provider/cloud/digital_ocean.py b/qhub/provider/cloud/digital_ocean.py index 479ee1e46f..813bfcfa5d 100644 --- a/qhub/provider/cloud/digital_ocean.py +++ b/qhub/provider/cloud/digital_ocean.py @@ -39,5 +39,10 @@ def regions(): return _kubernetes_options()["options"]["regions"] -def kubernetes_versions(): - return _kubernetes_options()["options"]["versions"] +def kubernetes_versions(grab_latest_version=False): + supported_kubernetes_versions = sorted( + [_["slug"] for _ in _kubernetes_options()["options"]["versions"]] + ) + if grab_latest_version: + return supported_kubernetes_versions[-1] + return supported_kubernetes_versions diff --git a/qhub/provider/cloud/google_cloud.py b/qhub/provider/cloud/google_cloud.py index 728f899238..c9f5368d75 100644 --- a/qhub/provider/cloud/google_cloud.py +++ b/qhub/provider/cloud/google_cloud.py @@ -29,7 +29,7 @@ def zones(project, region): @functools.lru_cache() -def kubernetes_versions(region): +def kubernetes_versions(region, grab_latest_version=False): output = subprocess.check_output( [ "gcloud", @@ -41,7 +41,12 @@ def kubernetes_versions(region): ] ) data = json.loads(output.decode("utf-8")) - return {_: _ for _ in data["validMasterVersions"]} + supported_kubernetes_version = sorted([_ for _ in data["validMasterVersions"]]) + + if grab_latest_version: + return supported_kubernetes_version[-1] + + return supported_kubernetes_version @functools.lru_cache() From 4e6a21f6ca7540f30570c5d60a471cbd8241fa40 Mon Sep 17 00:00:00 2001 From: iameskild Date: Wed, 20 Oct 2021 12:09:58 -0700 Subject: [PATCH 08/46] Update yaml call based on deprecation warning --- qhub/render/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qhub/render/__init__.py b/qhub/render/__init__.py index fc1e264bd7..b27e67112f 100644 --- a/qhub/render/__init__.py +++ b/qhub/render/__init__.py @@ -5,7 +5,7 @@ import os from shutil import rmtree -from ruamel import yaml +from ruamel.yaml import YAML from cookiecutter.generate import generate_files from ..version import __version__ from ..constants import TERRAFORM_VERSION @@ -115,7 +115,8 @@ def render_template(output_directory, config_filename, force=False): raise ValueError(f"cookiecutter configuration={filename} is not filename") with filename.open() as f: - config = yaml.safe_load(f) + yaml = YAML(typ="safe", pure=True) + config = yaml.load(f) # For any config values that start with # QHUB_SECRET_, set the values using the From 64767c88c2480b33d87fe42153a7de93cfba6062 Mon Sep 17 00:00:00 2001 From: iameskild Date: Thu, 21 Oct 2021 15:48:34 -0700 Subject: [PATCH 09/46] Add setup_fixture to conftest.py --- tests/conftest.py | 93 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 tests/conftest.py diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000000..0421973f2a --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,93 @@ +from functools import partial +from unittest.mock import Mock + +import pytest + +from qhub.initialize import render_config + + +INIT_INPUTS = [ + # project, namespace, domain, cloud_provider, ci_provider, auth_provider + ("do-pytest", "dev", "do.qhub.dev", "do", "github-actions", "github"), + ("aws-pytest", "dev", "aws.qhub.dev", "aws", "github-actions", "github"), + ("gcp-pytest", "dev", "gcp.qhub.dev", "gcp", "github-actions", "github"), + ("azure-pytest", "dev", "azure.qhub.dev", "azure", "github-actions", "github"), +] + +QHUB_CONFIG_FN = "qhub-config.yaml" +PRESERVED_DIR = "preserved_dir" +DEFAULT_GH_REPO = "github.com/test/test" +DEFAULT_TERRAFORM_STATE = "remote" + + +# use this partial function for all tests that need to call `render_config` +render_config_partial = partial( + render_config, + repository=DEFAULT_GH_REPO, + repository_auto_provision=False, + auth_auto_provision=False, + terraform_state=DEFAULT_TERRAFORM_STATE, + disable_prompt=True, +) + + +@pytest.fixture(params=INIT_INPUTS) +def setup_fixture(request, monkeypatch, tmp_path): + """This fixture helps simplify writing tests by: + - parametrizing the different cloud provider inputs in a single place + - creating a tmp directory (and file) for the `qhub-config.yaml` to be save to + - monkeypatching functions that call out to external APIs + """ + render_config_inputs = request.param + ( + project, + namespace, + domain, + cloud_provider, + ci_provider, + auth_provider, + ) = render_config_inputs + + def _mock_kubernetes_versions(grab_latest_version=False): + # template for all `kubernetes_versions` calls + # monkeypatched to avoid making outbound API calls in CI + k8s_versions = ["1.18", "1.19", "1.20"] + m = Mock() + m.return_value = k8s_versions + if grab_latest_version: + m.return_value = k8s_versions[-1] + return m + + if cloud_provider == "aws": + monkeypatch.setattr( + "qhub.initialize.amazon_web_services.kubernetes_versions", + _mock_kubernetes_versions(), + ) + elif cloud_provider == "azure": + monkeypatch.setattr( + "qhub.initialize.azure_cloud.kubernetes_versions", + _mock_kubernetes_versions(), + ) + elif cloud_provider == "do": + monkeypatch.setattr( + "qhub.initialize.digital_ocean.kubernetes_versions", + _mock_kubernetes_versions(), + ) + elif cloud_provider == "gcp": + monkeypatch.setattr( + "qhub.initialize.google_cloud.kubernetes_versions", + _mock_kubernetes_versions(), + ) + + output_directory = tmp_path / f"{cloud_provider}_output_dir" + output_directory.mkdir() + qhub_config_loc = output_directory / QHUB_CONFIG_FN + + # data that should NOT be deleted when `qhub render` is called + # see test_render.py::test_remove_existing_renders + preserved_directory = output_directory / PRESERVED_DIR + preserved_directory.mkdir() + preserved_filename = preserved_directory / "file.txt" + preserved_filename.write_text("This is a test...") + + yield (qhub_config_loc, render_config_inputs) From 59e7da73cc816bad3ff871899437f260275f3ba4 Mon Sep 17 00:00:00 2001 From: iameskild Date: Thu, 21 Oct 2021 15:49:55 -0700 Subject: [PATCH 10/46] Use copy of config dicts --- qhub/initialize.py | 120 +++++++++++++++++++++------------------------ 1 file changed, 55 insertions(+), 65 deletions(-) diff --git a/qhub/initialize.py b/qhub/initialize.py index ec132fe1c2..4321fe1dee 100644 --- a/qhub/initialize.py +++ b/qhub/initialize.py @@ -266,7 +266,7 @@ def render_config( kubernetes_version=None, disable_prompt=False, ): - config = BASE_CONFIGURATION + config = BASE_CONFIGURATION.copy() config["provider"] = cloud_provider if ci_provider is not None and ci_provider != "none": @@ -306,7 +306,7 @@ def render_config( oauth_callback_url = f"https://{qhub_domain}/hub/oauth_callback" if auth_provider == "github": - config["security"]["authentication"] = AUTH_OAUTH_GITHUB + config["security"]["authentication"] = AUTH_OAUTH_GITHUB.copy() print( "Visit https://github.com/settings/developers and create oauth application" ) @@ -323,12 +323,12 @@ def render_config( "oauth_callback_url" ] = oauth_callback_url elif auth_provider == "auth0": - config["security"]["authentication"] = AUTH_OAUTH_AUTH0 + config["security"]["authentication"] = AUTH_OAUTH_AUTH0.copy() config["security"]["authentication"]["config"][ "oauth_callback_url" ] = oauth_callback_url elif auth_provider == "password": - config["security"]["authentication"] = AUTH_PASSWORD + config["security"]["authentication"] = AUTH_PASSWORD.copy() # Generate default password for qhub-init default_password = "".join( @@ -353,15 +353,15 @@ def render_config( config["theme"]["jupyterhub"][ "hub_subtitle" ] = "Autoscaling Compute Environment on Digital Ocean" - config["digital_ocean"] = DIGITAL_OCEAN - _set_kubernetes_version(config, kubernetes_version) + config["digital_ocean"] = DIGITAL_OCEAN.copy() + _set_kubernetes_version(config, kubernetes_version, cloud_provider) elif cloud_provider == "gcp": config["theme"]["jupyterhub"][ "hub_subtitle" ] = "Autoscaling Compute Environment on Google Cloud Platform" - config["google_cloud_platform"] = GOOGLE_PLATFORM - _set_kubernetes_version(config, kubernetes_version) + config["google_cloud_platform"] = GOOGLE_PLATFORM.copy() + _set_kubernetes_version(config, kubernetes_version, cloud_provider) if "PROJECT_ID" in os.environ: config["google_cloud_platform"]["project"] = os.environ["PROJECT_ID"] @@ -374,24 +374,24 @@ def render_config( config["theme"]["jupyterhub"][ "hub_subtitle" ] = "Autoscaling Compute Environment on Azure" - config["azure"] = AZURE - _set_kubernetes_version(config, kubernetes_version) + config["azure"] = AZURE.copy() + _set_kubernetes_version(config, kubernetes_version, cloud_provider) elif cloud_provider == "aws": config["theme"]["jupyterhub"][ "hub_subtitle" ] = "Autoscaling Compute Environment on Amazon Web Services" - config["amazon_web_services"] = AMAZON_WEB_SERVICES - _set_kubernetes_version(config, kubernetes_version) + config["amazon_web_services"] = AMAZON_WEB_SERVICES.copy() + _set_kubernetes_version(config, kubernetes_version, cloud_provider) elif cloud_provider == "local": config["theme"]["jupyterhub"][ "hub_subtitle" ] = "Autoscaling Compute Environment" - config["local"] = LOCAL + config["local"] = LOCAL.copy() - config["profiles"] = DEFAULT_PROFILES - config["environments"] = DEFAULT_ENVIRONMENTS + config["profiles"] = DEFAULT_PROFILES.copy() + config["environments"] = DEFAULT_ENVIRONMENTS.copy() if auth_auto_provision: if auth_provider == "auth0": @@ -499,64 +499,54 @@ def auth0_auto_provision(config): ] -def _set_kubernetes_version(config, kubernetes_version): - cloud_provider = config["provider"] +def _set_kubernetes_version( + config, kubernetes_version, cloud_provider, grab_latest_version=True +): + cloud_provider_dict = { + "aws": { + "full_name": "amazon_web_services", + "k8s_version_checker_func": amazon_web_services.kubernetes_versions, + }, + "azure": { + "full_name": "azure", + "k8s_version_checker_func": azure_cloud.kubernetes_versions, + }, + "do": { + "full_name": "digital_ocean", + "k8s_version_checker_func": digital_ocean.kubernetes_versions, + }, + "gcp": { + "full_name": "google_cloud_platform", + "k8s_version_checker_func": google_cloud.kubernetes_versions, + }, + } + cloud_full_name = cloud_provider_dict[cloud_provider]["full_name"] + func = cloud_provider_dict[cloud_provider]["k8s_version_checker_func"] + cloud_config = config[cloud_full_name] def _raise_value_error(cloud_provider, k8s_versions): raise ValueError( f"\nInvalid `kubernetes-version` provided: {kubernetes_version}.\nPlease select from one of the following {cloud_provider.upper()} supported Kubernetes versions: {k8s_versions} or omit flag to use latest Kubernetes version available." ) - if cloud_provider == "aws": - if kubernetes_version: - aws_k8s_versions = amazon_web_services.kubernetes_versions() - if kubernetes_version in aws_k8s_versions: - config["amazon_web_services"]["kubernetes_version"] = kubernetes_version - else: - _raise_value_error(cloud_provider, aws_k8s_versions) - else: - config["amazon_web_services"][ - "kubernetes_version" - ] = amazon_web_services.kubernetes_versions(grab_latest_version=True) + def _check_and_set_kubernetes_version( + kubernetes_version=kubernetes_version, + cloud_provider=cloud_provider, + cloud_config=cloud_config, + func=func, + ): + region = cloud_config["region"] + k8s_versions = func(region) - elif cloud_provider == "azure": if kubernetes_version: - azure_k8s_versions = azure_cloud.kubernetes_versions( - config["azure"]["region"] - ) - if kubernetes_version in azure_k8s_versions: - config["azure"]["kubernetes_version"] = kubernetes_version + if kubernetes_version in k8s_versions: + cloud_config["kubernetes_version"] = kubernetes_version else: - _raise_value_error(cloud_provider, azure_k8s_versions) - - else: - config["azure"]["kubernetes_version"] = azure_cloud.kubernetes_versions( - config["azure"]["region"], grab_latest_version=True - ) - - elif cloud_provider == "do": - if kubernetes_version: - do_k8s_versions = digital_ocean.kubernetes_versions() - if kubernetes_version in do_k8s_versions: - config["digital_ocean"]["kubernetes_version"] = kubernetes_version - else: - _raise_value_error(cloud_provider, do_k8s_versions) + _raise_value_error(cloud_provider, k8s_versions) + elif grab_latest_version: + cloud_config["kubernetes_version"] = k8s_versions[-1] else: - config["digital_ocean"][ - "kubernetes_version" - ] = digital_ocean.kubernetes_versions(grab_latest_version=True) + # grab oldest version + cloud_config["kubernetes_version"] = k8s_versions[0] - elif cloud_provider == "gcp": - region = config["google_cloud_platform"]["region"] - if kubernetes_version: - gcp_k8s_versions = google_cloud.kubernetes_versions(region) - if kubernetes_version in gcp_k8s_versions: - config["google_cloud_platform"][ - "kubernetes_version" - ] = kubernetes_version - else: - _raise_value_error(cloud_provider, gcp_k8s_versions) - else: - config["google_cloud_platform"][ - "kubernetes_version" - ] = google_cloud.kubernetes_versions(region, grab_latest_version=True) + return _check_and_set_kubernetes_version() From 2350d2109d8050194925fd8df55f28432a628b00 Mon Sep 17 00:00:00 2001 From: iameskild Date: Thu, 21 Oct 2021 15:50:46 -0700 Subject: [PATCH 11/46] Standardize kubernetes_versions functions for all providers --- qhub/provider/cloud/amazon_web_services.py | 13 +++++++------ qhub/provider/cloud/azure_cloud.py | 8 +++----- qhub/provider/cloud/digital_ocean.py | 8 +++++--- qhub/provider/cloud/google_cloud.py | 7 +++---- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/qhub/provider/cloud/amazon_web_services.py b/qhub/provider/cloud/amazon_web_services.py index e4bb8609dc..cc9fee6548 100644 --- a/qhub/provider/cloud/amazon_web_services.py +++ b/qhub/provider/cloud/amazon_web_services.py @@ -1,6 +1,8 @@ import json import subprocess import functools +import os + import boto3 @@ -21,8 +23,12 @@ def zones(region): @functools.lru_cache() -def kubernetes_versions(grab_latest_version=False): +def kubernetes_versions(region="us-west-2"): + """Return list of available kubernetes supported by cloud provider. Sorted from oldest to latest.""" + # AWS SDK (boto3) currently doesn't offer an intuitive way to list available kubernetes version. This implementation grabs kubernetes versions for specific EKS addons. It will therefore always be (at the very least) a subset of all kubernetes versions still supported by AWS. + if not os.getenv("AWS_DEFAULT_REGION"): + os.environ["AWS_DEFAULT_REGION"] = region client = boto3.client("eks") supported_kubernetes_versions = list() available_addons = client.describe_addon_versions() @@ -33,13 +39,8 @@ def kubernetes_versions(grab_latest_version=False): k8sversion.get("clusterVersion", None) ) - # remove duplicate values - # then sort from oldest to newest available versions supported_kubernetes_versions = sorted(list(set(supported_kubernetes_versions))) - if grab_latest_version: - return supported_kubernetes_versions[-1] - return supported_kubernetes_versions diff --git a/qhub/provider/cloud/azure_cloud.py b/qhub/provider/cloud/azure_cloud.py index ed0417720b..ac99c63835 100644 --- a/qhub/provider/cloud/azure_cloud.py +++ b/qhub/provider/cloud/azure_cloud.py @@ -13,10 +13,11 @@ def initiate_container_service_client(): @functools.lru_cache() -def kubernetes_versions(azure_location="Central US", grab_latest_version=False): +def kubernetes_versions(region="Central US"): + """Return list of available kubernetes supported by cloud provider. Sorted from oldest to latest.""" client = initiate_container_service_client() - azure_location = azure_location.replace(" ", "").lower() + azure_location = region.replace(" ", "").lower() k8s_versions_list = client.container_services.list_orchestrators( azure_location, resource_type="managedClusters" @@ -29,7 +30,4 @@ def kubernetes_versions(azure_location="Central US", grab_latest_version=False): supported_kubernetes_versions = sorted(supported_kubernetes_versions) - if grab_latest_version: - return supported_kubernetes_versions[-1] - return supported_kubernetes_versions diff --git a/qhub/provider/cloud/digital_ocean.py b/qhub/provider/cloud/digital_ocean.py index 813bfcfa5d..2aee62447d 100644 --- a/qhub/provider/cloud/digital_ocean.py +++ b/qhub/provider/cloud/digital_ocean.py @@ -39,10 +39,12 @@ def regions(): return _kubernetes_options()["options"]["regions"] -def kubernetes_versions(grab_latest_version=False): +# keep `region` parameter - see `qhub.initialize._set_kubernetes_version` +def kubernetes_versions(region=None): + """Return list of available kubernetes supported by cloud provider. Sorted from oldest to latest.""" + supported_kubernetes_versions = sorted( [_["slug"] for _ in _kubernetes_options()["options"]["versions"]] ) - if grab_latest_version: - return supported_kubernetes_versions[-1] + return supported_kubernetes_versions diff --git a/qhub/provider/cloud/google_cloud.py b/qhub/provider/cloud/google_cloud.py index c9f5368d75..fc32e31c79 100644 --- a/qhub/provider/cloud/google_cloud.py +++ b/qhub/provider/cloud/google_cloud.py @@ -29,7 +29,9 @@ def zones(project, region): @functools.lru_cache() -def kubernetes_versions(region, grab_latest_version=False): +def kubernetes_versions(region): + """Return list of available kubernetes supported by cloud provider. Sorted from oldest to latest.""" + output = subprocess.check_output( [ "gcloud", @@ -43,9 +45,6 @@ def kubernetes_versions(region, grab_latest_version=False): data = json.loads(output.decode("utf-8")) supported_kubernetes_version = sorted([_ for _ in data["validMasterVersions"]]) - if grab_latest_version: - return supported_kubernetes_version[-1] - return supported_kubernetes_version From 570e00e16bf167ca01e1194efb097049fd52e96e Mon Sep 17 00:00:00 2001 From: iameskild Date: Thu, 21 Oct 2021 15:52:04 -0700 Subject: [PATCH 12/46] Update tests to use new setup_fixture --- tests/test_init.py | 60 +++++++++++++++++++++++++--------------- tests/test_render.py | 66 ++++++++++++-------------------------------- tests/test_schema.py | 32 +++++++++------------ 3 files changed, 67 insertions(+), 91 deletions(-) diff --git a/tests/test_init.py b/tests/test_init.py index cec43e7fc6..cab143554f 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -1,29 +1,43 @@ import pytest -from qhub.initialize import render_config +from .conftest import render_config_partial @pytest.mark.parametrize( - "project, namespace, domain, cloud_provider, ci_provider, auth_provider", - [ - ("do-pytest", "dev", "do.qhub.dev", "do", "github-actions", "github"), - ("aws-pytest", "dev", "aws.qhub.dev", "aws", "github-actions", "github"), - ("gcp-pytest", "dev", "gcp.qhub.dev", "gcp", "github-actions", "github"), - ("azure-pytest", "dev", "azure.qhub.dev", "azure", "github-actions", "github"), - ], + "k8s_version, expected", [(None, True), ("1.19", True), (1000, ValueError)] ) -def test_init(project, namespace, domain, cloud_provider, ci_provider, auth_provider): - render_config( - project_name=project, - namespace=namespace, - qhub_domain=domain, - cloud_provider=cloud_provider, - ci_provider=ci_provider, - repository="github.com/test/test", - auth_provider=auth_provider, - repository_auto_provision=False, - auth_auto_provision=False, - terraform_state="remote", - kubernetes_version=None, - disable_prompt=True, - ) +def test_init(setup_fixture, k8s_version, expected): + (qhub_config_loc, render_config_inputs) = setup_fixture + ( + project, + namespace, + domain, + cloud_provider, + ci_provider, + auth_provider, + ) = render_config_inputs + + if expected: + render_config_partial( + project_name=project, + namespace=namespace, + qhub_domain=domain, + cloud_provider=cloud_provider, + ci_provider=ci_provider, + auth_provider=auth_provider, + kubernetes_version=k8s_version, + ) + + # pass "unsupported" kubernetes version to `render_config` + # resulting in a `ValueError` + elif type(expected) == type and issubclass(expected, Exception): + with pytest.raises(expected): + render_config_partial( + project_name=project, + namespace=namespace, + qhub_domain=domain, + cloud_provider=cloud_provider, + ci_provider=ci_provider, + auth_provider=auth_provider, + kubernetes_version=k8s_version, + ) diff --git a/tests/test_render.py b/tests/test_render.py index 1fee4bc958..6afa103790 100644 --- a/tests/test_render.py +++ b/tests/test_render.py @@ -3,21 +3,12 @@ from ruamel.yaml import YAML from qhub.render import render_template, set_env_vars_in_config, remove_existing_renders -from qhub.initialize import render_config - -INIT_INPUTS = [ - # project, namespace, domain, cloud_provider, ci_provider, auth_provider - ("do-pytest", "dev", "do.qhub.dev", "do", "github-actions", "github"), - ("aws-pytest", "dev", "aws.qhub.dev", "aws", "github-actions", "github"), - ("gcp-pytest", "dev", "gcp.qhub.dev", "gcp", "github-actions", "github"), - ("azure-pytest", "dev", "azure.qhub.dev", "azure", "github-actions", "github"), -] -QHUB_CONFIG_FN = "qhub-config.yaml" -PRESERVED_DIR = "preserved_dir" +from .conftest import render_config_partial, PRESERVED_DIR @pytest.fixture -def init_render(request, tmp_path): +def write_qhub_config_to_file(setup_fixture): + qhub_config_loc, render_config_inputs = setup_fixture ( project, namespace, @@ -25,38 +16,25 @@ def init_render(request, tmp_path): cloud_provider, ci_provider, auth_provider, - ) = request.param - - output_directory = tmp_path / f"{cloud_provider}_output_dir" - output_directory.mkdir() - qhub_config = output_directory / QHUB_CONFIG_FN + ) = render_config_inputs - # data that should NOT be deleted when `qhub render` is called - preserved_directory = output_directory / PRESERVED_DIR - preserved_directory.mkdir() - preserved_filename = preserved_directory / "file.txt" - preserved_filename.write_text("This is a test...") - - config = render_config( + config = render_config_partial( project_name=project, namespace=namespace, qhub_domain=domain, cloud_provider=cloud_provider, ci_provider=ci_provider, - repository="github.com/test/test", auth_provider=auth_provider, - repository_auto_provision=False, - auth_auto_provision=False, - terraform_state="remote", kubernetes_version=None, - disable_prompt=True, ) + + # write to qhub_config.yaml yaml = YAML(typ="unsafe", pure=True) - yaml.dump(config, qhub_config) + yaml.dump(config, qhub_config_loc) - render_template(str(output_directory), qhub_config, force=True) + render_template(str(qhub_config_loc.parent), qhub_config_loc, force=True) - yield (output_directory, request) + yield setup_fixture def test_get_secret_config_entries(monkeypatch): @@ -91,13 +69,8 @@ def test_get_secret_config_entries(monkeypatch): assert config == expected -@pytest.mark.parametrize( - "init_render", - INIT_INPUTS, - indirect=True, -) -def test_render_template(init_render): - output_directory, request = init_render +def test_render_template(write_qhub_config_to_file): + qhub_config_loc, render_config_inputs = write_qhub_config_to_file ( project, namespace, @@ -105,11 +78,10 @@ def test_render_template(init_render): cloud_provider, ci_provider, auth_provider, - ) = request.param - qhub_config = output_directory / QHUB_CONFIG_FN + ) = render_config_inputs yaml = YAML() - qhub_config_json = yaml.load(qhub_config.read_text()) + qhub_config_json = yaml.load(qhub_config_loc.read_text()) assert qhub_config_json["project_name"] == project assert qhub_config_json["namespace"] == namespace @@ -117,13 +89,9 @@ def test_render_template(init_render): assert qhub_config_json["provider"] == cloud_provider -@pytest.mark.parametrize( - "init_render", - INIT_INPUTS, - indirect=True, -) -def test_remove_existing_renders(init_render): - output_directory, request = init_render +def test_remove_existing_renders(write_qhub_config_to_file): + qhub_config_loc, _ = write_qhub_config_to_file + output_directory = qhub_config_loc.parent dirs = [_.name for _ in output_directory.iterdir()] preserved_files = [_ for _ in (output_directory / PRESERVED_DIR).iterdir()] diff --git a/tests/test_schema.py b/tests/test_schema.py index 8aa7c10ced..ca44008a51 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -1,31 +1,25 @@ -import pytest - import qhub.schema -from qhub.initialize import render_config +from .conftest import render_config_partial + +def test_schema(setup_fixture): + (qhub_config_loc, render_config_inputs) = setup_fixture + ( + project, + namespace, + domain, + cloud_provider, + ci_provider, + auth_provider, + ) = render_config_inputs -@pytest.mark.parametrize( - "project, namespace, domain, cloud_provider, ci_provider, auth_provider", - [ - ("do-pytest", "dev", "do.qhub.dev", "do", "github-actions", "github"), - ("aws-pytest", "dev", "aws.qhub.dev", "aws", "github-actions", "github"), - ("gcp-pytest", "dev", "gcp.qhub.dev", "gcp", "github-actions", "github"), - ("azure-pytest", "dev", "azure.qhub.dev", "azure", "github-actions", "github"), - ], -) -def test_schema(project, namespace, domain, cloud_provider, ci_provider, auth_provider): - config = render_config( + config = render_config_partial( project_name=project, namespace=namespace, qhub_domain=domain, cloud_provider=cloud_provider, ci_provider=ci_provider, - repository="github.com/test/test", auth_provider=auth_provider, - repository_auto_provision=False, - auth_auto_provision=False, - terraform_state="remote", kubernetes_version=None, - disable_prompt=True, ) assert qhub.schema.verify(config) is None From 2ba2c8fd432f55c11f2a0a1692f44cac1f41777a Mon Sep 17 00:00:00 2001 From: iameskild Date: Thu, 21 Oct 2021 15:56:35 -0700 Subject: [PATCH 13/46] fix failing test --- tests/test_init.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/tests/test_init.py b/tests/test_init.py index cab143554f..da1146361e 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -17,20 +17,9 @@ def test_init(setup_fixture, k8s_version, expected): auth_provider, ) = render_config_inputs - if expected: - render_config_partial( - project_name=project, - namespace=namespace, - qhub_domain=domain, - cloud_provider=cloud_provider, - ci_provider=ci_provider, - auth_provider=auth_provider, - kubernetes_version=k8s_version, - ) - # pass "unsupported" kubernetes version to `render_config` # resulting in a `ValueError` - elif type(expected) == type and issubclass(expected, Exception): + if type(expected) == type and issubclass(expected, Exception): with pytest.raises(expected): render_config_partial( project_name=project, @@ -41,3 +30,13 @@ def test_init(setup_fixture, k8s_version, expected): auth_provider=auth_provider, kubernetes_version=k8s_version, ) + else: + render_config_partial( + project_name=project, + namespace=namespace, + qhub_domain=domain, + cloud_provider=cloud_provider, + ci_provider=ci_provider, + auth_provider=auth_provider, + kubernetes_version=k8s_version, + ) From df2feaef2f758303b428dc647db5c6b56d7f6eeb Mon Sep 17 00:00:00 2001 From: iameskild Date: Thu, 21 Oct 2021 16:54:51 -0700 Subject: [PATCH 14/46] Update docs to reflect changes --- docs/source/installation/configuration.md | 24 ++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/docs/source/installation/configuration.md b/docs/source/installation/configuration.md index 9cdf3c43be..f8be6cc322 100644 --- a/docs/source/installation/configuration.md +++ b/docs/source/installation/configuration.md @@ -338,6 +338,23 @@ and **Kubernetes versions** will be DIFFERENT. [duplicated info] ### Providers +To take advantage of the auto-scaling and dask-distributed computing capabilities, +QHub can be deployed on a handful of the most commonly used cloud providers. QHub +takes advantage of many of the resources these cloud providers have to offer, however, +at it's core, is the Kubernetes engine (or service). Each cloud provider has slightly +different ways Kubernetes is configured but fear not, all of this is handled by QHub. + +Listed below are the cloud providers QHub currently supports. + +> NOTE: Many of the cloud providers regularly update their internal Kubernetes +> versions so if you wish to specify a particular version, please check the following +> resources. This is *completely optional* as QHub will, by default, select the most +> recent version available for your choosen cloud provider. +> [Digital Ocean](https://docs.digitalocean.com/products/kubernetes/changelog/) +> [Google Cloud Platform](https://cloud.google.com/kubernetes-engine/docs/release-notes-stable) +> [Amazon Web Services](https://docs.aws.amazon.com/eks/latest/userguide/kubernetes-versions.html) +> [Microsoft Azure](https://docs.microsoft.com/en-us/azure/aks/supported-kubernetes-versions?tabs=azure-cli) + #### DigitalOcean DigitalOcean has a restriction with autoscaling in that the minimum @@ -347,13 +364,6 @@ Digital Ocean does not have accelerator/gpu support. Digital Ocean is a great default choice for tying out QHub. Below is the recommended setup. -> Note: DigitalOcean regularly updates Kubernetes versions hence, the -> field `kubernetes_version` will most likely have to be changed. -> [See available instance types for -> DigitalOcean](https://www.digitalocean.com/docs/droplets/). If you -> used `qhub init` this version will automatically be compute for you -> Do not copy the version you see bellow - To see available instance types refer to [Digital Ocean Instance Types](https://www.digitalocean.com/docs/droplets/). Additionally the Digital Ocean cli `doctl` has [support for listing From afd442ca28fa73f5051bd20b96da4e87563e95ad Mon Sep 17 00:00:00 2001 From: iameskild Date: Thu, 21 Oct 2021 19:45:16 -0700 Subject: [PATCH 15/46] Update usage.md based on linter --- docs/source/installation/usage.md | 70 ++++++++++++++++--------------- 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/docs/source/installation/usage.md b/docs/source/installation/usage.md index d0a2383cee..c321aa3acf 100644 --- a/docs/source/installation/usage.md +++ b/docs/source/installation/usage.md @@ -2,15 +2,16 @@ ## Cloud Deployment -Once all environment variables have been set, you will be able to run -commands on your terminal to set QHub's deployment. +Great, you've gone through the `qhub` [Installation](installation.md) and [Setup Initialization](setup.md) steps, +and have ensured that all the necessary environment variables have been properly set, it is time to deploy QHub +from your terminal. ### Initialize configuration There are several ways to generate your configuration file. You can -type your commands according to the terminal prompts, or you can set +type the commands when prompted by terminal, or you can set it all automatically from the start. In any case, we advise you to -start by creating a new project folder. Here, we will name the new +start by creating a new project folder. Here, let's name the new folder `qhub-test`. On your terminal run: @@ -21,38 +22,40 @@ mkdir qhub-test && cd qhub-test #### Fully automated deployment -To generate a fully automated deployment, on your terminal run: +To generate a fully automated configuration file, on your terminal run: ```shell qhub init aws --project projectname --domain qhub.dev --ci-provider github-actions --auth-provider auth0 --auth-auto-provision --repository github.com/quansight/project-name --repository-auto-provision ``` - -The command above will generate the `qhub-config.yaml` config file -with an infrastructure deployed on `aws`, named `projectname`, where -the domain will be `qhub.dev`. The deployment -will use `github-actions` as the continuous integration (CI) provider, -automatically provisioned and authenticated by `auth0`, initialized on -GitHub under the URL `github.com/quansight/projectname`. - There are several **optional** (yet highly recommended) flags that allow to configure the deployment: -- `aws` indicates that the project will be deployed on the Amazon AWS Cloud provider. - + Optional flags are: `gcp`, `do` and `azure`. +- `aws` indicates the QHub is going to be deployed on the Amazon AWS Cloud provider. + + Other optional flags include: `gcp`, `do` and `azure`. - `--project`: the name of the project is required to be a string compliant with the Cloud provider recommendations. For - more details see official Cloud provider docs on naming policies and check our docs on [naming convention](#project-naming-convention). + more details see official Cloud provider docs on naming policies and see below on the [project naming convention](#project-naming-convention). - `--domain`: base domain for your cluster. This pattern is also applicable if you are setting your own DNS through a different provider. - + `jupyter.qhub.dev` is the domain registered on CloudFlare. In case you chose not to use Cloudflare, skip this flag. + + `qhub.dev` is the domain registered on CloudFlare. If you chose not to use Cloudflare, skip this flag. - `--ci-provider`: specifies what provider to use for CI/CD. Currently, supports GitHub Actions, GitLab CI, or none. -- `--auth-provider`: This will set configuration file to use the specified provider for authentication. -- `--auth-auto-provision`: This will automatically create and configure an application using OAuth. -- `--repository`: Repository name that will be used to store the Infrastructure-as-Code on GitHub. +- `--auth-provider`: Used to specified authentication provider, in this case Auth0 +- `--auth-auto-provision`: Whether or not to automatically create and configure the authentication provider. +- `--repository`: Repository name used to store the Infrastructure-as-Code on GitHub. - `--repository-auto-provision`: Sets the secrets for the GitHub repository used for CI/CD actions. +The command above generates the `qhub-config.yaml` config file +with an infrastructure deployed on `aws`, named `projectname`, with a +domain name set to `qhub.dev`. The deployment uses `github-actions` as +the continuous integration (CI) provider, +automatically provisioned and authenticated by `auth0`, initialized on +GitHub under the URL `github.com/quansight/projectname`. + +If employing an infrastructure-as-code approach, this is where you would make the desired infrastructure changes +including adding users, changing Dask worker instance type and much more. Once you're happy with your changes you would redeploy those changes using using GitHub Actions. For more details on on the `qhub-config.yaml` please see [Configuration](configuration.md) + ##### Project Naming Convention In order to successfully deploy QHub, there are some project naming conventions which need to be followed. For starters, -make sure your name is compatible with the specific one for your chosen Cloud provider. In addition, QHub `projectname` +make sure your project name is compatible with the specifics of your chosen Cloud provider. In addition, QHub `projectname` should also obey to the following format requirements: + letters from A to Z (upper and lower case) and numbers; + Special characters are **NOT** allowed; @@ -63,13 +66,13 @@ should also obey to the following format requirements: ### Deploy QHub -Finally, we can deploy QHub with: +Finally, with the `qhub-config.yaml` created, we can deploy QHub for the first time: ```shell qhub deploy -c qhub-config.yaml --dns-provider cloudflare --dns-auto-provision ``` -This will create the following folder structure: +This creates the following folder structure: ``` . @@ -82,13 +85,12 @@ This will create the following folder structure: └── terraform-state # required by terraform to securely store the state of the deployment ``` -The terminal will prompt to press `[enter]` to check auth credentials -(which were added by the `qhub init` command). That will trigger the -deployment which will take around 10 minutes to complete. +The terminal then prompts you to press `[enter]` to check auth credentials +(which were added by the `qhub init` command). This triggers the +deployment, which takes around 10 minutes to complete. -Part of the output will show an "ip" address (DigitalOcean or GCP), or -a CNAME "hostname" (for AWS) according to the Cloud service -provider. Such as: +During the initial deployment, Digital Ocean, GCP and Azure are going to display an `"ip"` address +whereas AWS is going to display a CNAME `"hostname"`. + Digital Ocean/Google Cloud Platform ```shell @@ -126,17 +128,17 @@ Push the changes to the repository (your primary branch may be called git push origin main ``` -Once the files are in GitHub, all CI/CD changes will be triggered by -commits to main, and deployed via GitHub actions. Since the +Once pushed to GitHub, future CI/CD changes are triggered by +commits to main, and deployed via GitHub Actions. Since the infrastructure state is reflected in the repository, this workflow allows for team members to submit pull requests that can be reviewed before modifying the infrastructure, easing the maintenance process. -To automatically deploy: +To automatically deploy (and to keep track of changes more effectively): - make changes to the `qhub-config.yaml` file on a new branch. - create a pull request (PR) to main. -- Trigger the deployment by merging the PR. All changes will be - automatically applied to the new QHub instance. +- Trigger the deployment by merging the PR and changes are + automatically applied to the QHub cluster. Congratulations, you have now completed your QHub cloud deployment! From 2b907e8e13c2f5bf391bfb1d753749fd3cfdfc60 Mon Sep 17 00:00:00 2001 From: iameskild Date: Thu, 21 Oct 2021 20:11:40 -0700 Subject: [PATCH 16/46] Update usage.md based on linter --- docs/source/installation/usage.md | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/docs/source/installation/usage.md b/docs/source/installation/usage.md index c321aa3acf..dccf1fb654 100644 --- a/docs/source/installation/usage.md +++ b/docs/source/installation/usage.md @@ -3,16 +3,15 @@ ## Cloud Deployment Great, you've gone through the `qhub` [Installation](installation.md) and [Setup Initialization](setup.md) steps, -and have ensured that all the necessary environment variables have been properly set, it is time to deploy QHub +and have ensured that all the necessary environment variables have been properly set, it's time to deploy QHub from your terminal. ### Initialize configuration There are several ways to generate your configuration file. You can type the commands when prompted by terminal, or you can set -it all automatically from the start. In any case, we advise you to -start by creating a new project folder. Here, let's name the new -folder `qhub-test`. +it all automatically from the start. In any case, start by creating +a new project folder. Start by creating a directory `qhub-test`. On your terminal run: @@ -54,8 +53,8 @@ If employing an infrastructure-as-code approach, this is where you would make th including adding users, changing Dask worker instance type and much more. Once you're happy with your changes you would redeploy those changes using using GitHub Actions. For more details on on the `qhub-config.yaml` please see [Configuration](configuration.md) ##### Project Naming Convention -In order to successfully deploy QHub, there are some project naming conventions which need to be followed. For starters, -make sure your project name is compatible with the specifics of your chosen Cloud provider. In addition, QHub `projectname` +In order to successfully deploy QHub, please follow some project naming conventions. For starters, +make sure your project name is compatible with the specifics of your chosen cloud provider. In addition, QHub `projectname` should also obey to the following format requirements: + letters from A to Z (upper and lower case) and numbers; + Special characters are **NOT** allowed; @@ -66,7 +65,7 @@ should also obey to the following format requirements: ### Deploy QHub -Finally, with the `qhub-config.yaml` created, we can deploy QHub for the first time: +Finally, with the `qhub-config.yaml` created, QHub can be deployed for the first time: ```shell qhub deploy -c qhub-config.yaml --dns-provider cloudflare --dns-auto-provision @@ -128,11 +127,11 @@ Push the changes to the repository (your primary branch may be called git push origin main ``` -Once pushed to GitHub, future CI/CD changes are triggered by -commits to main, and deployed via GitHub Actions. Since the -infrastructure state is reflected in the repository, this workflow -allows for team members to submit pull requests that can be reviewed -before modifying the infrastructure, easing the maintenance process. +Once pushed to GitHub, future commits to `main` trigger CI/CD to redeploy +changes the QHub clusger. Since the infrastructure state is reflected in +the repository, this workflow allows for team members to submit pull requests +that can be reviewed before modifying the infrastructure, easing the +maintenance process. To automatically deploy (and to keep track of changes more effectively): - make changes to the `qhub-config.yaml` file on a new branch. From cc3373a2572e3805b01138d1b3141ba67416d0d7 Mon Sep 17 00:00:00 2001 From: iameskild Date: Fri, 22 Oct 2021 13:59:19 -0700 Subject: [PATCH 17/46] Split test workflow into two seperate workflows --- .github/workflows/test-provider.yaml | 52 ++++++++++++++++++++++++++++ .github/workflows/test.yaml | 34 ------------------ 2 files changed, 52 insertions(+), 34 deletions(-) create mode 100644 .github/workflows/test-provider.yaml diff --git a/.github/workflows/test-provider.yaml b/.github/workflows/test-provider.yaml new file mode 100644 index 0000000000..94839e254a --- /dev/null +++ b/.github/workflows/test-provider.yaml @@ -0,0 +1,52 @@ +name: "Test QHub Provider" + +on: + pull_request: # Workflow only runs for PR against main anyway + push: + branches: + - '**' + tags: + - 'v*' + paths-ignore: + - "docs/**" + - "*.md" + +env: + DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + +jobs: + test-render-providers: + name: 'Test QHub Provider' + runs-on: ubuntu-latest + strategy: + matrix: + provider: + - aws + # - azure + - do + - gcp + - local + steps: + - name: 'Checkout Infrastructure' + uses: actions/checkout@main + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: 3.8 + - name: Install QHub + run: | + pip install .[dev] + - name: QHub Initialize + run: | + qhub init "${{ matrix.provider }}" --project "${{ matrix.provider }}-test" --domain "${{ matrix.provider }}.qhub.dev" --auth-provider github --kubernetes-version "1.18.0" --disable-prompt + - name: QHub Render + run: | + qhub render -c "qhub-config.yaml" -o "qhub-${{ matrix.provider }}-deployment" + cp "qhub-config.yaml" "qhub-${{ matrix.provider }}-deployment/qhub-config.yaml" + - name: QHub Render Artifact + uses: actions/upload-artifact@master + with: + name: "qhub-${{ matrix.provider }}-artifact" + path: "qhub-${{ matrix.provider }}-deployment" diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 59e72ee501..97b2004328 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -40,37 +40,3 @@ jobs: run: | pytest --version pytest - - test-render-providers: - name: 'Test QHub Provider' - runs-on: ubuntu-latest - strategy: - matrix: - provider: - - aws - - azure - - do - - gcp - - local - steps: - - name: 'Checkout Infrastructure' - uses: actions/checkout@main - - name: Set up Python - uses: actions/setup-python@v1 - with: - python-version: 3.8 - - name: Install QHub - run: | - pip install .[dev] - - name: QHub Initialize - run: | - qhub init "${{ matrix.provider }}" --project "${{ matrix.provider }}-test" --domain "${{ matrix.provider }}.qhub.dev" --auth-provider github --kubernetes-version "1.18.0" --disable-prompt - - name: QHub Render - run: | - qhub render -c "qhub-config.yaml" -o "qhub-${{ matrix.provider }}-deployment" - cp "qhub-config.yaml" "qhub-${{ matrix.provider }}-deployment/qhub-config.yaml" - - name: QHub Render Artifact - uses: actions/upload-artifact@master - with: - name: "qhub-${{ matrix.provider }}-artifact" - path: "qhub-${{ matrix.provider }}-deployment" From fc084f79170848aee8cb39e813cd111bf1774954 Mon Sep 17 00:00:00 2001 From: iameskild Date: Fri, 22 Oct 2021 14:36:20 -0700 Subject: [PATCH 18/46] Update test-provider.yaml --- .github/workflows/test-provider.yaml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test-provider.yaml b/.github/workflows/test-provider.yaml index 94839e254a..30dbd8db21 100644 --- a/.github/workflows/test-provider.yaml +++ b/.github/workflows/test-provider.yaml @@ -11,15 +11,14 @@ on: - "docs/**" - "*.md" -env: - DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }} - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - jobs: test-render-providers: name: 'Test QHub Provider' runs-on: ubuntu-latest + env: + DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} strategy: matrix: provider: From 96ce5ad7d3bacd3e8188c2015b02b9b6a1ad435e Mon Sep 17 00:00:00 2001 From: iameskild Date: Fri, 22 Oct 2021 14:58:20 -0700 Subject: [PATCH 19/46] Test secrets --- .github/workflows/test-provider.yaml | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test-provider.yaml b/.github/workflows/test-provider.yaml index 30dbd8db21..41506eaaaa 100644 --- a/.github/workflows/test-provider.yaml +++ b/.github/workflows/test-provider.yaml @@ -11,14 +11,17 @@ on: - "docs/**" - "*.md" +env: + DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + HELLO: hello_world + PYPI_USERNAME: ${{ secrets.PYPI_USERNAME }} + jobs: test-render-providers: name: 'Test QHub Provider' runs-on: ubuntu-latest - env: - DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }} - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} strategy: matrix: provider: @@ -34,6 +37,10 @@ jobs: uses: actions/setup-python@v1 with: python-version: 3.8 + - name: Env Var Test + run: | + echo $HELLO + echo $PYPI_USERNAME - name: Install QHub run: | pip install .[dev] From c6b2c438ee2b1a23a7b04deaf61041cd1e021dcc Mon Sep 17 00:00:00 2001 From: iameskild Date: Fri, 22 Oct 2021 17:17:51 -0700 Subject: [PATCH 20/46] Add secrets, remove kubernetes_version --- .github/workflows/test-provider.yaml | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test-provider.yaml b/.github/workflows/test-provider.yaml index 41506eaaaa..b4584e4340 100644 --- a/.github/workflows/test-provider.yaml +++ b/.github/workflows/test-provider.yaml @@ -15,8 +15,8 @@ env: DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - HELLO: hello_world - PYPI_USERNAME: ${{ secrets.PYPI_USERNAME }} + GOOGLE_CREDENTIALS: ${{ secrets.GOOGLE_CREDENTIALS }} + PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }} jobs: test-render-providers: @@ -37,16 +37,12 @@ jobs: uses: actions/setup-python@v1 with: python-version: 3.8 - - name: Env Var Test - run: | - echo $HELLO - echo $PYPI_USERNAME - name: Install QHub run: | pip install .[dev] - name: QHub Initialize run: | - qhub init "${{ matrix.provider }}" --project "${{ matrix.provider }}-test" --domain "${{ matrix.provider }}.qhub.dev" --auth-provider github --kubernetes-version "1.18.0" --disable-prompt + qhub init "${{ matrix.provider }}" --project "${{ matrix.provider }}-test" --domain "${{ matrix.provider }}.qhub.dev" --auth-provider github --disable-prompt - name: QHub Render run: | qhub render -c "qhub-config.yaml" -o "qhub-${{ matrix.provider }}-deployment" From ae67b1746a633c356a138e0c4a11f660c459efaf Mon Sep 17 00:00:00 2001 From: iameskild Date: Sun, 24 Oct 2021 11:18:32 -0700 Subject: [PATCH 21/46] Add gcloud to test-provider workflow --- .github/workflows/test-provider.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/test-provider.yaml b/.github/workflows/test-provider.yaml index b4584e4340..2156229237 100644 --- a/.github/workflows/test-provider.yaml +++ b/.github/workflows/test-provider.yaml @@ -37,6 +37,13 @@ jobs: uses: actions/setup-python@v1 with: python-version: 3.8 + - name: Set up Cloud SDK + uses: google-github-actions/setup-gcloud@master + with: + project_id: $PROJECT_ID + service_account_key: $GOOGLE_CREDENTIALS + - name: Use gcloud CLI + run: gcloud info - name: Install QHub run: | pip install .[dev] From 9924a0fb01c056c2b324ed885b8b3ff6b67951b2 Mon Sep 17 00:00:00 2001 From: iameskild Date: Sun, 24 Oct 2021 11:25:10 -0700 Subject: [PATCH 22/46] Add gcloud to test-provider workflow --- .github/workflows/test-provider.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-provider.yaml b/.github/workflows/test-provider.yaml index 2156229237..d7495c0298 100644 --- a/.github/workflows/test-provider.yaml +++ b/.github/workflows/test-provider.yaml @@ -40,8 +40,8 @@ jobs: - name: Set up Cloud SDK uses: google-github-actions/setup-gcloud@master with: - project_id: $PROJECT_ID - service_account_key: $GOOGLE_CREDENTIALS + project_id: PROJECT_ID + service_account_key: GOOGLE_CREDENTIALS - name: Use gcloud CLI run: gcloud info - name: Install QHub From cb83e561c7fc07962d782fe78899fb157de965f3 Mon Sep 17 00:00:00 2001 From: iameskild Date: Sun, 24 Oct 2021 11:29:40 -0700 Subject: [PATCH 23/46] Add gcloud to test-provider workflow --- .github/workflows/test-provider.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-provider.yaml b/.github/workflows/test-provider.yaml index d7495c0298..646a3a7f78 100644 --- a/.github/workflows/test-provider.yaml +++ b/.github/workflows/test-provider.yaml @@ -40,8 +40,8 @@ jobs: - name: Set up Cloud SDK uses: google-github-actions/setup-gcloud@master with: - project_id: PROJECT_ID - service_account_key: GOOGLE_CREDENTIALS + project_id: ${{ secrets.GCP_PROJECT_ID }} + service_account_key: ${{ secrets.GOOGLE_CREDENTIALS }} - name: Use gcloud CLI run: gcloud info - name: Install QHub From 34d077aaefb5b4bc042375899e1b7d92be2ff3e0 Mon Sep 17 00:00:00 2001 From: iameskild Date: Sun, 24 Oct 2021 11:53:54 -0700 Subject: [PATCH 24/46] Add azure to test-provider --- .github/workflows/test-provider.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-provider.yaml b/.github/workflows/test-provider.yaml index 646a3a7f78..a651918298 100644 --- a/.github/workflows/test-provider.yaml +++ b/.github/workflows/test-provider.yaml @@ -26,7 +26,7 @@ jobs: matrix: provider: - aws - # - azure + - azure - do - gcp - local From 5b833fc619aaa7172f3ccfab1a7c568b9db71ba4 Mon Sep 17 00:00:00 2001 From: iameskild Date: Sun, 24 Oct 2021 11:56:39 -0700 Subject: [PATCH 25/46] Add azure env vars to test-provider --- .github/workflows/test-provider.yaml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-provider.yaml b/.github/workflows/test-provider.yaml index a651918298..2fc1ade668 100644 --- a/.github/workflows/test-provider.yaml +++ b/.github/workflows/test-provider.yaml @@ -15,8 +15,11 @@ env: DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - GOOGLE_CREDENTIALS: ${{ secrets.GOOGLE_CREDENTIALS }} - PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }} + ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }} + ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }} + ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }} + ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }} + jobs: test-render-providers: From 0ebae06c8758f94af898b9e64782c17649a6edf0 Mon Sep 17 00:00:00 2001 From: iameskild Date: Sun, 24 Oct 2021 12:29:09 -0700 Subject: [PATCH 26/46] Add azure cli login to test-provider --- .github/workflows/test-provider.yaml | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test-provider.yaml b/.github/workflows/test-provider.yaml index 2fc1ade668..c4b5b5efbf 100644 --- a/.github/workflows/test-provider.yaml +++ b/.github/workflows/test-provider.yaml @@ -15,11 +15,6 @@ env: DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }} - ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }} - ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }} - ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }} - jobs: test-render-providers: @@ -42,11 +37,21 @@ jobs: python-version: 3.8 - name: Set up Cloud SDK uses: google-github-actions/setup-gcloud@master + if: "${{ matrix.provider == gcp }}" with: project_id: ${{ secrets.GCP_PROJECT_ID }} service_account_key: ${{ secrets.GOOGLE_CREDENTIALS }} - name: Use gcloud CLI + if: "${{ matrix.provider == gcp }}" run: gcloud info + - name: Login to Azure + uses: azure/login@v1 + if: "${{ maxtrix.provider == azure }}" + with: + creds: ${{ secrets.AZURE_CREDENTIALS }} + - name: Use az CLI + if: "${{ maxtrix.provider == azure }}" + run: az version - name: Install QHub run: | pip install .[dev] From 847e333c9e5e495de85d8b13cb2d8944140b91e2 Mon Sep 17 00:00:00 2001 From: iameskild Date: Sun, 24 Oct 2021 12:30:30 -0700 Subject: [PATCH 27/46] Remove conditional setup --- .github/workflows/test-provider.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/test-provider.yaml b/.github/workflows/test-provider.yaml index c4b5b5efbf..ca274a8b2a 100644 --- a/.github/workflows/test-provider.yaml +++ b/.github/workflows/test-provider.yaml @@ -37,20 +37,16 @@ jobs: python-version: 3.8 - name: Set up Cloud SDK uses: google-github-actions/setup-gcloud@master - if: "${{ matrix.provider == gcp }}" with: project_id: ${{ secrets.GCP_PROJECT_ID }} service_account_key: ${{ secrets.GOOGLE_CREDENTIALS }} - name: Use gcloud CLI - if: "${{ matrix.provider == gcp }}" run: gcloud info - name: Login to Azure uses: azure/login@v1 - if: "${{ maxtrix.provider == azure }}" with: creds: ${{ secrets.AZURE_CREDENTIALS }} - name: Use az CLI - if: "${{ maxtrix.provider == azure }}" run: az version - name: Install QHub run: | From d2571c3bbe8bf1ab13c2222fc7d0e427457709ee Mon Sep 17 00:00:00 2001 From: eskild <42120229+iameskild@users.noreply.github.com> Date: Sun, 24 Oct 2021 13:42:23 -0700 Subject: [PATCH 28/46] Update test-provider.yaml --- .github/workflows/test-provider.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-provider.yaml b/.github/workflows/test-provider.yaml index ca274a8b2a..3bdd3f7880 100644 --- a/.github/workflows/test-provider.yaml +++ b/.github/workflows/test-provider.yaml @@ -45,7 +45,9 @@ jobs: - name: Login to Azure uses: azure/login@v1 with: - creds: ${{ secrets.AZURE_CREDENTIALS }} + client-id: ${{ secrets.ARM_CLIENT_ID }} + tenant-id: ${{ secrets.ARM_TENANT_ID }} + subscription-id: ${{ secrets.ARM_SUBSCRIPTION_ID }} - name: Use az CLI run: az version - name: Install QHub From 6214acca73b1b7bf3035883a23e356d3f85de8cf Mon Sep 17 00:00:00 2001 From: eskild <42120229+iameskild@users.noreply.github.com> Date: Sun, 24 Oct 2021 14:10:48 -0700 Subject: [PATCH 29/46] Update test-provider.yaml --- .github/workflows/test-provider.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test-provider.yaml b/.github/workflows/test-provider.yaml index 3bdd3f7880..240e269cff 100644 --- a/.github/workflows/test-provider.yaml +++ b/.github/workflows/test-provider.yaml @@ -15,6 +15,7 @@ env: DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }} jobs: test-render-providers: From 5e9c32354d24194f7a6a3eadfc2796b60c45fd19 Mon Sep 17 00:00:00 2001 From: iameskild Date: Sun, 24 Oct 2021 14:13:53 -0700 Subject: [PATCH 30/46] Update azure env vars --- .github/workflows/test-provider.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-provider.yaml b/.github/workflows/test-provider.yaml index 3bdd3f7880..8d85b84c2c 100644 --- a/.github/workflows/test-provider.yaml +++ b/.github/workflows/test-provider.yaml @@ -15,6 +15,7 @@ env: DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }} jobs: test-render-providers: @@ -45,9 +46,7 @@ jobs: - name: Login to Azure uses: azure/login@v1 with: - client-id: ${{ secrets.ARM_CLIENT_ID }} - tenant-id: ${{ secrets.ARM_TENANT_ID }} - subscription-id: ${{ secrets.ARM_SUBSCRIPTION_ID }} + creds: ${{ secrets.AZURE_CREDENTIALS }} - name: Use az CLI run: az version - name: Install QHub From 7ef9bdd3d399b62dd5597732758e0a139c61a8ad Mon Sep 17 00:00:00 2001 From: iameskild Date: Mon, 25 Oct 2021 10:26:51 -0700 Subject: [PATCH 31/46] Fix issue from merge --- qhub/provider/cloud/azure_cloud.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qhub/provider/cloud/azure_cloud.py b/qhub/provider/cloud/azure_cloud.py index 915b77139b..75defa4b00 100644 --- a/qhub/provider/cloud/azure_cloud.py +++ b/qhub/provider/cloud/azure_cloud.py @@ -28,6 +28,7 @@ def kubernetes_versions(region="Central US"): """Return list of available kubernetes supported by cloud provider. Sorted from oldest to latest.""" client = initiate_container_service_client() + azure_location = region.replace(" ", "").lower() k8s_versions_list = client.container_services.list_orchestrators( azure_location, resource_type="managedClusters" From cefd59ef17c3542976f80973ff34960e7bc4febf Mon Sep 17 00:00:00 2001 From: iameskild Date: Mon, 25 Oct 2021 11:52:14 -0700 Subject: [PATCH 32/46] Add azure env vars --- .github/workflows/test-provider.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test-provider.yaml b/.github/workflows/test-provider.yaml index 8d85b84c2c..445039f370 100644 --- a/.github/workflows/test-provider.yaml +++ b/.github/workflows/test-provider.yaml @@ -16,6 +16,7 @@ env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }} + ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }} jobs: test-render-providers: From 90c57c24727b80febb81f308b2a1ae79953b42c6 Mon Sep 17 00:00:00 2001 From: iameskild Date: Mon, 25 Oct 2021 11:55:09 -0700 Subject: [PATCH 33/46] Add azure env vars, part 2 --- .github/workflows/test-provider.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test-provider.yaml b/.github/workflows/test-provider.yaml index 445039f370..148600a381 100644 --- a/.github/workflows/test-provider.yaml +++ b/.github/workflows/test-provider.yaml @@ -15,6 +15,8 @@ env: DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }} + ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }} ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }} ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }} From f2706f37a37a8826b7f205d64ff0b4f166942a62 Mon Sep 17 00:00:00 2001 From: iameskild Date: Mon, 25 Oct 2021 12:03:52 -0700 Subject: [PATCH 34/46] Include gcloud and az cli conditionally --- .github/workflows/test-provider.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/test-provider.yaml b/.github/workflows/test-provider.yaml index 148600a381..4ce3703def 100644 --- a/.github/workflows/test-provider.yaml +++ b/.github/workflows/test-provider.yaml @@ -41,16 +41,20 @@ jobs: python-version: 3.8 - name: Set up Cloud SDK uses: google-github-actions/setup-gcloud@master + if: ${{ matrix.provider == 'gcp' }} with: project_id: ${{ secrets.GCP_PROJECT_ID }} service_account_key: ${{ secrets.GOOGLE_CREDENTIALS }} - name: Use gcloud CLI + if: ${{ matrix.provider == 'gcp' }} run: gcloud info - name: Login to Azure uses: azure/login@v1 + if: ${{ matrix.provider == 'azure' }} with: creds: ${{ secrets.AZURE_CREDENTIALS }} - name: Use az CLI + if: ${{ matrix.provider == 'azure' }} run: az version - name: Install QHub run: | From c673e1991d4ba14aba80bc9090377807a70379cb Mon Sep 17 00:00:00 2001 From: iameskild Date: Tue, 26 Oct 2021 09:36:18 -0700 Subject: [PATCH 35/46] Update project-name in tests/conftest based on PR 761 --- tests/conftest.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 0421973f2a..00672d2d42 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,10 +8,10 @@ INIT_INPUTS = [ # project, namespace, domain, cloud_provider, ci_provider, auth_provider - ("do-pytest", "dev", "do.qhub.dev", "do", "github-actions", "github"), - ("aws-pytest", "dev", "aws.qhub.dev", "aws", "github-actions", "github"), - ("gcp-pytest", "dev", "gcp.qhub.dev", "gcp", "github-actions", "github"), - ("azure-pytest", "dev", "azure.qhub.dev", "azure", "github-actions", "github"), + ("pytestdo", "dev", "do.qhub.dev", "do", "github-actions", "github"), + ("pytestaws", "dev", "aws.qhub.dev", "aws", "github-actions", "github"), + ("pytestgcp", "dev", "gcp.qhub.dev", "gcp", "github-actions", "github"), + ("pytestazure", "dev", "azure.qhub.dev", "azure", "github-actions", "github"), ] QHUB_CONFIG_FN = "qhub-config.yaml" From c97c89701ea626471d5f3fb2eefda05fb90a9523 Mon Sep 17 00:00:00 2001 From: iameskild Date: Tue, 26 Oct 2021 09:51:42 -0700 Subject: [PATCH 36/46] Minor docs change --- docs/source/installation/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/installation/configuration.md b/docs/source/installation/configuration.md index f8be6cc322..d5fca08faa 100644 --- a/docs/source/installation/configuration.md +++ b/docs/source/installation/configuration.md @@ -340,7 +340,7 @@ and **Kubernetes versions** will be DIFFERENT. [duplicated info] To take advantage of the auto-scaling and dask-distributed computing capabilities, QHub can be deployed on a handful of the most commonly used cloud providers. QHub -takes advantage of many of the resources these cloud providers have to offer, however, +utilizes many of the resources these cloud providers have to offer, however, at it's core, is the Kubernetes engine (or service). Each cloud provider has slightly different ways Kubernetes is configured but fear not, all of this is handled by QHub. From 0d2a4495ad1bb0b148509b7ae51c5cddf43186ad Mon Sep 17 00:00:00 2001 From: iameskild Date: Tue, 16 Nov 2021 17:36:32 -0800 Subject: [PATCH 37/46] Add QHUB_K8S_VERSION and docs --- docs/source/dev_guide/testing.md | 16 ++++++++++++---- qhub/initialize.py | 10 +++++++++- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/docs/source/dev_guide/testing.md b/docs/source/dev_guide/testing.md index 746e5161dc..ba60ee307b 100644 --- a/docs/source/dev_guide/testing.md +++ b/docs/source/dev_guide/testing.md @@ -31,6 +31,14 @@ component which has not been released. So you may need to manually modify the qhub-config.yaml to 'downgrade' the tags to a full release version. +### Kubernetes Version Check for Cloud Providers + +When `qhub init ` is called, it checks that the `--kubernetes-version` provided is supported by the choosen cloud provider. This flag is optional and if not provided, the `kubernetes_version` is set to the most recent kubernetes version available. This is achieved by using the cloud provider's SDK which thus requires their approiate credentials to be set. To get around this, simply set the `QHUB_K8S_VERSION` environment variable like so: + +``` +export QHUB_K8S_VERSION=1.20 +``` + ## Modifying Docker Images All QHub docker images are located in [`qhub/templates/{{ @@ -76,7 +84,7 @@ check out the [Troubleshooting documentation](https://docs.qhub.dev/en/stable/so ## Cypress Tests -Cypress automates testing within a web browser environment. It is integrated into the GitHub Actions tests.yaml workflows in this repo, and +Cypress automates testing within a web browser environment. It is integrated into the GitHub Actions tests.yaml workflows in this repo, and you can also run it locally. To do so: ``` @@ -91,9 +99,9 @@ npm run cypress:open ``` The Base URL can point anywhere that should be accessible - it can be the URL of a QHub cloud deployment. -The QHub Config Path should point to the associated yaml file for that site. Most importantly, the tests will inspect the yaml file to understand -what tests are relevant. To start with, it checks security.authentication.type to determine what should be available on the login page, and -how to test it. If the login type is 'password' then it uses the value in CYPRESS_EXAMPLE_USER_PASSWORD as the password (default username is +The QHub Config Path should point to the associated yaml file for that site. Most importantly, the tests will inspect the yaml file to understand +what tests are relevant. To start with, it checks security.authentication.type to determine what should be available on the login page, and +how to test it. If the login type is 'password' then it uses the value in CYPRESS_EXAMPLE_USER_PASSWORD as the password (default username is `example-user` but this can be changed by setting CYPRESS_EXAMPLE_USER_NAME). The final command above should open the Cypress UI where you can run the tests manually and see the actions in the browser. diff --git a/qhub/initialize.py b/qhub/initialize.py index 4321fe1dee..424ce2441a 100644 --- a/qhub/initialize.py +++ b/qhub/initialize.py @@ -23,6 +23,8 @@ logger = logging.getLogger(__name__) +QHUB_K8S_VERSION = os.getenv("QHUB_K8S_VERSION", None) + BASE_CONFIGURATION = { "project_name": None, "provider": None, @@ -536,7 +538,13 @@ def _check_and_set_kubernetes_version( func=func, ): region = cloud_config["region"] - k8s_versions = func(region) + + # to avoid using cloud provider SDK + # set QHUB_K8S_VERSION environment variable + if not QHUB_K8S_VERSION: + k8s_versions = func(region) + else: + k8s_versions = [QHUB_K8S_VERSION] if kubernetes_version: if kubernetes_version in k8s_versions: From 6891480a83f1151974cac9d86b18e74563939504 Mon Sep 17 00:00:00 2001 From: iameskild Date: Wed, 17 Nov 2021 10:02:20 -0800 Subject: [PATCH 38/46] doc lint only modified files --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index cc3103f6ec..42fb2f6689 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -20,7 +20,7 @@ jobs: - name: documentation quality check uses: errata-ai/vale-action@v1.4.0 with: - files: '["docs", "CONTRIBUTING.md", "README.md", "RELEASE.md", "qhub"]' + files: __onlyModified onlyAnnotateModifiedLines: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 7451010dc75acd6373c8aee3cef1877fb182ea54 Mon Sep 17 00:00:00 2001 From: iameskild Date: Wed, 17 Nov 2021 10:30:53 -0800 Subject: [PATCH 39/46] Fix vale complaints --- docs/source/dev_guide/testing.md | 10 +++++----- docs/source/installation/usage.md | 22 +++++++++++----------- tests/vale/styles/vocab.txt | 2 ++ 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/docs/source/dev_guide/testing.md b/docs/source/dev_guide/testing.md index c7e0333f96..f7e8097acd 100644 --- a/docs/source/dev_guide/testing.md +++ b/docs/source/dev_guide/testing.md @@ -115,16 +115,16 @@ npm run cypress:open ``` The Base URL can point anywhere that should be accessible - it can be the URL of a QHub cloud deployment. -The QHub Config Path should point to the associated yaml file for that site. Most importantly, the tests will inspect the yaml file to understand +The QHub Config Path should point to the associated yaml file for that site. Most importantly, the tests inspect the yaml file to understand what tests are relevant. To start with, it checks security.authentication.type to determine what should be available on the login page, and -how to test it. If the login type is 'password' then it uses the value in CYPRESS_EXAMPLE_USER_PASSWORD as the password (default username is -`example-user` but this can be changed by setting CYPRESS_EXAMPLE_USER_NAME). +how to test it. If the login type is `password` then it uses the value in `CYPRESS_EXAMPLE_USER_PASSWORD` as the password (default username is +`example-user`, to change the default username, update `CYPRESS_EXAMPLE_USER_NAME`). -The final command above should open the Cypress UI where you can run the tests manually and see the actions in the browser. +The final command, in the preceding code-snippet, opens the Cypress UI where you can run the tests manually and see the actions in the browser. Note that tests are heavily state dependent, so any changes or use of the deployed QHub could affect the results. -## Deployment/Integration Tests +## Deployment and integration tests Deployment and Integration testing makes it easier to test various features of deployed QHub on minikube such as Dask Gateway, external integrations, state of the kubernetes cluster via diff --git a/docs/source/installation/usage.md b/docs/source/installation/usage.md index 2ca07014c9..1a159f0e66 100644 --- a/docs/source/installation/usage.md +++ b/docs/source/installation/usage.md @@ -11,7 +11,7 @@ from your terminal. There are several ways to generate your configuration file, `qhub-config.yaml`. You can type the commands when prompted by terminal, or you can set it all automatically from the start. In any case, start by creating -a new project folder. Start by creating a directory `qhub-test`. +a new project folder. Start by creating a directory `qhub-test`. On your terminal run: @@ -47,27 +47,27 @@ allow to configure the deployment: - `--repository-auto-provision`: Sets the secrets for the GitHub repository used for CI/CD actions. - `--ssl-cert-email`: Provide an admin's email address so that LetsEncrypt can generate a real SSL certificate for your site. If omitted, the site will use a self-signed cert that may cause problems for some browsers but may be sufficient for testing. -You will be prompted to enter values for some of the choices above if they are ommited as command line arguments (e.g. project name and domain). +For configuration items not set as command line arguments, the script is going to prompt you to enter these values. -The `qhub init` command will also generate an initial password for your root Keycloak user: +The `qhub init` command also generates an initial password for your root Keycloak user: ``` Securely generated default random password=R1E8aWedaQVU6kKv for Keycloak root user stored at path=/tmp/QHUB_DEFAULT_PASSWORD ``` -The password will also be available in the `qhub-config.yaml` file under the security.keycloak.initial_root_password field. It will be needed in the next page of these docs for logging in to your QHub. +This password is also available in the `qhub-config.yaml` file under the security.keycloak.initial_root_password field. It is required in the next page of these docs for logging in to your QHub. -The command above generates the `qhub-config.yaml` config file +This `qhub init` command generates the `qhub-config.yaml` config file with an infrastructure to be deployed on `aws`, named `projectname`, with a domain name set to `qhub.dev`. The deployment uses `github-actions` as -the continuous integration (CI) provider, -automatically provisioned and authenticated by `auth0`. And initialized on +the continuous integration provider, +automatically provisioned and authenticated by `auth0`. And finally, initialized on GitHub under the URL `github.com/quansight/projectname`. If employing an infrastructure-as-code approach, this is where you would make the desired infrastructure changes -including adding users, changing Dask worker instance type and much more. Once you're happy with your changes you would redeploy those changes using using GitHub Actions. For more details on on the `qhub-config.yaml` please see [Configuration](configuration.md) +including adding users, changing Dask worker instance type and much more. Once you're happy with your changes you would redeploy those changes using GitHub Actions. For more details on the `qhub-config.yaml` please see [Configuration](configuration.md) -##### Project Naming Convention +##### Project naming convention In order to successfully deploy QHub, please follow some project naming conventions. For starters, make sure your project name is compatible with the specifics of your chosen cloud provider. In addition, QHub `projectname` should also obey to the following format requirements: @@ -158,8 +158,8 @@ git push origin main Once pushed to GitHub, future commits to `main` trigger CI/CD to redeploy changes the QHub cluster. Since the infrastructure state is reflected in the repository, this workflow allows for team members to submit pull requests -that can be reviewed before modifying the infrastructure, easing the -maintenance process. +and perform code reviews before modifying the infrastructure, easing the +overall maintenance process. To automatically deploy (and to keep track of changes more effectively): - make changes to the `qhub-config.yaml` file on a new branch. diff --git a/tests/vale/styles/vocab.txt b/tests/vale/styles/vocab.txt index d4e988078b..6c5d9b1915 100644 --- a/tests/vale/styles/vocab.txt +++ b/tests/vale/styles/vocab.txt @@ -129,3 +129,5 @@ yaml ClearML Hadolint Dockerfiles +keycloak +Keycloak From 6ed07bf43c2adeca9d77e6d29d1f0d754d9cef9c Mon Sep 17 00:00:00 2001 From: iameskild Date: Wed, 17 Nov 2021 10:33:14 -0800 Subject: [PATCH 40/46] Fix vale complaints --- docs/source/installation/login.md | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/docs/source/installation/login.md b/docs/source/installation/login.md index bddb1c4282..582715d68f 100644 --- a/docs/source/installation/login.md +++ b/docs/source/installation/login.md @@ -28,11 +28,7 @@ This will create a user called bob with the initial password provided. Omit the ### Add user using Keycloak console -To add a QHub user from the web console for Keycloak, visit: - -https://myqhubsite.com/auth/admin/ - -(Switch 'myqhubsite.com' for the domain you provided for your QHub deployment.) +To add a QHub user from the web console for Keycloak, visit: https:///auth/admin/ ![Root Login to Keycloak](/source/images/keycloak_master_login.png) @@ -60,7 +56,7 @@ It is best to unset the 'Temporary' on/off button so the user won't be forced to Your new user can now log into QHub proper (not Keycloak's admin console). -Visit https://myqhubsite.com/ (or whatever domain you have chosen for your QHub). +Visit https:/// (or whatever domain you have chosen for your QHub). Click 'Sign in with Keycloak'. @@ -74,7 +70,7 @@ If you chose GitHub or Auth0 login, click the 'GitHub' button to be taken to a G You should change your root password for Keycloak now that you've got things running. -Back in https://myqhubsite.com/auth/admin/ you can click on the 'Root' dropdown in the top right of the screen, and select 'Manage account'. +Back in https:///auth/admin/ you can click on the 'Root' dropdown in the top right of the screen, and select 'Manage account'. Under 'Account Security' click 'Signing In'. @@ -84,7 +80,7 @@ From this point, the security.keycloak.initial_root_password field in `qhub-conf # Groups -Add Groups in the same Keycloak backend as you can add users - that is, login as `root` to https://myqhubsite.com/auth/admin/. Click Groups on the left-hand side. +Add Groups in the same Keycloak backend as you can add users - that is, login as `root` to https:///auth/admin/. Click Groups on the left-hand side. Groups named `users` and `admin` will have been created automatically by QHub. All users will be added to the `users` group automatically when you create them. You should never remove them from the `users` group as that group must contain all users. From 5582c1ac35f19eacc5511d7a1e6876741a53e69f Mon Sep 17 00:00:00 2001 From: iameskild Date: Wed, 17 Nov 2021 10:41:11 -0800 Subject: [PATCH 41/46] Fix vale complaints --- docs/source/installation/usage.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/source/installation/usage.md b/docs/source/installation/usage.md index 1a159f0e66..0bf392747d 100644 --- a/docs/source/installation/usage.md +++ b/docs/source/installation/usage.md @@ -55,7 +55,7 @@ The `qhub init` command also generates an initial password for your root Keycloa Securely generated default random password=R1E8aWedaQVU6kKv for Keycloak root user stored at path=/tmp/QHUB_DEFAULT_PASSWORD ``` -This password is also available in the `qhub-config.yaml` file under the security.keycloak.initial_root_password field. It is required in the next page of these docs for logging in to your QHub. +This password is also available in the `qhub-config.yaml` file under the security.keycloak.initial_root_password field. It's required in the next page of these docs for logging in to your QHub. This `qhub init` command generates the `qhub-config.yaml` config file with an infrastructure to be deployed on `aws`, named `projectname`, with a @@ -82,9 +82,9 @@ The `qhub init` command may have some side-effects such automatically creating a This file is the configuration file that will determine how the cloud infrastructure and QHub is built and deployed in the next step. -But at this point it is just a text file! You could edit it manually if you are unhappy with the choices, or delete it and start over again. +But at this point it's just a text file! You could edit it manually if you are unhappy with the choices, or delete it and start over again. -Or it would be possible to create from scratch or re-use a `qhub-config.yaml` file - it is not essential to use `qhub init` at all, but it is often the easiest way to get started. +Or it would be possible to create from scratch or re-use a `qhub-config.yaml` file - it's not essential to use `qhub init` at all, but it's often the easiest way to get started. To understand some ways in which you could decide to edit the YAML file, see [Advanced Configuration](configuration.md). @@ -156,7 +156,7 @@ git push origin main ``` Once pushed to GitHub, future commits to `main` trigger CI/CD to redeploy -changes the QHub cluster. Since the infrastructure state is reflected in +changes the QHub cluster. Since the infrastructure state is reflected in the repository, this workflow allows for team members to submit pull requests and perform code reviews before modifying the infrastructure, easing the overall maintenance process. From 1a9c73f57ced14e4362ab5fd831b8fba55af7cc3 Mon Sep 17 00:00:00 2001 From: iameskild Date: Wed, 17 Nov 2021 11:22:21 -0800 Subject: [PATCH 42/46] Ignore myqhubsite.com md lint --- .github/workflows/markdown.links.config.json | 3 +++ docs/source/installation/usage.md | 8 ++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/markdown.links.config.json b/.github/workflows/markdown.links.config.json index 19a7ec4dae..05ba79ba79 100644 --- a/.github/workflows/markdown.links.config.json +++ b/.github/workflows/markdown.links.config.json @@ -14,6 +14,9 @@ }, { "pattern": "127.0.0.1:8000" + }, + { + "pattern": "myqhubsite.com" } ] } diff --git a/docs/source/installation/usage.md b/docs/source/installation/usage.md index 1a159f0e66..0bf392747d 100644 --- a/docs/source/installation/usage.md +++ b/docs/source/installation/usage.md @@ -55,7 +55,7 @@ The `qhub init` command also generates an initial password for your root Keycloa Securely generated default random password=R1E8aWedaQVU6kKv for Keycloak root user stored at path=/tmp/QHUB_DEFAULT_PASSWORD ``` -This password is also available in the `qhub-config.yaml` file under the security.keycloak.initial_root_password field. It is required in the next page of these docs for logging in to your QHub. +This password is also available in the `qhub-config.yaml` file under the security.keycloak.initial_root_password field. It's required in the next page of these docs for logging in to your QHub. This `qhub init` command generates the `qhub-config.yaml` config file with an infrastructure to be deployed on `aws`, named `projectname`, with a @@ -82,9 +82,9 @@ The `qhub init` command may have some side-effects such automatically creating a This file is the configuration file that will determine how the cloud infrastructure and QHub is built and deployed in the next step. -But at this point it is just a text file! You could edit it manually if you are unhappy with the choices, or delete it and start over again. +But at this point it's just a text file! You could edit it manually if you are unhappy with the choices, or delete it and start over again. -Or it would be possible to create from scratch or re-use a `qhub-config.yaml` file - it is not essential to use `qhub init` at all, but it is often the easiest way to get started. +Or it would be possible to create from scratch or re-use a `qhub-config.yaml` file - it's not essential to use `qhub init` at all, but it's often the easiest way to get started. To understand some ways in which you could decide to edit the YAML file, see [Advanced Configuration](configuration.md). @@ -156,7 +156,7 @@ git push origin main ``` Once pushed to GitHub, future commits to `main` trigger CI/CD to redeploy -changes the QHub cluster. Since the infrastructure state is reflected in +changes the QHub cluster. Since the infrastructure state is reflected in the repository, this workflow allows for team members to submit pull requests and perform code reviews before modifying the infrastructure, easing the overall maintenance process. From 893044f0bd540eae0c8d10333c92dadb4565f04e Mon Sep 17 00:00:00 2001 From: iameskild Date: Wed, 17 Nov 2021 11:25:11 -0800 Subject: [PATCH 43/46] Undo fix to login.md --- docs/source/installation/login.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/source/installation/login.md b/docs/source/installation/login.md index 582715d68f..bddb1c4282 100644 --- a/docs/source/installation/login.md +++ b/docs/source/installation/login.md @@ -28,7 +28,11 @@ This will create a user called bob with the initial password provided. Omit the ### Add user using Keycloak console -To add a QHub user from the web console for Keycloak, visit: https:///auth/admin/ +To add a QHub user from the web console for Keycloak, visit: + +https://myqhubsite.com/auth/admin/ + +(Switch 'myqhubsite.com' for the domain you provided for your QHub deployment.) ![Root Login to Keycloak](/source/images/keycloak_master_login.png) @@ -56,7 +60,7 @@ It is best to unset the 'Temporary' on/off button so the user won't be forced to Your new user can now log into QHub proper (not Keycloak's admin console). -Visit https:/// (or whatever domain you have chosen for your QHub). +Visit https://myqhubsite.com/ (or whatever domain you have chosen for your QHub). Click 'Sign in with Keycloak'. @@ -70,7 +74,7 @@ If you chose GitHub or Auth0 login, click the 'GitHub' button to be taken to a G You should change your root password for Keycloak now that you've got things running. -Back in https:///auth/admin/ you can click on the 'Root' dropdown in the top right of the screen, and select 'Manage account'. +Back in https://myqhubsite.com/auth/admin/ you can click on the 'Root' dropdown in the top right of the screen, and select 'Manage account'. Under 'Account Security' click 'Signing In'. @@ -80,7 +84,7 @@ From this point, the security.keycloak.initial_root_password field in `qhub-conf # Groups -Add Groups in the same Keycloak backend as you can add users - that is, login as `root` to https:///auth/admin/. Click Groups on the left-hand side. +Add Groups in the same Keycloak backend as you can add users - that is, login as `root` to https://myqhubsite.com/auth/admin/. Click Groups on the left-hand side. Groups named `users` and `admin` will have been created automatically by QHub. All users will be added to the `users` group automatically when you create them. You should never remove them from the `users` group as that group must contain all users. From b96407c5485c260491af516e55a999060edc5949 Mon Sep 17 00:00:00 2001 From: eskild <42120229+iameskild@users.noreply.github.com> Date: Mon, 22 Nov 2021 08:15:21 -0800 Subject: [PATCH 44/46] Update docs/source/installation/usage.md Co-authored-by: Vinicius D. Cerutti <51954708+viniciusdc@users.noreply.github.com> --- docs/source/installation/usage.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/installation/usage.md b/docs/source/installation/usage.md index 0bf392747d..c454d4d8b9 100644 --- a/docs/source/installation/usage.md +++ b/docs/source/installation/usage.md @@ -44,7 +44,7 @@ allow to configure the deployment: - `--auth-provider`: Used to specified authentication provider, in this case Auth0 - `--auth-auto-provision`: Whether or not to automatically create and configure the authentication provider. - `--repository`: Repository name used to store the Infrastructure-as-Code on GitHub. -- `--repository-auto-provision`: Sets the secrets for the GitHub repository used for CI/CD actions. +- `--repository-auto-provision`: Sets the secrets for the **GitHub** repository used for CI/CD actions. - `--ssl-cert-email`: Provide an admin's email address so that LetsEncrypt can generate a real SSL certificate for your site. If omitted, the site will use a self-signed cert that may cause problems for some browsers but may be sufficient for testing. For configuration items not set as command line arguments, the script is going to prompt you to enter these values. From 6ae9847e385c7b4b65843c90fd2159fa9f5b1197 Mon Sep 17 00:00:00 2001 From: iameskild Date: Mon, 22 Nov 2021 08:52:28 -0800 Subject: [PATCH 45/46] Updates based on code review --- .github/workflows/test-provider.yaml | 2 +- docs/source/installation/usage.md | 9 ++-- qhub/initialize.py | 80 ++++------------------------ qhub/utils.py | 68 +++++++++++++++++++++++ tests/conftest.py | 8 +-- 5 files changed, 88 insertions(+), 79 deletions(-) diff --git a/.github/workflows/test-provider.yaml b/.github/workflows/test-provider.yaml index 4ce3703def..ee7be2f06f 100644 --- a/.github/workflows/test-provider.yaml +++ b/.github/workflows/test-provider.yaml @@ -61,7 +61,7 @@ jobs: pip install .[dev] - name: QHub Initialize run: | - qhub init "${{ matrix.provider }}" --project "${{ matrix.provider }}-test" --domain "${{ matrix.provider }}.qhub.dev" --auth-provider github --disable-prompt + qhub init "${{ matrix.provider }}" --project "test-${{ matrix.provider }}" --domain "${{ matrix.provider }}.qhub.dev" --auth-provider github --disable-prompt - name: QHub Render run: | qhub render -c "qhub-config.yaml" -o "qhub-${{ matrix.provider }}-deployment" diff --git a/docs/source/installation/usage.md b/docs/source/installation/usage.md index 0bf392747d..c926441ffa 100644 --- a/docs/source/installation/usage.md +++ b/docs/source/installation/usage.md @@ -44,7 +44,7 @@ allow to configure the deployment: - `--auth-provider`: Used to specified authentication provider, in this case Auth0 - `--auth-auto-provision`: Whether or not to automatically create and configure the authentication provider. - `--repository`: Repository name used to store the Infrastructure-as-Code on GitHub. -- `--repository-auto-provision`: Sets the secrets for the GitHub repository used for CI/CD actions. +- `--repository-auto-provision`: Sets the secrets for the **GitHub** repository used for CI/CD actions. - `--ssl-cert-email`: Provide an admin's email address so that LetsEncrypt can generate a real SSL certificate for your site. If omitted, the site will use a self-signed cert that may cause problems for some browsers but may be sufficient for testing. For configuration items not set as command line arguments, the script is going to prompt you to enter these values. @@ -55,7 +55,7 @@ The `qhub init` command also generates an initial password for your root Keycloa Securely generated default random password=R1E8aWedaQVU6kKv for Keycloak root user stored at path=/tmp/QHUB_DEFAULT_PASSWORD ``` -This password is also available in the `qhub-config.yaml` file under the security.keycloak.initial_root_password field. It's required in the next page of these docs for logging in to your QHub. +This password is also available in the `qhub-config.yaml` file under the `security.keycloak.initial_root_password field`. It's required in the next page of these docs for logging in to your QHub. This `qhub init` command generates the `qhub-config.yaml` config file with an infrastructure to be deployed on `aws`, named `projectname`, with a @@ -101,6 +101,7 @@ This creates the following folder structure: ``` . +├── .github # if "--ci-provider github-actions" was passed in, then an action scripts folder is also generated ├── environments # stores the conda environments ├── image # docker images used on deployment: jupyterhub, jupyterlab, and dask-gateway │   ├── dask-worker @@ -111,8 +112,8 @@ This creates the following folder structure: ``` The terminal then prompts you to press `[enter]` to check auth credentials -(which were added by the `qhub init` command). This triggers the -deployment, which takes around 10 minutes to complete. +(which were added by the `qhub init` command); to disable the prompt, add `--disable-prompt` to the qhub deploy command. +A first time deeployment can take around 10 minutes to complete. During the initial deployment, Digital Ocean, GCP and Azure are going to display an `"ip"` address whereas AWS is going to display a CNAME `"hostname"`. diff --git a/qhub/initialize.py b/qhub/initialize.py index 4532185c97..055318814a 100644 --- a/qhub/initialize.py +++ b/qhub/initialize.py @@ -11,18 +11,17 @@ from qhub.provider.oauth.auth0 import create_client from qhub.provider.cicd import github from qhub.provider import git -from qhub.provider.cloud import ( - digital_ocean, - azure_cloud, - amazon_web_services, - google_cloud, + +from qhub.utils import ( + namestr_regex, + qhub_image_tag, + check_cloud_credentials, + set_kubernetes_version, ) -from qhub.utils import namestr_regex, qhub_image_tag, check_cloud_credentials from .version import __version__ logger = logging.getLogger(__name__) -QHUB_K8S_VERSION = os.getenv("QHUB_K8S_VERSION", None) BASE_CONFIGURATION = { "project_name": None, @@ -345,14 +344,14 @@ def render_config( "hub_subtitle" ] = "Autoscaling Compute Environment on Digital Ocean" config["digital_ocean"] = DIGITAL_OCEAN.copy() - _set_kubernetes_version(config, kubernetes_version, cloud_provider) + set_kubernetes_version(config, kubernetes_version, cloud_provider) elif cloud_provider == "gcp": config["theme"]["jupyterhub"][ "hub_subtitle" ] = "Autoscaling Compute Environment on Google Cloud Platform" config["google_cloud_platform"] = GOOGLE_PLATFORM.copy() - _set_kubernetes_version(config, kubernetes_version, cloud_provider) + set_kubernetes_version(config, kubernetes_version, cloud_provider) if "PROJECT_ID" in os.environ: config["google_cloud_platform"]["project"] = os.environ["PROJECT_ID"] @@ -366,14 +365,14 @@ def render_config( "hub_subtitle" ] = "Autoscaling Compute Environment on Azure" config["azure"] = AZURE.copy() - _set_kubernetes_version(config, kubernetes_version, cloud_provider) + set_kubernetes_version(config, kubernetes_version, cloud_provider) elif cloud_provider == "aws": config["theme"]["jupyterhub"][ "hub_subtitle" ] = "Autoscaling Compute Environment on Amazon Web Services" config["amazon_web_services"] = AMAZON_WEB_SERVICES.copy() - _set_kubernetes_version(config, kubernetes_version, cloud_provider) + set_kubernetes_version(config, kubernetes_version, cloud_provider) elif cloud_provider == "local": config["theme"]["jupyterhub"][ @@ -497,62 +496,3 @@ def auth0_auto_provision(config): config["security"]["authentication"]["config"]["auth0_subdomain"] = auth0_config[ "auth0_subdomain" ] - - -def _set_kubernetes_version( - config, kubernetes_version, cloud_provider, grab_latest_version=True -): - cloud_provider_dict = { - "aws": { - "full_name": "amazon_web_services", - "k8s_version_checker_func": amazon_web_services.kubernetes_versions, - }, - "azure": { - "full_name": "azure", - "k8s_version_checker_func": azure_cloud.kubernetes_versions, - }, - "do": { - "full_name": "digital_ocean", - "k8s_version_checker_func": digital_ocean.kubernetes_versions, - }, - "gcp": { - "full_name": "google_cloud_platform", - "k8s_version_checker_func": google_cloud.kubernetes_versions, - }, - } - cloud_full_name = cloud_provider_dict[cloud_provider]["full_name"] - func = cloud_provider_dict[cloud_provider]["k8s_version_checker_func"] - cloud_config = config[cloud_full_name] - - def _raise_value_error(cloud_provider, k8s_versions): - raise ValueError( - f"\nInvalid `kubernetes-version` provided: {kubernetes_version}.\nPlease select from one of the following {cloud_provider.upper()} supported Kubernetes versions: {k8s_versions} or omit flag to use latest Kubernetes version available." - ) - - def _check_and_set_kubernetes_version( - kubernetes_version=kubernetes_version, - cloud_provider=cloud_provider, - cloud_config=cloud_config, - func=func, - ): - region = cloud_config["region"] - - # to avoid using cloud provider SDK - # set QHUB_K8S_VERSION environment variable - if not QHUB_K8S_VERSION: - k8s_versions = func(region) - else: - k8s_versions = [QHUB_K8S_VERSION] - - if kubernetes_version: - if kubernetes_version in k8s_versions: - cloud_config["kubernetes_version"] = kubernetes_version - else: - _raise_value_error(cloud_provider, k8s_versions) - elif grab_latest_version: - cloud_config["kubernetes_version"] = k8s_versions[-1] - else: - # grab oldest version - cloud_config["kubernetes_version"] = k8s_versions[0] - - return _check_and_set_kubernetes_version() diff --git a/qhub/utils.py b/qhub/utils.py index e16d53581a..e743981e0b 100644 --- a/qhub/utils.py +++ b/qhub/utils.py @@ -7,8 +7,17 @@ import contextlib from ruamel.yaml import YAML +from qhub.provider.cloud import ( + digital_ocean, + azure_cloud, + amazon_web_services, + google_cloud, +) + from .version import __version__ +QHUB_K8S_VERSION = os.getenv("QHUB_K8S_VERSION", None) + DO_ENV_DOCS = ( "https://docs.qhub.dev/en/latest/source/02_get_started/02_setup.html#digital-ocean" ) @@ -172,3 +181,62 @@ def backup_config_file(filename: pathlib.Path, extrasuffix: str = ""): filename.rename(backup_filename) print(f"Backing up {filename} as {backup_filename}") + + +def set_kubernetes_version( + config, kubernetes_version, cloud_provider, grab_latest_version=True +): + cloud_provider_dict = { + "aws": { + "full_name": "amazon_web_services", + "k8s_version_checker_func": amazon_web_services.kubernetes_versions, + }, + "azure": { + "full_name": "azure", + "k8s_version_checker_func": azure_cloud.kubernetes_versions, + }, + "do": { + "full_name": "digital_ocean", + "k8s_version_checker_func": digital_ocean.kubernetes_versions, + }, + "gcp": { + "full_name": "google_cloud_platform", + "k8s_version_checker_func": google_cloud.kubernetes_versions, + }, + } + cloud_full_name = cloud_provider_dict[cloud_provider]["full_name"] + func = cloud_provider_dict[cloud_provider]["k8s_version_checker_func"] + cloud_config = config[cloud_full_name] + + def _raise_value_error(cloud_provider, k8s_versions): + raise ValueError( + f"\nInvalid `kubernetes-version` provided: {kubernetes_version}.\nPlease select from one of the following {cloud_provider.upper()} supported Kubernetes versions: {k8s_versions} or omit flag to use latest Kubernetes version available." + ) + + def _check_and_set_kubernetes_version( + kubernetes_version=kubernetes_version, + cloud_provider=cloud_provider, + cloud_config=cloud_config, + func=func, + ): + region = cloud_config["region"] + + # to avoid using cloud provider SDK + # set QHUB_K8S_VERSION environment variable + if not QHUB_K8S_VERSION: + k8s_versions = func(region) + else: + k8s_versions = [QHUB_K8S_VERSION] + + if kubernetes_version: + if kubernetes_version in k8s_versions: + cloud_config["kubernetes_version"] = kubernetes_version + else: + _raise_value_error(cloud_provider, k8s_versions) + elif grab_latest_version: + cloud_config["kubernetes_version"] = k8s_versions[-1] + else: + # grab oldest version + cloud_config["kubernetes_version"] = k8s_versions[0] + + return _check_and_set_kubernetes_version() diff --git a/tests/conftest.py b/tests/conftest.py index 00672d2d42..98de234f1e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -60,22 +60,22 @@ def _mock_kubernetes_versions(grab_latest_version=False): if cloud_provider == "aws": monkeypatch.setattr( - "qhub.initialize.amazon_web_services.kubernetes_versions", + "qhub.utils.amazon_web_services.kubernetes_versions", _mock_kubernetes_versions(), ) elif cloud_provider == "azure": monkeypatch.setattr( - "qhub.initialize.azure_cloud.kubernetes_versions", + "qhub.utils.azure_cloud.kubernetes_versions", _mock_kubernetes_versions(), ) elif cloud_provider == "do": monkeypatch.setattr( - "qhub.initialize.digital_ocean.kubernetes_versions", + "qhub.utils.digital_ocean.kubernetes_versions", _mock_kubernetes_versions(), ) elif cloud_provider == "gcp": monkeypatch.setattr( - "qhub.initialize.google_cloud.kubernetes_versions", + "qhub.utils.google_cloud.kubernetes_versions", _mock_kubernetes_versions(), ) From f1351d6b0c3c5ac3313c216f265514738b4395a0 Mon Sep 17 00:00:00 2001 From: eskild <42120229+iameskild@users.noreply.github.com> Date: Wed, 24 Nov 2021 14:11:47 -0800 Subject: [PATCH 46/46] Update digital_ocean.py --- qhub/provider/cloud/digital_ocean.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qhub/provider/cloud/digital_ocean.py b/qhub/provider/cloud/digital_ocean.py index 2aee62447d..f888771cbd 100644 --- a/qhub/provider/cloud/digital_ocean.py +++ b/qhub/provider/cloud/digital_ocean.py @@ -39,7 +39,7 @@ def regions(): return _kubernetes_options()["options"]["regions"] -# keep `region` parameter - see `qhub.initialize._set_kubernetes_version` +# keep `region` parameter def kubernetes_versions(region=None): """Return list of available kubernetes supported by cloud provider. Sorted from oldest to latest."""