snapshot of https://github.com/seaweedfs/seaweedfs.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
go: build dev binaries / cleanup (push) Successful in 4s
Codespell / Check for spelling errors (push) Failing after 3s
docker: build dev containers / build-rust-binaries (amd64, x86_64-unknown-linux-musl) (push) Failing after 3s
docker: build dev containers / build-dev-containers (push) Has been skipped
docker: build dev containers / build-rust-binaries (arm64, true, aarch64-unknown-linux-musl) (push) Failing after 3s
End to End / FUSE Mount (push) Failing after 2s
EC Integration Tests / EC Integration Tests (push) Failing after 30s
FUSE DLM Integration Tests / FUSE DLM Integration Tests (push) Failing after 3s
FUSE Volume Server Failover Tests / FUSE Volume Server Failover (push) Failing after 3s
FUSE Integration Tests / FUSE Integration Testing (push) Failing after 2s
FUSE P2P Peer Chunk Sharing Integration Tests / FUSE P2P Peer Chunk Sharing (push) Failing after 3s
go: build binary / Go Vet (push) Failing after 2s
go: build binary / Go Vet 32-bit (push) Failing after 3s
go: build binary / Build (push) Failing after 4s
go: build binary / Build cross-platform (freebsd/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (darwin/arm64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/arm64) (push) Failing after 3s
go: build binary / Test (push) Failing after 2s
go: build binary / Test 32-bit (push) Failing after 3s
helm: lint and test charts / lint-test (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (11) (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (17) (push) Failing after 3s
go: build dev binaries / build_dev_linux_windows (amd64, linux) (push) Failing after 2s
go: build dev binaries / build_dev_linux_windows (amd64, windows) (push) Failing after 3s
go: build dev binaries / build_dev_darwin (amd64, darwin) (push) Has been skipped
go: build dev binaries / build_dev_darwin (arm64, darwin) (push) Has been skipped
Java Client Unit Tests / Java Unit Tests (11) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (17) (push) Failing after 2s
Java Client Unit Tests / Java Unit Tests (21) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (8) (push) Failing after 3s
Kafka Quick Test (Load Test with Schema Registry) / Kafka Client Load Test (Quick) (push) Failing after 3s
Kafka Gateway Tests / Kafka Unit Tests (unit-tests-1) (push) Failing after 11s
Kafka Gateway Tests / Kafka Integration Tests (Critical) (integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka End-to-End Tests (with SMQ) (e2e-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Consumer Group Tests (Highly Isolated) (consumer-group-1) (push) Failing after 2s
Kafka Gateway Tests / Kafka Client Compatibility (with SMQ) (client-compat-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka SMQ Integration Tests (Full Stack) (smq-integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Protocol Tests (Isolated) (protocol-1) (push) Failing after 3s
KMS Tests / KMS Provider Integration Tests (push) Failing after 2s
KMS Tests / S3 KMS End-to-End Tests (push) Failing after 3s
Master Cold Start Tests / Master Cold Start Tests (push) Failing after 4s
Metadata Subscribe Integration Tests / Metadata Subscribe Integration Tests (push) Failing after 3s
Multi-Master Tests / Multi-Master Failover Tests (push) Failing after 3s
Performance / CPU and Heap Profile (push) Failing after 2s
Performance / S3 Read/Write Benchmark (rust volume) (push) Failing after 3s
pjdfstest POSIX Compliance / pjdfstest (push) Failing after 2s
Plugin Worker Integration Tests / Plugin Worker: erasure_coding (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: vacuum (push) Failing after 3s
Rust Volume Server Tests / Rust Integration Tests (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 1) (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 1) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 3) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Distributed Tests (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Performance Tests (push) Failing after 3s
Performance / Throughput Benchmark (go volume) (push) Failing after 3s
Performance / Throughput Benchmark (rust volume) (push) Failing after 2s
Performance / S3 Read/Write Benchmark (go volume) (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: volume_balance (push) Failing after 3s
PostgreSQL Gateway Tests / PostgreSQL Basic Tests (push) Failing after 3s
Rust Volume Server Tests / Rust Unit Tests (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 3) (push) Failing after 3s
Rust Plugin Worker Tests / Rust Plugin Worker Build and Unit Tests (push) Failing after 3s
rust: build dev volume server binaries / cleanup (push) Successful in 3s
S3 ETag and ACL Tests / S3 ETag + ACL Integration Tests (push) Failing after 3s
S3 IAM Integration Tests / IAM Unit Tests (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (advanced) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (basic) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (group) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (policy-enforcement) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (sts) (push) Failing after 4s
S3 Keycloak Integration Tests / S3 Keycloak Integration Tests (push) Failing after 3s
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.11) (push) Failing after 3s
S3 PyArrow Parquet Tests / Go Unit Tests (Implicit Directory) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.12) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-amd64, x86_64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-arm64, aarch64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-linux (linux-amd64, x86_64-unknown-linux-gnu) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.9) (push) Has been cancelled
S3 Proxy Signature Tests / S3 Proxy Signature Verification Tests (push) Has been cancelled
S3 SDK V2 Route Disambiguation Tests / S3 SDK V2 Routing Tests (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (quick) (push) Has been cancelled
S3 SSE Tests / S3 SSE Compatibility Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Metadata Persistence Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Copy Operations Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Multipart Upload Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Performance Test (push) Has been cancelled
S3 SSE Tests / S3 Volume Encryption Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (comprehensive) (push) Has been cancelled
Spark Integration Tests / Spark Integration Tests (push) Failing after 2s
Telemetry Integration Tests / telemetry-integration-test (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 1) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 1) (push) Failing after 3s
Ceph S3 tests / Basic S3 tests (KV store) (push) Has been cancelled
Ceph S3 tests / S3 Versioning & Object Lock tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Unit Tests (push) Failing after 3s
Ceph S3 tests / S3 CORS tests (push) Has been cancelled
Ceph S3 tests / SeaweedFS Custom S3 Copy tests (push) Has been cancelled
Ceph S3 tests / Basic S3 tests (SQL store) (push) Has been cancelled
test s3 over https using aws-cli / awscli-tests (push) Has been cancelled
TLS Rotation Integration Tests / TLS Rotation Integration Tests (push) Has been cancelled
Vacuum Integration Tests / Vacuum Integration Tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Variables Integration Tests (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (advanced-policy) (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (basic-policy) (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (contextual-policy) (push) Failing after 4s
S3 Policy Integration Tests / Trusted Proxy Detection Tests (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Shell Integration Tests (push) Failing after 3s
Samba on FUSE Integration / samba-integration (push) Failing after 2s
SFTP Integration Tests / SFTP Integration Testing (push) Failing after 3s
terraform: validate and test modules / fmt, validate, plan-level tests (push) Failing after 3s
terraform: validate and test modules / local cluster smoke test (real weed) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 3) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 2) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 2) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 3) (push) Failing after 5s
mount: windows conformance / WinFsp conformance (push) Has been cancelled
mount: windows / Mount on Windows (push) Has been cancelled
Some checks failed
go: build dev binaries / cleanup (push) Successful in 4s
Codespell / Check for spelling errors (push) Failing after 3s
docker: build dev containers / build-rust-binaries (amd64, x86_64-unknown-linux-musl) (push) Failing after 3s
docker: build dev containers / build-dev-containers (push) Has been skipped
docker: build dev containers / build-rust-binaries (arm64, true, aarch64-unknown-linux-musl) (push) Failing after 3s
End to End / FUSE Mount (push) Failing after 2s
EC Integration Tests / EC Integration Tests (push) Failing after 30s
FUSE DLM Integration Tests / FUSE DLM Integration Tests (push) Failing after 3s
FUSE Volume Server Failover Tests / FUSE Volume Server Failover (push) Failing after 3s
FUSE Integration Tests / FUSE Integration Testing (push) Failing after 2s
FUSE P2P Peer Chunk Sharing Integration Tests / FUSE P2P Peer Chunk Sharing (push) Failing after 3s
go: build binary / Go Vet (push) Failing after 2s
go: build binary / Go Vet 32-bit (push) Failing after 3s
go: build binary / Build (push) Failing after 4s
go: build binary / Build cross-platform (freebsd/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (darwin/arm64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/arm64) (push) Failing after 3s
go: build binary / Test (push) Failing after 2s
go: build binary / Test 32-bit (push) Failing after 3s
helm: lint and test charts / lint-test (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (11) (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (17) (push) Failing after 3s
go: build dev binaries / build_dev_linux_windows (amd64, linux) (push) Failing after 2s
go: build dev binaries / build_dev_linux_windows (amd64, windows) (push) Failing after 3s
go: build dev binaries / build_dev_darwin (amd64, darwin) (push) Has been skipped
go: build dev binaries / build_dev_darwin (arm64, darwin) (push) Has been skipped
Java Client Unit Tests / Java Unit Tests (11) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (17) (push) Failing after 2s
Java Client Unit Tests / Java Unit Tests (21) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (8) (push) Failing after 3s
Kafka Quick Test (Load Test with Schema Registry) / Kafka Client Load Test (Quick) (push) Failing after 3s
Kafka Gateway Tests / Kafka Unit Tests (unit-tests-1) (push) Failing after 11s
Kafka Gateway Tests / Kafka Integration Tests (Critical) (integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka End-to-End Tests (with SMQ) (e2e-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Consumer Group Tests (Highly Isolated) (consumer-group-1) (push) Failing after 2s
Kafka Gateway Tests / Kafka Client Compatibility (with SMQ) (client-compat-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka SMQ Integration Tests (Full Stack) (smq-integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Protocol Tests (Isolated) (protocol-1) (push) Failing after 3s
KMS Tests / KMS Provider Integration Tests (push) Failing after 2s
KMS Tests / S3 KMS End-to-End Tests (push) Failing after 3s
Master Cold Start Tests / Master Cold Start Tests (push) Failing after 4s
Metadata Subscribe Integration Tests / Metadata Subscribe Integration Tests (push) Failing after 3s
Multi-Master Tests / Multi-Master Failover Tests (push) Failing after 3s
Performance / CPU and Heap Profile (push) Failing after 2s
Performance / S3 Read/Write Benchmark (rust volume) (push) Failing after 3s
pjdfstest POSIX Compliance / pjdfstest (push) Failing after 2s
Plugin Worker Integration Tests / Plugin Worker: erasure_coding (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: vacuum (push) Failing after 3s
Rust Volume Server Tests / Rust Integration Tests (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 1) (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 1) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 3) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Distributed Tests (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Performance Tests (push) Failing after 3s
Performance / Throughput Benchmark (go volume) (push) Failing after 3s
Performance / Throughput Benchmark (rust volume) (push) Failing after 2s
Performance / S3 Read/Write Benchmark (go volume) (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: volume_balance (push) Failing after 3s
PostgreSQL Gateway Tests / PostgreSQL Basic Tests (push) Failing after 3s
Rust Volume Server Tests / Rust Unit Tests (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 3) (push) Failing after 3s
Rust Plugin Worker Tests / Rust Plugin Worker Build and Unit Tests (push) Failing after 3s
rust: build dev volume server binaries / cleanup (push) Successful in 3s
S3 ETag and ACL Tests / S3 ETag + ACL Integration Tests (push) Failing after 3s
S3 IAM Integration Tests / IAM Unit Tests (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (advanced) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (basic) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (group) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (policy-enforcement) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (sts) (push) Failing after 4s
S3 Keycloak Integration Tests / S3 Keycloak Integration Tests (push) Failing after 3s
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.11) (push) Failing after 3s
S3 PyArrow Parquet Tests / Go Unit Tests (Implicit Directory) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.12) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-amd64, x86_64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-arm64, aarch64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-linux (linux-amd64, x86_64-unknown-linux-gnu) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.9) (push) Has been cancelled
S3 Proxy Signature Tests / S3 Proxy Signature Verification Tests (push) Has been cancelled
S3 SDK V2 Route Disambiguation Tests / S3 SDK V2 Routing Tests (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (quick) (push) Has been cancelled
S3 SSE Tests / S3 SSE Compatibility Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Metadata Persistence Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Copy Operations Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Multipart Upload Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Performance Test (push) Has been cancelled
S3 SSE Tests / S3 Volume Encryption Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (comprehensive) (push) Has been cancelled
Spark Integration Tests / Spark Integration Tests (push) Failing after 2s
Telemetry Integration Tests / telemetry-integration-test (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 1) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 1) (push) Failing after 3s
Ceph S3 tests / Basic S3 tests (KV store) (push) Has been cancelled
Ceph S3 tests / S3 Versioning & Object Lock tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Unit Tests (push) Failing after 3s
Ceph S3 tests / S3 CORS tests (push) Has been cancelled
Ceph S3 tests / SeaweedFS Custom S3 Copy tests (push) Has been cancelled
Ceph S3 tests / Basic S3 tests (SQL store) (push) Has been cancelled
test s3 over https using aws-cli / awscli-tests (push) Has been cancelled
TLS Rotation Integration Tests / TLS Rotation Integration Tests (push) Has been cancelled
Vacuum Integration Tests / Vacuum Integration Tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Variables Integration Tests (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (advanced-policy) (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (basic-policy) (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (contextual-policy) (push) Failing after 4s
S3 Policy Integration Tests / Trusted Proxy Detection Tests (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Shell Integration Tests (push) Failing after 3s
Samba on FUSE Integration / samba-integration (push) Failing after 2s
SFTP Integration Tests / SFTP Integration Testing (push) Failing after 3s
terraform: validate and test modules / fmt, validate, plan-level tests (push) Failing after 3s
terraform: validate and test modules / local cluster smoke test (real weed) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 3) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 2) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 2) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 3) (push) Failing after 5s
mount: windows conformance / WinFsp conformance (push) Has been cancelled
mount: windows / Mount on Windows (push) Has been cancelled
This commit is contained in:
297
test/s3/tagging/Makefile
Normal file
297
test/s3/tagging/Makefile
Normal file
@@ -0,0 +1,297 @@
|
||||
# S3 Object Tagging Tests
|
||||
# Tests for GitHub issue #7589: S3 object Tags query comes back empty
|
||||
|
||||
.PHONY: help build-weed setup-server start-server stop-server test-tagging test-tagging-quick test-tagging-comprehensive test-all clean logs check-deps test-with-server
|
||||
|
||||
# Configuration
|
||||
WEED_BINARY := ../../../weed/weed_binary
|
||||
S3_PORT := 8006
|
||||
MASTER_PORT := 9338
|
||||
VOLUME_PORT := 8085
|
||||
FILER_PORT := 8893
|
||||
TEST_TIMEOUT := 10m
|
||||
TEST_PATTERN := TestObjectTaggingOnUpload|TestPutObjectTaggingAPI|TestDeleteObjectTagging|TestTag
|
||||
|
||||
# Let the tests force-drop collections at the master they actually run against
|
||||
export MASTER_ENDPOINT ?= http://127.0.0.1:$(MASTER_PORT)
|
||||
|
||||
# Default target
|
||||
help:
|
||||
@echo "S3 Object Tagging Tests Makefile"
|
||||
@echo ""
|
||||
@echo "Available targets:"
|
||||
@echo " help - Show this help message"
|
||||
@echo " build-weed - Build the SeaweedFS binary"
|
||||
@echo " check-deps - Check dependencies and build binary if needed"
|
||||
@echo " start-server - Start SeaweedFS server for testing"
|
||||
@echo " stop-server - Stop SeaweedFS server"
|
||||
@echo " test-tagging - Run all tagging tests"
|
||||
@echo " test-tagging-quick - Run core tagging tests only"
|
||||
@echo " test-tagging-comprehensive - Run comprehensive tagging tests"
|
||||
@echo " test-with-server - Start server, run tests, stop server"
|
||||
@echo " logs - Show server logs"
|
||||
@echo " clean - Clean up test artifacts and stop server"
|
||||
@echo " health-check - Check if server is accessible"
|
||||
@echo ""
|
||||
@echo "Configuration:"
|
||||
@echo " S3_PORT=${S3_PORT}"
|
||||
@echo " TEST_TIMEOUT=${TEST_TIMEOUT}"
|
||||
|
||||
# Build the SeaweedFS binary
|
||||
build-weed:
|
||||
@echo "Building SeaweedFS binary..."
|
||||
@cd ../../../weed && go build -o weed_binary .
|
||||
@chmod +x $(WEED_BINARY)
|
||||
@echo "✅ SeaweedFS binary built at $(WEED_BINARY)"
|
||||
|
||||
check-deps: build-weed
|
||||
@echo "Checking dependencies..."
|
||||
@echo "🔍 DEBUG: Checking Go installation..."
|
||||
@command -v go >/dev/null 2>&1 || (echo "Go is required but not installed" && exit 1)
|
||||
@echo "🔍 DEBUG: Go version: $$(go version)"
|
||||
@echo "🔍 DEBUG: Checking binary at $(WEED_BINARY)..."
|
||||
@test -f $(WEED_BINARY) || (echo "SeaweedFS binary not found at $(WEED_BINARY)" && exit 1)
|
||||
@echo "🔍 DEBUG: Binary size: $$(ls -lh $(WEED_BINARY) | awk '{print $$5}')"
|
||||
@echo "🔍 DEBUG: Binary permissions: $$(ls -la $(WEED_BINARY) | awk '{print $$1}')"
|
||||
@echo "🔍 DEBUG: Checking Go module dependencies..."
|
||||
@go list -m github.com/aws/aws-sdk-go-v2 >/dev/null 2>&1 || (echo "AWS SDK Go v2 not found. Run 'go mod tidy'." && exit 1)
|
||||
@go list -m github.com/stretchr/testify >/dev/null 2>&1 || (echo "Testify not found. Run 'go mod tidy'." && exit 1)
|
||||
@echo "✅ All dependencies are available"
|
||||
|
||||
# Start SeaweedFS server for testing
|
||||
start-server: check-deps
|
||||
@echo "Starting SeaweedFS server..."
|
||||
@echo "🔍 DEBUG: Current working directory: $$(pwd)"
|
||||
@echo "🔍 DEBUG: Checking for existing weed processes..."
|
||||
@ps aux | grep weed | grep -v grep || echo "No existing weed processes found"
|
||||
@echo "🔍 DEBUG: Cleaning up any existing PID file..."
|
||||
@rm -f weed-server.pid
|
||||
@echo "🔍 DEBUG: Checking for port conflicts..."
|
||||
@if netstat -tlnp 2>/dev/null | grep $(S3_PORT) >/dev/null; then \
|
||||
echo "⚠️ Port $(S3_PORT) is already in use, trying to find the process..."; \
|
||||
netstat -tlnp 2>/dev/null | grep $(S3_PORT) || true; \
|
||||
else \
|
||||
echo "✅ Port $(S3_PORT) is available"; \
|
||||
fi
|
||||
@echo "🔍 DEBUG: Checking binary at $(WEED_BINARY)"
|
||||
@ls -la $(WEED_BINARY) || (echo "❌ Binary not found!" && exit 1)
|
||||
@echo "🔍 DEBUG: Creating volume directory..."
|
||||
@mkdir -p ./test-volume-data
|
||||
@echo "🔍 DEBUG: Launching SeaweedFS server in background..."
|
||||
@echo "🔍 DEBUG: Command: $(WEED_BINARY) mini -dir=./test-volume-data -s3.port=$(S3_PORT)"
|
||||
@$(WEED_BINARY) mini \
|
||||
-filer.maxMB=64 \
|
||||
-dir=./test-volume-data \
|
||||
-master.raftHashicorp \
|
||||
-s3.port=$(S3_PORT) \
|
||||
> weed-test.log 2>&1 & \
|
||||
echo $$! > weed-server.pid
|
||||
@echo "🔍 DEBUG: Waiting for server to start (up to 90 seconds)..."
|
||||
@for i in $$(seq 1 90); do \
|
||||
echo "🔍 DEBUG: Attempt $$i/90 - checking port $(S3_PORT)"; \
|
||||
if curl -s http://localhost:$(S3_PORT) >/dev/null 2>&1; then \
|
||||
echo "✅ SeaweedFS server started successfully on port $(S3_PORT) after $$i seconds"; \
|
||||
exit 0; \
|
||||
fi; \
|
||||
if [ $$i -eq 5 ]; then \
|
||||
echo "🔍 DEBUG: After 5 seconds, checking process and logs..."; \
|
||||
ps aux | grep weed | grep -v grep || echo "No weed processes found"; \
|
||||
if [ -f weed-test.log ]; then \
|
||||
echo "=== First server logs ==="; \
|
||||
head -20 weed-test.log; \
|
||||
fi; \
|
||||
fi; \
|
||||
if [ $$i -eq 15 ]; then \
|
||||
echo "🔍 DEBUG: After 15 seconds, checking port bindings..."; \
|
||||
netstat -tlnp 2>/dev/null | grep $(S3_PORT) || echo "Port $(S3_PORT) not bound"; \
|
||||
fi; \
|
||||
if [ $$i -eq 30 ]; then \
|
||||
echo "⚠️ Server taking longer than expected (30s), checking logs..."; \
|
||||
if [ -f weed-test.log ]; then \
|
||||
echo "=== Recent server logs ==="; \
|
||||
tail -20 weed-test.log; \
|
||||
fi; \
|
||||
fi; \
|
||||
sleep 1; \
|
||||
done; \
|
||||
echo "❌ Server failed to start within 90 seconds"; \
|
||||
echo "🔍 DEBUG: Final process check:"; \
|
||||
ps aux | grep weed | grep -v grep || echo "No weed processes found"; \
|
||||
echo "🔍 DEBUG: Final port check:"; \
|
||||
netstat -tlnp 2>/dev/null | grep -E "($(S3_PORT))" || echo "No ports bound"; \
|
||||
echo "=== Full server logs ==="; \
|
||||
if [ -f weed-test.log ]; then \
|
||||
cat weed-test.log; \
|
||||
else \
|
||||
echo "No log file found"; \
|
||||
fi; \
|
||||
exit 1
|
||||
|
||||
# Stop SeaweedFS server
|
||||
stop-server:
|
||||
@echo "Stopping SeaweedFS server..."
|
||||
@if [ -f weed-server.pid ]; then \
|
||||
SERVER_PID=$$(cat weed-server.pid); \
|
||||
echo "Killing server PID $$SERVER_PID"; \
|
||||
if ps -p $$SERVER_PID >/dev/null 2>&1; then \
|
||||
kill -TERM $$SERVER_PID 2>/dev/null || true; \
|
||||
sleep 2; \
|
||||
if ps -p $$SERVER_PID >/dev/null 2>&1; then \
|
||||
echo "Process still running, sending KILL signal..."; \
|
||||
kill -KILL $$SERVER_PID 2>/dev/null || true; \
|
||||
sleep 1; \
|
||||
fi; \
|
||||
else \
|
||||
echo "Process $$SERVER_PID not found (already stopped)"; \
|
||||
fi; \
|
||||
rm -f weed-server.pid; \
|
||||
else \
|
||||
echo "No PID file found, checking for running processes..."; \
|
||||
echo "⚠️ Skipping automatic process cleanup to avoid CI issues"; \
|
||||
echo "Note: Any remaining weed processes should be cleaned up by the CI environment"; \
|
||||
fi
|
||||
@echo "✅ SeaweedFS server stopped"
|
||||
|
||||
# Show server logs
|
||||
logs:
|
||||
@if test -f weed-test.log; then \
|
||||
echo "=== SeaweedFS Server Logs ==="; \
|
||||
tail -f weed-test.log; \
|
||||
else \
|
||||
echo "No log file found. Server may not be running."; \
|
||||
fi
|
||||
|
||||
# Core tagging tests (basic functionality)
|
||||
test-tagging-quick: check-deps
|
||||
@echo "Running core tagging tests..."
|
||||
@go test -v -timeout=$(TEST_TIMEOUT) -run "TestObjectTaggingOnUpload|TestPutObjectTaggingAPI" .
|
||||
@echo "✅ Core tagging tests completed"
|
||||
|
||||
# All tagging tests (comprehensive)
|
||||
test-tagging: check-deps
|
||||
@echo "Running all tagging tests..."
|
||||
@go test -v -timeout=$(TEST_TIMEOUT) -run "$(TEST_PATTERN)" .
|
||||
@echo "✅ All tagging tests completed"
|
||||
|
||||
# Comprehensive tagging tests (all features)
|
||||
test-tagging-comprehensive: check-deps
|
||||
@echo "Running comprehensive tagging tests..."
|
||||
@go test -v -timeout=$(TEST_TIMEOUT) .
|
||||
@echo "✅ Comprehensive tagging tests completed"
|
||||
|
||||
# All tests without server management
|
||||
test-tagging-simple: check-deps
|
||||
@echo "Running tagging tests (assuming server is already running)..."
|
||||
@go test -v -timeout=$(TEST_TIMEOUT) .
|
||||
@echo "✅ All tagging tests completed"
|
||||
|
||||
# Start server, run tests, stop server
|
||||
test-with-server: start-server
|
||||
@echo "Running tagging tests with managed server..."
|
||||
@sleep 5 # Give server time to fully start
|
||||
@make test-tagging-comprehensive || (echo "Tests failed, stopping server..." && make stop-server && exit 1)
|
||||
@make stop-server
|
||||
@echo "✅ All tests completed with managed server"
|
||||
|
||||
# Health check
|
||||
health-check:
|
||||
@echo "Checking server health..."
|
||||
@if curl -s http://localhost:$(S3_PORT) >/dev/null 2>&1; then \
|
||||
echo "✅ Server is accessible on port $(S3_PORT)"; \
|
||||
else \
|
||||
echo "❌ Server is not accessible on port $(S3_PORT)"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
# Clean up
|
||||
clean:
|
||||
@echo "Cleaning up test artifacts..."
|
||||
@make stop-server
|
||||
@rm -f weed-test.log
|
||||
@rm -f weed-server.pid
|
||||
@rm -rf ./test-volume-data
|
||||
@rm -f tagging.test
|
||||
@go clean -testcache
|
||||
@echo "✅ Cleanup completed"
|
||||
|
||||
# Individual test targets for specific functionality
|
||||
test-upload:
|
||||
@echo "Running upload tagging tests..."
|
||||
@go test -v -timeout=$(TEST_TIMEOUT) -run "TestObjectTaggingOnUpload" .
|
||||
|
||||
test-special-chars:
|
||||
@echo "Running special characters tagging tests..."
|
||||
@go test -v -timeout=$(TEST_TIMEOUT) -run "TestObjectTaggingOnUploadWithSpecialCharacters" .
|
||||
|
||||
test-api:
|
||||
@echo "Running API tagging tests..."
|
||||
@go test -v -timeout=$(TEST_TIMEOUT) -run "TestPutObjectTaggingAPI" .
|
||||
|
||||
test-get:
|
||||
@echo "Running get tagging tests..."
|
||||
@go test -v -timeout=$(TEST_TIMEOUT) -run "TestGetObjectTaggingAPI" .
|
||||
|
||||
test-delete:
|
||||
@echo "Running delete tagging tests..."
|
||||
@go test -v -timeout=$(TEST_TIMEOUT) -run "TestDeleteObjectTaggingAPI" .
|
||||
|
||||
# Development targets
|
||||
dev-start: start-server
|
||||
@echo "Development server started. Access S3 API at http://localhost:$(S3_PORT)"
|
||||
@echo "To stop: make stop-server"
|
||||
|
||||
dev-test: check-deps
|
||||
@echo "Running tests in development mode..."
|
||||
@go test -v -timeout=$(TEST_TIMEOUT) -run "TestObjectTaggingOnUpload" .
|
||||
|
||||
# CI targets
|
||||
ci-test: check-deps
|
||||
@echo "Running tests in CI mode..."
|
||||
@go test -v -timeout=$(TEST_TIMEOUT) -race .
|
||||
|
||||
# All targets
|
||||
test-all: test-tagging test-tagging-comprehensive
|
||||
@echo "✅ All tagging tests completed"
|
||||
|
||||
# Benchmark targets
|
||||
benchmark-tagging:
|
||||
@echo "Running tagging performance benchmarks..."
|
||||
@go test -v -timeout=$(TEST_TIMEOUT) -bench=. -benchmem .
|
||||
|
||||
# Coverage targets
|
||||
coverage:
|
||||
@echo "Running tests with coverage..."
|
||||
@go test -v -timeout=$(TEST_TIMEOUT) -coverprofile=coverage.out .
|
||||
@go tool cover -html=coverage.out -o coverage.html
|
||||
@echo "Coverage report generated: coverage.html"
|
||||
|
||||
# Format and lint
|
||||
fmt:
|
||||
@echo "Formatting Go code..."
|
||||
@go fmt .
|
||||
|
||||
lint:
|
||||
@echo "Running linter..."
|
||||
@golint . || echo "golint not available, skipping..."
|
||||
|
||||
# Install dependencies for development
|
||||
install-deps:
|
||||
@echo "Installing Go dependencies..."
|
||||
@go mod tidy
|
||||
@go mod download
|
||||
|
||||
# Show current configuration
|
||||
show-config:
|
||||
@echo "Current configuration:"
|
||||
@echo " WEED_BINARY: $(WEED_BINARY)"
|
||||
@echo " S3_PORT: $(S3_PORT)"
|
||||
@echo " TEST_TIMEOUT: $(TEST_TIMEOUT)"
|
||||
@echo " TEST_PATTERN: $(TEST_PATTERN)"
|
||||
|
||||
# Legacy targets for backward compatibility
|
||||
test: test-with-server
|
||||
test-verbose: test-tagging-comprehensive
|
||||
test-single: test-upload
|
||||
test-clean: clean
|
||||
build: check-deps
|
||||
setup: check-deps
|
||||
53
test/s3/tagging/README.md
Normal file
53
test/s3/tagging/README.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# S3 Object Tagging Tests
|
||||
|
||||
This directory contains tests for S3 object tagging functionality.
|
||||
|
||||
## Issue Reference
|
||||
|
||||
These tests were created to verify the fix for [GitHub Issue #7589](https://github.com/seaweedfs/seaweedfs/issues/7589):
|
||||
**S3 object Tags query comes back empty**
|
||||
|
||||
## Problem Description
|
||||
|
||||
When uploading an object with tags using the `X-Amz-Tagging` header, the tags were not being stored.
|
||||
When querying the object tagging with `GetObjectTagging`, the response was empty.
|
||||
|
||||
This was a regression between SeaweedFS 4.00 and 4.01.
|
||||
|
||||
## Root Cause
|
||||
|
||||
The `putToFiler` function in `s3api_object_handlers_put.go` was not parsing the `X-Amz-Tagging` header
|
||||
and storing the tags in the entry's Extended metadata. The code was only copying user metadata
|
||||
(headers starting with `X-Amz-Meta-`) but not object tags.
|
||||
|
||||
## Fix
|
||||
|
||||
Added tag parsing logic to `putToFiler` that:
|
||||
1. Reads the `X-Amz-Tagging` header
|
||||
2. Parses it using `url.ParseQuery()` for proper URL decoding
|
||||
3. Stores each tag with the prefix `X-Amz-Tagging-` in the entry's Extended metadata
|
||||
|
||||
## Running Tests
|
||||
|
||||
```bash
|
||||
# Run all tagging tests
|
||||
cd test/s3/tagging
|
||||
make test
|
||||
|
||||
# Run specific test
|
||||
make test-upload
|
||||
|
||||
# Or using go test directly
|
||||
go test -v ./...
|
||||
```
|
||||
|
||||
## Test Cases
|
||||
|
||||
1. **TestObjectTaggingOnUpload** - Basic test for tags sent during object upload
|
||||
2. **TestObjectTaggingOnUploadWithSpecialCharacters** - Tests URL-encoded tag values
|
||||
3. **TestObjectTaggingOnUploadWithEmptyValue** - Tests tags with empty values
|
||||
4. **TestPutObjectTaggingAPI** - Tests the PutObjectTagging API separately
|
||||
5. **TestDeleteObjectTagging** - Tests tag deletion
|
||||
6. **TestTagsNotPreservedAfterObjectOverwrite** - Verifies AWS S3 behavior on overwrite
|
||||
7. **TestMaximumNumberOfTags** - Tests storing the maximum 10 tags
|
||||
8. **TestTagCountHeader** - Tests the x-amz-tagging-count header in HeadObject
|
||||
567
test/s3/tagging/s3_tagging_test.go
Normal file
567
test/s3/tagging/s3_tagging_test.go
Normal file
@@ -0,0 +1,567 @@
|
||||
package tagging
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/config"
|
||||
"github.com/aws/aws-sdk-go-v2/credentials"
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// S3TestConfig holds configuration for S3 tests
|
||||
type S3TestConfig struct {
|
||||
Endpoint string
|
||||
MasterEndpoint string
|
||||
AccessKey string
|
||||
SecretKey string
|
||||
Region string
|
||||
BucketPrefix string
|
||||
UseSSL bool
|
||||
SkipVerifySSL bool
|
||||
}
|
||||
|
||||
// allTestBucketPrefixes lists every prefix used to name buckets in this test suite.
|
||||
// cleanupAllTestBuckets uses it to find stale buckets from prior tests/runs.
|
||||
// Add the new prefix here whenever a test introduces one.
|
||||
var allTestBucketPrefixes = []string{
|
||||
"test-tagging-",
|
||||
}
|
||||
|
||||
// testRunID uniquely identifies this `go test` invocation. It's embedded into
|
||||
// every bucket created by createTestBucket (after defaultConfig.BucketPrefix), so
|
||||
// the cleanup sweep can scope deletions to buckets that belong to this run only —
|
||||
// letting concurrent runs against the same endpoints coexist without tearing each
|
||||
// other's buckets down. Stale buckets left behind by a crashed prior run share the
|
||||
// family prefix but a different runID, so they are not swept here; `make clean`
|
||||
// (or wiping the data dir) is the right cleanup for that case.
|
||||
var testRunID = strconv.FormatInt(time.Now().UnixNano(), 36)
|
||||
|
||||
// getDefaultConfig returns a fresh instance of the default test configuration
|
||||
func getDefaultConfig() *S3TestConfig {
|
||||
endpoint := os.Getenv("S3_ENDPOINT")
|
||||
if endpoint == "" {
|
||||
endpoint = "http://localhost:8333" // Default SeaweedFS S3 port
|
||||
}
|
||||
masterEndpoint := os.Getenv("MASTER_ENDPOINT")
|
||||
if masterEndpoint == "" {
|
||||
masterEndpoint = "http://127.0.0.1:9333" // Default SeaweedFS master HTTP port
|
||||
}
|
||||
accessKey := os.Getenv("S3_ACCESS_KEY")
|
||||
if accessKey == "" {
|
||||
accessKey = "some_access_key1"
|
||||
}
|
||||
secretKey := os.Getenv("S3_SECRET_KEY")
|
||||
if secretKey == "" {
|
||||
secretKey = "some_secret_key1"
|
||||
}
|
||||
return &S3TestConfig{
|
||||
Endpoint: endpoint,
|
||||
MasterEndpoint: masterEndpoint,
|
||||
AccessKey: accessKey,
|
||||
SecretKey: secretKey,
|
||||
Region: "us-east-1",
|
||||
BucketPrefix: "test-tagging-",
|
||||
UseSSL: false,
|
||||
SkipVerifySSL: true,
|
||||
}
|
||||
}
|
||||
|
||||
// getS3Client creates an AWS S3 client for testing
|
||||
func getS3Client(t *testing.T) *s3.Client {
|
||||
defaultConfig := getDefaultConfig()
|
||||
cfg, err := config.LoadDefaultConfig(context.TODO(),
|
||||
config.WithRegion(defaultConfig.Region),
|
||||
config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(
|
||||
defaultConfig.AccessKey,
|
||||
defaultConfig.SecretKey,
|
||||
"",
|
||||
)),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
client := s3.NewFromConfig(cfg, func(o *s3.Options) {
|
||||
o.UsePathStyle = true
|
||||
o.BaseEndpoint = aws.String(defaultConfig.Endpoint)
|
||||
})
|
||||
return client
|
||||
}
|
||||
|
||||
// createTestBucket creates a test bucket with a unique name
|
||||
func createTestBucket(t *testing.T, client *s3.Client) string {
|
||||
defaultConfig := getDefaultConfig()
|
||||
// Sweep stale buckets from prior tests in this run so each new bucket starts
|
||||
// on a fresh slate. Without this, leaked collection volumes accumulate on a
|
||||
// single `weed mini` data node and the suite eventually exhausts its volume
|
||||
// slots.
|
||||
cleanupAllTestBuckets(t, client)
|
||||
bucketName := fmt.Sprintf("%sr%s-%d", defaultConfig.BucketPrefix, testRunID, time.Now().UnixNano())
|
||||
|
||||
_, err := client.CreateBucket(context.TODO(), &s3.CreateBucketInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Wait for bucket metadata to be fully processed
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
return bucketName
|
||||
}
|
||||
|
||||
// cleanupTestBucket removes the test bucket and all its contents.
|
||||
// Always force-drops the underlying collection at the master afterwards: the S3
|
||||
// DeleteBucket can race with concurrent `volume_grow` requests (the warm-create
|
||||
// batch keeps registering volumes after the master's collection-delete sweep has
|
||||
// already snapshotted the layout), so 1-3 volumes per bucket can leak. Without
|
||||
// this, running enough tests on a single `weed mini` server exhausts the data
|
||||
// node's volume slots and every subsequent PutObject 500s with "Not enough data
|
||||
// nodes found".
|
||||
func cleanupTestBucket(t *testing.T, client *s3.Client, bucketName string) {
|
||||
defer forceDeleteCollection(t, bucketName)
|
||||
// First, delete all objects in the bucket
|
||||
listResp, err := client.ListObjectsV2(context.TODO(), &s3.ListObjectsV2Input{
|
||||
Bucket: aws.String(bucketName),
|
||||
})
|
||||
if err == nil {
|
||||
for _, obj := range listResp.Contents {
|
||||
_, err := client.DeleteObject(context.TODO(), &s3.DeleteObjectInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: obj.Key,
|
||||
})
|
||||
if err != nil {
|
||||
t.Logf("Warning: failed to delete object %s: %v", *obj.Key, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete all versions and delete markers if versioning is enabled
|
||||
listVersionsResp, err := client.ListObjectVersions(context.TODO(), &s3.ListObjectVersionsInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
})
|
||||
if err == nil {
|
||||
for _, version := range listVersionsResp.Versions {
|
||||
_, err := client.DeleteObject(context.TODO(), &s3.DeleteObjectInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: version.Key,
|
||||
VersionId: version.VersionId,
|
||||
})
|
||||
if err != nil {
|
||||
t.Logf("Warning: failed to delete version %s: %v", *version.Key, err)
|
||||
}
|
||||
}
|
||||
for _, marker := range listVersionsResp.DeleteMarkers {
|
||||
_, err := client.DeleteObject(context.TODO(), &s3.DeleteObjectInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: marker.Key,
|
||||
VersionId: marker.VersionId,
|
||||
})
|
||||
if err != nil {
|
||||
t.Logf("Warning: failed to delete marker %s: %v", *marker.Key, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Then delete the bucket
|
||||
_, err = client.DeleteBucket(context.TODO(), &s3.DeleteBucketInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
})
|
||||
if err != nil {
|
||||
t.Logf("Warning: failed to delete bucket %s: %v", bucketName, err)
|
||||
}
|
||||
}
|
||||
|
||||
// forceDeleteCollection drops the SeaweedFS collection backing a test bucket via the master's
|
||||
// /col/delete admin endpoint. The S3 layer normally drops the collection on DeleteBucket, but
|
||||
// in-flight `volume_grow` requests can register volumes after the master's first sweep, leaking
|
||||
// them. Best-effort: a 400 from the master means the collection was already gone, which is the
|
||||
// success path and not an error.
|
||||
func forceDeleteCollection(t *testing.T, bucketName string) {
|
||||
masterEndpoint := getDefaultConfig().MasterEndpoint
|
||||
if masterEndpoint == "" {
|
||||
return
|
||||
}
|
||||
endpoint := strings.TrimRight(masterEndpoint, "/") + "/col/delete?collection=" + url.QueryEscape(bucketName)
|
||||
req, err := http.NewRequest(http.MethodGet, endpoint, nil)
|
||||
if err != nil {
|
||||
t.Logf("Note: building collection delete request for %s failed: %v", bucketName, err)
|
||||
return
|
||||
}
|
||||
httpClient := &http.Client{Timeout: 5 * time.Second}
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
t.Logf("Note: force-delete collection %s failed: %v", bucketName, err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
io.Copy(io.Discard, resp.Body)
|
||||
switch resp.StatusCode {
|
||||
case http.StatusNoContent:
|
||||
t.Logf("Force-deleted collection %s", bucketName)
|
||||
case http.StatusBadRequest:
|
||||
// Collection already gone - normal path when DeleteBucket succeeded.
|
||||
default:
|
||||
t.Logf("Note: force-delete collection %s returned HTTP %d", bucketName, resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
// cleanupAllTestBuckets cleans up any leftover test buckets owned by this run
|
||||
// (prefix + runID marker). Called from createTestBucket before each new bucket
|
||||
// creation so a single `weed mini` data node does not exhaust its volume slots
|
||||
// after many tests.
|
||||
func cleanupAllTestBuckets(t *testing.T, client *s3.Client) {
|
||||
listResp, err := client.ListBuckets(context.TODO(), &s3.ListBucketsInput{})
|
||||
if err != nil {
|
||||
t.Logf("Warning: failed to list buckets for cleanup: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, bucket := range listResp.Buckets {
|
||||
if bucket.Name == nil {
|
||||
continue
|
||||
}
|
||||
for _, prefix := range allTestBucketPrefixes {
|
||||
// Only sweep buckets owned by this run (prefix + runID marker).
|
||||
// Buckets from concurrent runs carry a different runID and are skipped.
|
||||
if strings.HasPrefix(*bucket.Name, prefix+"r"+testRunID+"-") {
|
||||
t.Logf("Cleaning up leftover test bucket: %s", *bucket.Name)
|
||||
cleanupTestBucket(t, client, *bucket.Name)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestObjectTaggingOnUpload tests that tags sent during object upload (via X-Amz-Tagging header)
|
||||
// are properly stored and can be retrieved. This is the fix for GitHub issue #7589.
|
||||
func TestObjectTaggingOnUpload(t *testing.T) {
|
||||
client := getS3Client(t)
|
||||
bucketName := createTestBucket(t, client)
|
||||
defer cleanupTestBucket(t, client, bucketName)
|
||||
|
||||
objectKey := "test-object-with-tags"
|
||||
objectContent := "Hello, World!"
|
||||
|
||||
// Put object with tags using the Tagging parameter (X-Amz-Tagging header)
|
||||
_, err := client.PutObject(context.TODO(), &s3.PutObjectInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
Body: strings.NewReader(objectContent),
|
||||
Tagging: aws.String("env=production&team=platform"),
|
||||
})
|
||||
require.NoError(t, err, "Should be able to put object with tags")
|
||||
|
||||
// Get the tags back
|
||||
tagResp, err := client.GetObjectTagging(context.TODO(), &s3.GetObjectTaggingInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
})
|
||||
require.NoError(t, err, "Should be able to get object tags")
|
||||
|
||||
// Verify tags were stored correctly
|
||||
require.Len(t, tagResp.TagSet, 2, "Should have 2 tags")
|
||||
|
||||
// Build a map for easier assertion
|
||||
tagMap := make(map[string]string)
|
||||
for _, tag := range tagResp.TagSet {
|
||||
tagMap[*tag.Key] = *tag.Value
|
||||
}
|
||||
|
||||
assert.Equal(t, "production", tagMap["env"], "env tag should be 'production'")
|
||||
assert.Equal(t, "platform", tagMap["team"], "team tag should be 'platform'")
|
||||
}
|
||||
|
||||
// TestObjectTaggingOnUploadWithSpecialCharacters tests tags with URL-encoded characters
|
||||
func TestObjectTaggingOnUploadWithSpecialCharacters(t *testing.T) {
|
||||
client := getS3Client(t)
|
||||
bucketName := createTestBucket(t, client)
|
||||
defer cleanupTestBucket(t, client, bucketName)
|
||||
|
||||
objectKey := "test-object-with-special-tags"
|
||||
objectContent := "Hello, World!"
|
||||
|
||||
// Put object with tags containing special characters
|
||||
// AWS SDK will URL-encode these automatically
|
||||
_, err := client.PutObject(context.TODO(), &s3.PutObjectInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
Body: strings.NewReader(objectContent),
|
||||
Tagging: aws.String("timestamp=2025-07-16 14:40:39&path=/tmp/file.txt"),
|
||||
})
|
||||
require.NoError(t, err, "Should be able to put object with special character tags")
|
||||
|
||||
// Get the tags back
|
||||
tagResp, err := client.GetObjectTagging(context.TODO(), &s3.GetObjectTaggingInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
})
|
||||
require.NoError(t, err, "Should be able to get object tags")
|
||||
|
||||
// Verify tags were stored and URL-decoded correctly
|
||||
require.Len(t, tagResp.TagSet, 2, "Should have 2 tags")
|
||||
|
||||
tagMap := make(map[string]string)
|
||||
for _, tag := range tagResp.TagSet {
|
||||
tagMap[*tag.Key] = *tag.Value
|
||||
}
|
||||
|
||||
assert.Equal(t, "2025-07-16 14:40:39", tagMap["timestamp"], "timestamp tag should be decoded correctly")
|
||||
assert.Equal(t, "/tmp/file.txt", tagMap["path"], "path tag should be decoded correctly")
|
||||
}
|
||||
|
||||
// TestObjectTaggingOnUploadWithEmptyValue tests tags with empty values
|
||||
func TestObjectTaggingOnUploadWithEmptyValue(t *testing.T) {
|
||||
client := getS3Client(t)
|
||||
bucketName := createTestBucket(t, client)
|
||||
defer cleanupTestBucket(t, client, bucketName)
|
||||
|
||||
objectKey := "test-object-with-empty-tag"
|
||||
objectContent := "Hello, World!"
|
||||
|
||||
// Put object with a tag that has an empty value
|
||||
_, err := client.PutObject(context.TODO(), &s3.PutObjectInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
Body: strings.NewReader(objectContent),
|
||||
Tagging: aws.String("marker=&env=dev"),
|
||||
})
|
||||
require.NoError(t, err, "Should be able to put object with empty tag value")
|
||||
|
||||
// Get the tags back
|
||||
tagResp, err := client.GetObjectTagging(context.TODO(), &s3.GetObjectTaggingInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
})
|
||||
require.NoError(t, err, "Should be able to get object tags")
|
||||
|
||||
// Verify tags were stored correctly
|
||||
require.Len(t, tagResp.TagSet, 2, "Should have 2 tags")
|
||||
|
||||
tagMap := make(map[string]string)
|
||||
for _, tag := range tagResp.TagSet {
|
||||
tagMap[*tag.Key] = *tag.Value
|
||||
}
|
||||
|
||||
assert.Equal(t, "", tagMap["marker"], "marker tag should have empty value")
|
||||
assert.Equal(t, "dev", tagMap["env"], "env tag should be 'dev'")
|
||||
}
|
||||
|
||||
// TestPutObjectTaggingAPI tests the PutObjectTagging API separately from upload
|
||||
func TestPutObjectTaggingAPI(t *testing.T) {
|
||||
client := getS3Client(t)
|
||||
bucketName := createTestBucket(t, client)
|
||||
defer cleanupTestBucket(t, client, bucketName)
|
||||
|
||||
objectKey := "test-object-for-tagging-api"
|
||||
objectContent := "Hello, World!"
|
||||
|
||||
// First, put object without tags
|
||||
_, err := client.PutObject(context.TODO(), &s3.PutObjectInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
Body: strings.NewReader(objectContent),
|
||||
})
|
||||
require.NoError(t, err, "Should be able to put object without tags")
|
||||
|
||||
// Get tags - should be empty
|
||||
tagResp, err := client.GetObjectTagging(context.TODO(), &s3.GetObjectTaggingInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
})
|
||||
require.NoError(t, err, "Should be able to get object tags")
|
||||
assert.Len(t, tagResp.TagSet, 0, "Should have no tags initially")
|
||||
|
||||
// Now add tags using PutObjectTagging API
|
||||
_, err = client.PutObjectTagging(context.TODO(), &s3.PutObjectTaggingInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
Tagging: &types.Tagging{
|
||||
TagSet: []types.Tag{
|
||||
{Key: aws.String("env"), Value: aws.String("staging")},
|
||||
{Key: aws.String("version"), Value: aws.String("1.0")},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err, "Should be able to put object tags via API")
|
||||
|
||||
// Get tags - should now have the tags
|
||||
tagResp, err = client.GetObjectTagging(context.TODO(), &s3.GetObjectTaggingInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
})
|
||||
require.NoError(t, err, "Should be able to get object tags after PutObjectTagging")
|
||||
require.Len(t, tagResp.TagSet, 2, "Should have 2 tags")
|
||||
|
||||
tagMap := make(map[string]string)
|
||||
for _, tag := range tagResp.TagSet {
|
||||
tagMap[*tag.Key] = *tag.Value
|
||||
}
|
||||
|
||||
assert.Equal(t, "staging", tagMap["env"], "env tag should be 'staging'")
|
||||
assert.Equal(t, "1.0", tagMap["version"], "version tag should be '1.0'")
|
||||
}
|
||||
|
||||
// TestDeleteObjectTagging tests the DeleteObjectTagging API
|
||||
func TestDeleteObjectTagging(t *testing.T) {
|
||||
client := getS3Client(t)
|
||||
bucketName := createTestBucket(t, client)
|
||||
defer cleanupTestBucket(t, client, bucketName)
|
||||
|
||||
objectKey := "test-object-for-delete-tags"
|
||||
objectContent := "Hello, World!"
|
||||
|
||||
// Put object with tags
|
||||
_, err := client.PutObject(context.TODO(), &s3.PutObjectInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
Body: strings.NewReader(objectContent),
|
||||
Tagging: aws.String("env=production"),
|
||||
})
|
||||
require.NoError(t, err, "Should be able to put object with tags")
|
||||
|
||||
// Verify tags exist
|
||||
tagResp, err := client.GetObjectTagging(context.TODO(), &s3.GetObjectTaggingInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
})
|
||||
require.NoError(t, err, "Should be able to get object tags")
|
||||
require.Len(t, tagResp.TagSet, 1, "Should have 1 tag")
|
||||
|
||||
// Delete tags
|
||||
_, err = client.DeleteObjectTagging(context.TODO(), &s3.DeleteObjectTaggingInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
})
|
||||
require.NoError(t, err, "Should be able to delete object tags")
|
||||
|
||||
// Verify tags are deleted
|
||||
tagResp, err = client.GetObjectTagging(context.TODO(), &s3.GetObjectTaggingInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
})
|
||||
require.NoError(t, err, "Should be able to get object tags after deletion")
|
||||
assert.Len(t, tagResp.TagSet, 0, "Should have no tags after deletion")
|
||||
}
|
||||
|
||||
// TestTagsNotPreservedAfterObjectOverwrite tests that tags are NOT preserved when an object is overwritten
|
||||
// This matches AWS S3 behavior where overwriting an object replaces all metadata including tags
|
||||
func TestTagsNotPreservedAfterObjectOverwrite(t *testing.T) {
|
||||
client := getS3Client(t)
|
||||
bucketName := createTestBucket(t, client)
|
||||
defer cleanupTestBucket(t, client, bucketName)
|
||||
|
||||
objectKey := "test-object-overwrite-tags"
|
||||
objectContent := "Original content"
|
||||
|
||||
// Put object with tags
|
||||
_, err := client.PutObject(context.TODO(), &s3.PutObjectInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
Body: strings.NewReader(objectContent),
|
||||
Tagging: aws.String("original=true"),
|
||||
})
|
||||
require.NoError(t, err, "Should be able to put object with tags")
|
||||
|
||||
// Verify original tags exist
|
||||
tagResp, err := client.GetObjectTagging(context.TODO(), &s3.GetObjectTaggingInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
})
|
||||
require.NoError(t, err, "Should be able to get object tags")
|
||||
require.Len(t, tagResp.TagSet, 1, "Should have 1 tag")
|
||||
assert.Equal(t, "original", *tagResp.TagSet[0].Key)
|
||||
|
||||
// Overwrite the object WITHOUT tags
|
||||
_, err = client.PutObject(context.TODO(), &s3.PutObjectInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
Body: strings.NewReader("New content"),
|
||||
})
|
||||
require.NoError(t, err, "Should be able to overwrite object")
|
||||
|
||||
// Tags should be gone after overwrite (matches AWS S3 behavior)
|
||||
tagResp, err = client.GetObjectTagging(context.TODO(), &s3.GetObjectTaggingInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
})
|
||||
require.NoError(t, err, "Should be able to get object tags after overwrite")
|
||||
assert.Len(t, tagResp.TagSet, 0, "Tags should be cleared after object overwrite")
|
||||
}
|
||||
|
||||
// TestMaximumNumberOfTags tests that we can store the maximum 10 tags per object
|
||||
func TestMaximumNumberOfTags(t *testing.T) {
|
||||
client := getS3Client(t)
|
||||
bucketName := createTestBucket(t, client)
|
||||
defer cleanupTestBucket(t, client, bucketName)
|
||||
|
||||
objectKey := "test-object-max-tags"
|
||||
objectContent := "Hello, World!"
|
||||
|
||||
// Build 10 tags (S3 max)
|
||||
tags := []string{}
|
||||
for i := 1; i <= 10; i++ {
|
||||
tags = append(tags, fmt.Sprintf("key%d=value%d", i, i))
|
||||
}
|
||||
tagging := strings.Join(tags, "&")
|
||||
|
||||
// Put object with 10 tags
|
||||
_, err := client.PutObject(context.TODO(), &s3.PutObjectInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
Body: strings.NewReader(objectContent),
|
||||
Tagging: aws.String(tagging),
|
||||
})
|
||||
require.NoError(t, err, "Should be able to put object with 10 tags")
|
||||
|
||||
// Get the tags back
|
||||
tagResp, err := client.GetObjectTagging(context.TODO(), &s3.GetObjectTaggingInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
})
|
||||
require.NoError(t, err, "Should be able to get object tags")
|
||||
assert.Len(t, tagResp.TagSet, 10, "Should have 10 tags")
|
||||
}
|
||||
|
||||
// TestTagCountHeader tests that the x-amz-tagging-count header is returned in HeadObject
|
||||
func TestTagCountHeader(t *testing.T) {
|
||||
client := getS3Client(t)
|
||||
bucketName := createTestBucket(t, client)
|
||||
defer cleanupTestBucket(t, client, bucketName)
|
||||
|
||||
objectKey := "test-object-tag-count"
|
||||
objectContent := "Hello, World!"
|
||||
|
||||
// Put object with tags
|
||||
_, err := client.PutObject(context.TODO(), &s3.PutObjectInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
Body: strings.NewReader(objectContent),
|
||||
Tagging: aws.String("env=prod&team=backend&version=2.0"),
|
||||
})
|
||||
require.NoError(t, err, "Should be able to put object with tags")
|
||||
|
||||
// Head object to get tag count
|
||||
headResp, err := client.HeadObject(context.TODO(), &s3.HeadObjectInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
})
|
||||
require.NoError(t, err, "Should be able to head object")
|
||||
|
||||
// Check tag count header
|
||||
if headResp.TagCount != nil {
|
||||
assert.Equal(t, int32(3), *headResp.TagCount, "Tag count should be 3")
|
||||
} else {
|
||||
t.Log("Warning: TagCount header not returned - this may be expected depending on implementation")
|
||||
}
|
||||
}
|
||||
434
test/s3/tagging/s3_tagging_versioning_test.go
Normal file
434
test/s3/tagging/s3_tagging_versioning_test.go
Normal file
@@ -0,0 +1,434 @@
|
||||
package tagging
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// This is the fix for GitHub issue #7868 where tagging failed with "no entry is found in filer store"
|
||||
// TestPutObjectTaggingOnVersionedBucket tests setting tags on objects in a versioned bucket
|
||||
func TestPutObjectTaggingOnVersionedBucket(t *testing.T) {
|
||||
client := getS3Client(t)
|
||||
bucketName := createVersionedTestBucket(t, client)
|
||||
defer cleanupTestBucket(t, client, bucketName)
|
||||
|
||||
// Put object in versioned bucket
|
||||
objectKey := "versioned-object-with-tags"
|
||||
objectContent := "Hello, Versioned World!"
|
||||
_, err := client.PutObject(context.TODO(), &s3.PutObjectInput{
|
||||
Body: strings.NewReader(objectContent),
|
||||
Key: aws.String(objectKey),
|
||||
Bucket: aws.String(bucketName),
|
||||
})
|
||||
require.NoError(t, err, "Should be able to put object in versioned bucket")
|
||||
|
||||
// Set tags on the object in versioned bucket
|
||||
_, err = client.PutObjectTagging(context.TODO(), &s3.PutObjectTaggingInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
Tagging: &types.Tagging{
|
||||
TagSet: []types.Tag{
|
||||
{
|
||||
Key: aws.String("env"),
|
||||
Value: aws.String("production"),
|
||||
},
|
||||
{
|
||||
Key: aws.String("team"),
|
||||
Value: aws.String("platform"),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err, "Should be able to put tags on versioned object")
|
||||
|
||||
// Get the tags back
|
||||
tagResp, err := client.GetObjectTagging(context.TODO(), &s3.GetObjectTaggingInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
})
|
||||
require.NoError(t, err, "Should be able to get tags from versioned object")
|
||||
|
||||
// Verify tags
|
||||
assert.Len(t, tagResp.TagSet, 2, "Should have 2 tags")
|
||||
tagMap := make(map[string]string)
|
||||
for _, tag := range tagResp.TagSet {
|
||||
tagMap[*tag.Key] = *tag.Value
|
||||
}
|
||||
assert.Equal(t, "production", tagMap["env"], "env tag should be 'production'")
|
||||
assert.Equal(t, "platform", tagMap["team"], "team tag should be 'platform'")
|
||||
}
|
||||
|
||||
// TestPutObjectTaggingOnSpecificVersionInVersionedBucket tests setting tags on a specific version
|
||||
func TestPutObjectTaggingOnSpecificVersionInVersionedBucket(t *testing.T) {
|
||||
client := getS3Client(t)
|
||||
bucketName := createVersionedTestBucket(t, client)
|
||||
defer cleanupTestBucket(t, client, bucketName)
|
||||
|
||||
// Create multiple versions of the object
|
||||
objectKey := "multi-version-object"
|
||||
version1Resp, err := client.PutObject(context.TODO(), &s3.PutObjectInput{
|
||||
Body: strings.NewReader("Version 1"),
|
||||
Key: aws.String(objectKey),
|
||||
Bucket: aws.String(bucketName),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Small delay to ensure different version IDs
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
versionId1 := *version1Resp.VersionId
|
||||
|
||||
version2Resp, err := client.PutObject(context.TODO(), &s3.PutObjectInput{
|
||||
Body: strings.NewReader("Version 2"),
|
||||
Key: aws.String(objectKey),
|
||||
Bucket: aws.String(bucketName),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
versionId2 := *version2Resp.VersionId
|
||||
|
||||
// Set tags on version 1
|
||||
_, err = client.PutObjectTagging(context.TODO(), &s3.PutObjectTaggingInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
VersionId: aws.String(versionId1),
|
||||
Tagging: &types.Tagging{
|
||||
TagSet: []types.Tag{
|
||||
{
|
||||
Key: aws.String("version"),
|
||||
Value: aws.String("v1"),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err, "Should be able to put tags on specific version 1")
|
||||
|
||||
// Set tags on version 2
|
||||
_, err = client.PutObjectTagging(context.TODO(), &s3.PutObjectTaggingInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
VersionId: aws.String(versionId2),
|
||||
Tagging: &types.Tagging{
|
||||
TagSet: []types.Tag{
|
||||
{
|
||||
Key: aws.String("version"),
|
||||
Value: aws.String("v2"),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err, "Should be able to put tags on specific version 2")
|
||||
|
||||
// Get tags from version 1
|
||||
tagResp1, err := client.GetObjectTagging(context.TODO(), &s3.GetObjectTaggingInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
VersionId: aws.String(versionId1),
|
||||
})
|
||||
require.NoError(t, err, "Should be able to get tags from version 1")
|
||||
assert.Len(t, tagResp1.TagSet, 1, "Version 1 should have 1 tag")
|
||||
assert.Equal(t, "v1", *tagResp1.TagSet[0].Value, "Version 1 tag value should be 'v1'")
|
||||
|
||||
// Get tags from version 2
|
||||
tagResp2, err := client.GetObjectTagging(context.TODO(), &s3.GetObjectTaggingInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
VersionId: aws.String(versionId2),
|
||||
})
|
||||
require.NoError(t, err, "Should be able to get tags from version 2")
|
||||
assert.Len(t, tagResp2.TagSet, 1, "Version 2 should have 1 tag")
|
||||
assert.Equal(t, "v2", *tagResp2.TagSet[0].Value, "Version 2 tag value should be 'v2'")
|
||||
}
|
||||
|
||||
// TestDeleteObjectTaggingOnVersionedBucket tests deleting tags from versioned objects
|
||||
func TestDeleteObjectTaggingOnVersionedBucket(t *testing.T) {
|
||||
client := getS3Client(t)
|
||||
bucketName := createVersionedTestBucket(t, client)
|
||||
defer cleanupTestBucket(t, client, bucketName)
|
||||
|
||||
// Put object with tags in versioned bucket
|
||||
objectKey := "versioned-object-tag-delete"
|
||||
objectContent := "Hello, Delete Tags!"
|
||||
_, err := client.PutObject(context.TODO(), &s3.PutObjectInput{
|
||||
Body: strings.NewReader(objectContent),
|
||||
Key: aws.String(objectKey),
|
||||
Bucket: aws.String(bucketName),
|
||||
Tagging: aws.String("env=dev&purpose=testing"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify tags exist
|
||||
tagResp, err := client.GetObjectTagging(context.TODO(), &s3.GetObjectTaggingInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, tagResp.TagSet, 2, "Should have 2 tags before deletion")
|
||||
|
||||
// Delete tags from the versioned object
|
||||
_, err = client.DeleteObjectTagging(context.TODO(), &s3.DeleteObjectTaggingInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
})
|
||||
require.NoError(t, err, "Should be able to delete tags from versioned object")
|
||||
|
||||
// Verify tags are deleted
|
||||
tagResp, err = client.GetObjectTagging(context.TODO(), &s3.GetObjectTaggingInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, tagResp.TagSet, 0, "Should have 0 tags after deletion")
|
||||
}
|
||||
|
||||
// TestDeleteObjectTaggingOnSpecificVersionInVersionedBucket tests deleting tags on a specific version
|
||||
func TestDeleteObjectTaggingOnSpecificVersionInVersionedBucket(t *testing.T) {
|
||||
client := getS3Client(t)
|
||||
bucketName := createVersionedTestBucket(t, client)
|
||||
defer cleanupTestBucket(t, client, bucketName)
|
||||
|
||||
// Create two versions with tags
|
||||
objectKey := "versioned-multi-delete-tags"
|
||||
version1Resp, err := client.PutObject(context.TODO(), &s3.PutObjectInput{
|
||||
Body: strings.NewReader("Version 1"),
|
||||
Key: aws.String(objectKey),
|
||||
Bucket: aws.String(bucketName),
|
||||
Tagging: aws.String("version=v1&keep=true"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Small delay to ensure different version IDs
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
versionId1 := *version1Resp.VersionId
|
||||
|
||||
version2Resp, err := client.PutObject(context.TODO(), &s3.PutObjectInput{
|
||||
Body: strings.NewReader("Version 2"),
|
||||
Key: aws.String(objectKey),
|
||||
Bucket: aws.String(bucketName),
|
||||
Tagging: aws.String("version=v2&keep=true"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
versionId2 := *version2Resp.VersionId
|
||||
|
||||
// Delete tags from version 1 only
|
||||
_, err = client.DeleteObjectTagging(context.TODO(), &s3.DeleteObjectTaggingInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
VersionId: aws.String(versionId1),
|
||||
})
|
||||
require.NoError(t, err, "Should be able to delete tags from version 1")
|
||||
|
||||
// Verify version 1 has no tags
|
||||
tagResp1, err := client.GetObjectTagging(context.TODO(), &s3.GetObjectTaggingInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
VersionId: aws.String(versionId1),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, tagResp1.TagSet, 0, "Version 1 should have 0 tags after deletion")
|
||||
|
||||
// Verify version 2 still has tags
|
||||
tagResp2, err := client.GetObjectTagging(context.TODO(), &s3.GetObjectTaggingInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
VersionId: aws.String(versionId2),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, tagResp2.TagSet, 2, "Version 2 should still have 2 tags")
|
||||
}
|
||||
|
||||
// TestGetObjectTaggingOnVersionedBucket tests retrieving tags from versioned objects
|
||||
func TestGetObjectTaggingOnVersionedBucket(t *testing.T) {
|
||||
client := getS3Client(t)
|
||||
bucketName := createVersionedTestBucket(t, client)
|
||||
defer cleanupTestBucket(t, client, bucketName)
|
||||
|
||||
// Create multiple versions
|
||||
objectKey := "versioned-object-get-tags"
|
||||
version1Resp, err := client.PutObject(context.TODO(), &s3.PutObjectInput{
|
||||
Body: strings.NewReader("Version 1"),
|
||||
Key: aws.String(objectKey),
|
||||
Bucket: aws.String(bucketName),
|
||||
Tagging: aws.String("v=1&stage=dev"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
versionId1 := *version1Resp.VersionId
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
version2Resp, err := client.PutObject(context.TODO(), &s3.PutObjectInput{
|
||||
Body: strings.NewReader("Version 2"),
|
||||
Key: aws.String(objectKey),
|
||||
Bucket: aws.String(bucketName),
|
||||
Tagging: aws.String("v=2&stage=prod"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
versionId2 := *version2Resp.VersionId
|
||||
|
||||
// Get tags from specific versions
|
||||
tagResp1, err := client.GetObjectTagging(context.TODO(), &s3.GetObjectTaggingInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
VersionId: aws.String(versionId1),
|
||||
})
|
||||
require.NoError(t, err, "Should be able to get tags from version 1")
|
||||
assert.Len(t, tagResp1.TagSet, 2, "Version 1 should have 2 tags")
|
||||
tagMap1 := make(map[string]string)
|
||||
for _, tag := range tagResp1.TagSet {
|
||||
tagMap1[*tag.Key] = *tag.Value
|
||||
}
|
||||
assert.Equal(t, "1", tagMap1["v"], "Version 1 should have v=1")
|
||||
assert.Equal(t, "dev", tagMap1["stage"], "Version 1 should have stage=dev")
|
||||
|
||||
tagResp2, err := client.GetObjectTagging(context.TODO(), &s3.GetObjectTaggingInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
VersionId: aws.String(versionId2),
|
||||
})
|
||||
require.NoError(t, err, "Should be able to get tags from version 2")
|
||||
assert.Len(t, tagResp2.TagSet, 2, "Version 2 should have 2 tags")
|
||||
tagMap2 := make(map[string]string)
|
||||
for _, tag := range tagResp2.TagSet {
|
||||
tagMap2[*tag.Key] = *tag.Value
|
||||
}
|
||||
assert.Equal(t, "2", tagMap2["v"], "Version 2 should have v=2")
|
||||
assert.Equal(t, "prod", tagMap2["stage"], "Version 2 should have stage=prod")
|
||||
|
||||
// Get tags from latest version (should be version 2)
|
||||
tagRespLatest, err := client.GetObjectTagging(context.TODO(), &s3.GetObjectTaggingInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
})
|
||||
require.NoError(t, err, "Should be able to get tags from latest version")
|
||||
assert.Len(t, tagRespLatest.TagSet, 2, "Latest version should have 2 tags")
|
||||
tagMapLatest := make(map[string]string)
|
||||
for _, tag := range tagRespLatest.TagSet {
|
||||
tagMapLatest[*tag.Key] = *tag.Value
|
||||
}
|
||||
assert.Equal(t, "2", tagMapLatest["v"], "Latest version should have v=2")
|
||||
assert.Equal(t, "prod", tagMapLatest["stage"], "Latest version should have stage=prod")
|
||||
}
|
||||
|
||||
// TestModifyTagsOnVersionedObject tests changing tags on different versions independently
|
||||
func TestModifyTagsOnVersionedObject(t *testing.T) {
|
||||
client := getS3Client(t)
|
||||
bucketName := createVersionedTestBucket(t, client)
|
||||
defer cleanupTestBucket(t, client, bucketName)
|
||||
|
||||
// Create version 1
|
||||
objectKey := "versioned-modify-tags"
|
||||
version1Resp, err := client.PutObject(context.TODO(), &s3.PutObjectInput{
|
||||
Body: strings.NewReader("Version 1"),
|
||||
Key: aws.String(objectKey),
|
||||
Bucket: aws.String(bucketName),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
versionId1 := *version1Resp.VersionId
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
// Create version 2
|
||||
version2Resp, err := client.PutObject(context.TODO(), &s3.PutObjectInput{
|
||||
Body: strings.NewReader("Version 2"),
|
||||
Key: aws.String(objectKey),
|
||||
Bucket: aws.String(bucketName),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
versionId2 := *version2Resp.VersionId
|
||||
|
||||
// Add tags to version 1
|
||||
_, err = client.PutObjectTagging(context.TODO(), &s3.PutObjectTaggingInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
VersionId: aws.String(versionId1),
|
||||
Tagging: &types.Tagging{
|
||||
TagSet: []types.Tag{
|
||||
{
|
||||
Key: aws.String("status"),
|
||||
Value: aws.String("old"),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Modify tags on version 1 (replace status and add new tag)
|
||||
_, err = client.PutObjectTagging(context.TODO(), &s3.PutObjectTaggingInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
VersionId: aws.String(versionId1),
|
||||
Tagging: &types.Tagging{
|
||||
TagSet: []types.Tag{
|
||||
{
|
||||
Key: aws.String("status"),
|
||||
Value: aws.String("archived"),
|
||||
},
|
||||
{
|
||||
Key: aws.String("archived-date"),
|
||||
Value: aws.String("2024-01-01"),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Add tags to version 2
|
||||
_, err = client.PutObjectTagging(context.TODO(), &s3.PutObjectTaggingInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
VersionId: aws.String(versionId2),
|
||||
Tagging: &types.Tagging{
|
||||
TagSet: []types.Tag{
|
||||
{
|
||||
Key: aws.String("status"),
|
||||
Value: aws.String("current"),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify final state
|
||||
tagResp1, err := client.GetObjectTagging(context.TODO(), &s3.GetObjectTaggingInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
VersionId: aws.String(versionId1),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, tagResp1.TagSet, 2, "Version 1 should have 2 tags after modification")
|
||||
|
||||
tagResp2, err := client.GetObjectTagging(context.TODO(), &s3.GetObjectTaggingInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
Key: aws.String(objectKey),
|
||||
VersionId: aws.String(versionId2),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, tagResp2.TagSet, 1, "Version 2 should have 1 tag")
|
||||
}
|
||||
|
||||
// createVersionedTestBucket creates a test bucket with versioning enabled
|
||||
func createVersionedTestBucket(t *testing.T, client *s3.Client) string {
|
||||
bucketName := createTestBucket(t, client)
|
||||
|
||||
// Enable versioning on the bucket
|
||||
_, err := client.PutBucketVersioning(context.TODO(), &s3.PutBucketVersioningInput{
|
||||
Bucket: aws.String(bucketName),
|
||||
VersioningConfiguration: &types.VersioningConfiguration{
|
||||
Status: types.BucketVersioningStatusEnabled,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err, "Should be able to enable versioning")
|
||||
|
||||
// Wait for versioning configuration to be applied
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
return bucketName
|
||||
}
|
||||
Reference in New Issue
Block a user