V0.6.10 #16

Merged
ton merged 8 commits from v0.6.10 into main 2026-09-05 14:16:56 +00:00
2 changed files with 57 additions and 41 deletions
Showing only changes of commit 599765d7ef - Show all commits
+55 -39
View File
@@ -2,9 +2,9 @@ module garageS3
export
LifecycleExpiration, build_lifecycle_xml,
GarageStorage, put_file, get_file, list_files, delete_file,
set_lifecycle_expiration, get_lifecycle, delete_lifecycle,
s3_sign_url, s3_sign_url_web
GarageStorage, s3PutFile, s3GetFile, s3ListFiles, s3DeleteFile,
s3SetLifecycleExpiration, s3GetLifecycle, s3DeleteLifecycle,
s3SignUrl, s3SignUrlWeb
using AWS
@service S3
@@ -29,8 +29,8 @@ storage1 = GeneralUtils.GarageStorage(
# Safe to run inside concurrent HTTP handlers (e.g., Oxygen.jl, HTTP.jl)
# use UUID as filename because GarageS3 requires unique filename for each uploaded object
urls = GeneralUtils.put_file(storage1, "transferjob-(GeneralUtils.uuid4snakecase())", "{\"status\": \"inactive\"}")
key = GeneralUtils.list_files(storage)
urls = GeneralUtils.s3PutFile(storage1, "transferjob-(GeneralUtils.uuid4snakecase())", "{\"status\": \"inactive\"}")
key = GeneralUtils.s3ListFiles(storage)
println("Read back: ", data)
println("Bucket keys: ", key)
@@ -41,7 +41,7 @@ curl -v \
http://192.168.88.106:3902/users-1002.json
# test with API
data = String(GeneralUtils.get_file(storage, key[1]))
data = String(GeneralUtils.s3GetFile(storage, key[1]))
# test with a browser (urls.web)
https://s3-web.mydomain.com/my_bucket_name/users-1005.json
@@ -143,8 +143,8 @@ end
# Examples
```jldoctest
julia> storage1 = GarageStorage("https://s3-api.yiem.cc", "GKb08015", "55114ff94828febca1", "sommpanion-s3")
julia> put_file1 = put_file(storage1)
julia> downloadUrl = put_file1("test.json", "{\"status\": \"active\"}")
julia> s3PutFile1 = s3PutFile(storage1)
julia> downloadUrl = s3PutFile1("test.json", "{\"status\": \"active\"}")
Successfully uploaded: test.json
julia> downloadUrl.api
"https://s3-api.yiem.cc/sommpanion-s3/test.json"
@@ -152,10 +152,10 @@ julia> downloadUrl.web
"https://s3-web.yiem.cc/sommpanion-s3/test.json"
```
"""
mutable struct put_file
mutable struct s3PutFile
storage::GarageStorage
end
function (pf::put_file)(key::String, data::Union{String, Vector{UInt8}}
function (pf::s3PutFile)(key::String, data::Union{String, Vector{UInt8}}
)::NamedTuple{(:api, :web), Tuple{String, String}}
AWSS3.s3_put(pf.storage.config, pf.storage.bucket, key, data)
println("Successfully uploaded: ", key)
@@ -184,7 +184,7 @@ end
# Examples
```julia
julia> result = put_file(storage, "test.json", "{\"status\": \"active\"}")
julia> result = s3PutFile(storage, "test.json", "{\"status\": \"active\"}")
Successfully uploaded: test.json
julia> result.api
"https://s3-api.yiem.cc/sommpanion-s3/test.json"
@@ -192,7 +192,7 @@ julia> result.web
"https://s3-web.yiem.cc/sommpanion-s3/test.json"
```
"""
function put_file(storage::GarageStorage, key::String, data::Union{String, Vector{UInt8}}
function s3PutFile(storage::GarageStorage, key::String, data::Union{String, Vector{UInt8}}
)::NamedTuple{(:api, :web), Tuple{String, String}}
AWSS3.s3_put(storage.config, storage.bucket, key, data)
println("Successfully uploaded: ", key)
@@ -219,11 +219,11 @@ end
# Examples
```julia
julia> data = String(get_file(storage, "test.json"))
julia> data = String(s3GetFile(storage, "test.json"))
"\"{\\\"status\\\": \\\"active\\\"}\""
```
"""
function get_file(storage::GarageStorage, key::String)::Union{Vector{UInt8}, Nothing}
function s3GetFile(storage::GarageStorage, key::String)::Union{Vector{UInt8}, Nothing}
try
return AWSS3.s3_get(storage.config, storage.bucket, key)
catch
@@ -242,14 +242,14 @@ end
# Examples
```julia
julia> keys = list_files(storage)
julia> keys = s3ListFiles(storage)
3-element Vector{String}:
"test1.json"
"test2.json"
"users-1002.json"
```
"""
function list_files(storage::GarageStorage)
function s3ListFiles(storage::GarageStorage)
# Use delimiter="" to get all individual keys (no grouping by "directory")
objects = AWSS3.s3_list_objects(storage.config, storage.bucket; delimiter="")
return [obj["Key"] for obj in objects]
@@ -265,15 +265,15 @@ end
# Examples
```julia
julia> delete_file(storage, "test.json")
julia> s3DeleteFile(storage, "test.json")
```
"""
function delete_file(storage::GarageStorage, key::String)
function s3DeleteFile(storage::GarageStorage, key::String)
AWSS3.s3_delete(storage.config, storage.bucket, key)
end
"""
s3_sign_url(storage, key, seconds=3600; verb="GET", content_type="application/octet-stream", signature_version="v4") -> String
s3SignUrl(storage, key, seconds=3600; verb="GET", content_type="application/octet-stream", signature_version="v4") -> String
Generate a pre-signed URL for an object in the Garage S3 bucket.
@@ -297,23 +297,39 @@ using AWS Signature Version 4 (HMAC-SHA256).
# Examples
```julia
storage = GarageStorage("https://s3-api.yiem.cc", "key", "secret", "testbucket")
using GeneralUtils: GarageStorage, s3SignUrl, s3ListFiles, s3PutFile
# Download — GET (default)
url = s3_sign_url(storage, "docs/report.pdf", 3600)
storage = GarageStorage(
"https://s3-api.yiem.cc",
"GKb080154a2e5b19100b1b2c6e",
"a2c6b1379c2f731d3e6e5a408dd4d6cffca7511717675f55114ff94828febca1",
"sommpanion"
)
# Upload — PUT
url = s3_sign_url(storage, "avatars/user123.png", 300;
verb="PUT", content_type="image/png")
# Upload — PUT with curl
url = s3SignUrl(storage, "photo.jpg", 3600; verb="PUT", content_type="image/jpeg")
# curl -X PUT -T "/path/to/photo.jpg" -H 'Host: s3-api.yiem.cc' -H 'Content-Type: image/jpeg' <url>
# Download — GET with curl
url = s3SignUrl(storage, "photo.jpg", 3600; verb="GET")
# curl <url> → returns file content
# Upload JSON
url = s3SignUrl(storage, "data/config.json", 3600; verb="PUT", content_type="application/json")
# curl -X PUT -T "/tmp/config.json" -H 'Host: s3-api.yiem.cc' -H 'Content-Type: application/json' <url>
# Delete
url = s3SignUrl(storage, "photo.jpg", 3600; verb="DELETE")
# curl -X DELETE url
# Multiple uploads (chat app)
urls = [s3_sign_url(storage, "chat/GeneralUtils.uuid4snakecase()", 300;
urls = [s3SignUrl(storage, "chat/\$(GeneralUtils.uuid4snakecase())", 300;
verb="PUT", content_type="image/png")
for f in filenames]
for _ in 1:10]
# Signature differs by verb and content_type
url_get = s3_sign_url(storage, "file.txt", 3600; verb="GET")
url_put = s3_sign_url(storage, "file.txt", 3600; verb="PUT", content_type="text/plain")
url_get = s3SignUrl(storage, "file.txt", 3600; verb="GET")
url_put = s3SignUrl(storage, "file.txt", 3600; verb="PUT", content_type="text/plain")
```
# Notes
@@ -322,7 +338,7 @@ url_put = s3_sign_url(storage, "file.txt", 3600; verb="PUT", content_type="text/
- PUT uploads require the client's `Content-Type` to match `content_type`.
- For browser CORS uploads, configure Garage bucket CORS rules for `Origin` and `Method`.
"""
function s3_sign_url(
function s3SignUrl(
storage::GarageStorage,
key::String,
seconds::Int=3600;
@@ -419,9 +435,9 @@ end
Useful for direct browser access — URLs look like
`https://s3-web.yiem.cc/bucket/key?X-Amz-Signature=...`
Arguments and keyword arguments are the same as `s3_sign_url`.
Arguments and keyword arguments are the same as `s3SignUrl`.
"""
function s3_sign_url_web(
function s3SignUrlWeb(
storage::GarageStorage,
key::String,
seconds::Int=3600;
@@ -437,7 +453,7 @@ function s3_sign_url_web(
)
web_config = GarageConfig(web_endpoint, storage.config.region, web_creds)
web_storage = GarageStorage(web_config, storage.bucket)
return s3_sign_url(web_storage, key, seconds; verb, content_type, signature_version)
return s3SignUrl(web_storage, key, seconds; verb, content_type, signature_version)
end
# ===================================================================
@@ -496,10 +512,10 @@ end
# Example
```julia
rule = LifecycleExpiration("uploads/", 30)
set_lifecycle_expiration(storage, rule)
s3SetLifecycleExpiration(storage, rule)
```
"""
function set_lifecycle_expiration(storage::GarageStorage, rule::LifecycleExpiration)
function s3SetLifecycleExpiration(storage::GarageStorage, rule::LifecycleExpiration)
xml_body = build_lifecycle_xml(rule)
args = Dict{String,Any}("body" => xml_body)
return S3.put_bucket_lifecycle_configuration(storage.bucket, args; aws_config=storage.config)
@@ -515,10 +531,10 @@ end
# Example
```julia
config = get_lifecycle(storage)
config = s3GetLifecycle(storage)
```
"""
function get_lifecycle(storage::GarageStorage)::Union{Dict{String,Any},Nothing}
function s3GetLifecycle(storage::GarageStorage)::Union{Dict{String,Any},Nothing}
try
return S3.get_bucket_lifecycle_configuration(storage.bucket; aws_config=storage.config)
catch e
@@ -539,10 +555,10 @@ end
# Example
```julia
delete_lifecycle(storage)
s3DeleteLifecycle(storage)
```
"""
function delete_lifecycle(storage::GarageStorage)
function s3DeleteLifecycle(storage::GarageStorage)
return S3.delete_bucket_lifecycle(storage.bucket; aws_config=storage.config)
end
+2 -2
View File
@@ -13,10 +13,10 @@ curl -X PUT -H "Content-Type: application/json" \
curl -v -X PUT -T "/home/ton/docker-apps/sommpanion/etc/GeneralUtils/photo.jpg" \
-H 'Host: s3-api.yiem.cc' \
-H 'Content-Type: image/jpeg' \
"https://s3-api.yiem.cc/sommpanion/photo.jpg?X-Amz-Expires=3600&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=GKb080154a2e5b19100b1b2c6e%2F20260905%2Fgarage%2Fs3%2Faws4_request&X-Amz-Date=20260905T025455Z&X-Amz-SignedHeaders=host&X-Amz-Signature=92e86746a92fa07914d8f08169dac66e2c64f36b807f86504bf8efec41fed153"
"https://s3-api.yiem.cc/sommpanion/photo.jpg?X-Amz-Expires=300&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=GKb080154a2e5b19100b1b2c6e%2F20260905%2Fgarage%2Fs3%2Faws4_request&X-Amz-Date=20260905T030315Z&X-Amz-SignedHeaders=content-type%3Bhost&X-Amz-Signature=1107a31f36f8b8ceb9663d69b9a7be7733df14d019e700e750944cbdad50df5d"