Repository: meltano/meltano

Issue:
`default_environment` can only be set in `meltano.yml`, not using `meltano config meltano set`
@tayloramurphy pointed out that `meltano config meltano set default_environment prod` or `meltano config meltano unset default_environment` doesn't do anything.

This is because `default_environment` is not actually a setting, but a core property of `meltano.yml`: https://github.com/meltano/meltano/blob/dfd9650170ff97fd69fdff7ac8dc7dc0841bf663/src/meltano/core/meltano_file.py#L21

https://docs.meltano.com/concepts/environments#default-environments doesn’t suggest that `meltano config meltano` would work here, only setting it directly in `meltano.yml`. But https://docs.meltano.com/getting-started#view-and-activate-your-environments does call it a setting, and someone seeing it in `meltano.yml` could reasonably expect it to work just like the other keys in there like `send_anonymous_usage_stats` and `project_id`.

If we want to make it a proper setting, we should be able to define it in `settings.yml` and replace https://github.com/meltano/meltano/blob/main/src/meltano/cli/cli.py#L70 with `ProjectSettingsService(project).get("default_environment")`.


Discussion:
@DouweM - Thanks for this writeup. Super helpful.

I don't see any downsides of making it a formal setting. Anything you can anticipate that would keep us from making this change?

cc @tayloramurphy, @edgarrmondragon 
@aaronsteers I don't see any reason why this wouldn't work (or not be improved!) by making it a project setting. It'd even make it possible to toggle it with environment variables.

What I'd worry about is related to https://github.com/meltano/meltano/issues/3419, i.e. what is the effect of

```console
$ meltano config meltano set default_environment prod
```

if there's a `default_environment` already configured.
@edgarrmondragon Yeah, if `default_environment` is currently `dev`, `meltano config meltano set default_environment prod` would store that under `environments: - dev`, which would be confusing and may not actually stick, because the next time `default_environment` is read, it won't know where to read from yet and would read from the root which wouldn't have the key. In this case, it really doesn't make any sense to store this anywhere other than the top-level of `meltano.yml`.

That's another point in favor of not allowing top-level settings in envs, although I said in https://github.com/meltano/meltano/issues/3419#issuecomment-1150239801 that there are situations where that does make sense like with `send_anonymous_usage_stats` or `ui.bind_port`.
Along with disabling environments for top-level Meltano configuration (https://github.com/meltano/meltano/issues/6101), let's make `default_environment` a setting! 

@aaronsteers I suggest assigning these two to the same person.
@DouweM and @tayloramurphy - This should be blocked by 

- #6247 

If we have that in place, and if we categorized this as a Meltano setting, this becomes doable as a top-level only setting that can be modified via `.env`, terminal env vars, and a top-level entry in `meltano.yml`.

Resolving patch:
diff --git a/src/meltano/cli/cli.py b/src/meltano/cli/cli.py
--- a/src/meltano/cli/cli.py
+++ b/src/meltano/cli/cli.py
@@ -1,6 +1,8 @@
+"""Definition of the top-level Click group for the Meltano CLI."""
+
 from __future__ import annotations
 
-import logging  # noqa: D100
+import logging
 import sys
 from typing import NoReturn
 
@@ -77,11 +79,13 @@ def cli(  # noqa: WPS231
         ProjectSettingsService.config_override["cli.log_config"] = log_config
 
     ctx.obj["verbosity"] = verbose
+
     try:  # noqa: WPS229
         project = Project.find()
         setup_logging(project)
+        project_setting_service = ProjectSettingsService(project)
 
-        readonly = ProjectSettingsService(project).get("project_readonly")
+        readonly = project_setting_service.get("project_readonly")
         if readonly:
             project.readonly = True
         if project.readonly:
@@ -94,8 +98,8 @@ def cli(  # noqa: WPS231
             logger.info("No environment is active")
         elif environment:
             selected_environment = environment
-        elif project.meltano.default_environment:
-            selected_environment = project.meltano.default_environment
+        elif project_setting_service.get("default_environment"):
+            selected_environment = project_setting_service.get("default_environment")
             is_default_environment = True
         # activate environment
         if selected_environment:
diff --git a/src/meltano/core/meltano_file.py b/src/meltano/core/meltano_file.py
--- a/src/meltano/core/meltano_file.py
+++ b/src/meltano/core/meltano_file.py
@@ -20,7 +20,6 @@ class MeltanoFile(Canonical):
     def __init__(
         self,
         version: int = VERSION,
-        default_environment: str | None = None,
         plugins: dict[str, dict] = None,
         schedules: list[dict] = None,
         environments: list[dict] = None,
@@ -32,7 +31,6 @@ def __init__(
 
         Args:
             version: The meltano.yml version, currently always 1.
-            default_environment: The default environment to use for commands in this project.
             plugins: Plugin configuration for this project.
             schedules: Schedule configuration for this project.
             environments: Environment configuration for this project.
@@ -43,7 +41,6 @@ def __init__(
         super().__init__(
             # Attributes will be listed in meltano.yml in this order:
             version=version,
-            default_environment=default_environment,
             extras=extras,
             plugins=self.load_plugins(plugins or {}),
             schedules=self.load_schedules(schedules or []),


Test patch: