From 64575d81b5fd368a9e928491823243e5d07cb7f7 Mon Sep 17 00:00:00 2001 From: narawat Date: Thu, 27 Aug 2026 19:40:21 +0700 Subject: [PATCH] add garageS3 feature --- Project.toml | 2 +- src/GeneralUtils.jl | 2 + src/garageS3.jl | 137 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 src/garageS3.jl diff --git a/Project.toml b/Project.toml index eb6b085..df9d4fe 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "GeneralUtils" uuid = "c6c72f09-b708-4ac8-ac7c-2084d70108fe" -version = "0.5.11" +version = "0.6.0" authors = ["tonaerospace "] [deps] diff --git a/src/GeneralUtils.jl b/src/GeneralUtils.jl index 5addf11..ae90f88 100644 --- a/src/GeneralUtils.jl +++ b/src/GeneralUtils.jl @@ -21,6 +21,8 @@ using .llmUtil include("interface.jl") using .interface +include("garageS3.jl") +using .garageS3 #------------------------------------------------------------------------------------------------100 diff --git a/src/garageS3.jl b/src/garageS3.jl new file mode 100644 index 0000000..3ad7e4e --- /dev/null +++ b/src/garageS3.jl @@ -0,0 +1,137 @@ +module GarageS3 + +export + GarageStorage, + put_file, + get_file, + list_files, + delete_file + +using AWS +using AWSS3 +# ---------------------------------------------- 100 --------------------------------------------- # + +# Garage uses Path-Style routing (https://s3-api.my-domain.com/bucket/key). +# this file use local AWS config + +""" Example +const storage = GarageStorage( + "https://s3-api.yiem.cc", + "GKb080154a2e5b19100b1b2c6e", # key ID (create at garage-ui.yiem.cc) + "a2c6b1379c2f731d3e6e5a408dd4d6cffca7511717675f55114ff94828febca1", # key ID's secret key + "sommpanion-s3" +) + +# Safe to run inside concurrent HTTP handlers (e.g., Oxygen.jl, HTTP.jl) +put_file(storage, "users-1005.json", "{\"status\": \"active\"}") +keys = list_files(storage) +data = String(get_file(storage, "users-1004.json")) + +println("Read back: ", data) +println("Bucket keys: ", keys) + +# test with curl +curl -v \ + -H 'Host: sommpanion-s3.s3-web.yiem.cc' \ + http://192.168.88.106:3902/users-1002.json + + +# test with a browser +https://s3-web.mydomain.com/my_bucket_name/users-1002.json + +""" + +# =================================================================== +# 1. Custom GarageConfig Definition (Thread-Safe, No Global State) +# =================================================================== + +struct SimpleCredentials + access_key_id::String + secret_key::String + token::String +end + +struct GarageConfig <: AWS.AbstractAWSConfig + endpoint::String + region::String + credentials::SimpleCredentials +end + +# Required extensions for AWS.jl pipeline +AWS.refresh!(c::SimpleCredentials; force::Bool=false) = c +AWS.credentials(c::SimpleCredentials) = c +AWS.check_credentials(c::SimpleCredentials) = c +AWS.region(aws::GarageConfig) = aws.region +AWS.credentials(aws::GarageConfig) = aws.credentials + +function AWS.generate_service_url(aws::GarageConfig, service::String, region::String) + return strip(aws.endpoint, '/') +end + +function AWS.generate_service_url(aws::GarageConfig, service::String, resource::String) + endpoint = strip(aws.endpoint, '/') + resource_path = startswith(resource, '/') ? resource : "/" * resource + return string(endpoint, resource_path) +end + +# =================================================================== +# 2. Thread-Safe Storage Client Wrapper +# =================================================================== + +struct GarageStorage + config::GarageConfig + bucket::String +end + +function GarageStorage(endpoint::String, key::String, secret::String, bucket::String; region::String="garage") + creds = SimpleCredentials(key, secret, "") + config = GarageConfig(endpoint, region, creds) + return GarageStorage(config, bucket) +end + +# Write object to Garage +function put_file(storage::GarageStorage, key::String, data::Union{String, Vector{UInt8}}) + # Check if the key contains a slash (virtual folder character) + if occursin('/', key) + @warn "Upload aborted! Slashes ('/') are not allowed in keys ('$key') to prevent S3 listing issues. Use a flat naming convention instead (e.g., 'users-1002.json')." + return nothing + end + + # Proceed if the key is flat + s3_put(storage.config, storage.bucket, key, data) + println("Successfully uploaded: ", key) +end + +# Read object from Garage +function get_file(storage::GarageStorage, key::String)::Vector{UInt8} + return s3_get(storage.config, storage.bucket, key) +end + +# List keys in bucket (Thread-safe & explicit credentials) +function list_files(storage::GarageStorage) + # Approach 2: s3_list_objects returns a Vector of Dicts with object details + objects = s3_list_objects(storage.config, storage.bucket) + return [obj["Key"] for obj in objects] +end + +# Delete object from Garage +function delete_file(storage::GarageStorage, key::String) + s3_delete(storage.config, storage.bucket, key) +end + + + + + + + + + + + + + + + + +end # module GarageS3