From 92ea12b7b3c08fe73a6ee24297e19b693e27f91d Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Thu, 20 Jul 2023 12:29:46 +0100 Subject: [PATCH 01/26] Initial commit --- src/_nebari/deploy.py | 1 + src/_nebari/provider/cloud/digital_ocean.py | 5 +- src/_nebari/stages/checks.py | 3 +- tests/integration_fixtures.py | 0 tests/test_integration.py | 65 +++++++++++++++++++++ 5 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 tests/integration_fixtures.py create mode 100644 tests/test_integration.py diff --git a/src/_nebari/deploy.py b/src/_nebari/deploy.py index 8dbd62013c..cbe579bf48 100644 --- a/src/_nebari/deploy.py +++ b/src/_nebari/deploy.py @@ -302,5 +302,6 @@ def deploy_configuration( skip_remote_state_provision, ) except subprocess.CalledProcessError as e: + logger.error("subprocess command failed") logger.error(e.output) raise e diff --git a/src/_nebari/provider/cloud/digital_ocean.py b/src/_nebari/provider/cloud/digital_ocean.py index 94b4d3dd05..645406f7c5 100644 --- a/src/_nebari/provider/cloud/digital_ocean.py +++ b/src/_nebari/provider/cloud/digital_ocean.py @@ -44,6 +44,7 @@ def regions(): def kubernetes_versions(region): """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"]] + [_["slug"].split('-')[0] for _ in _kubernetes_options()["options"]["versions"]] ) - return filter_by_highest_supported_k8s_version(supported_kubernetes_versions) + filtered_versions = filter_by_highest_supported_k8s_version(supported_kubernetes_versions) + return [f"{v}-do.0" for v in filtered_versions] diff --git a/src/_nebari/stages/checks.py b/src/_nebari/stages/checks.py index 795bce5505..86259846af 100644 --- a/src/_nebari/stages/checks.py +++ b/src/_nebari/stages/checks.py @@ -198,7 +198,8 @@ def _attempt_keycloak_connection( ) print(f"Attempt {i+1} succeeded connecting to keycloak master realm") return True - except KeycloakError: + except KeycloakError as e: + print(e) print(f"Attempt {i+1} failed connecting to keycloak master realm") time.sleep(timeout) return False diff --git a/tests/integration_fixtures.py b/tests/integration_fixtures.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/test_integration.py b/tests/test_integration.py new file mode 100644 index 0000000000..e94c873df8 --- /dev/null +++ b/tests/test_integration.py @@ -0,0 +1,65 @@ +import logging +import os +import pathlib + +import yaml + +from _nebari.deploy import deploy_configuration +from _nebari.render import render_template +from .conftest import render_config_partial + +import random +import string +import warnings + +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s %(levelname)9s %(lineno)4s %(module)s: %(message)s" +) + +logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) + + +def random_letters(length=5): + letters = string.ascii_letters + return ''.join(random.choice(letters) for _ in range(length)).lower() + + +def test_integration(): + # Ignore this for now, as test is failing due to a + # DeprecationWarning + warnings.filterwarnings("ignore", category=DeprecationWarning) + # project_name = f"pytestdo{random_letters()}" + project_name = "pytestdoxvzyr" + config = render_config_partial( + project_name=project_name, + namespace="dev", + nebari_domain="do.nebari.dev", + cloud_provider="do", + ci_provider="github-actions", + auth_provider="github", + ) + tmpdir = os.getcwd() / pathlib.Path("pytestdotemp") + os.chdir(tmpdir) + print(f"Temporary directory: {tmpdir}") + config_filepath = tmpdir / "nebari-config.yaml" + with open(config_filepath, "w") as f: + yaml.dump(config, f) + + render_template(tmpdir, config_filepath) + try: + deploy_configuration( + config=config, + dns_provider="cloudflare", + dns_auto_provision=True, + disable_prompt=True, + disable_checks=False, + skip_remote_state_provision=False, + ) + except Exception as e: + print(f"Test Exception: {e}") + logger.exception(e) + import ipdb as pdb; pdb.set_trace() + raise + assert 1 == 1 From 85be14a33915f82d6c4a41df9620134231d20112 Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Thu, 20 Jul 2023 15:27:03 +0100 Subject: [PATCH 02/26] working --- src/_nebari/utils.py | 1 + tests/test_integration.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/_nebari/utils.py b/src/_nebari/utils.py index 6e78dd0c05..4ca07f82bd 100644 --- a/src/_nebari/utils.py +++ b/src/_nebari/utils.py @@ -106,6 +106,7 @@ def kill_process(): if timeout_timer is not None: timeout_timer.cancel() + process.stdout.close() return process.wait( timeout=10 ) # Should already have finished because we have drained stdout diff --git a/tests/test_integration.py b/tests/test_integration.py index e94c873df8..76499b4d98 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -1,6 +1,7 @@ import logging import os import pathlib +from urllib3.exceptions import InsecureRequestWarning import yaml @@ -30,6 +31,7 @@ def test_integration(): # Ignore this for now, as test is failing due to a # DeprecationWarning warnings.filterwarnings("ignore", category=DeprecationWarning) + warnings.filterwarnings("ignore", category=InsecureRequestWarning) # project_name = f"pytestdo{random_letters()}" project_name = "pytestdoxvzyr" config = render_config_partial( @@ -47,7 +49,7 @@ def test_integration(): with open(config_filepath, "w") as f: yaml.dump(config, f) - render_template(tmpdir, config_filepath) + # render_template(tmpdir, config_filepath) try: deploy_configuration( config=config, From c02dcb5e5e011d77a97c153c93686618c2caf782 Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Thu, 20 Jul 2023 15:27:58 +0100 Subject: [PATCH 03/26] undo render --- tests/test_integration.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/test_integration.py b/tests/test_integration.py index 76499b4d98..f613483550 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -49,7 +49,7 @@ def test_integration(): with open(config_filepath, "w") as f: yaml.dump(config, f) - # render_template(tmpdir, config_filepath) + render_template(tmpdir, config_filepath) try: deploy_configuration( config=config, @@ -60,8 +60,7 @@ def test_integration(): skip_remote_state_provision=False, ) except Exception as e: - print(f"Test Exception: {e}") + print(f"Deploy Failed, Exception: {e}") logger.exception(e) - import ipdb as pdb; pdb.set_trace() raise assert 1 == 1 From 50c225b8ddb0ff048284437348ab2a09c0729ab8 Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Thu, 20 Jul 2023 16:55:10 +0100 Subject: [PATCH 04/26] working do deployment --- .gitignore | 3 ++ tests/integration_fixtures.py | 0 tests/test_integration.py | 53 ++++++++++++++++++++++++++--------- 3 files changed, 42 insertions(+), 14 deletions(-) delete mode 100644 tests/integration_fixtures.py diff --git a/.gitignore b/.gitignore index d7c6ef5a6f..f3128025da 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,6 @@ nebari-config.yaml .ipynb_checkpoints .DS_Store /.ruff_cache + +# Integration tests deployments +_test_deploy diff --git a/tests/integration_fixtures.py b/tests/integration_fixtures.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/test_integration.py b/tests/test_integration.py index f613483550..a59e6edbfb 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -1,6 +1,7 @@ import logging +import pytest import os -import pathlib +from pathlib import Path from urllib3.exceptions import InsecureRequestWarning import yaml @@ -13,6 +14,8 @@ import string import warnings +DEPLOYMENT_DIR = '_test_deploy' + logging.basicConfig( level=logging.INFO, format="%(asctime)s %(levelname)9s %(lineno)4s %(module)s: %(message)s" @@ -27,29 +30,42 @@ def random_letters(length=5): return ''.join(random.choice(letters) for _ in range(length)).lower() -def test_integration(): +def get_or_create_deployment_directory(cloud): + deployment_dirs = list(Path(Path(DEPLOYMENT_DIR) / cloud).glob("pytestdoxvzyr")) + if deployment_dirs: + deployment_dir = deployment_dirs[0] + else: + project_name = f"pytest{cloud}{random_letters()}" + deployment_dir = Path(Path(Path(DEPLOYMENT_DIR) / cloud) / project_name) + deployment_dir.mkdir() + return deployment_dir + + +@pytest.fixture +def deploy( + request, +): # Ignore this for now, as test is failing due to a # DeprecationWarning + cloud = request.param warnings.filterwarnings("ignore", category=DeprecationWarning) warnings.filterwarnings("ignore", category=InsecureRequestWarning) - # project_name = f"pytestdo{random_letters()}" - project_name = "pytestdoxvzyr" + # deployment_dirs = list(Path(Path(DEPLOYMENT_DIR) / cloud).glob("do*")) + deployment_dir = get_or_create_deployment_directory(cloud) config = render_config_partial( - project_name=project_name, + project_name=deployment_dir.name, namespace="dev", - nebari_domain="do.nebari.dev", - cloud_provider="do", + nebari_domain=f"{cloud}.nebari.dev", + cloud_provider=cloud, ci_provider="github-actions", auth_provider="github", ) - tmpdir = os.getcwd() / pathlib.Path("pytestdotemp") - os.chdir(tmpdir) - print(f"Temporary directory: {tmpdir}") - config_filepath = tmpdir / "nebari-config.yaml" - with open(config_filepath, "w") as f: + deployment_dir_abs = deployment_dir.absolute() + os.chdir(deployment_dir) + print(f"Temporary directory: {deployment_dir}") + with open(Path("nebari-config.yaml"), "w") as f: yaml.dump(config, f) - - render_template(tmpdir, config_filepath) + render_template(deployment_dir_abs, Path("nebari-config.yaml")) try: deploy_configuration( config=config, @@ -64,3 +80,12 @@ def test_integration(): logger.exception(e) raise assert 1 == 1 + + +def on_cloud(param): + return pytest.mark.parametrize("deploy", [param], indirect=True) + + +@on_cloud("do") +def test_do_deployment(deploy): + assert True From fa95497f19f66df5633bf5a0dfeeea9ef1ce7d63 Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Thu, 20 Jul 2023 17:06:45 +0100 Subject: [PATCH 05/26] organize tests --- tests/conftest.py | 27 +---------- tests/deployment_fixtures.py | 80 ++++++++++++++++++++++++++++++++ tests/test_init.py | 2 +- tests/test_integration.py | 88 +----------------------------------- tests/test_render.py | 2 +- tests/test_schema.py | 2 +- tests/utils.py | 25 ++++++++++ 7 files changed, 112 insertions(+), 114 deletions(-) create mode 100644 tests/deployment_fixtures.py create mode 100644 tests/utils.py diff --git a/tests/conftest.py b/tests/conftest.py index cbcef549f1..969931731c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,33 +1,10 @@ -from functools import partial from unittest.mock import Mock import pytest -from _nebari.initialize import render_config +from tests.utils import INIT_INPUTS, NEBARI_CONFIG_FN, PRESERVED_DIR -INIT_INPUTS = [ - # project, namespace, domain, cloud_provider, ci_provider, auth_provider - ("pytestdo", "dev", "do.nebari.dev", "do", "github-actions", "github"), - ("pytestaws", "dev", "aws.nebari.dev", "aws", "github-actions", "github"), - ("pytestgcp", "dev", "gcp.nebari.dev", "gcp", "github-actions", "github"), - ("pytestazure", "dev", "azure.nebari.dev", "azure", "github-actions", "github"), -] - -NEBARI_CONFIG_FN = "nebari-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_plugins = ["tests.deployment_fixtures"] @pytest.fixture(params=INIT_INPUTS) diff --git a/tests/deployment_fixtures.py b/tests/deployment_fixtures.py new file mode 100644 index 0000000000..16fa650d0d --- /dev/null +++ b/tests/deployment_fixtures.py @@ -0,0 +1,80 @@ +import logging +import pytest +import os +from pathlib import Path +from urllib3.exceptions import InsecureRequestWarning + +import yaml + +from _nebari.deploy import deploy_configuration +from _nebari.render import render_template +from .utils import render_config_partial + +import random +import string +import warnings + +DEPLOYMENT_DIR = '_test_deploy' + +logger = logging.getLogger(__name__) + + +def random_letters(length=5): + letters = string.ascii_letters + return ''.join(random.choice(letters) for _ in range(length)).lower() + + +def get_or_create_deployment_directory(cloud): + deployment_dirs = list(Path(Path(DEPLOYMENT_DIR) / cloud).glob("pytestdoxvzyr")) + if deployment_dirs: + deployment_dir = deployment_dirs[0] + else: + project_name = f"pytest{cloud}{random_letters()}" + deployment_dir = Path(Path(Path(DEPLOYMENT_DIR) / cloud) / project_name) + deployment_dir.mkdir() + return deployment_dir + + +@pytest.fixture +def deploy( + request, +): + # Ignore this for now, as test is failing due to a + # DeprecationWarning + cloud = request.param + warnings.filterwarnings("ignore", category=DeprecationWarning) + warnings.filterwarnings("ignore", category=InsecureRequestWarning) + # deployment_dirs = list(Path(Path(DEPLOYMENT_DIR) / cloud).glob("do*")) + deployment_dir = get_or_create_deployment_directory(cloud) + config = render_config_partial( + project_name=deployment_dir.name, + namespace="dev", + nebari_domain=f"{cloud}.nebari.dev", + cloud_provider=cloud, + ci_provider="github-actions", + auth_provider="github", + ) + deployment_dir_abs = deployment_dir.absolute() + os.chdir(deployment_dir) + print(f"Temporary directory: {deployment_dir}") + with open(Path("nebari-config.yaml"), "w") as f: + yaml.dump(config, f) + render_template(deployment_dir_abs, Path("nebari-config.yaml")) + try: + deploy_configuration( + config=config, + dns_provider="cloudflare", + dns_auto_provision=True, + disable_prompt=True, + disable_checks=False, + skip_remote_state_provision=False, + ) + except Exception as e: + print(f"Deploy Failed, Exception: {e}") + logger.exception(e) + raise + assert 1 == 1 + + +def on_cloud(param): + return pytest.mark.parametrize("deploy", [param], indirect=True) diff --git a/tests/test_init.py b/tests/test_init.py index 69a007fead..a64d511fc7 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -1,6 +1,6 @@ import pytest -from .conftest import render_config_partial +from .utils import render_config_partial @pytest.mark.parametrize( diff --git a/tests/test_integration.py b/tests/test_integration.py index a59e6edbfb..6447d12829 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -1,91 +1,7 @@ -import logging -import pytest -import os -from pathlib import Path -from urllib3.exceptions import InsecureRequestWarning - -import yaml - -from _nebari.deploy import deploy_configuration -from _nebari.render import render_template -from .conftest import render_config_partial - -import random -import string -import warnings - -DEPLOYMENT_DIR = '_test_deploy' - -logging.basicConfig( - level=logging.INFO, - format="%(asctime)s %(levelname)9s %(lineno)4s %(module)s: %(message)s" -) - -logger = logging.getLogger(__name__) -logger.setLevel(logging.INFO) - - -def random_letters(length=5): - letters = string.ascii_letters - return ''.join(random.choice(letters) for _ in range(length)).lower() - - -def get_or_create_deployment_directory(cloud): - deployment_dirs = list(Path(Path(DEPLOYMENT_DIR) / cloud).glob("pytestdoxvzyr")) - if deployment_dirs: - deployment_dir = deployment_dirs[0] - else: - project_name = f"pytest{cloud}{random_letters()}" - deployment_dir = Path(Path(Path(DEPLOYMENT_DIR) / cloud) / project_name) - deployment_dir.mkdir() - return deployment_dir - - -@pytest.fixture -def deploy( - request, -): - # Ignore this for now, as test is failing due to a - # DeprecationWarning - cloud = request.param - warnings.filterwarnings("ignore", category=DeprecationWarning) - warnings.filterwarnings("ignore", category=InsecureRequestWarning) - # deployment_dirs = list(Path(Path(DEPLOYMENT_DIR) / cloud).glob("do*")) - deployment_dir = get_or_create_deployment_directory(cloud) - config = render_config_partial( - project_name=deployment_dir.name, - namespace="dev", - nebari_domain=f"{cloud}.nebari.dev", - cloud_provider=cloud, - ci_provider="github-actions", - auth_provider="github", - ) - deployment_dir_abs = deployment_dir.absolute() - os.chdir(deployment_dir) - print(f"Temporary directory: {deployment_dir}") - with open(Path("nebari-config.yaml"), "w") as f: - yaml.dump(config, f) - render_template(deployment_dir_abs, Path("nebari-config.yaml")) - try: - deploy_configuration( - config=config, - dns_provider="cloudflare", - dns_auto_provision=True, - disable_prompt=True, - disable_checks=False, - skip_remote_state_provision=False, - ) - except Exception as e: - print(f"Deploy Failed, Exception: {e}") - logger.exception(e) - raise - assert 1 == 1 - - -def on_cloud(param): - return pytest.mark.parametrize("deploy", [param], indirect=True) +from tests.deployment_fixtures import on_cloud @on_cloud("do") def test_do_deployment(deploy): + """Tests if deployment on DigitalOcean succeeds""" assert True diff --git a/tests/test_render.py b/tests/test_render.py index 7667a3163e..3c0a2090cc 100644 --- a/tests/test_render.py +++ b/tests/test_render.py @@ -6,7 +6,7 @@ from _nebari.render import render_template, set_env_vars_in_config -from .conftest import PRESERVED_DIR, render_config_partial +from .utils import render_config_partial, PRESERVED_DIR @pytest.fixture diff --git a/tests/test_schema.py b/tests/test_schema.py index ea4d0d3299..d4d8cf8781 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -1,6 +1,6 @@ import _nebari.schema -from .conftest import render_config_partial +from .utils import render_config_partial def test_schema(setup_fixture): diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 0000000000..82dffdcd3c --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,25 @@ +from functools import partial + +from _nebari.initialize import render_config + +DEFAULT_TERRAFORM_STATE = "remote" + +DEFAULT_GH_REPO = "github.com/test/test" +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, +) +INIT_INPUTS = [ + # project, namespace, domain, cloud_provider, ci_provider, auth_provider + ("pytestdo", "dev", "do.nebari.dev", "do", "github-actions", "github"), + ("pytestaws", "dev", "aws.nebari.dev", "aws", "github-actions", "github"), + ("pytestgcp", "dev", "gcp.nebari.dev", "gcp", "github-actions", "github"), + ("pytestazure", "dev", "azure.nebari.dev", "azure", "github-actions", "github"), +] + +NEBARI_CONFIG_FN = "nebari-config.yaml" +PRESERVED_DIR = "preserved_dir" From 101e4e850ed90720718233475c436a38423c49fc Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Thu, 20 Jul 2023 17:15:04 +0100 Subject: [PATCH 06/26] create destroy fixture --- tests/deployment_fixtures.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/deployment_fixtures.py b/tests/deployment_fixtures.py index 16fa650d0d..15f32fc208 100644 --- a/tests/deployment_fixtures.py +++ b/tests/deployment_fixtures.py @@ -7,6 +7,7 @@ import yaml from _nebari.deploy import deploy_configuration +from _nebari.destroy import destroy_configuration from _nebari.render import render_template from .utils import render_config_partial @@ -25,6 +26,7 @@ def random_letters(length=5): def get_or_create_deployment_directory(cloud): + # deployment_dirs = list(Path(Path(DEPLOYMENT_DIR) / cloud).glob("do*")) deployment_dirs = list(Path(Path(DEPLOYMENT_DIR) / cloud).glob("pytestdoxvzyr")) if deployment_dirs: deployment_dir = deployment_dirs[0] @@ -44,7 +46,6 @@ def deploy( cloud = request.param warnings.filterwarnings("ignore", category=DeprecationWarning) warnings.filterwarnings("ignore", category=InsecureRequestWarning) - # deployment_dirs = list(Path(Path(DEPLOYMENT_DIR) / cloud).glob("do*")) deployment_dir = get_or_create_deployment_directory(cloud) config = render_config_partial( project_name=deployment_dir.name, @@ -76,5 +77,12 @@ def deploy( assert 1 == 1 +def destroy(cloud): + deployment_dirs = list(Path(Path(DEPLOYMENT_DIR) / cloud).glob(f"{cloud}*")) + if not deployment_dirs: + print("Configuration not found") + destroy_configuration(deployment_dirs[0] / Path("nebari-config.yaml")) + + def on_cloud(param): return pytest.mark.parametrize("deploy", [param], indirect=True) From 01bc8192ae12367aeadafecf4159b87d03a64e7e Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Thu, 20 Jul 2023 17:19:42 +0100 Subject: [PATCH 07/26] return stage outputs from deploy --- src/_nebari/deploy.py | 3 ++- tests/deployment_fixtures.py | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/_nebari/deploy.py b/src/_nebari/deploy.py index cbe579bf48..0dcd951af7 100644 --- a/src/_nebari/deploy.py +++ b/src/_nebari/deploy.py @@ -256,6 +256,7 @@ def guided_install( print( "Additional administration docs can be found at https://docs.nebari.dev/en/stable/source/admin_guide/" ) + return stage_outputs def deploy_configuration( @@ -293,7 +294,7 @@ def deploy_configuration( with timer(logger, "deploying Nebari"): try: - guided_install( + return guided_install( config, dns_provider, dns_auto_provision, diff --git a/tests/deployment_fixtures.py b/tests/deployment_fixtures.py index 15f32fc208..9274ec8f0a 100644 --- a/tests/deployment_fixtures.py +++ b/tests/deployment_fixtures.py @@ -62,7 +62,7 @@ def deploy( yaml.dump(config, f) render_template(deployment_dir_abs, Path("nebari-config.yaml")) try: - deploy_configuration( + return deploy_configuration( config=config, dns_provider="cloudflare", dns_auto_provision=True, @@ -74,7 +74,6 @@ def deploy( print(f"Deploy Failed, Exception: {e}") logger.exception(e) raise - assert 1 == 1 def destroy(cloud): From 28230af81fcd4e2b650cc4b974d0b9069ab588bd Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Thu, 20 Jul 2023 17:23:57 +0100 Subject: [PATCH 08/26] remove calling tests in every file --- tests_e2e/playwright/navigator.py | 19 ------------------ tests_e2e/playwright/run_notebook.py | 29 ---------------------------- 2 files changed, 48 deletions(-) diff --git a/tests_e2e/playwright/navigator.py b/tests_e2e/playwright/navigator.py index d60ffdde6c..5bf0d3efe3 100644 --- a/tests_e2e/playwright/navigator.py +++ b/tests_e2e/playwright/navigator.py @@ -1,12 +1,10 @@ import contextlib import datetime as dt import logging -import os import re import time import urllib -import dotenv from playwright.sync_api import expect, sync_playwright logger = logging.getLogger() @@ -410,20 +408,3 @@ def write_file(self, filepath, content): self.run_terminal_command(f"ls {filepath}") logger.debug(f"time to complete {dt.datetime.now() - start}") time.sleep(2) - - -if __name__ == "__main__": - dotenv.load_dotenv() - nav = Navigator( - nebari_url="https://nebari.quansight.dev/", - username=os.environ["KEYCLOAK_USERNAME"], - password=os.environ["KEYCLOAK_PASSWORD"], - auth="password", - instance_name="small-instance", - headless=False, - slow_mo=100, - ) - nav.login() - nav.start_server() - nav.reset_workspace() - nav.teardown() diff --git a/tests_e2e/playwright/run_notebook.py b/tests_e2e/playwright/run_notebook.py index 351f266de9..a54a507fd8 100644 --- a/tests_e2e/playwright/run_notebook.py +++ b/tests_e2e/playwright/run_notebook.py @@ -84,32 +84,3 @@ def _restart_run_all(self): ) if restart_dialog_button.is_visible(): restart_dialog_button.click() - - -if __name__ == "__main__": - dotenv.load_dotenv() - nav = Navigator( - nebari_url="https://nebari.quansight.dev/", - username=os.environ["KEYCLOAK_USERNAME"], - password=os.environ["KEYCLOAK_PASSWORD"], - auth="password", - instance_name="small-instance", - headless=False, - slow_mo=100, - ) - nav.login() - nav.start_server() - nav.reset_workspace() - test_app = RunNotebook(navigator=nav) - notebook_filepath_in_repo = "test_data/test_notebook_output.ipynb" - notebook_filepath_on_nebari = "test_notebook_output.ipynb" - with open(notebook_filepath_in_repo, "r") as notebook: - test_app.nav.write_file( - filepath=notebook_filepath_on_nebari, content=notebook.read() - ) - test_app.run_notebook( - path="nebari/tests_e2e/playwright/test_data/test_notebook_output.ipynb", - expected_output_text="success: 6", - conda_env="conda-env-default-py", - ) - nav.teardown() From cc52b66e1cb23f1c2d8cf1c714feb38731547647 Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Thu, 20 Jul 2023 17:29:50 +0100 Subject: [PATCH 09/26] fix names of Notebook classes --- tests_e2e/playwright/run_notebook.py | 4 ++-- tests_e2e/playwright/test_playwright.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests_e2e/playwright/run_notebook.py b/tests_e2e/playwright/run_notebook.py index a54a507fd8..97c5867364 100644 --- a/tests_e2e/playwright/run_notebook.py +++ b/tests_e2e/playwright/run_notebook.py @@ -9,12 +9,12 @@ logger = logging.getLogger() -class RunNotebook: +class Notebook: def __init__(self, navigator: Navigator): self.nav = navigator self.nav.initialize - def run_notebook( + def run( self, path, expected_output_text, conda_env, runtime=30000, retry=2 ): """Run jupyter notebook and check for expected output text anywhere on diff --git a/tests_e2e/playwright/test_playwright.py b/tests_e2e/playwright/test_playwright.py index ff636904b6..264243fd90 100644 --- a/tests_e2e/playwright/test_playwright.py +++ b/tests_e2e/playwright/test_playwright.py @@ -1,12 +1,12 @@ -from run_notebook import RunNotebook +from run_notebook import Notebook def test_notebook(navigator, test_data_root): - test_app = RunNotebook(navigator=navigator) + test_app = Notebook(navigator=navigator) notebook_name = "test_notebook_output.ipynb" with open(test_data_root / notebook_name, "r") as notebook: test_app.nav.write_file(filepath=notebook_name, content=notebook.read()) - test_app.run_notebook( + test_app.run( path=notebook_name, expected_output_text="success: 6", conda_env="conda-env-default-py", From 3d2975144f784375ceceb2936234edc90fccb2fd Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Thu, 20 Jul 2023 17:34:47 +0100 Subject: [PATCH 10/26] remove unused imports --- tests_e2e/playwright/run_notebook.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests_e2e/playwright/run_notebook.py b/tests_e2e/playwright/run_notebook.py index 97c5867364..ae461dcff9 100644 --- a/tests_e2e/playwright/run_notebook.py +++ b/tests_e2e/playwright/run_notebook.py @@ -1,9 +1,7 @@ import contextlib import logging -import os from pathlib import Path -import dotenv from navigator import Navigator logger = logging.getLogger() From 9e3f499086040190665e7484bd9dfb4ea9342e24 Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Thu, 20 Jul 2023 18:18:43 +0100 Subject: [PATCH 11/26] add test for health urls --- tests/test_integration.py | 7 ------- {tests => tests_deployment}/deployment_fixtures.py | 2 +- tests_deployment/test_integration.py | 14 ++++++++++++++ 3 files changed, 15 insertions(+), 8 deletions(-) delete mode 100644 tests/test_integration.py rename {tests => tests_deployment}/deployment_fixtures.py (98%) create mode 100644 tests_deployment/test_integration.py diff --git a/tests/test_integration.py b/tests/test_integration.py deleted file mode 100644 index 6447d12829..0000000000 --- a/tests/test_integration.py +++ /dev/null @@ -1,7 +0,0 @@ -from tests.deployment_fixtures import on_cloud - - -@on_cloud("do") -def test_do_deployment(deploy): - """Tests if deployment on DigitalOcean succeeds""" - assert True diff --git a/tests/deployment_fixtures.py b/tests_deployment/deployment_fixtures.py similarity index 98% rename from tests/deployment_fixtures.py rename to tests_deployment/deployment_fixtures.py index 9274ec8f0a..67285698d0 100644 --- a/tests/deployment_fixtures.py +++ b/tests_deployment/deployment_fixtures.py @@ -9,7 +9,7 @@ from _nebari.deploy import deploy_configuration from _nebari.destroy import destroy_configuration from _nebari.render import render_template -from .utils import render_config_partial +from tests.utils import render_config_partial import random import string diff --git a/tests_deployment/test_integration.py b/tests_deployment/test_integration.py new file mode 100644 index 0000000000..74896b6d73 --- /dev/null +++ b/tests_deployment/test_integration.py @@ -0,0 +1,14 @@ +import requests + +from tests_deployment.deployment_fixtures import on_cloud + + +@on_cloud("do") +def test_do_deployment(deploy): + """Tests if deployment on DigitalOcean succeeds""" + service_urls = deploy['stages/07-kubernetes-services']['service_urls']['value'] + assert requests.get(service_urls["jupyterhub"]['health_url'], verify=False).status_code == 200 + assert requests.get(service_urls["keycloak"]['health_url'], verify=False).status_code == 200 + assert requests.get(service_urls["dask_gateway"]['health_url'], verify=False).status_code == 200 + assert requests.get(service_urls["conda_store"]['health_url'], verify=False).status_code == 200 + assert requests.get(service_urls["monitoring"]['health_url'], verify=False).status_code == 200 From 79e6c8b8289bfe1261e7e68cf7d1f4ed1e865b6e Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Thu, 20 Jul 2023 19:31:28 +0100 Subject: [PATCH 12/26] add a trivial keycloak test --- tests/conftest.py | 2 -- tests_deployment/test_integration.py | 14 -------- tests_integration/__init__.py | 0 tests_integration/conftest.py | 1 + .../deployment_fixtures.py | 33 +++++++++-------- tests_integration/test_integration.py | 36 +++++++++++++++++++ 6 files changed, 56 insertions(+), 30 deletions(-) delete mode 100644 tests_deployment/test_integration.py create mode 100644 tests_integration/__init__.py create mode 100644 tests_integration/conftest.py rename {tests_deployment => tests_integration}/deployment_fixtures.py (85%) create mode 100644 tests_integration/test_integration.py diff --git a/tests/conftest.py b/tests/conftest.py index 969931731c..65abddf335 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,8 +4,6 @@ from tests.utils import INIT_INPUTS, NEBARI_CONFIG_FN, PRESERVED_DIR -pytest_plugins = ["tests.deployment_fixtures"] - @pytest.fixture(params=INIT_INPUTS) def setup_fixture(request, monkeypatch, tmp_path): diff --git a/tests_deployment/test_integration.py b/tests_deployment/test_integration.py deleted file mode 100644 index 74896b6d73..0000000000 --- a/tests_deployment/test_integration.py +++ /dev/null @@ -1,14 +0,0 @@ -import requests - -from tests_deployment.deployment_fixtures import on_cloud - - -@on_cloud("do") -def test_do_deployment(deploy): - """Tests if deployment on DigitalOcean succeeds""" - service_urls = deploy['stages/07-kubernetes-services']['service_urls']['value'] - assert requests.get(service_urls["jupyterhub"]['health_url'], verify=False).status_code == 200 - assert requests.get(service_urls["keycloak"]['health_url'], verify=False).status_code == 200 - assert requests.get(service_urls["dask_gateway"]['health_url'], verify=False).status_code == 200 - assert requests.get(service_urls["conda_store"]['health_url'], verify=False).status_code == 200 - assert requests.get(service_urls["monitoring"]['health_url'], verify=False).status_code == 200 diff --git a/tests_integration/__init__.py b/tests_integration/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests_integration/conftest.py b/tests_integration/conftest.py new file mode 100644 index 0000000000..6a64a20abc --- /dev/null +++ b/tests_integration/conftest.py @@ -0,0 +1 @@ +pytest_plugins = ["tests_integration.deployment_fixtures"] diff --git a/tests_deployment/deployment_fixtures.py b/tests_integration/deployment_fixtures.py similarity index 85% rename from tests_deployment/deployment_fixtures.py rename to tests_integration/deployment_fixtures.py index 67285698d0..152be2407f 100644 --- a/tests_deployment/deployment_fixtures.py +++ b/tests_integration/deployment_fixtures.py @@ -1,25 +1,33 @@ import logging import pytest import os +import warnings + from pathlib import Path -from urllib3.exceptions import InsecureRequestWarning import yaml from _nebari.deploy import deploy_configuration from _nebari.destroy import destroy_configuration from _nebari.render import render_template +from urllib3.exceptions import InsecureRequestWarning from tests.utils import render_config_partial import random import string -import warnings DEPLOYMENT_DIR = '_test_deploy' logger = logging.getLogger(__name__) +def ignore_warnings(): + # Ignore this for now, as test is failing due to a + # DeprecationWarning and InsecureRequestWarning + warnings.filterwarnings("ignore", category=DeprecationWarning) + warnings.filterwarnings("ignore", category=InsecureRequestWarning) + + def random_letters(length=5): letters = string.ascii_letters return ''.join(random.choice(letters) for _ in range(length)).lower() @@ -37,15 +45,10 @@ def get_or_create_deployment_directory(cloud): return deployment_dir -@pytest.fixture -def deploy( - request, -): - # Ignore this for now, as test is failing due to a - # DeprecationWarning +@pytest.fixture(scope="session") +def deploy(request): + ignore_warnings() cloud = request.param - warnings.filterwarnings("ignore", category=DeprecationWarning) - warnings.filterwarnings("ignore", category=InsecureRequestWarning) deployment_dir = get_or_create_deployment_directory(cloud) config = render_config_partial( project_name=deployment_dir.name, @@ -57,12 +60,12 @@ def deploy( ) deployment_dir_abs = deployment_dir.absolute() os.chdir(deployment_dir) - print(f"Temporary directory: {deployment_dir}") + logger.info(f"Temporary directory: {deployment_dir}") with open(Path("nebari-config.yaml"), "w") as f: yaml.dump(config, f) render_template(deployment_dir_abs, Path("nebari-config.yaml")) try: - return deploy_configuration( + yield deploy_configuration( config=config, dns_provider="cloudflare", dns_auto_provision=True, @@ -71,15 +74,17 @@ def deploy( skip_remote_state_provision=False, ) except Exception as e: - print(f"Deploy Failed, Exception: {e}") + logger.info(f"Deploy Failed, Exception: {e}") logger.exception(e) raise + logger.info("Teardown") + return destroy(cloud) def destroy(cloud): deployment_dirs = list(Path(Path(DEPLOYMENT_DIR) / cloud).glob(f"{cloud}*")) if not deployment_dirs: - print("Configuration not found") + logger.info("Configuration not found") destroy_configuration(deployment_dirs[0] / Path("nebari-config.yaml")) diff --git a/tests_integration/test_integration.py b/tests_integration/test_integration.py new file mode 100644 index 0000000000..19214b9f90 --- /dev/null +++ b/tests_integration/test_integration.py @@ -0,0 +1,36 @@ +import pytest +import requests + +from tests_integration.deployment_fixtures import on_cloud, ignore_warnings + + +@pytest.fixture(autouse=True) +def disable_warnings(): + ignore_warnings() + + +@on_cloud("do") +def test_do_service_status(deploy): + """Tests if deployment on DigitalOcean succeeds""" + service_urls = deploy['stages/07-kubernetes-services']['service_urls']['value'] + assert requests.get(service_urls["jupyterhub"]['health_url'], verify=False).status_code == 200 + assert requests.get(service_urls["keycloak"]['health_url'], verify=False).status_code == 200 + assert requests.get(service_urls["dask_gateway"]['health_url'], verify=False).status_code == 200 + assert requests.get(service_urls["conda_store"]['health_url'], verify=False).status_code == 200 + assert requests.get(service_urls["monitoring"]['health_url'], verify=False).status_code == 200 + + +@on_cloud("do") +def test_verify_keycloak_users(deploy): + """Tests if keycloak is working and it has expected users""" + keycloak_credentials = deploy['stages/05-kubernetes-keycloak']['keycloak_credentials']['value'] + from keycloak import KeycloakAdmin + keycloak_admin = KeycloakAdmin( + server_url=f"{keycloak_credentials['url']}/auth/", + username=keycloak_credentials['username'], + password=keycloak_credentials['password'], + realm_name=keycloak_credentials['realm'], + client_id=keycloak_credentials['client_id'], + verify=False, + ) + assert set([u['username'] for u in keycloak_admin.get_users()]) == {'nebari-bot', 'read-only-user', 'root'} From 178ee3979771b8c19f1eafbdd8f2caf14cf1e4aa Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Thu, 20 Jul 2023 23:35:24 +0100 Subject: [PATCH 13/26] fix teardown --- tests_integration/deployment_fixtures.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/tests_integration/deployment_fixtures.py b/tests_integration/deployment_fixtures.py index 152be2407f..cc4ef129b1 100644 --- a/tests_integration/deployment_fixtures.py +++ b/tests_integration/deployment_fixtures.py @@ -34,8 +34,7 @@ def random_letters(length=5): def get_or_create_deployment_directory(cloud): - # deployment_dirs = list(Path(Path(DEPLOYMENT_DIR) / cloud).glob("do*")) - deployment_dirs = list(Path(Path(DEPLOYMENT_DIR) / cloud).glob("pytestdoxvzyr")) + deployment_dirs = list(Path(Path(DEPLOYMENT_DIR) / cloud).glob("pytest{cloud}*")) if deployment_dirs: deployment_dir = deployment_dirs[0] else: @@ -78,14 +77,11 @@ def deploy(request): logger.exception(e) raise logger.info("Teardown") - return destroy(cloud) + return destroy(config) -def destroy(cloud): - deployment_dirs = list(Path(Path(DEPLOYMENT_DIR) / cloud).glob(f"{cloud}*")) - if not deployment_dirs: - logger.info("Configuration not found") - destroy_configuration(deployment_dirs[0] / Path("nebari-config.yaml")) +def destroy(config): + destroy_configuration(config) def on_cloud(param): From 6d06133e1b64edad34f4e7bea7883f9272118aca Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Fri, 21 Jul 2023 00:05:00 +0100 Subject: [PATCH 14/26] add workflow for integration tests --- .github/workflows/integration_test.yaml | 33 ++++++++++++++++++++++++ tests_integration/deployment_fixtures.py | 10 +++++-- 2 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/integration_test.yaml diff --git a/.github/workflows/integration_test.yaml b/.github/workflows/integration_test.yaml new file mode 100644 index 0000000000..6b5f336281 --- /dev/null +++ b/.github/workflows/integration_test.yaml @@ -0,0 +1,33 @@ +name: "Integration Tests" + +on: + pull_request: + push: + +jobs: + test-integration: + name: "Pytest Integration" + runs-on: ubuntu-latest + steps: + - name: "Checkout Infrastructure" + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: 3.11 + - name: Install Nebari + run: | + pip install .[dev] + conda install --quiet --yes conda-build + - name: Integration Tests + run: | + pytest --version + pytest tests_integration -vvv + env: + NEBARI_K8S_VERSION: 1.24.13-do.0 + DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }} + SPACES_ACCESS_KEY_ID: ${{ secrets.SPACES_ACCESS_KEY_ID }} + SPACES_SECRET_ACCESS_KEY: ${{ secrets.SPACES_SECRET_ACCESS_KEY }} + CLOUDFLARE_TOKEN: ${{ secrets.CLOUDFLARE_TOKEN }} diff --git a/tests_integration/deployment_fixtures.py b/tests_integration/deployment_fixtures.py index cc4ef129b1..e6ba9e93dc 100644 --- a/tests_integration/deployment_fixtures.py +++ b/tests_integration/deployment_fixtures.py @@ -34,7 +34,7 @@ def random_letters(length=5): def get_or_create_deployment_directory(cloud): - deployment_dirs = list(Path(Path(DEPLOYMENT_DIR) / cloud).glob("pytest{cloud}*")) + deployment_dirs = list(Path(Path(DEPLOYMENT_DIR) / cloud).glob(f"pytest{cloud}*")) if deployment_dirs: deployment_dir = deployment_dirs[0] else: @@ -44,15 +44,21 @@ def get_or_create_deployment_directory(cloud): return deployment_dir +def set_do_environment(): + os.environ['AWS_ACCESS_KEY_ID'] = os.environ['SPACES_ACCESS_KEY_ID'] + os.environ['AWS_SECRET_ACCESS_KEY'] = os.environ['SPACES_SECRET_ACCESS_KEY'] + + @pytest.fixture(scope="session") def deploy(request): ignore_warnings() cloud = request.param + set_do_environment() deployment_dir = get_or_create_deployment_directory(cloud) config = render_config_partial( project_name=deployment_dir.name, namespace="dev", - nebari_domain=f"{cloud}.nebari.dev", + nebari_domain=f"ci-{cloud}.nebari.dev", cloud_provider=cloud, ci_provider="github-actions", auth_provider="github", From 064417597b02f6196330208defc30a8b71e80a04 Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Fri, 21 Jul 2023 00:09:29 +0100 Subject: [PATCH 15/26] mkdir with parents true --- tests_integration/deployment_fixtures.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests_integration/deployment_fixtures.py b/tests_integration/deployment_fixtures.py index e6ba9e93dc..18da6a1f18 100644 --- a/tests_integration/deployment_fixtures.py +++ b/tests_integration/deployment_fixtures.py @@ -40,7 +40,7 @@ def get_or_create_deployment_directory(cloud): else: project_name = f"pytest{cloud}{random_letters()}" deployment_dir = Path(Path(Path(DEPLOYMENT_DIR) / cloud) / project_name) - deployment_dir.mkdir() + deployment_dir.mkdir(parents=True) return deployment_dir From 0a9515b3fdb82f94b0cd899bf05e00c64b6f59a7 Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Fri, 21 Jul 2023 09:45:16 +0100 Subject: [PATCH 16/26] pytest command fix --- .github/workflows/integration_test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration_test.yaml b/.github/workflows/integration_test.yaml index 6b5f336281..ce6ecf02ef 100644 --- a/.github/workflows/integration_test.yaml +++ b/.github/workflows/integration_test.yaml @@ -24,7 +24,7 @@ jobs: - name: Integration Tests run: | pytest --version - pytest tests_integration -vvv + pytest tests_integration/ -vvv env: NEBARI_K8S_VERSION: 1.24.13-do.0 DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }} From c2e1387b723306da8e18c9217896099f2b9f3462 Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Fri, 21 Jul 2023 09:46:44 +0100 Subject: [PATCH 17/26] debug --- .github/workflows/integration_test.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/integration_test.yaml b/.github/workflows/integration_test.yaml index ce6ecf02ef..89a5a726af 100644 --- a/.github/workflows/integration_test.yaml +++ b/.github/workflows/integration_test.yaml @@ -21,6 +21,9 @@ jobs: run: | pip install .[dev] conda install --quiet --yes conda-build + - name: Debugging with tmate + uses: mxschmitt/action-tmate@v3.16 + - name: Integration Tests run: | pytest --version From 18fb30b8e7b63a7253df51c121d9fb82a111f601 Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Fri, 21 Jul 2023 10:06:22 +0100 Subject: [PATCH 18/26] add some documentation to run tests --- .github/workflows/integration_test.yaml | 11 +++++----- tests_integration/README.md | 26 ++++++++++++++++++++++++ tests_integration/deployment_fixtures.py | 23 ++++++++++++--------- 3 files changed, 45 insertions(+), 15 deletions(-) create mode 100644 tests_integration/README.md diff --git a/.github/workflows/integration_test.yaml b/.github/workflows/integration_test.yaml index 89a5a726af..5dc954995f 100644 --- a/.github/workflows/integration_test.yaml +++ b/.github/workflows/integration_test.yaml @@ -1,7 +1,10 @@ -name: "Integration Tests" +name: "Deploy on Digital Ocean" on: - pull_request: + schedule: + - cron: "0 0 * * MON" + workflow_dispatch: + # DISABLE push before merge push: jobs: @@ -21,13 +24,11 @@ jobs: run: | pip install .[dev] conda install --quiet --yes conda-build - - name: Debugging with tmate - uses: mxschmitt/action-tmate@v3.16 - name: Integration Tests run: | pytest --version - pytest tests_integration/ -vvv + pytest tests_integration/ -vvv -s env: NEBARI_K8S_VERSION: 1.24.13-do.0 DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }} diff --git a/tests_integration/README.md b/tests_integration/README.md new file mode 100644 index 0000000000..6735ae4eaa --- /dev/null +++ b/tests_integration/README.md @@ -0,0 +1,26 @@ +# Integration Testing via Pytest + +These tests are designed to test things on Nebari deployed +on cloud. At the moment it only deploys on DigitalOcean. + +You need the following environment variables to run these. + +```bash +DIGITALOCEAN_TOKEN +NEBARI_K8S_VERSION +SPACES_ACCESS_KEY_ID +SPACES_SECRET_ACCESS_KEY +CLOUDFLARE_TOKEN +``` + +For instructions on how to get these variables check the documentation +for DigitalOcean deployment. + +Running Tests: + +```bash +pytest tests_integration -vvv -s +``` + +This would deploy on digitalocean, run tests on the deployment +and then teardown the cluster. diff --git a/tests_integration/deployment_fixtures.py b/tests_integration/deployment_fixtures.py index 18da6a1f18..28a1f9124a 100644 --- a/tests_integration/deployment_fixtures.py +++ b/tests_integration/deployment_fixtures.py @@ -28,33 +28,37 @@ def ignore_warnings(): warnings.filterwarnings("ignore", category=InsecureRequestWarning) -def random_letters(length=5): +def _random_letters(length=5): letters = string.ascii_letters return ''.join(random.choice(letters) for _ in range(length)).lower() -def get_or_create_deployment_directory(cloud): +def _get_or_create_deployment_directory(cloud): + """This will create a directory to initialise and deploy + Nebari from. + """ deployment_dirs = list(Path(Path(DEPLOYMENT_DIR) / cloud).glob(f"pytest{cloud}*")) if deployment_dirs: deployment_dir = deployment_dirs[0] else: - project_name = f"pytest{cloud}{random_letters()}" + project_name = f"pytest{cloud}{_random_letters()}" deployment_dir = Path(Path(Path(DEPLOYMENT_DIR) / cloud) / project_name) deployment_dir.mkdir(parents=True) return deployment_dir -def set_do_environment(): +def _set_do_environment(): os.environ['AWS_ACCESS_KEY_ID'] = os.environ['SPACES_ACCESS_KEY_ID'] os.environ['AWS_SECRET_ACCESS_KEY'] = os.environ['SPACES_SECRET_ACCESS_KEY'] @pytest.fixture(scope="session") def deploy(request): + """Deploy Nebari on the given cloud, currently only DigitalOcean""" ignore_warnings() cloud = request.param - set_do_environment() - deployment_dir = get_or_create_deployment_directory(cloud) + _set_do_environment() + deployment_dir = _get_or_create_deployment_directory(cloud) config = render_config_partial( project_name=deployment_dir.name, namespace="dev", @@ -81,12 +85,11 @@ def deploy(request): except Exception as e: logger.info(f"Deploy Failed, Exception: {e}") logger.exception(e) - raise - logger.info("Teardown") - return destroy(config) + logger.info("Tearing down") + return _destroy(config) -def destroy(config): +def _destroy(config): destroy_configuration(config) From 8f92bb370f6d15eb1caf06467c729c812077203e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 21 Jul 2023 09:14:24 +0000 Subject: [PATCH 19/26] [pre-commit.ci] Apply automatic pre-commit fixes --- src/_nebari/provider/cloud/digital_ocean.py | 6 ++- tests/test_render.py | 2 +- tests_e2e/playwright/run_notebook.py | 4 +- tests_integration/deployment_fixtures.py | 18 ++++--- tests_integration/test_integration.py | 52 +++++++++++++++------ 5 files changed, 53 insertions(+), 29 deletions(-) diff --git a/src/_nebari/provider/cloud/digital_ocean.py b/src/_nebari/provider/cloud/digital_ocean.py index 645406f7c5..fd55672f1e 100644 --- a/src/_nebari/provider/cloud/digital_ocean.py +++ b/src/_nebari/provider/cloud/digital_ocean.py @@ -44,7 +44,9 @@ def regions(): def kubernetes_versions(region): """Return list of available kubernetes supported by cloud provider. Sorted from oldest to latest.""" supported_kubernetes_versions = sorted( - [_["slug"].split('-')[0] for _ in _kubernetes_options()["options"]["versions"]] + [_["slug"].split("-")[0] for _ in _kubernetes_options()["options"]["versions"]] + ) + filtered_versions = filter_by_highest_supported_k8s_version( + supported_kubernetes_versions ) - filtered_versions = filter_by_highest_supported_k8s_version(supported_kubernetes_versions) return [f"{v}-do.0" for v in filtered_versions] diff --git a/tests/test_render.py b/tests/test_render.py index 3c0a2090cc..2ec7f407a1 100644 --- a/tests/test_render.py +++ b/tests/test_render.py @@ -6,7 +6,7 @@ from _nebari.render import render_template, set_env_vars_in_config -from .utils import render_config_partial, PRESERVED_DIR +from .utils import PRESERVED_DIR, render_config_partial @pytest.fixture diff --git a/tests_e2e/playwright/run_notebook.py b/tests_e2e/playwright/run_notebook.py index ae461dcff9..12d2e20382 100644 --- a/tests_e2e/playwright/run_notebook.py +++ b/tests_e2e/playwright/run_notebook.py @@ -12,9 +12,7 @@ def __init__(self, navigator: Navigator): self.nav = navigator self.nav.initialize - def run( - self, path, expected_output_text, conda_env, runtime=30000, retry=2 - ): + def run(self, path, expected_output_text, conda_env, runtime=30000, retry=2): """Run jupyter notebook and check for expected output text anywhere on the page. diff --git a/tests_integration/deployment_fixtures.py b/tests_integration/deployment_fixtures.py index 28a1f9124a..bbf7190a59 100644 --- a/tests_integration/deployment_fixtures.py +++ b/tests_integration/deployment_fixtures.py @@ -1,22 +1,20 @@ import logging -import pytest import os +import random +import string import warnings - from pathlib import Path +import pytest import yaml +from urllib3.exceptions import InsecureRequestWarning from _nebari.deploy import deploy_configuration from _nebari.destroy import destroy_configuration from _nebari.render import render_template -from urllib3.exceptions import InsecureRequestWarning from tests.utils import render_config_partial -import random -import string - -DEPLOYMENT_DIR = '_test_deploy' +DEPLOYMENT_DIR = "_test_deploy" logger = logging.getLogger(__name__) @@ -30,7 +28,7 @@ def ignore_warnings(): def _random_letters(length=5): letters = string.ascii_letters - return ''.join(random.choice(letters) for _ in range(length)).lower() + return "".join(random.choice(letters) for _ in range(length)).lower() def _get_or_create_deployment_directory(cloud): @@ -48,8 +46,8 @@ def _get_or_create_deployment_directory(cloud): def _set_do_environment(): - os.environ['AWS_ACCESS_KEY_ID'] = os.environ['SPACES_ACCESS_KEY_ID'] - os.environ['AWS_SECRET_ACCESS_KEY'] = os.environ['SPACES_SECRET_ACCESS_KEY'] + os.environ["AWS_ACCESS_KEY_ID"] = os.environ["SPACES_ACCESS_KEY_ID"] + os.environ["AWS_SECRET_ACCESS_KEY"] = os.environ["SPACES_SECRET_ACCESS_KEY"] @pytest.fixture(scope="session") diff --git a/tests_integration/test_integration.py b/tests_integration/test_integration.py index 19214b9f90..7ea18dfc23 100644 --- a/tests_integration/test_integration.py +++ b/tests_integration/test_integration.py @@ -1,7 +1,7 @@ import pytest import requests -from tests_integration.deployment_fixtures import on_cloud, ignore_warnings +from tests_integration.deployment_fixtures import ignore_warnings, on_cloud @pytest.fixture(autouse=True) @@ -12,25 +12,51 @@ def disable_warnings(): @on_cloud("do") def test_do_service_status(deploy): """Tests if deployment on DigitalOcean succeeds""" - service_urls = deploy['stages/07-kubernetes-services']['service_urls']['value'] - assert requests.get(service_urls["jupyterhub"]['health_url'], verify=False).status_code == 200 - assert requests.get(service_urls["keycloak"]['health_url'], verify=False).status_code == 200 - assert requests.get(service_urls["dask_gateway"]['health_url'], verify=False).status_code == 200 - assert requests.get(service_urls["conda_store"]['health_url'], verify=False).status_code == 200 - assert requests.get(service_urls["monitoring"]['health_url'], verify=False).status_code == 200 + service_urls = deploy["stages/07-kubernetes-services"]["service_urls"]["value"] + assert ( + requests.get(service_urls["jupyterhub"]["health_url"], verify=False).status_code + == 200 + ) + assert ( + requests.get(service_urls["keycloak"]["health_url"], verify=False).status_code + == 200 + ) + assert ( + requests.get( + service_urls["dask_gateway"]["health_url"], verify=False + ).status_code + == 200 + ) + assert ( + requests.get( + service_urls["conda_store"]["health_url"], verify=False + ).status_code + == 200 + ) + assert ( + requests.get(service_urls["monitoring"]["health_url"], verify=False).status_code + == 200 + ) @on_cloud("do") def test_verify_keycloak_users(deploy): """Tests if keycloak is working and it has expected users""" - keycloak_credentials = deploy['stages/05-kubernetes-keycloak']['keycloak_credentials']['value'] + keycloak_credentials = deploy["stages/05-kubernetes-keycloak"][ + "keycloak_credentials" + ]["value"] from keycloak import KeycloakAdmin + keycloak_admin = KeycloakAdmin( server_url=f"{keycloak_credentials['url']}/auth/", - username=keycloak_credentials['username'], - password=keycloak_credentials['password'], - realm_name=keycloak_credentials['realm'], - client_id=keycloak_credentials['client_id'], + username=keycloak_credentials["username"], + password=keycloak_credentials["password"], + realm_name=keycloak_credentials["realm"], + client_id=keycloak_credentials["client_id"], verify=False, ) - assert set([u['username'] for u in keycloak_admin.get_users()]) == {'nebari-bot', 'read-only-user', 'root'} + assert set([u["username"] for u in keycloak_admin.get_users()]) == { + "nebari-bot", + "read-only-user", + "root", + } From e50d3586d0efdf3d51b9bb69483381d04f49c928 Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Fri, 21 Jul 2023 10:21:10 +0100 Subject: [PATCH 20/26] remove DO deployment on push --- .github/workflows/integration_test.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/integration_test.yaml b/.github/workflows/integration_test.yaml index 5dc954995f..ac90ea57e0 100644 --- a/.github/workflows/integration_test.yaml +++ b/.github/workflows/integration_test.yaml @@ -4,8 +4,6 @@ on: schedule: - cron: "0 0 * * MON" workflow_dispatch: - # DISABLE push before merge - push: jobs: test-integration: From 9bc5f67544d78ad63b3a43b485ee0f6e77c71d23 Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Wed, 26 Jul 2023 14:57:27 +0100 Subject: [PATCH 21/26] bump highest supported k8s version to 1.24.16 --- src/_nebari/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_nebari/constants.py b/src/_nebari/constants.py index cc8f8e776a..387194a1f7 100644 --- a/src/_nebari/constants.py +++ b/src/_nebari/constants.py @@ -5,7 +5,7 @@ # 04-kubernetes-ingress DEFAULT_TRAEFIK_IMAGE_TAG = "2.9.1" -HIGHEST_SUPPORTED_K8S_VERSION = "1.24.13" +HIGHEST_SUPPORTED_K8S_VERSION = "1.24.16" DEFAULT_GKE_RELEASE_CHANNEL = "UNSPECIFIED" DEFAULT_NEBARI_DASK_VERSION = CURRENT_RELEASE From b72af9d1bc3ddc59599a01c71d92e8018caf37fb Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Thu, 3 Aug 2023 12:38:27 +0100 Subject: [PATCH 22/26] retrieve secrets from vault --- .github/workflows/integration_test.yaml | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/.github/workflows/integration_test.yaml b/.github/workflows/integration_test.yaml index ac90ea57e0..a0fcbf1029 100644 --- a/.github/workflows/integration_test.yaml +++ b/.github/workflows/integration_test.yaml @@ -18,6 +18,22 @@ jobs: uses: actions/setup-python@v4 with: python-version: 3.11 + + - name: Retrieve secret from Vault + uses: hashicorp/vault-action@v2.5.0 + with: + method: jwt + url: "https://quansight-vault-public-vault-b2379fa7.d415e30e.z1.hashicorp.cloud:8200" + namespace: "admin/quansight" + role: "repository-nebari-dev-nebari-role" + secrets: | + kv/data/repository/nebari-dev/nebari/amazon_web_services/nebari-dev-ci role_name | AWS_ROLE_ARN; + kv/data/repository/nebari-dev/nebari/google_cloud_platform/nebari-dev-ci/github-nebari-dev-repo-ci project_id | PROJECT_ID; + kv/data/repository/nebari-dev/nebari/google_cloud_platform/nebari-dev-ci/github-nebari-dev-repo-ci workload_identity_provider | GCP_WORKFLOW_PROVIDER; + kv/data/repository/nebari-dev/nebari/google_cloud_platform/nebari-dev-ci/github-nebari-dev-repo-ci service_account_name | GCP_SERVICE_ACCOUNT; + kv/data/repository/nebari-dev/nebari/shared_secrets DIGITALOCEAN_TOKEN | DIGITALOCEAN_TOKEN; + kv/data/repository/nebari-dev/nebari/cloudflare/internal-devops@quansight.com/nebari-dev token | CLOUDFLARE_TOKEN; + - name: Install Nebari run: | pip install .[dev] @@ -28,8 +44,6 @@ jobs: pytest --version pytest tests_integration/ -vvv -s env: - NEBARI_K8S_VERSION: 1.24.13-do.0 - DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }} + NEBARI_K8S_VERSION: 1.25.12-do.0 SPACES_ACCESS_KEY_ID: ${{ secrets.SPACES_ACCESS_KEY_ID }} SPACES_SECRET_ACCESS_KEY: ${{ secrets.SPACES_SECRET_ACCESS_KEY }} - CLOUDFLARE_TOKEN: ${{ secrets.CLOUDFLARE_TOKEN }} From 52222372cea44e97b37e9e8599fdaca98ffda563 Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Thu, 3 Aug 2023 12:38:59 +0100 Subject: [PATCH 23/26] trigger deploy --- .github/workflows/integration_test.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/integration_test.yaml b/.github/workflows/integration_test.yaml index a0fcbf1029..dd981a0b17 100644 --- a/.github/workflows/integration_test.yaml +++ b/.github/workflows/integration_test.yaml @@ -1,6 +1,7 @@ name: "Deploy on Digital Ocean" on: + push: schedule: - cron: "0 0 * * MON" workflow_dispatch: From abaf975ad668286b2f9aaa12c06f6181fea711db Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Thu, 3 Aug 2023 12:42:46 +0100 Subject: [PATCH 24/26] add permissions to pr and bump k8s version --- .github/workflows/integration_test.yaml | 3 +++ src/_nebari/constants.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/integration_test.yaml b/.github/workflows/integration_test.yaml index dd981a0b17..abfaed697e 100644 --- a/.github/workflows/integration_test.yaml +++ b/.github/workflows/integration_test.yaml @@ -10,6 +10,9 @@ jobs: test-integration: name: "Pytest Integration" runs-on: ubuntu-latest + permissions: + id-token: write + contents: read steps: - name: "Checkout Infrastructure" uses: actions/checkout@v3 diff --git a/src/_nebari/constants.py b/src/_nebari/constants.py index 387194a1f7..2f79a62738 100644 --- a/src/_nebari/constants.py +++ b/src/_nebari/constants.py @@ -5,7 +5,7 @@ # 04-kubernetes-ingress DEFAULT_TRAEFIK_IMAGE_TAG = "2.9.1" -HIGHEST_SUPPORTED_K8S_VERSION = "1.24.16" +HIGHEST_SUPPORTED_K8S_VERSION = "1.25.12" DEFAULT_GKE_RELEASE_CHANNEL = "UNSPECIFIED" DEFAULT_NEBARI_DASK_VERSION = CURRENT_RELEASE From ed32b8ebbecf7c7a88280754fbf3cb782be223f2 Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Thu, 3 Aug 2023 13:58:52 +0100 Subject: [PATCH 25/26] fix cloudflare token --- .github/workflows/integration_test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration_test.yaml b/.github/workflows/integration_test.yaml index abfaed697e..9581fcef27 100644 --- a/.github/workflows/integration_test.yaml +++ b/.github/workflows/integration_test.yaml @@ -36,7 +36,7 @@ jobs: kv/data/repository/nebari-dev/nebari/google_cloud_platform/nebari-dev-ci/github-nebari-dev-repo-ci workload_identity_provider | GCP_WORKFLOW_PROVIDER; kv/data/repository/nebari-dev/nebari/google_cloud_platform/nebari-dev-ci/github-nebari-dev-repo-ci service_account_name | GCP_SERVICE_ACCOUNT; kv/data/repository/nebari-dev/nebari/shared_secrets DIGITALOCEAN_TOKEN | DIGITALOCEAN_TOKEN; - kv/data/repository/nebari-dev/nebari/cloudflare/internal-devops@quansight.com/nebari-dev token | CLOUDFLARE_TOKEN; + kv/data/repository/nebari-dev/nebari/cloudflare/internal-devops@quansight.com/nebari-dev-ci token | CLOUDFLARE_TOKEN; - name: Install Nebari run: | From ff323292944dffa10cc48d86200ba3c47d264ffb Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Thu, 3 Aug 2023 14:56:09 +0100 Subject: [PATCH 26/26] disable DO deployment --- .../workflows/{integration_test.yaml => test_integration.yaml} | 1 - 1 file changed, 1 deletion(-) rename .github/workflows/{integration_test.yaml => test_integration.yaml} (99%) diff --git a/.github/workflows/integration_test.yaml b/.github/workflows/test_integration.yaml similarity index 99% rename from .github/workflows/integration_test.yaml rename to .github/workflows/test_integration.yaml index 9581fcef27..5607649ea5 100644 --- a/.github/workflows/integration_test.yaml +++ b/.github/workflows/test_integration.yaml @@ -1,7 +1,6 @@ name: "Deploy on Digital Ocean" on: - push: schedule: - cron: "0 0 * * MON" workflow_dispatch: