diff --git a/README.md b/README.md index 30320ad..c47ff14 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Actions Status](https://github.com/theriverman/django-minio-backend/workflows/build-n-publish/badge.svg)](https://github.com/theriverman/django-minio-backend/actions) +[![Actions Status](https://github.com/theriverman/django-minio-backend/workflows/publish-py-dist-to-pypi/badge.svg)](https://github.com/theriverman/django-minio-backend/actions) [![PYPI](https://img.shields.io/pypi/v/django-minio-backend.svg)](https://pypi.python.org/pypi/django-minio-backend) # django-minio-backend diff --git a/django_minio_backend/management/commands/is_minio_available.py b/django_minio_backend/management/commands/is_minio_available.py new file mode 100644 index 0000000..ef938d0 --- /dev/null +++ b/django_minio_backend/management/commands/is_minio_available.py @@ -0,0 +1,23 @@ +from django.core.management.base import BaseCommand, CommandError +from django_minio_backend.models import MinioBackend + + +class Command(BaseCommand): + help = 'Checks if the configured MinIO service is available.' + + def add_arguments(self, parser): + parser.add_argument('--silenced', action='store_true', default=False, help='No console messages') + + def handle(self, *args, **options): + m = MinioBackend('') # no configured bucket + silenced = options.get('silenced') + self.stdout.write(f"Checking the availability of MinIO at {m.base_url}\n") if not silenced else None + + available = m.is_minio_available() + if not available: + self.stdout.flush() + raise CommandError(f'MinIO is NOT available at {m.base_url}\n' + f'Reason: {available.details}') + + self.stdout.write(f'MinIO is available at {m.base_url}', ending='\n') if not silenced else None + self.stdout.flush() diff --git a/django_minio_backend/models.py b/django_minio_backend/models.py index f19630c..ab8291e 100644 --- a/django_minio_backend/models.py +++ b/django_minio_backend/models.py @@ -9,8 +9,10 @@ # noinspection PyPackageRequirements minIO_requirement import certifi -import minio.definitions +import minio +import minio.datatypes import minio.error +import minio.helpers # noinspection PyPackageRequirements minIO_requirement import urllib3 from django.core.files import File @@ -18,7 +20,6 @@ from django.core.files.uploadedfile import InMemoryUploadedFile from django.utils.deconstruct import deconstructible from django.utils.timezone import utc -from minio import Minio from .utils import MinioServerStatus, PrivatePublicMixedError, ConfigurationError, get_setting @@ -56,12 +57,14 @@ def __init__(self, self._REPLACE_EXISTING = kwargs.get('replace_existing', False) - self.__CLIENT: Union[Minio, None] = None + self.__CLIENT: Union[minio.Minio, None] = None self.__MINIO_ENDPOINT: str = get_setting("MINIO_ENDPOINT") self.__MINIO_ACCESS_KEY: str = get_setting("MINIO_ACCESS_KEY") self.__MINIO_SECRET_KEY: str = get_setting("MINIO_SECRET_KEY") self.__MINIO_USE_HTTPS: bool = get_setting("MINIO_USE_HTTPS") + self.__BASE_URL = ("https://" if self.__MINIO_USE_HTTPS else "http://") + self.__MINIO_ENDPOINT + self.PRIVATE_BUCKETS: List[str] = get_setting("MINIO_PRIVATE_BUCKETS", []) self.PUBLIC_BUCKETS: List[str] = get_setting("MINIO_PUBLIC_BUCKETS", []) @@ -124,12 +127,12 @@ def _open(self, object_name, mode='rb', **kwargs): resp.release_conn() return file - def stat(self, name: str) -> Union[minio.definitions.Object, bool]: + def stat(self, name: str) -> Union[minio.datatypes.Object, bool]: object_name = Path(name).as_posix() try: obj = self.client.stat_object(self._BUCKET_NAME, object_name=object_name) return obj - except (minio.error.NoSuchKey, minio.error.NoSuchBucket): + except (minio.error.S3Error, minio.error.ServerError): return False except urllib3.exceptions.MaxRetryError: return False @@ -152,7 +155,7 @@ def exists(self, name: str) -> bool: return False def listdir(self, bucket_name: str): - objects = self.client.list_objects_v2(bucket_name=bucket_name, recursive=True) + objects = self.client.list_objects(bucket_name=bucket_name, recursive=True) return [(obj.object_name, obj) for obj in objects] def size(self, name: str) -> int: @@ -171,8 +174,7 @@ def url(self, name: str): :return: (str) URL to object """ if self.is_bucket_public: - # noinspection PyProtectedMember - return f'{self.client._endpoint_url}/{self._BUCKET_NAME}/{name}' + return f'{self.__BASE_URL}/{self._BUCKET_NAME}/{name}' try: return self.client.presigned_get_object( @@ -237,12 +239,11 @@ def is_minio_available(self) -> MinioServerStatus: with urllib3.PoolManager(cert_reqs=ssl.CERT_REQUIRED, ca_certs=certifi.where()) as http: try: - scheme = "https" if self.__MINIO_USE_HTTPS else "http" - r = http.request('GET', f'{scheme}://{self.__MINIO_ENDPOINT}/minio/index.html') + r = http.request('GET', f'{self.__BASE_URL}/minio/index.html') return MinioServerStatus(r) except urllib3.exceptions.MaxRetryError as e: mss = MinioServerStatus(None) - mss.add_message(f'Could not open connection to {self.__MINIO_ENDPOINT}/minio/index.html\n' + mss.add_message(f'Could not open connection to {self.__BASE_URL}/minio/index.html\n' f'Reason: {e}') return mss except Exception as e: @@ -251,12 +252,16 @@ def is_minio_available(self) -> MinioServerStatus: return mss @property - def client(self) -> Minio: + def client(self) -> minio.Minio: if not self.__CLIENT: self.new_client() return self.__CLIENT return self.__CLIENT + @property + def base_url(self) -> str: + return self.__BASE_URL + def new_client(self): """ Instantiates a new Minio client and @@ -270,7 +275,7 @@ def new_client(self): 'is not configured properly in your settings.py (or equivalent)' ) - mc = Minio( + mc = minio.Minio( endpoint=self.__MINIO_ENDPOINT, access_key=self.__MINIO_ACCESS_KEY, secret_key=self.__MINIO_SECRET_KEY, diff --git a/django_minio_backend/utils.py b/django_minio_backend/utils.py index 6ddfcfc..2cee815 100644 --- a/django_minio_backend/utils.py +++ b/django_minio_backend/utils.py @@ -25,22 +25,20 @@ def __init__(self, request: Union[urllib3.response.HTTPResponse, None]): self._details: List[str] = [] self.status = None self.data = None - self.eval() self.__OK = 'MinIO is available' self.___NOK = 'MinIO is NOT available' - def eval(self): if not self._request: self.add_message('There was no HTTP request provided for MinioServerStatus upon initialisation.') - return False - self.status = self._request.status - self.data = self._request.data.decode() if self._request.data else 'No data available' - if self.status == 403: # Request was a legal, but the server refuses to respond to it -> it's running fine - self._bool = True else: - self._details.append(self.__OK) - self._details.append('Reason: ' + self.data) + self.status = self._request.status + self.data = self._request.data.decode() if self._request.data else 'No data available' + if self.status == 403: # Request was a legal, but the server refuses to respond to it -> it's running fine + self._bool = True + else: + self._details.append(self.__OK) + self._details.append('Reason: ' + self.data) def __bool__(self): return self._bool diff --git a/requirements.txt b/requirements.txt index c4d09a9..acb5598 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -Django~=3.1.3 -minio~=6.0.0,<7 +Django>=2.2.2 +minio>=7.0.0 Pillow -setuptools~=50.3.2 +setuptools diff --git a/setup.py b/setup.py index e7b7750..992a68c 100644 --- a/setup.py +++ b/setup.py @@ -25,13 +25,11 @@ author_email='kristof@daja.hu', install_requires=[ 'Django>=2.2.2', - 'minio>=4.0.9,<7' + 'minio>=7.0.0' ], classifiers=[ 'Environment :: Web Environment', 'Framework :: Django', - 'Framework :: Django :: 2.0', - 'Framework :: Django :: 2.1', 'Framework :: Django :: 2.2', 'Framework :: Django :: 3.0', 'Framework :: Django :: 3.1',