From d8cfdef1620391a15309d4d168f25c8119eba7ca Mon Sep 17 00:00:00 2001 From: Ken Foster Date: Tue, 12 Sep 2023 19:21:34 +0000 Subject: [PATCH 1/4] standardize regex and messaging for names --- src/_nebari/subcommands/init.py | 28 +++++++++++++++------------- src/nebari/schema.py | 12 ++++++++---- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/src/_nebari/subcommands/init.py b/src/_nebari/subcommands/init.py index 068be5de07..08b22bcefa 100644 --- a/src/_nebari/subcommands/init.py +++ b/src/_nebari/subcommands/init.py @@ -81,9 +81,9 @@ class GitRepoEnum(str, enum.Enum): class InitInputs(schema.Base): cloud_provider: ProviderEnum = ProviderEnum.local - project_name: schema.letter_dash_underscore_pydantic = "" + project_name: schema.project_name_pydantic = "" domain_name: typing.Optional[str] = None - namespace: typing.Optional[schema.letter_dash_underscore_pydantic] = "dev" + namespace: typing.Optional[schema.namespace_pydantic] = "dev" auth_provider: AuthenticationEnum = AuthenticationEnum.password auth_auto_provision: bool = False repository: typing.Union[str, None] = None @@ -473,8 +473,8 @@ def init( "--project", "-p", callback=typer_validate_regex( - schema.namestr_regex, - "Project name must begin with a letter and consist of letters, numbers, dashes, or underscores.", + schema.project_name_regex, + "Project name must (1) consist of only letters, numbers, hyphens, and underscores, (2) begin and end with a letter, and (3) contain between 3 and 32 characters.", ), ), domain_name: typing.Optional[str] = typer.Option( @@ -486,8 +486,8 @@ def init( namespace: str = typer.Option( "dev", callback=typer_validate_regex( - schema.namestr_regex, - "Namespace must begin with a letter and consist of letters, numbers, dashes, or underscores.", + schema.namespace_regex, + "Namespace must begin and end with a letter and consist of letters, dashes, or underscores.", ), ), region: str = typer.Option( @@ -677,13 +677,15 @@ def guided_init_wizard(ctx: typer.Context, guided_init: str): name_guidelines = """ The project name must adhere to the following requirements: - - Letters from A to Z (upper and lower case) and numbers - - Maximum accepted length of the name string is 16 characters + - Letters from A to Z (upper and lower case), numbers, hyphens, and dashes + - Length from 3 to 32 characters + - Begin and end with a letter """ - if inputs.cloud_provider == ProviderEnum.aws.value.lower(): - name_guidelines += "- Should NOT start with the string `aws`\n" - elif inputs.cloud_provider == ProviderEnum.azure.value.lower(): - name_guidelines += "- Should NOT contain `-`\n" + + #if inputs.cloud_provider == ProviderEnum.aws.value.lower(): + # name_guidelines += "- Should NOT start with the string `aws`\n" + #elif inputs.cloud_provider == ProviderEnum.azure.value.lower(): + # name_guidelines += "- Should NOT contain `-`\n" # PROJECT NAME rich.print( @@ -694,7 +696,7 @@ def guided_init_wizard(ctx: typer.Context, guided_init: str): inputs.project_name = questionary.text( "What project name would you like to use?", qmark=qmark, - validate=questionary_validate_regex(schema.namestr_regex), + validate=questionary_validate_regex(schema.project_name_regex), ).unsafe_ask() # DOMAIN NAME diff --git a/src/nebari/schema.py b/src/nebari/schema.py index b3a5c169a0..c26ee3aa45 100644 --- a/src/nebari/schema.py +++ b/src/nebari/schema.py @@ -7,8 +7,12 @@ from _nebari.version import __version__, rounded_ver_parse # Regex for suitable project names -namestr_regex = r"^[A-Za-z][A-Za-z\-_]*[A-Za-z]$" -letter_dash_underscore_pydantic = pydantic.constr(regex=namestr_regex) +project_name_regex = r"^[A-Za-z][A-Za-z0-9\-_]{1,30}[A-Za-z]$" +project_name_pydantic = pydantic.constr(regex=project_name_regex) + +# Regex for suitable namespaces +namespace_regex = r"^[A-Za-z][A-Za-z\-_]*[A-Za-z]$" +namespace_pydantic = pydantic.constr(regex=namespace_regex) email_regex = "^[^ @]+@[^ @]+\\.[^ @]+$" email_pydantic = pydantic.constr(regex=email_regex) @@ -38,8 +42,8 @@ def to_yaml(cls, representer, node): class Main(Base): - project_name: letter_dash_underscore_pydantic - namespace: letter_dash_underscore_pydantic = "dev" + project_name: project_name_pydantic + namespace: namespace_pydantic = "dev" provider: ProviderEnum = ProviderEnum.local # In nebari_version only use major.minor.patch version - drop any pre/post/dev suffixes nebari_version: str = __version__ From 6a049f444f4b08278fd50e19e482ff2060a3b122 Mon Sep 17 00:00:00 2001 From: Scott Blair Date: Wed, 13 Sep 2023 21:15:10 +0000 Subject: [PATCH 2/4] pr feedback and pre-commit cleanup --- src/_nebari/subcommands/init.py | 5 ----- src/nebari/schema.py | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/_nebari/subcommands/init.py b/src/_nebari/subcommands/init.py index 08b22bcefa..386039f8dd 100644 --- a/src/_nebari/subcommands/init.py +++ b/src/_nebari/subcommands/init.py @@ -681,11 +681,6 @@ def guided_init_wizard(ctx: typer.Context, guided_init: str): - Length from 3 to 32 characters - Begin and end with a letter """ - - #if inputs.cloud_provider == ProviderEnum.aws.value.lower(): - # name_guidelines += "- Should NOT start with the string `aws`\n" - #elif inputs.cloud_provider == ProviderEnum.azure.value.lower(): - # name_guidelines += "- Should NOT contain `-`\n" # PROJECT NAME rich.print( diff --git a/src/nebari/schema.py b/src/nebari/schema.py index c26ee3aa45..2d6b5f41e6 100644 --- a/src/nebari/schema.py +++ b/src/nebari/schema.py @@ -7,7 +7,7 @@ from _nebari.version import __version__, rounded_ver_parse # Regex for suitable project names -project_name_regex = r"^[A-Za-z][A-Za-z0-9\-_]{1,30}[A-Za-z]$" +project_name_regex = r"^[A-Za-z][A-Za-z0-9\-_]{1,30}[A-Za-z0-9]$" project_name_pydantic = pydantic.constr(regex=project_name_regex) # Regex for suitable namespaces From 0e7cbafa117e36354f4741ea9a5f023846766046 Mon Sep 17 00:00:00 2001 From: Scott Blair Date: Wed, 13 Sep 2023 21:34:12 +0000 Subject: [PATCH 3/4] added project_name validation unit tests --- .../local.error.project_name.ends_with_special.yaml | 1 + ...> local.error.project_name.starts_with_number.yaml} | 0 .../local.error.project_name.too_long.yaml | 1 + .../local.happy.project_name.with_numbers.yaml | 1 + tests/tests_unit/test_cli_validate.py | 10 ++++++---- 5 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 tests/tests_unit/cli_validate/local.error.project_name.ends_with_special.yaml rename tests/tests_unit/cli_validate/{local.error.project_name.yaml => local.error.project_name.starts_with_number.yaml} (100%) create mode 100644 tests/tests_unit/cli_validate/local.error.project_name.too_long.yaml create mode 100644 tests/tests_unit/cli_validate/local.happy.project_name.with_numbers.yaml diff --git a/tests/tests_unit/cli_validate/local.error.project_name.ends_with_special.yaml b/tests/tests_unit/cli_validate/local.error.project_name.ends_with_special.yaml new file mode 100644 index 0000000000..1884becb38 --- /dev/null +++ b/tests/tests_unit/cli_validate/local.error.project_name.ends_with_special.yaml @@ -0,0 +1 @@ +project_name: invalidproject- diff --git a/tests/tests_unit/cli_validate/local.error.project_name.yaml b/tests/tests_unit/cli_validate/local.error.project_name.starts_with_number.yaml similarity index 100% rename from tests/tests_unit/cli_validate/local.error.project_name.yaml rename to tests/tests_unit/cli_validate/local.error.project_name.starts_with_number.yaml diff --git a/tests/tests_unit/cli_validate/local.error.project_name.too_long.yaml b/tests/tests_unit/cli_validate/local.error.project_name.too_long.yaml new file mode 100644 index 0000000000..d589d92d96 --- /dev/null +++ b/tests/tests_unit/cli_validate/local.error.project_name.too_long.yaml @@ -0,0 +1 @@ +project_name: thisprojectnameissolongitshouldbeinvalid diff --git a/tests/tests_unit/cli_validate/local.happy.project_name.with_numbers.yaml b/tests/tests_unit/cli_validate/local.happy.project_name.with_numbers.yaml new file mode 100644 index 0000000000..79d6ec8e03 --- /dev/null +++ b/tests/tests_unit/cli_validate/local.happy.project_name.with_numbers.yaml @@ -0,0 +1 @@ +project_name: my-test-1-2-3 \ No newline at end of file diff --git a/tests/tests_unit/test_cli_validate.py b/tests/tests_unit/test_cli_validate.py index 7f8d754413..44b7ce0f0d 100644 --- a/tests/tests_unit/test_cli_validate.py +++ b/tests/tests_unit/test_cli_validate.py @@ -48,7 +48,9 @@ def generate_test_data_test_cli_validate_local_happy_path(): test_data = [] for f in TEST_DATA_DIR.iterdir(): - if f.is_file() and re.match(r"^\w*\.happy\.yaml$", f.name): # sample.happy.yaml + if f.is_file() and re.match( + r"^\w*\.happy.*\.yaml$", f.name + ): # sample.happy.optional-description.yaml test_data.append((f.name)) keys = [ "config_yaml", @@ -259,9 +261,9 @@ def generate_test_data_test_cli_validate_error(): test_data = [] for f in TEST_DATA_DIR.iterdir(): if f.is_file(): - m = re.match( - r"^\w*\.error\.([\w-]*)\.yaml$", f.name - ) # sample.error.message.yaml + m = re.match(r"^\w*\.error\.([\w-]*)\.yaml$", f.name) or re.match( + r"^\w*\.error\.([\w-]*)\.[\w-]*\.yaml$", f.name + ) # sample.error.assert-message.optional-description.yaml if m: test_data.append((f.name, m.groups()[0])) elif re.match(r"^\w*\.error\.yaml$", f.name): # sample.error.yaml From a380cd83ae6429971f4f931ca6721685f5235548 Mon Sep 17 00:00:00 2001 From: Scott Blair Date: Wed, 13 Sep 2023 21:39:49 +0000 Subject: [PATCH 4/4] pre-commit fix --- .../cli_validate/local.happy.project_name.with_numbers.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tests_unit/cli_validate/local.happy.project_name.with_numbers.yaml b/tests/tests_unit/cli_validate/local.happy.project_name.with_numbers.yaml index 79d6ec8e03..eab740ff1d 100644 --- a/tests/tests_unit/cli_validate/local.happy.project_name.with_numbers.yaml +++ b/tests/tests_unit/cli_validate/local.happy.project_name.with_numbers.yaml @@ -1 +1 @@ -project_name: my-test-1-2-3 \ No newline at end of file +project_name: my-test-1-2-3