fix: Better UI for S3 download and S3 TS SDK endpoints (#3065)

* Better UI for S3 download and S3 TS SDK endpoints

* gitignore typescript client node_modules

* USe Windmill BE upload endpoint for TS SDK

* Use WM backend endpoint in Python upload SDK endpoint

* revert changes

* Add expiration for Python

* Add downaload endpoint

* Add toggle for public S3 resource

* revert changes to package.json

* Add link to doc page

* fix unauthorized bug
This commit is contained in:
Guillaume Bouvignies
2024-01-25 15:34:40 +01:00
committed by GitHub
parent 2d54b7edb0
commit ef76a90e34
126 changed files with 762 additions and 84186 deletions

View File

@@ -372,7 +372,7 @@ class Windmill:
except JSONDecodeError as e:
raise Exception("Could not generate Boto3 S3 connection settings from the provided resource") from e
def load_s3_file(self, s3object: S3Object, s3_resource_path: str = ""):
def load_s3_file(self, s3object: S3Object, s3_resource_path: str | None) -> bytes:
"""
Load a file from the workspace s3 bucket and returns the bytes stream.
@@ -384,22 +384,39 @@ class Windmill:
file_content = my_obj["Body"].read().decode("utf-8")
'''
"""
try:
s3_resource = self.post(
f"/w/{self.workspace}/job_helpers/v2/s3_resource_info",
json={} if s3_resource_path == "" else {"s3_resource_path": s3_resource_path},
).json()
except JSONDecodeError as e:
raise Exception("Could not generate Boto3 S3 connection settings from the provided resource") from e
import boto3
part_number = 0
file_total_size = None
file_content: list[int] = []
while True:
if part_number is None:
break
try:
part_response = self.post(
f"/w/{self.workspace}/job_helpers/multipart_download_s3_file",
json={
"file_key": s3object.s3,
"part_number": part_number,
"file_size": file_total_size,
"s3_resource_path": s3_resource_path,
},
).json()
except JSONDecodeError as e:
raise Exception("Could not generate download S3 file part") from e
args = self.__boto3_connection_settings(s3_resource)
s3client = boto3.client("s3", **args)
bucket = s3_resource["bucket"]
return s3client.get_object(bucket, Key=s3object["s3"])
if len(part_response["part_content"]) > 0:
file_content = file_content + part_response["part_content"]
part_number = part_response["next_part_number"]
file_total_size = part_response["file_size"]
return bytes(file_content)
def write_s3_file(self, s3object: S3Object, file_content: bytes, s3_resource_path: str = ""):
def write_s3_file(
self,
s3object: S3Object | None,
file_content: bytes,
file_expiration: dt.datetime | None,
s3_resource_path: str | None,
) -> S3Object:
"""
Write a file to the workspace S3 bucket
@@ -412,19 +429,21 @@ class Windmill:
'''
"""
try:
s3_resource = self.post(
f"/w/{self.workspace}/job_helpers/v2/s3_resource_info",
json={} if s3_resource_path == "" else {"s3_resource_path": s3_resource_path},
result = self.post(
f"/w/{self.workspace}/job_helpers/multipart_upload_s3_file",
json={
"file_key": s3object.s3 if s3object is not None else None,
"part_content": file_content,
"parts": [],
"is_final": True,
"cancel_upload": False,
"s3_resource_path": s3_resource_path if s3_resource_path != "" else None,
"file_expiration": file_expiration.isoformat() if file_expiration else None,
},
).json()
except JSONDecodeError as e:
raise Exception("Could not generate Boto3 S3 connection settings from the provided resource") from e
import boto3
args = self.__boto3_connection_settings(s3_resource)
s3client = boto3.client("s3", **args)
bucket = s3_resource["bucket"]
s3client.put_object(bucket, Key=s3object["s3"], Body=file_content)
except Exception as e:
raise Exception("Could not write file to S3") from e
return S3Object(s3=result["file_key"])
def __boto3_connection_settings(self, s3_resource) -> Boto3ConnectionSettings:
endpoint_url_prefix = "https://" if s3_resource["useSSL"] else "http://"
@@ -676,6 +695,29 @@ def boto3_connection_settings(s3_resource_path: str = "") -> Boto3ConnectionSett
return _client.get_boto3_connection_settings(s3_resource_path)
@init_global_client
def load_s3_file(s3object: S3Object, s3_resource_path: str = "") -> bytes:
"""
Load the content of a file stored in S3
"""
return _client.load_s3_file(s3object, s3_resource_path if s3_resource_path != "" else None)
@init_global_client
def write_s3_file(
s3object: S3Object | None,
file_content: bytes,
file_expiration: dt.datetime | None,
s3_resource_path: str = "",
) -> S3Object:
"""
Upload a file to S3
"""
return _client.write_s3_file(
s3object, file_content, file_expiration, s3_resource_path if s3_resource_path != "" else None
)
@init_global_client
def whoami() -> dict:
"""