Repository: line/promgen
Issue: Validation of redis URL fails during bootstrap

Validation of redis URLs such as `redis://redis:6379/0` (i.e. without a tld part) fails during bootstrap process with the error: `Enter a valid URL.`
When using docker this URL will often lack a tld part.

The root cause is the Django [`URLValidator`](https://docs.djangoproject.com/en/2.0/ref/validators/#urlvalidator) utility.
It seems to be directed more at end user input on a web page than for validating admin input.

[Ticket #25418](https://code.djangoproject.com/ticket/25418) tracks progress on the topic in the Django Project. [Ticket #9202](https://code.djangoproject.com/ticket/9202) is also related.

I would suggest either removing validation or replacing it with something more meaningful, for example a simple RegEx or an actual check which tests whether the URL's target is reachable.

What are your thoughts on the subject?

Changed files:
promgen/settings.py, promgen/management/commands/bootstrap.py

diff --git a/promgen/management/commands/bootstrap.py b/promgen/management/commands/bootstrap.py
index 9c61551..a2eedf1 100644
--- a/promgen/management/commands/bootstrap.py
+++ b/promgen/management/commands/bootstrap.py
@@ -4,57 +4,51 @@
 import os
 import shutil
 
-import dj_database_url
 from django.conf import settings
 from django.core.management.base import BaseCommand
-from django.core.validators import URLValidator
 
 
 class Command(BaseCommand):
     def prompt(self, prompt, *args, **kwargs):
         return input(prompt.format(*args, **kwargs))
 
+    def heading(self, str):
+        self.stdout.write(self.style.MIGRATE_HEADING(str))
+
     def write(self, fmtstr, *args, **kwargs):
         self.stdout.write(fmtstr.format(*args, **kwargs))
 
-    def write_setting(self, key, default='', test=None):
-        path = os.path.join(settings.PROMGEN_CONFIG_DIR, key)
-        if os.path.exists(path):
-            self.write('  Setting {} exists', key)
+    def write_setting(self, key, default=None, value=None):
+        path = settings.PROMGEN_CONFIG_DIR / key
+        if path.exists():
+            self.write("  Setting {} exists", key)
             return
 
-        value = None
         if default:
-            if self.prompt('Use {} for {} ? (yes/no) ', default, key).lower() == 'yes':
+            if self.prompt("Use {} for {} ? (yes/no) ", default, key).lower() == "yes":
                 value = default
 
         while not value:
-            value = self.prompt('Please enter a value for {}: ', key).strip()
-            if test:
-                try:
-                    test(value)
-                except:
-                    self.write('Invalid value {} for {}: ', value, key)
-                    value = None
-
-        self.write('Writing {} to {}', value, path)
-        with open(path, 'w', encoding='utf8') as fp:
+            value = self.prompt("Please enter a value for {}: ", key).strip()
+
+        self.write("Writing {} to {}", value, path)
+        with path.open("w", encoding="utf8") as fp:
             fp.write(value)
 
     def handle(self, **kwargs):
-        self.stdout.write(self.style.MIGRATE_HEADING('Bootstrapping Promgen'))
+        self.heading("Bootstrapping Promgen")
 
-        if not os.path.exists(settings.PROMGEN_CONFIG_DIR):
-            self.write('Creating config directory {} ', settings.PROMGEN_CONFIG_DIR)
+        if not settings.PROMGEN_CONFIG_DIR.exists():
+            self.write("Creating config directory {} ", settings.PROMGEN_CONFIG_DIR)
             os.makedirs(settings.PROMGEN_CONFIG_DIR)
 
-        if not os.path.exists(settings.PROMGEN_CONFIG):
-            path = os.path.join(settings.BASE_DIR, 'promgen', 'tests', 'examples', 'promgen.yml')
-            self.write('  Creating promgen config {} from {}', settings.PROMGEN_CONFIG, path)
+        if not settings.PROMGEN_CONFIG.exists():
+            path = settings.BASE_DIR / "promgen" / "tests" / "examples" / "promgen.yml"
+            self.write("  Creating promgen config {} from {}", settings.PROMGEN_CONFIG, path)
             shutil.copy(path, settings.PROMGEN_CONFIG)
 
-        self.write_setting('SECRET_KEY', default=settings.SECRET_KEY)
-        self.write_setting('DATABASE_URL', test=dj_database_url.parse)
+        self.write_setting("SECRET_KEY", default=settings.SECRET_KEY)
+        self.write_setting("DATABASE_URL")
         # Schemes based on list of supported brokers
         # http://docs.celeryproject.org/en/latest/getting-started/brokers/index.html
-        self.write_setting('CELERY_BROKER_URL', test=URLValidator(schemes=['redis', 'amqp', 'sqs']))
+        self.write_setting("CELERY_BROKER_URL")
diff --git a/promgen/settings.py b/promgen/settings.py
index 197fb18..7572a86 100644
--- a/promgen/settings.py
+++ b/promgen/settings.py
@@ -38,13 +38,12 @@ SECRET_KEY = os.environ.get('SECRET_KEY')
 DEBUG = os.environ.get('DEBUG', '0') != '0'
 
 # Settings for Prometheus paths and such
-PROMGEN_CONFIG_DIR = os.environ['PROMGEN_CONFIG_DIR']
-PROMGEN_CONFIG = os.environ.get(
-    'PROMGEN_CONFIG',
-    os.path.join(PROMGEN_CONFIG_DIR, 'promgen.yml')
+PROMGEN_CONFIG_DIR = pathlib.Path(os.environ["PROMGEN_CONFIG_DIR"])
+PROMGEN_CONFIG = pathlib.Path(
+    os.environ.get("PROMGEN_CONFIG", PROMGEN_CONFIG_DIR / "promgen.yml")
 )
-if os.path.exists(PROMGEN_CONFIG):
-    with open(PROMGEN_CONFIG) as fp:
+if PROMGEN_CONFIG.exists():
+    with PROMGEN_CONFIG.open() as fp:
         PROMGEN = yaml.safe_load(fp)
 else:
     PROMGEN = {}