Repository: nautobot/nautobot

Issue:
Dockerfile permission denied config file
<!--
    NOTE: IF YOUR ISSUE DOES NOT FOLLOW THIS TEMPLATE, IT WILL BE CLOSED.

    This form is only for reporting reproducible bugs. If you need assistance
    with Nautobot installation, or if you have a general question, please start a
    discussion instead: https://github.com/nautobot/nautobot/discussions

    Please describe the environment in which you are running Nautobot. Be sure
    that you are running an unmodified instance of the latest stable release
    before submitting a bug report, and that any plugins have been disabled.
-->
### Environment
* Nautobot version (Docker tag too if applicable): 1.4.8 -> 1.4.9
* Python version: 3.10.6
* Database platform, version: postgres 14 alpine
* Middleware(s):

<!--
    Describe in detail the exact steps that someone else can take to reproduce
    this bug using the current stable release of Nautobot. Begin with the
    creation of any necessary database objects and call out every operation
    being performed explicitly. If reporting a bug in the REST API, be sure to
    reconstruct the raw HTTP request(s) being made: Don't rely on a client
    library such as pynautobot.
-->
Change in #2715 #2671 
### Steps to Reproduce
1. Own Dockerfile based on official Nautobot docker image
2. Try to overwrite config file with `cat /opt/nautobot/nautobot_config.append.py >> /opt/nautobot/nautobot_config.py`
3. `cannot create /opt/nautobot/nautobot_config.py: Permission denied`

<!-- What did you expect to happen? -->
### Expected Behavior
Able to change the configuration file while building the image

<!-- What happened instead? -->
### Observed Behavior
Permission denied error

I think changing the line https://github.com/nautobot/nautobot/blob/9f7b414408cef9931d675abfbc5d394e65a6e420/docker/Dockerfile#L314 :
`COPY --from=final-dev /opt/nautobot/nautobot_config.py /opt/nautobot/nautobot_config.py` to 
`COPY --chown=nautobot --from=final-dev /opt/nautobot/nautobot_config.py /opt/nautobot/nautobot_config.py`
should solve the issue


Discussion:
Thanks for the report @Yannis100!

Can you provide a sanitized example of your Dockerfile?

Going to use it as an example for some future housekeeping tests.
Confirmed:

```
nautobot@692f1c8a9a0d:~$ ls -l
total 32
-rw-r--r-- 1 nautobot nautobot  1980 Nov  4 21:36 nautobot.crt
-rw------- 1 nautobot nautobot  3276 Nov  4 21:36 nautobot.key
-rw-r--r-- 1 root     root     18818 Nov  4 21:36 nautobot_config.py
-rw-r--r-- 1 nautobot nautobot  2198 Nov  4 21:27 uwsgi.ini
```
Thanks for the quick fix !

@bryanculver 
My Dockerfile is basically a mix between the LDAP and Plugins ones in docker-compose project https://github.com/nautobot/nautobot-docker-compose/blob/main/Dockerfile-LDAP & https://github.com/nautobot/nautobot-docker-compose/blob/main/plugin_example/Dockerfile-Plugins

I just didn't want to copy a full config file as it might change between versions, so I use
````
COPY --chown=nautobot config/nautobot_config.append.py /opt/nautobot/
RUN cat /opt/nautobot/nautobot_config.append.py >> /opt/nautobot/nautobot_config.py && \
    rm -f /opt/nautobot/nautobot_config.append.py
````
With the plugins and LDAP configs in nautobot_config.append.py file

Resolving patch:
diff --git a/nautobot/core/settings.py b/nautobot/core/settings.py
--- a/nautobot/core/settings.py
+++ b/nautobot/core/settings.py
@@ -1,6 +1,7 @@
 import os
 import platform
 import re
+import sys
 
 from django.contrib.messages import constants as messages
 import django.forms
@@ -302,40 +303,49 @@
 DEBUG = is_truthy(os.getenv("NAUTOBOT_DEBUG", "False"))
 INTERNAL_IPS = ("127.0.0.1", "::1")
 FORCE_SCRIPT_NAME = None
