diff --git a/.github/workflows/kubernetes_test.yaml b/.github/workflows/kubernetes_test.yaml index 1b00cfefb..04cb91c07 100644 --- a/.github/workflows/kubernetes_test.yaml +++ b/.github/workflows/kubernetes_test.yaml @@ -14,7 +14,7 @@ on: push: branches: - main - - release/\d{4}.\d{2}.\d{1,2} + - release/\d{4}.\d{1,2}.\d{1,2} paths: - ".github/workflows/kubernetes_test.yaml" - "tests/**" diff --git a/.github/workflows/run-pre-commit.yaml b/.github/workflows/run-pre-commit.yaml index f89254ace..160bf5350 100644 --- a/.github/workflows/run-pre-commit.yaml +++ b/.github/workflows/run-pre-commit.yaml @@ -4,7 +4,7 @@ on: push: branches: - main - - release/\d{4}.\d{2}.\d{1,2} + - release/\d{4}.\d{1,2}.\d{1,2} pull_request: jobs: diff --git a/.github/workflows/test-provider.yaml b/.github/workflows/test-provider.yaml index 51ad35e48..ffcb86f41 100644 --- a/.github/workflows/test-provider.yaml +++ b/.github/workflows/test-provider.yaml @@ -14,7 +14,7 @@ on: push: branches: - main - - release/\d{4}.\d{2}.\d{1,2} + - release/\d{4}.\d{1,2}.\d{1,2} paths: - ".github/workflows/test-provider.yaml" - "tests/**" diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 37970cd98..3cc55fe5a 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -14,7 +14,7 @@ on: push: branches: - main - - release/\d{4}.\d{2}.\d{1,2} + - release/\d{4}.\d{1,2}.\d{1,2} paths: - ".github/workflows/test.yaml" - "tests/**" @@ -27,7 +27,7 @@ on: jobs: test-general: - name: 'Test Package' + name: 'Pytest' runs-on: ubuntu-latest strategy: matrix: @@ -48,15 +48,6 @@ jobs: - name: Install Nebari run: | pip install .[dev] - - name: Black Formatting - run: | - black --version - black nebari --diff --exclude "nebari/_version.py" - black --check nebari --exclude "nebari/_version.py" - - name: Flake8 Formatting - run: | - flake8 --version - flake8 - name: Test Nebari run: | pytest --version diff --git a/nebari/cli/_init.py b/nebari/cli/_init.py index 4ea8a489f..fcecccb8e 100644 --- a/nebari/cli/_init.py +++ b/nebari/cli/_init.py @@ -1,4 +1,6 @@ import os +import re +from pathlib import Path import questionary import rich @@ -263,6 +265,13 @@ def check_project_name(ctx: typer.Context, project_name: str): return project_name +def check_ssl_cert_email(ctx: typer.Context, ssl_cert_email: str): + if ssl_cert_email and not re.match("^[^ @]+@[^ @]+\\.[^ @]+$", ssl_cert_email): + raise ValueError("ssl-cert-email should be a valid email address") + + return ssl_cert_email + + def guided_init_wizard(ctx: typer.Context, guided_init: str): """ Guided Init Wizard is a user-friendly questionnaire used to help generate the `nebari-config.yaml`. @@ -270,6 +279,11 @@ def guided_init_wizard(ctx: typer.Context, guided_init: str): qmark = " " disable_checks = os.environ.get("NEBARI_DISABLE_INIT_CHECKS", False) + if Path("nebari-config.yaml").exists(): + raise ValueError( + "A nebari-config.yaml file already exists. Please move or delete it and try again." + ) + if not guided_init: return guided_init @@ -459,6 +473,9 @@ def guided_init_wizard(ctx: typer.Context, guided_init: str): qmark=qmark, ).unsafe_ask() + if not disable_checks: + check_ssl_cert_email(ctx, ssl_cert_email=inputs.ssl_cert_email) + # ADVANCED FEATURES rich.print( ( diff --git a/nebari/cli/main.py b/nebari/cli/main.py index ddb95eb87..acf13ae60 100644 --- a/nebari/cli/main.py +++ b/nebari/cli/main.py @@ -14,6 +14,7 @@ check_auth_provider_creds, check_cloud_provider_creds, check_project_name, + check_ssl_cert_email, enum_to_list, guided_init_wizard, handle_init, @@ -139,6 +140,7 @@ def init( ), ssl_cert_email: str = typer.Option( None, + callback=check_ssl_cert_email, ), disable_prompt: bool = typer.Option( False, @@ -271,7 +273,7 @@ def deploy( dns_auto_provision: bool = typer.Option( False, "--dns-auto-provision", - help="Attempt to automatically provision DNS. For Auth0 is requires environment variables AUTH0_DOMAIN, AUTH0_CLIENTID, AUTH0_CLIENT_SECRET", + help="Attempt to automatically provision DNS, currently only available for `cloudflare`", ), disable_prompt: bool = typer.Option( False, @@ -283,6 +285,16 @@ def deploy( "--disable-render", help="Disable auto-rendering in deploy stage", ), + disable_checks: bool = typer.Option( + False, + "--disable-checks", + help="Disable the checks performed after each stage", + ), + skip_remote_state_provision: bool = typer.Option( + True, + "--skip-remote-state-provision", + help="Skip terraform state deployment which is often required in CI once the terraform remote state bootstrapping phase is complete", + ), ): """ Deploy the Nebari cluster from your [purple]nebari-config.yaml[/purple] file. @@ -306,8 +318,8 @@ def deploy( dns_provider=dns_provider, dns_auto_provision=dns_auto_provision, disable_prompt=disable_prompt, - disable_checks=False, - skip_remote_state_provision=False, + disable_checks=disable_checks, + skip_remote_state_provision=skip_remote_state_provision, ) @@ -317,7 +329,8 @@ def destroy( ..., "-c", "--config", help="nebari configuration file path" ), output: str = typer.Option( - "./" "-o", + "./", + "-o", "--output", help="output directory", ), @@ -335,10 +348,8 @@ def destroy( """ Destroy the Nebari cluster from your [purple]nebari-config.yaml[/purple] file. """ - if not disable_prompt: - if typer.confirm("Are you sure you want to destroy your Nebari cluster?"): - raise typer.Abort() - else: + + def _run_destroy(config=config, disable_render=disable_render): config_filename = Path(config) if not config_filename.is_file(): raise ValueError( @@ -354,6 +365,13 @@ def destroy( destroy_configuration(config_yaml) + if disable_prompt: + _run_destroy() + elif typer.confirm("Are you sure you want to destroy your Nebari cluster?"): + _run_destroy() + else: + raise typer.Abort() + @app.command(rich_help_panel=SECOND_COMMAND_GROUP_NAME) def cost( diff --git a/nebari/initialize.py b/nebari/initialize.py index e75bf8492..3df7e4060 100644 --- a/nebari/initialize.py +++ b/nebari/initialize.py @@ -427,8 +427,6 @@ def render_config( config["environments"] = default_environments().copy() if ssl_cert_email is not None: - if not re.match("^[^ @]+@[^ @]+\\.[^ @]+$", ssl_cert_email): - raise ValueError("ssl-cert-email should be a valid email address") config["certificate"] = { "type": "lets-encrypt", "acme_email": ssl_cert_email, diff --git a/nebari/schema.py b/nebari/schema.py index 81aee396d..8a1ccc5bb 100644 --- a/nebari/schema.py +++ b/nebari/schema.py @@ -528,7 +528,6 @@ def project_name_convention(value: typing.Any, values): return letter_dash_underscore_pydantic -# CLEAN UP class InitInputs(Base): cloud_provider: typing.Type[ProviderEnum] = "local" project_name: str = "" @@ -539,7 +538,7 @@ class InitInputs(Base): repository: typing.Union[str, None] = None repository_auto_provision: bool = False ci_provider: typing.Optional[CiEnum] = None - terraform_state: typing.Optional[TerraformStateEnum] = None + terraform_state: typing.Optional[TerraformStateEnum] = "remote" kubernetes_version: typing.Union[str, None] = None ssl_cert_email: typing.Union[str, None] = None disable_prompt: bool = False diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 000000000..6a29f605d --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,77 @@ +import os +import subprocess +import typing +from pathlib import Path + +import pytest + +from nebari.schema import InitInputs +from nebari.utils import load_yaml + +PROJECT_NAME = "clitest" +DOMAIN_NAME = "clitest.dev" + + +def run_cli_cmd(command: str, working_dir: typing.Union[str, Path]): + """Run the provided CLI command using subprocess.""" + + try: + os.chdir(working_dir) + subprocess.call(command.split()) + except subprocess.CalledProcessError: + return False + + return True + + +@pytest.mark.parametrize( + "namespace, auth_provider, ci_provider, ssl_cert_email", + ( + [None, None, None, None], + ["prod", "github", "github-actions", "it@acme.org"], + ), +) +def test_nebari_init(tmp_path, namespace, auth_provider, ci_provider, ssl_cert_email): + """Test `nebari init` CLI command.""" + + command = f"nebari init local --project {PROJECT_NAME} --domain {DOMAIN_NAME} --disable-prompt" + + default_values = InitInputs() + + if namespace: + command += f" --namespace {namespace}" + else: + namespace = default_values.namespace + if auth_provider: + command += f" --auth-provider {auth_provider}" + else: + auth_provider = default_values.auth_provider + if ci_provider: + command += f" --ci-provider {ci_provider}" + else: + ci_provider = default_values.ci_provider + if ssl_cert_email: + command += f" --ssl-cert-email {ssl_cert_email}" + else: + ssl_cert_email = default_values.ssl_cert_email + + assert run_cli_cmd(command, tmp_path) + + config = load_yaml(tmp_path / "nebari-config.yaml") + + assert config.get("namespace") == namespace + assert ( + config.get("security", {}).get("authentication", {}).get("type").lower() + == auth_provider + ) + ci_cd = config.get("ci_cd", None) + if ci_cd: + assert ci_cd.get("type", {}) == ci_provider + else: + assert ci_cd == ci_provider + ci_cd = config.get("ci_cd", None) + acme_email = config.get("certificate", None) + if acme_email: + assert acme_email.get("acme_email") == ssl_cert_email + else: + assert acme_email == ssl_cert_email