Skip to content

Commit

Permalink
Add support for ssl/tls settings (#93)
Browse files Browse the repository at this point in the history
---------
Co-authored-by: Christoph Kuhnke <[email protected]>
  • Loading branch information
Nicoretti authored Feb 16, 2024
1 parent 797be53 commit e16cc33
Show file tree
Hide file tree
Showing 7 changed files with 287 additions and 114 deletions.
3 changes: 3 additions & 0 deletions doc/changes/unreleased.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Unreleased

## Feature
- Added support for configuring `SSL/TLS` validation

## Documentation
- Added overview of bucketfs system

Expand Down
2 changes: 2 additions & 0 deletions doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
".md": "markdown",
}

napoleon_include_init_with_doc = True

# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]

Expand Down
72 changes: 52 additions & 20 deletions exasol/bucketfs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
CURL:
$ curl -u "w:write" --output myfile.txt http://127.0.0.1:6666/default/myfile.txt
"""
from __future__ import annotations

import hashlib
from collections import defaultdict
from pathlib import Path
Expand Down Expand Up @@ -115,25 +117,37 @@ class Service:
buckets: lists all available buckets.
"""

def __init__(self, url: str, credentials: Mapping[str, Mapping[str, str]] = None):
def __init__(
self,
url: str,
credentials: Mapping[str, Mapping[str, str]] = None,
verify: bool | str = True,
):
"""Create a new Service instance.
Args:
url: of the bucketfs service, e.g. `http(s)://127.0.0.1:2580`.
credentials: a mapping containing credentials (username and password) for buckets.
url:
Url of the bucketfs service, e.g. `http(s)://127.0.0.1:2580`.
credentials:
A mapping containing credentials (username and password) for buckets.
E.g. {"bucket1": { "username": "foo", "password": "bar" }}
verify:
Either a boolean, in which case it controls whether we verify
the server's TLS certificate, or a string, in which case it must be a path
to a CA bundle to use. Defaults to ``True``.
"""
self._url = _parse_service_url(url)
self._authenticator = defaultdict(
lambda: {"username": "r", "password": "read"},
credentials if credentials is not None else {},
)
self._verify = verify

@property
def buckets(self) -> MutableMapping[str, "Bucket"]:
def buckets(self) -> MutableMapping[str, Bucket]:
"""List all available buckets."""
url = _build_url(service_url=self._url)
response = requests.get(url)
response = requests.get(url, verify=self._verify)
try:
response.raise_for_status()
except HTTPError as ex:
Expand All @@ -155,28 +169,44 @@ def buckets(self) -> MutableMapping[str, "Bucket"]:
def __str__(self) -> str:
return f"Service<{self._url}>"

def __iter__(self) -> Iterator["Bucket"]:
def __iter__(self) -> Iterator[Bucket]:
yield from self.buckets

def __getitem__(self, item: str) -> "Bucket":
def __getitem__(self, item: str) -> Bucket:
return self.buckets[item]


class Bucket:
def __init__(self, name: str, service: str, username: str, password: str):
def __init__(
self,
name: str,
service: str,
username: str,
password: str,
verify: bool | str = True,
):
"""
Create a new bucket instance.
Args:
name: of the bucket.
service: url where this bucket is hosted on.
username: used for authentication.
password: used for authentication.
name:
Name of the bucket.
service:
Url where this bucket is hosted on.
username:
Username used for authentication.
password:
Password used for authentication.
verify:
Either a boolean, in which case it controls whether we verify
the server's TLS certificate, or a string, in which case it must be a path
to a CA bundle to use. Defaults to ``True``.
"""
self._name = name
self._service = _parse_service_url(service)
self._username = username
self._password = password
self._verify = verify

def __str__(self):
return f"Bucket<{self.name} | on: {self._service}>"
Expand All @@ -192,7 +222,7 @@ def _auth(self) -> HTTPBasicAuth:
@property
def files(self) -> Iterable[str]:
url = _build_url(service_url=self._service, bucket=self.name)
response = requests.get(url, auth=self._auth)
response = requests.get(url, auth=self._auth, verify=self._verify)
try:
response.raise_for_status()
except HTTPError as ex:
Expand All @@ -205,7 +235,7 @@ def __iter__(self) -> Iterator[str]:
yield from self.files

def upload(
self, path: str, data: Union[ByteString, BinaryIO, Iterable[ByteString]]
self, path: str, data: ByteString | BinaryIO | Iterable[ByteString]
) -> None:
"""
Uploads a file onto this bucket
Expand All @@ -215,7 +245,7 @@ def upload(
data: raw content of the file.
"""
url = _build_url(service_url=self._service, bucket=self.name, path=path)
response = requests.put(url, data=data, auth=self._auth)
response = requests.put(url, data=data, auth=self._auth, verify=self._verify)
try:
response.raise_for_status()
except HTTPError as ex:
Expand All @@ -232,7 +262,7 @@ def delete(self, path) -> None:
A BucketFsError if the operation couldn't be executed successfully.
"""
url = _build_url(service_url=self._service, bucket=self.name, path=path)
response = requests.delete(url, auth=self._auth)
response = requests.delete(url, auth=self._auth, verify=self._verify)
try:
response.raise_for_status()
except HTTPError as ex:
Expand All @@ -250,7 +280,9 @@ def download(self, path: str, chunk_size: int = 8192) -> Iterable[ByteString]:
An iterable of binary chunks representing the downloaded file.
"""
url = _build_url(service_url=self._service, bucket=self.name, path=path)
with requests.get(url, stream=True, auth=self._auth) as response:
with requests.get(
url, stream=True, auth=self._auth, verify=self._verify
) as response:
try:
response.raise_for_status()
except HTTPError as ex:
Expand Down Expand Up @@ -296,7 +328,7 @@ def __iter__(self) -> Iterable[str]:
yield from self._bucket.files

def __setitem__(
self, key: str, value: Union[ByteString, BinaryIO, Iterable[ByteString]]
self, key: str, value: ByteString | BinaryIO | Iterable[ByteString]
) -> None:
"""
Uploads a file onto this bucket.
Expand Down Expand Up @@ -325,7 +357,7 @@ def __str__(self):
return f"MappedBucket<{self._bucket}>"


def _chunk_as_bytes(chunk: Union[int, ByteString]) -> ByteString:
def _chunk_as_bytes(chunk: int | ByteString) -> ByteString:
"""
In some scenarios python converts single bytes to integers:
>>> chunks = [type(chunk) for chunk in b"abc"]
Expand Down Expand Up @@ -373,7 +405,7 @@ def as_string(chunks: Iterable[ByteString], encoding: str = "utf-8") -> str:
return _bytes(chunks).decode(encoding)


def as_file(chunks: Iterable[ByteString], filename: Union[str, Path]) -> Path:
def as_file(chunks: Iterable[ByteString], filename: str | Path) -> Path:
"""
Transforms a set of byte chunks into a string.
Expand Down
5 changes: 4 additions & 1 deletion exasol/bucketfs/version.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# ATTENTION:
# This file is generated, do not edit it manually!
# This file is generated by exasol/toolbox/pre_commit_hooks/package_version.py when using:
# * either "poetry run nox -s fix"
# * or "poetry run version-check <path/version.py> --fix"
# Do not edit this file manually!
# If you need to change the version, do so in the project.toml, e.g. by using `poetry version X.Y.Z`.
MAJOR = 0
MINOR = 9
Expand Down
Loading

0 comments on commit e16cc33

Please sign in to comment.