Repository: dbt-labs/dbt-core

Issue:
Support console env var DO_NOT_TRACK
### Describe the feature
If the console environment variable `DO_NOT_TRACK` is set (to any value), do not send anonymous usage stats.  The current configuration equivalent is in `~/.dbt/profiles.yml`
```
config:
  send_anonymous_usage_stats: False
```
The website promoting and documenting the standard is: https://consoledonottrack.com/

### Describe alternatives you've considered
The current yaml config: send_anonymous_usage_stats serves the same purpose, but is specific to DBT, where `DO_NOT_TRACK` is intended to be universal.

### Who will this benefit?
Users who would like to avoid sending usage statistics by default would no longer need to:
* discover dbt sends usage statistics
* read documentation to find how to opt-out

### Are you interested in contributing this feature?
Yes, I would be more than willing to implement the feature if the issue is accepted.



Discussion:
@sethwoodworth Thanks for opening the issue! This sounds like a great standard to follow. I'd be interested in supporting this via two env vars:
- `DO_NOT_TRACK=0|1` (standard)
- `SEND_ANONYMOUS_USAGE_STATS=True|False` (dbt-specific)

Ultimately, I'd like for every runtime config to have an associated flag, profile config, and env var, as outlined in #2990.

In any case, I'd welcome a contribution for this.
We've implemented this as `DBT_SEND_ANONYMOUS_USAGE_STATS=True|False` for v1.0.0-b1, as part of the work to implement #2990.

Many of the issues/PRs linked from https://consoledonottrack.com/ seem to have gotten ... quite heated ... and were ultimately closed, but I don't see a really good reason to oppose adding/aliasing the same functionality with `DO_NOT_TRACK`, even if it hasn't yet been adopted as a universal standard.

Resolving patch:
diff --git a/core/dbt/flags.py b/core/dbt/flags.py
--- a/core/dbt/flags.py
+++ b/core/dbt/flags.py
@@ -36,6 +36,16 @@
 EVENT_BUFFER_SIZE = 100000
 QUIET = None
 
+_NON_BOOLEAN_FLAGS = [
+    "LOG_FORMAT",
+    "PRINTER_WIDTH",
+    "PROFILES_DIR",
+    "INDIRECT_SELECTION",
+    "EVENT_BUFFER_SIZE",
+]
+
+_NON_DBT_ENV_FLAGS = ["DO_NOT_TRACK"]
+
 # Global CLI defaults. These flags are set from three places:
 # CLI args, environment variables, and user_config (profiles.yml).
 # Environment variables use the pattern 'DBT_{flag name}', like DBT_PROFILES_DIR
@@ -133,31 +143,22 @@ def set_from_args(args, user_config):
     EVENT_BUFFER_SIZE = get_flag_value("EVENT_BUFFER_SIZE", args, user_config)
     QUIET = get_flag_value("QUIET", args, user_config)
 
+    _set_overrides_from_env()
 
-def get_flag_value(flag, args, user_config):
-    lc_flag = flag.lower()
-    flag_value = getattr(args, lc_flag, None)
+
+def _set_overrides_from_env():
+    global SEND_ANONYMOUS_USAGE_STATS
+
+    flag_value = _get_flag_value_from_env("DO_NOT_TRACK")
     if flag_value is None:
-        # Environment variables use pattern 'DBT_{flag name}'
-        env_flag = f"DBT_{flag}"
-        env_value = os.getenv(env_flag)
-        if env_value is not None and env_value != "":
-            env_value = env_value.lower()
-            # non Boolean values
-            if flag in [
-                "LOG_FORMAT",
-                "PRINTER_WIDTH",
-                "PROFILES_DIR",
-                "INDIRECT_SELECTION",
-                "EVENT_BUFFER_SIZE",
-            ]:
-                flag_value = env_value
-            else:
-                flag_value = env_set_bool(env_value)
-        elif user_config is not None and getattr(user_config, lc_flag, None) is not None:
-            flag_value = getattr(user_config, lc_flag)
-        else:
-            flag_value = flag_defaults[flag]
+        return
+
+    SEND_ANONYMOUS_USAGE_STATS = not flag_value
+
+
+def get_flag_value(flag, args, user_config):
+    flag_value = _load_flag_value(flag, args, user_config)
+
     if flag in ["PRINTER_WIDTH", "EVENT_BUFFER_SIZE"]:  # must be ints
         flag_value = int(flag_value)
     if flag == "PROFILES_DIR":
@@ -166,6 +167,42 @@ def get_flag_value(flag, args, user_config):
     return flag_value
 
 
+def _load_flag_value(flag, args, user_config):
+    lc_flag = flag.lower()
+    flag_value = getattr(args, lc_flag, None)
+    if flag_value is not None:
+        return flag_value
+
+    flag_value = _get_flag_value_from_env(flag)
+    if flag_value is not None:
+        return flag_value
+
+    if user_config is not None and getattr(user_config, lc_flag, None) is not None:
+        return getattr(user_config, lc_flag)
+
+    return flag_defaults[flag]
+
+
+def _get_flag_value_from_env(flag):
+    # Environment variables use pattern 'DBT_{flag name}'
+    env_flag = _get_env_flag(flag)
+    env_value = os.getenv(env_flag)
+    if env_value is None or env_value == "":
+        return None
+
+    env_value = env_value.lower()
+    if flag in _NON_BOOLEAN_FLAGS:
+        flag_value = env_value
+    else:
+        flag_value = env_set_bool(env_value)
+
+    return flag_value
+
+
+def _get_env_flag(flag):
+    return flag if flag in _NON_DBT_ENV_FLAGS else f"DBT_{flag}"
+
+
 def get_flag_dict():
     return {
         "use_experimental_parser": USE_EXPERIMENTAL_PARSER,


Test patch:
diff --git a/test/unit/test_flags.py b/test/unit/test_flags.py
--- a/test/unit/test_flags.py
+++ b/test/unit/test_flags.py
@@ -178,8 +178,12 @@ def test__flags(self):
         setattr(self.args, 'send_anonymous_usage_stats', True)
         flags.set_from_args(self.args, self.user_config)
         self.assertEqual(flags.SEND_ANONYMOUS_USAGE_STATS, True)
+        os.environ['DO_NOT_TRACK'] = '1'
+        flags.set_from_args(self.args, self.user_config)
+        self.assertEqual(flags.SEND_ANONYMOUS_USAGE_STATS, False)
         # cleanup
         os.environ.pop('DBT_SEND_ANONYMOUS_USAGE_STATS')
+        os.environ.pop('DO_NOT_TRACK')
         delattr(self.args, 'send_anonymous_usage_stats')
 
         # printer_width
diff --git a/test/unit/test_tracking.py b/test/unit/test_tracking.py
--- a/test/unit/test_tracking.py
+++ b/test/unit/test_tracking.py
@@ -71,3 +71,16 @@ def test_disable_never_enabled(self):
         assert dbt.tracking.active_user.id is None
         assert isinstance(dbt.tracking.active_user.invocation_id, str)
         assert isinstance(dbt.tracking.active_user.run_started_at, datetime.datetime)
+
+    def test_initialize_from_flags(self):
+        for send_aonymous_usage_stats in [True, False]:
+            with self.subTest(
+                send_aonymous_usage_stats=send_aonymous_usage_stats
+            ):
+                dbt.tracking.flags.SEND_ANONYMOUS_USAGE_STATS = (
+                    send_aonymous_usage_stats
+                )
+
+                dbt.tracking.initialize_from_flags()
+
+                assert dbt.tracking.active_user.do_not_track != send_aonymous_usage_stats