V0.6.2 #14

Merged
ton merged 5 commits from v0.6.2 into main 2026-08-29 02:58:21 +00:00
2 changed files with 217 additions and 0 deletions
Showing only changes of commit f88656172c - Show all commits
+100
View File
@@ -1,6 +1,7 @@
module garageS3 module garageS3
export export
LifecycleExpiration, build_lifecycle_xml,
GarageStorage, put_file, get_file, list_files, delete_file, GarageStorage, put_file, get_file, list_files, delete_file,
set_lifecycle_expiration, get_lifecycle, delete_lifecycle set_lifecycle_expiration, get_lifecycle, delete_lifecycle
@@ -266,11 +267,110 @@ function delete_file(storage::GarageStorage, key::String)
end end
# ===================================================================
# 3. Bucket Lifecycle Configuration
# ===================================================================
""" Lifecycle expiration configuration for a bucket.
# Fields
- `prefix::String`: Object key prefix this rule applies to.
- `days::Int`: Number of days after object creation to expire/delete.
- `enabled::Bool`: Whether the rule is active (default: `true`).
- `id::String`: Unique identifier for the rule (default: auto-generated).
"""
struct LifecycleExpiration
prefix::String
days::Int
enabled::Bool
id::String
end
function LifecycleExpiration(prefix::String, days::Int; enabled::Bool=true, id::String="")
rule_id = isempty(id) ? "expire-" * replace(prefix, "/" => "_") * "-" * string(days) * "d" : id
return LifecycleExpiration(prefix, days, enabled, rule_id)
end
""" Build the XML body for a PutBucketLifecycleConfiguration request.
Supports a single rule with expiration by prefix.
"""
function build_lifecycle_xml(rule::LifecycleExpiration)::String
status = rule.enabled ? "Enabled" : "Disabled"
return """<?xml version="1.0" encoding="UTF-8"?>
<LifecycleConfiguration>
<Rule>
<ID>$(rule.id)</ID>
<Filter>
<Prefix>$(rule.prefix)</Prefix>
</Filter>
<Status>$status</Status>
<Expiration>
<Days>$(rule.days)</Days>
</Expiration>
</Rule>
</LifecycleConfiguration>"""
end
""" Set bucket lifecycle expiration rule.
# Arguments
- `storage::GarageStorage`: The storage client instance.
- `rule::LifecycleExpiration`: The lifecycle expiration rule.
# Returns
- `Dict{String, Any}`: Parsed response from the server.
# Example
```julia
rule = LifecycleExpiration("uploads/", 30)
set_lifecycle_expiration(storage, rule)
```
"""
function set_lifecycle_expiration(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)
end
""" Get the bucket lifecycle configuration.
# Arguments
- `storage::GarageStorage`: The storage client instance.
# Returns
- `Dict{String, Any}`: Parsed lifecycle configuration, or `nothing` if no lifecycle is configured.
# Example
```julia
config = get_lifecycle(storage)
```
"""
function get_lifecycle(storage::GarageStorage)::Union{Dict{String,Any},Nothing}
try
return S3.get_bucket_lifecycle_configuration(storage.bucket; aws_config=storage.config)
catch e
if isa(e, AWS.AWSException) && e.code == "NoSuchLifecycleConfiguration"
return nothing
end
rethrow(e)
end
end
""" Delete the bucket lifecycle configuration.
# Arguments
- `storage::GarageStorage`: The storage client instance.
# Returns
- `Dict{String, Any}`: Parsed response from the server.
# Example
```julia
delete_lifecycle(storage)
```
"""
function delete_lifecycle(storage::GarageStorage)
return S3.delete_bucket_lifecycle(storage.bucket; aws_config=storage.config)
end
end # module GarageS3 end # module GarageS3
+117
View File
@@ -343,4 +343,121 @@ using UUIDs: uuid4
delete_file(storage, test_key) delete_file(storage, test_key)
end end
# --- Lifecycle configuration tests ---
@testset "LifecycleExpiration construction with default ID" begin
rule = LifecycleExpiration("uploads/", 30)
@test rule.prefix == "uploads/"
@test rule.days == 30
@test rule.enabled == true
@test rule.id == "expire-uploads_-30d"
end
@testset "LifecycleExpiration construction with custom ID" begin
rule = LifecycleExpiration("data/", 7; id="my-rule")
@test rule.prefix == "data/"
@test rule.days == 7
@test rule.enabled == true
@test rule.id == "my-rule"
end
@testset "LifecycleExpiration with disabled flag" begin
rule = LifecycleExpiration("temp/", 1; enabled=false)
@test rule.enabled == false
end
@testset "LifecycleExpiration with nested prefix" begin
rule = LifecycleExpiration("deep/nested/", 90)
@test rule.prefix == "deep/nested/"
end
@testset "build_lifecycle_xml generates valid XML" begin
rule = LifecycleExpiration("test/", 14; id="test-rule")
xml = build_lifecycle_xml(rule)
@test occursin("<?xml version=\"1.0\"", xml)
@test occursin("<LifecycleConfiguration>", xml)
@test occursin("<ID>test-rule</ID>", xml)
@test occursin("<Prefix>test/</Prefix>", xml)
@test occursin("<Status>Enabled</Status>", xml)
@test occursin("<Days>14</Days>", xml)
@test occursin("</LifecycleConfiguration>", xml)
end
@testset "build_lifecycle_xml generates disabled rule XML" begin
rule = LifecycleExpiration("temp/", 1; enabled=false)
xml = build_lifecycle_xml(rule)
@test occursin("<Status>Disabled</Status>", xml)
end
@testset "integration: lifecycle CRUD - set and get" begin
storage = GarageStorage(
"https://s3-api.yiem.cc",
"GKb080154a2e5b19100b1b2c6e",
"a2c6b1379c2f731d3e6e5a408dd4d6cffca7511717675f55114ff94828febca1",
"testbucket"
)
# Should return nothing when no lifecycle is set
lc = get_lifecycle(storage)
@test lc === nothing
# Set lifecycle
rule = LifecycleExpiration("lifecycle-test/", 45)
set_lifecycle_expiration(storage, rule)
# Should return configuration after setting
lc = get_lifecycle(storage)
@test lc !== nothing
@test haskey(lc, "Rule")
# Delete lifecycle
delete_lifecycle(storage)
# Should return nothing after deletion
lc = get_lifecycle(storage)
@test lc === nothing
end
@testset "integration: lifecycle with nested prefix" begin
storage = GarageStorage(
"https://s3-api.yiem.cc",
"GKb080154a2e5b19100b1b2c6e",
"a2c6b1379c2f731d3e6e5a408dd4d6cffca7511717675f55114ff94828febca1",
"testbucket"
)
rule = LifecycleExpiration("a/b/c/", 20; id="nested-rule")
set_lifecycle_expiration(storage, rule)
lc = get_lifecycle(storage)
@test lc !== nothing
@test haskey(lc, "Rule")
delete_lifecycle(storage)
@test get_lifecycle(storage) === nothing
end
@testset "integration: lifecycle enabled/disabled toggle" begin
storage = GarageStorage(
"https://s3-api.yiem.cc",
"GKb080154a2e5b19100b1b2c6e",
"a2c6b1379c2f731d3e6e5a408dd4d6cffca7511717675f55114ff94828febca1",
"testbucket"
)
# Set enabled rule
rule1 = LifecycleExpiration("toggle/", 10; enabled=true)
set_lifecycle_expiration(storage, rule1)
lc = get_lifecycle(storage)
@test lc !== nothing
# Override with disabled rule
rule2 = LifecycleExpiration("toggle/", 10; enabled=false)
set_lifecycle_expiration(storage, rule2)
lc = get_lifecycle(storage)
@test lc !== nothing
delete_lifecycle(storage)
end
end end