diff --git a/netbox/extras/models.py b/netbox/extras/models.py index 52dea8674..09fd63772 100644 --- a/netbox/extras/models.py +++ b/netbox/extras/models.py @@ -611,11 +611,20 @@ class ImageAttachment(models.Model): @property def size(self): """ - Wrapper around `image.size` to suppress an OSError in case the file is inaccessible. + Wrapper around `image.size` to suppress an OSError in case the file is inaccessible. Also opportunistically + catch other exceptions that we know other storage back-ends to throw. """ + expected_exceptions = [OSError] + + try: + from botocore.exceptions import ClientError + expected_exceptions.append(ClientError) + except ImportError: + pass + try: return self.image.size - except OSError: + except tuple(expected_exceptions): return None diff --git a/netbox/netbox/configuration.example.py b/netbox/netbox/configuration.example.py index 6b0680da0..3bd271581 100644 --- a/netbox/netbox/configuration.example.py +++ b/netbox/netbox/configuration.example.py @@ -141,6 +141,16 @@ MAX_PAGE_SIZE = 1000 # the default value of this setting is derived from the installed location. # MEDIA_ROOT = '/opt/netbox/netbox/media' +# By default uploaded media is stored on the local filesystem. Using Django-storages is also supported. Provide the +# class path of the storage driver in STORAGE_BACKEND and any configuration options in STORAGE_CONFIG. For example: +# STORAGE_BACKEND = 'storages.backends.s3boto3.S3Boto3Storage' +# STORAGE_CONFIG = { +# 'AWS_ACCESS_KEY_ID': 'Key ID', +# 'AWS_SECRET_ACCESS_KEY': 'Secret', +# 'AWS_STORAGE_BUCKET_NAME': 'netbox', +# 'AWS_S3_REGION_NAME': 'eu-west-1', +# } + # Expose Prometheus monitoring metrics at the HTTP endpoint '/metrics' METRICS_ENABLED = False diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index eb1b9a523..3a5ea4069 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -83,6 +83,8 @@ LOGIN_TIMEOUT = getattr(configuration, 'LOGIN_TIMEOUT', None) MAINTENANCE_MODE = getattr(configuration, 'MAINTENANCE_MODE', False) MAX_PAGE_SIZE = getattr(configuration, 'MAX_PAGE_SIZE', 1000) MEDIA_ROOT = getattr(configuration, 'MEDIA_ROOT', os.path.join(BASE_DIR, 'media')).rstrip('/') +STORAGE_BACKEND = getattr(configuration, 'STORAGE_BACKEND', None) +STORAGE_CONFIG = getattr(configuration, 'STORAGE_CONFIG', {}) METRICS_ENABLED = getattr(configuration, 'METRICS_ENABLED', False) NAPALM_ARGS = getattr(configuration, 'NAPALM_ARGS', {}) NAPALM_PASSWORD = getattr(configuration, 'NAPALM_PASSWORD', '') @@ -118,6 +120,23 @@ DATABASES = { 'default': DATABASE, } +# +# Media storage +# + +if STORAGE_BACKEND is not None: + DEFAULT_FILE_STORAGE = STORAGE_BACKEND + + if STORAGE_BACKEND.startswith('storages.'): + # Monkey-patch Django-storages to also fetch settings from STORAGE_CONFIG + import storages.utils + + def _setting(name, default=None): + if name in STORAGE_CONFIG: + return STORAGE_CONFIG[name] + return globals().get(name, default) + + storages.utils.setting = _setting # # Redis