+
+TESTING = len(sys.argv) > 1 and sys.argv[1] == "test"
+
 LOG_LEVEL = "DEBUG" if DEBUG else "INFO"
-LOGGING = {
-    "version": 1,
-    "disable_existing_loggers": False,
-    "formatters": {
-        "normal": {
-            "format": "%(asctime)s.%(msecs)03d %(levelname)-7s %(name)s :\n  %(message)s",
-            "datefmt": "%H:%M:%S",
-        },
-        "verbose": {
-            "format": "%(asctime)s.%(msecs)03d %(levelname)-7s %(name)-20s %(filename)-15s %(funcName)30s() :\n  %(message)s",
-            "datefmt": "%H:%M:%S",
+
+if TESTING:
+    # keep log quiet by default when running unit/integration tests
+    LOGGING = {}
+else:
+    LOGGING = {
+        "version": 1,
+        "disable_existing_loggers": False,
+        "formatters": {
+            "normal": {
+                "format": "%(asctime)s.%(msecs)03d %(levelname)-7s %(name)s :\n  %(message)s",
+                "datefmt": "%H:%M:%S",
+            },
+            "verbose": {
+                "format": "%(asctime)s.%(msecs)03d %(levelname)-7s %(name)-20s %(filename)-15s %(funcName)30s() :\n  %(message)s",
+                "datefmt": "%H:%M:%S",
+            },
         },
-    },
-    "handlers": {
-        "normal_console": {
-            "level": "INFO",
-            "class": "logging.StreamHandler",
-            "formatter": "normal",
+        "handlers": {
+            "normal_console": {
+                "level": "INFO",
+                "class": "logging.StreamHandler",
+                "formatter": "normal",
+            },
+            "verbose_console": {
+                "level": "DEBUG",
+                "class": "logging.StreamHandler",
+                "formatter": "verbose",
+            },
         },
-        "verbose_console": {
-            "level": "DEBUG",
-            "class": "logging.StreamHandler",
-            "formatter": "verbose",
+        "loggers": {
+            "django": {"handlers": ["normal_console"], "level": "INFO"},
+            "nautobot": {
+                "handlers": ["verbose_console" if DEBUG else "normal_console"],
+                "level": LOG_LEVEL,
+            },
         },
-    },
-    "loggers": {
-        "django": {"handlers": ["normal_console"], "level": "INFO"},
-        "nautobot": {
-            "handlers": ["verbose_console" if DEBUG else "normal_console"],
-            "level": LOG_LEVEL,
-        },
-    },
-}
+    }
+
 MEDIA_ROOT = os.path.join(NAUTOBOT_ROOT, "media").rstrip("/")
 SESSION_COOKIE_AGE = int(os.getenv("NAUTOBOT_SESSION_COOKIE_AGE", "1209600"))  # 2 weeks, in seconds
 SESSION_FILE_PATH = os.getenv("NAUTOBOT_SESSION_FILE_PATH", None)
diff --git a/tasks.py b/tasks.py
--- a/tasks.py
+++ b/tasks.py
@@ -671,7 +671,8 @@ def unittest(
         build_and_check_docs(context)
 
     append_arg = " --append" if append else ""
-    command = f"coverage run{append_arg} --module nautobot.core.cli --config=nautobot/core/tests/nautobot_config.py test {label}"
+    command = f"coverage run{append_arg} --module nautobot.core.cli test {label}"
+    command += " --config=nautobot/core/tests/nautobot_config.py"
     # booleans
     if keepdb:
         command += " --keepdb"


Test patch:
diff --git a/nautobot/core/tests/nautobot_config.py b/nautobot/core/tests/nautobot_config.py
--- a/nautobot/core/tests/nautobot_config.py
+++ b/nautobot/core/tests/nautobot_config.py
@@ -48,6 +48,3 @@
 TEST_FACTORY_SEED = "Nautobot"
 # File in which all performance-specifc test baselines are stored
 TEST_PERFORMANCE_BASELINE_FILE = "nautobot/core/tests/performance_baselines.yml"
-
-# Disable logging for tests
-LOGGING = {}