From cf0f7541f0d99af852e94c518d2ef7849331b852 Mon Sep 17 00:00:00 2001
From: Adam Lewis <23342526+Adam-D-Lewis@users.noreply.github.com>
Date: Mon, 17 Jun 2024 14:19:34 -0500
Subject: [PATCH] Explicit config (#2294)

Co-authored-by: Fangchen Li <fangchen.li@outlook.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Amit Kumar <dtu.amit@gmail.com>
Co-authored-by: Vinicius D. Cerutti <51954708+viniciusdc@users.noreply.github.com>
---
 src/_nebari/config.py             |  3 ++-
 src/_nebari/subcommands/init.py   | 19 ++++++++++++++++-
 src/nebari/plugins.py             |  2 +-
 src/nebari/schema.py              |  4 +++-
 tests/tests_unit/test_cli_init.py | 34 +++++++++++++++++++------------
 5 files changed, 45 insertions(+), 17 deletions(-)

diff --git a/src/_nebari/config.py b/src/_nebari/config.py
index 7c27274f36..9d7dec4bd4 100644
--- a/src/_nebari/config.py
+++ b/src/_nebari/config.py
@@ -103,7 +103,8 @@ def write_configuration(
     """Write the nebari configuration file to disk"""
     with config_filename.open(mode) as f:
         if isinstance(config, pydantic.BaseModel):
-            yaml.dump(config.model_dump(), f)
+            config_dict = config.model_dump()
+            yaml.dump(config_dict, f)
         else:
             config = dump_nested_model(config)
             yaml.dump(config, f)
diff --git a/src/_nebari/subcommands/init.py b/src/_nebari/subcommands/init.py
index 9040f3d201..8c3de6d5b2 100644
--- a/src/_nebari/subcommands/init.py
+++ b/src/_nebari/subcommands/init.py
@@ -106,6 +106,7 @@ class InitInputs(schema.Base):
     ssl_cert_email: Optional[schema.email_pydantic] = None
     disable_prompt: bool = False
     output: pathlib.Path = pathlib.Path("nebari-config.yaml")
+    explicit: int = 0
 
 
 def enum_to_list(enum_cls):
@@ -152,7 +153,7 @@ def handle_init(inputs: InitInputs, config_schema: BaseModel):
     try:
         write_configuration(
             inputs.output,
-            config,
+            config if not inputs.explicit else config_schema(**config),
             mode="x",
         )
     except FileExistsError:
@@ -565,6 +566,13 @@ def init(
             "-o",
             help="Output file path for the rendered config file.",
         ),
+        explicit: int = typer.Option(
+            0,
+            "--explicit",
+            "-e",
+            count=True,
+            help="Write explicit nebari config file (advanced users only).",
+        ),
     ):
         """
         Create and initialize your [purple]nebari-config.yaml[/purple] file.
@@ -604,6 +612,7 @@ def init(
         inputs.ssl_cert_email = ssl_cert_email
         inputs.disable_prompt = disable_prompt
         inputs.output = output
+        inputs.explicit = explicit
 
         from nebari.plugins import nebari_plugin_manager
 
@@ -894,6 +903,14 @@ def guided_init_wizard(ctx: typer.Context, guided_init: str):
                 )
             inputs.kubernetes_version = kubernetes_version
 
+            # EXPLICIT CONFIG
+            inputs.explicit = questionary.confirm(
+                "Would you like the nebari config to show all available options? (recommended for advanced users only)",
+                default=False,
+                qmark=qmark,
+                auto_enter=False,
+            ).unsafe_ask()
+
         from nebari.plugins import nebari_plugin_manager
 
         config_schema = nebari_plugin_manager.config_schema
diff --git a/src/nebari/plugins.py b/src/nebari/plugins.py
index c5148e9e1d..a523c0324f 100644
--- a/src/nebari/plugins.py
+++ b/src/nebari/plugins.py
@@ -128,7 +128,7 @@ def config_schema(self):
         classes = [schema.Main] + [
             _.input_schema for _ in self.ordered_stages if _.input_schema is not None
         ]
-        return type("ConfigSchema", tuple(classes), {})
+        return type("ConfigSchema", tuple(classes[::-1]), {})
 
 
 nebari_plugin_manager = NebariPluginManager()
diff --git a/src/nebari/schema.py b/src/nebari/schema.py
index 70b9589e6f..2cc1c1ea3f 100644
--- a/src/nebari/schema.py
+++ b/src/nebari/schema.py
@@ -25,7 +25,9 @@
 
 class Base(pydantic.BaseModel):
     model_config = ConfigDict(
-        extra="forbid", validate_assignment=True, populate_by_name=True
+        extra="forbid",
+        validate_assignment=True,
+        populate_by_name=True,
     )
 
 
diff --git a/tests/tests_unit/test_cli_init.py b/tests/tests_unit/test_cli_init.py
index 0cd0fe03d2..9afab5ddc5 100644
--- a/tests/tests_unit/test_cli_init.py
+++ b/tests/tests_unit/test_cli_init.py
@@ -51,6 +51,8 @@
         (["--ssl-cert-email"], 2, ["requires an argument"]),
         (["--output"], 2, ["requires an argument"]),
         (["-o"], 2, ["requires an argument"]),
+        (["--explicit"], 2, ["Missing option"]),
+        (["-e"], 2, ["Missing option"]),
     ],
 )
 def test_cli_init_stdout(args: List[str], exit_code: int, content: List[str]):
@@ -90,20 +92,22 @@ def generate_test_data_test_cli_init_happy_path():
                                         ) in get_kubernetes_versions(provider) + [
                                             "latest"
                                         ]:
-                                            test_data.append(
-                                                (
-                                                    provider,
-                                                    region,
-                                                    project_name,
-                                                    domain_name,
-                                                    namespace,
-                                                    auth_provider,
-                                                    ci_provider,
-                                                    terraform_state,
-                                                    email,
-                                                    kubernetes_version,
+                                            for explicit in [True, False]:
+                                                test_data.append(
+                                                    (
+                                                        provider,
+                                                        region,
+                                                        project_name,
+                                                        domain_name,
+                                                        namespace,
+                                                        auth_provider,
+                                                        ci_provider,
+                                                        terraform_state,
+                                                        email,
+                                                        kubernetes_version,
+                                                        explicit,
+                                                    )
                                                 )
-                                            )
 
     keys = [
         "provider",
@@ -116,6 +120,7 @@ def generate_test_data_test_cli_init_happy_path():
         "terraform_state",
         "email",
         "kubernetes_version",
+        "explicit",
     ]
     return {"keys": keys, "test_data": test_data}
 
@@ -131,6 +136,7 @@ def test_cli_init_happy_path(
     terraform_state: str,
     email: str,
     kubernetes_version: str,
+    explicit: bool,
 ):
     app = create_cli()
     args = [
@@ -159,6 +165,8 @@ def test_cli_init_happy_path(
         "--region",
         region,
     ]
+    if explicit:
+        args += ["--explicit"]
 
     expected_yaml = f"""
     provider: {provider}