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:
535
weed/s3api/s3tables/utils.go
Normal file
535
weed/s3api/s3tables/utils.go
Normal file
@@ -0,0 +1,535 @@
|
||||
package s3tables
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
||||
)
|
||||
|
||||
const (
|
||||
bucketNamePatternStr = `[a-z0-9-]+`
|
||||
tableNamespacePatternStr = `[a-z0-9_.-]+`
|
||||
tableNamePatternStr = `[a-z0-9_-]+`
|
||||
)
|
||||
|
||||
const (
|
||||
tableObjectRootDirName = ".objects"
|
||||
)
|
||||
|
||||
// ARNPartitionPatternStr matches any AWS partition, not just the commercial
|
||||
// one: aws-cn and aws-us-gov ARNs are valid and must reach the handlers.
|
||||
const ARNPartitionPatternStr = `aws[-a-z0-9]*`
|
||||
|
||||
// ARNPrefixPatternStr is the leading, partition-tolerant part of every S3
|
||||
// Tables ARN. The HTTP router shares it so routes and parsing agree.
|
||||
const ARNPrefixPatternStr = `arn:` + ARNPartitionPatternStr + `:s3tables`
|
||||
|
||||
var (
|
||||
bucketARNPattern = regexp.MustCompile(`^` + ARNPrefixPatternStr + `:[^:]*:[^:]*:bucket/(` + bucketNamePatternStr + `)$`)
|
||||
tableARNPattern = regexp.MustCompile(`^` + ARNPrefixPatternStr + `:[^:]*:[^:]*:bucket/(` + bucketNamePatternStr + `)/table/(` + tableNamespacePatternStr + `)/(` + tableNamePatternStr + `)$`)
|
||||
tagPattern = regexp.MustCompile(`^([\p{L}\p{Z}\p{N}_.:/=+\-@]*)$`)
|
||||
)
|
||||
|
||||
// ARN parsing functions
|
||||
|
||||
// parseBucketNameFromARN extracts bucket name from table bucket ARN
|
||||
// ARN format: arn:aws:s3tables:{region}:{account}:bucket/{bucket-name}
|
||||
func parseBucketNameFromARN(arn string) (string, error) {
|
||||
matches := bucketARNPattern.FindStringSubmatch(arn)
|
||||
if len(matches) != 2 {
|
||||
return "", fmt.Errorf("invalid bucket ARN: %s", arn)
|
||||
}
|
||||
bucketName := matches[1]
|
||||
if !isValidBucketName(bucketName) {
|
||||
return "", fmt.Errorf("invalid bucket name in ARN: %s", bucketName)
|
||||
}
|
||||
return bucketName, nil
|
||||
}
|
||||
|
||||
// ParseBucketNameFromARN is a wrapper to validate bucket ARN for other packages.
|
||||
func ParseBucketNameFromARN(arn string) (string, error) {
|
||||
return parseBucketNameFromARN(arn)
|
||||
}
|
||||
|
||||
// IsValidBucketName is a wrapper to validate a table bucket name for other packages.
|
||||
func IsValidBucketName(name string) bool {
|
||||
return isValidBucketName(name)
|
||||
}
|
||||
|
||||
// parseTableFromARN extracts bucket name, namespace, and table name from ARN
|
||||
// ARN format: arn:aws:s3tables:{region}:{account}:bucket/{bucket-name}/table/{namespace}/{table-name}
|
||||
func parseTableFromARN(arn string) (bucketName, namespace, tableName string, err error) {
|
||||
matches := tableARNPattern.FindStringSubmatch(arn)
|
||||
if len(matches) != 4 {
|
||||
return "", "", "", fmt.Errorf("invalid table ARN: %s", arn)
|
||||
}
|
||||
|
||||
// Validate bucket name
|
||||
bucketName = matches[1]
|
||||
if err := validateBucketName(bucketName); err != nil {
|
||||
return "", "", "", fmt.Errorf("invalid bucket name in ARN: %v", err)
|
||||
}
|
||||
|
||||
namespace, err = validateNamespace([]string{matches[2]})
|
||||
if err != nil {
|
||||
return "", "", "", fmt.Errorf("invalid namespace in ARN: %v", err)
|
||||
}
|
||||
|
||||
// URL decode and validate the table name from the ARN path component
|
||||
tableNameUnescaped, err := url.PathUnescape(matches[3])
|
||||
if err != nil {
|
||||
return "", "", "", fmt.Errorf("invalid table name encoding in ARN: %v", err)
|
||||
}
|
||||
if _, err := validateTableName(tableNameUnescaped); err != nil {
|
||||
return "", "", "", fmt.Errorf("invalid table name in ARN: %v", err)
|
||||
}
|
||||
return bucketName, namespace, tableNameUnescaped, nil
|
||||
}
|
||||
|
||||
// Path helpers
|
||||
|
||||
// GetTableBucketPath returns the filer path for a table bucket
|
||||
func GetTableBucketPath(bucketName string) string {
|
||||
return path.Join(TablesPath, bucketName)
|
||||
}
|
||||
|
||||
// GetNamespacePath returns the filer path for a namespace
|
||||
func GetNamespacePath(bucketName, namespace string) string {
|
||||
return path.Join(TablesPath, bucketName, namespace)
|
||||
}
|
||||
|
||||
// GetTablePath returns the filer path for a table
|
||||
func GetTablePath(bucketName, namespace, tableName string) string {
|
||||
return path.Join(TablesPath, bucketName, namespace, tableName)
|
||||
}
|
||||
|
||||
// TableDataDirFromMetadataLocation maps a table's s3:// metadata location to the
|
||||
// filer directory holding its data. A renamed table is catalog-only, so its data
|
||||
// stays at the original location while its catalog entry moves; this lets a drop
|
||||
// purge the real data instead of the now-empty catalog path.
|
||||
func TableDataDirFromMetadataLocation(metadataLocation string) string {
|
||||
loc := strings.TrimSuffix(metadataLocation, "/")
|
||||
if idx := strings.LastIndex(loc, "/metadata/"); idx != -1 {
|
||||
loc = loc[:idx]
|
||||
}
|
||||
loc = strings.TrimPrefix(loc, "s3://")
|
||||
if loc == "" {
|
||||
return ""
|
||||
}
|
||||
return path.Join(TablesPath, loc)
|
||||
}
|
||||
|
||||
// ValidateMetadataLocation checks that an s3:// metadata location stays within
|
||||
// the authorized table bucket and rejects traversal segments that path.Join
|
||||
// would collapse to escape the bucket directory. Empty locations are allowed
|
||||
// (the catalog derives one). A non-empty location must include a table path so
|
||||
// its metadata directory is table-specific, not shared at the bucket level.
|
||||
func ValidateMetadataLocation(metadataLocation, bucketName string) error {
|
||||
if metadataLocation == "" {
|
||||
return nil
|
||||
}
|
||||
bucket, tablePath, err := parseS3Location(metadataLocation)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if bucket != bucketName {
|
||||
return fmt.Errorf("metadata location must be within bucket %s", bucketName)
|
||||
}
|
||||
hasSegment := false
|
||||
for _, segment := range strings.Split(tablePath, "/") {
|
||||
if segment == "" {
|
||||
continue
|
||||
}
|
||||
if segment == "." || segment == ".." || strings.ContainsAny(segment, "\\\x00") {
|
||||
return fmt.Errorf("invalid metadata location path")
|
||||
}
|
||||
hasSegment = true
|
||||
}
|
||||
if !hasSegment {
|
||||
return fmt.Errorf("metadata location must include a table path")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseS3Location(location string) (bucket, tablePath string, err error) {
|
||||
if !strings.HasPrefix(location, "s3://") {
|
||||
return "", "", fmt.Errorf("unsupported location: %s", location)
|
||||
}
|
||||
trimmed := strings.TrimPrefix(location, "s3://")
|
||||
trimmed = strings.TrimSuffix(trimmed, "/")
|
||||
if trimmed == "" {
|
||||
return "", "", fmt.Errorf("invalid location: %s", location)
|
||||
}
|
||||
parts := strings.SplitN(trimmed, "/", 2)
|
||||
bucket = parts[0]
|
||||
if bucket == "" {
|
||||
return "", "", fmt.Errorf("invalid location bucket: %s", location)
|
||||
}
|
||||
if len(parts) == 2 {
|
||||
tablePath = parts[1]
|
||||
}
|
||||
return bucket, tablePath, nil
|
||||
}
|
||||
|
||||
// GetTableObjectRootDir returns the root path for table bucket object storage
|
||||
func GetTableObjectRootDir() string {
|
||||
return path.Join(TablesPath, tableObjectRootDirName)
|
||||
}
|
||||
|
||||
// GetTableObjectBucketPath returns the filer path for table bucket object storage
|
||||
func GetTableObjectBucketPath(bucketName string) string {
|
||||
return path.Join(GetTableObjectRootDir(), bucketName)
|
||||
}
|
||||
|
||||
// Metadata structures
|
||||
|
||||
type tableBucketMetadata struct {
|
||||
Name string `json:"name"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
OwnerAccountID string `json:"ownerAccountId"`
|
||||
Format string `json:"format,omitempty"`
|
||||
}
|
||||
|
||||
// namespaceMetadata stores metadata for a namespace
|
||||
type namespaceMetadata struct {
|
||||
Namespace []string `json:"namespace"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
OwnerAccountID string `json:"ownerAccountId"`
|
||||
Properties map[string]string `json:"properties,omitempty"`
|
||||
}
|
||||
|
||||
// tableMetadataInternal stores metadata for a table
|
||||
type tableMetadataInternal struct {
|
||||
Name string `json:"name"`
|
||||
Namespace string `json:"namespace"`
|
||||
Format string `json:"format"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
ModifiedAt time.Time `json:"modifiedAt"`
|
||||
OwnerAccountID string `json:"ownerAccountId"`
|
||||
VersionToken string `json:"versionToken"`
|
||||
MetadataVersion int `json:"metadataVersion"`
|
||||
MetadataLocation string `json:"metadataLocation,omitempty"`
|
||||
Metadata *TableMetadata `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
// IsTableBucketEntry returns true when the entry is marked as a table bucket.
|
||||
func IsTableBucketEntry(entry *filer_pb.Entry) bool {
|
||||
if entry == nil || entry.Extended == nil {
|
||||
return false
|
||||
}
|
||||
_, ok := entry.Extended[ExtendedKeyTableBucket]
|
||||
return ok
|
||||
}
|
||||
|
||||
// EntryType returns the entry-type marker for a catalog entry. Tables and views
|
||||
// share the same on-disk layout; the marker distinguishes them. An absent marker
|
||||
// means table for back-compat.
|
||||
func EntryType(extended map[string][]byte) string {
|
||||
if extended == nil {
|
||||
return EntryTypeTable
|
||||
}
|
||||
if v, ok := extended[ExtendedKeyEntryType]; ok && len(v) > 0 {
|
||||
return string(v)
|
||||
}
|
||||
return EntryTypeTable
|
||||
}
|
||||
|
||||
// Utility functions
|
||||
|
||||
// validateBucketName validates bucket name and returns an error if invalid.
|
||||
// Bucket names must contain only lowercase letters, numbers, and hyphens.
|
||||
// Length must be between 3 and 63 characters.
|
||||
// Must start and end with a letter or digit.
|
||||
// Reserved prefixes/suffixes are rejected.
|
||||
func validateBucketName(name string) error {
|
||||
if name == "" {
|
||||
return fmt.Errorf("bucket name is required")
|
||||
}
|
||||
|
||||
if len(name) < 3 || len(name) > 63 {
|
||||
return fmt.Errorf("bucket name must be between 3 and 63 characters")
|
||||
}
|
||||
|
||||
// Must start and end with a letter or digit
|
||||
start := name[0]
|
||||
end := name[len(name)-1]
|
||||
if !((start >= 'a' && start <= 'z') || (start >= '0' && start <= '9')) {
|
||||
return fmt.Errorf("bucket name must start with a letter or digit")
|
||||
}
|
||||
if !((end >= 'a' && end <= 'z') || (end >= '0' && end <= '9')) {
|
||||
return fmt.Errorf("bucket name must end with a letter or digit")
|
||||
}
|
||||
|
||||
// Allowed characters: a-z, 0-9, -
|
||||
for i := 0; i < len(name); i++ {
|
||||
ch := name[i]
|
||||
if (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '-' {
|
||||
continue
|
||||
}
|
||||
return fmt.Errorf("bucket name can only contain lowercase letters, numbers, and hyphens")
|
||||
}
|
||||
|
||||
// Reserved prefixes
|
||||
reservedPrefixes := []string{"xn--", "sthree-", "amzn-s3-demo-", "aws"}
|
||||
for _, p := range reservedPrefixes {
|
||||
if strings.HasPrefix(name, p) {
|
||||
return fmt.Errorf("bucket name cannot start with reserved prefix: %s", p)
|
||||
}
|
||||
}
|
||||
|
||||
// Reserved suffixes
|
||||
reservedSuffixes := []string{"-s3alias", "--ol-s3", "--x-s3", "--table-s3"}
|
||||
for _, s := range reservedSuffixes {
|
||||
if strings.HasSuffix(name, s) {
|
||||
return fmt.Errorf("bucket name cannot end with reserved suffix: %s", s)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// BuildBucketARN builds a bucket ARN with the provided region and account ID.
|
||||
// If region is empty, the ARN will omit the region field.
|
||||
func BuildBucketARN(region, accountID, bucketName string) (string, error) {
|
||||
if bucketName == "" {
|
||||
return "", fmt.Errorf("bucket name is required")
|
||||
}
|
||||
if err := validateBucketName(bucketName); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if accountID == "" {
|
||||
accountID = DefaultAccountID
|
||||
}
|
||||
return buildARN(region, accountID, fmt.Sprintf("bucket/%s", bucketName)), nil
|
||||
}
|
||||
|
||||
// BuildTableARN builds a table ARN with the provided region and account ID.
|
||||
func BuildTableARN(region, accountID, bucketName, namespace, tableName string) (string, error) {
|
||||
if bucketName == "" {
|
||||
return "", fmt.Errorf("bucket name is required")
|
||||
}
|
||||
if err := validateBucketName(bucketName); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if namespace == "" {
|
||||
return "", fmt.Errorf("namespace is required")
|
||||
}
|
||||
normalizedNamespace, err := validateNamespace([]string{namespace})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if tableName == "" {
|
||||
return "", fmt.Errorf("table name is required")
|
||||
}
|
||||
normalizedTable, err := validateTableName(tableName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if accountID == "" {
|
||||
accountID = DefaultAccountID
|
||||
}
|
||||
return buildARN(region, accountID, fmt.Sprintf("bucket/%s/table/%s/%s", bucketName, normalizedNamespace, normalizedTable)), nil
|
||||
}
|
||||
|
||||
func buildARN(region, accountID, resourcePath string) string {
|
||||
return fmt.Sprintf("arn:%s:s3tables:%s:%s:%s", arnPartitionForRegion(region), region, accountID, resourcePath)
|
||||
}
|
||||
|
||||
// arnPartitionForRegion returns the ARN partition a region belongs to, so an
|
||||
// ARN this handler emits round-trips through a client in that partition.
|
||||
func arnPartitionForRegion(region string) string {
|
||||
switch {
|
||||
case strings.HasPrefix(region, "cn-"):
|
||||
return "aws-cn"
|
||||
case strings.HasPrefix(region, "us-gov-"):
|
||||
return "aws-us-gov"
|
||||
case strings.HasPrefix(region, "us-iso-"):
|
||||
return "aws-iso"
|
||||
case strings.HasPrefix(region, "us-isob-"):
|
||||
return "aws-iso-b"
|
||||
case strings.HasPrefix(region, "eu-isoe-"):
|
||||
return "aws-iso-e"
|
||||
case strings.HasPrefix(region, "us-isof-"):
|
||||
return "aws-iso-f"
|
||||
case strings.HasPrefix(region, "eusc-"):
|
||||
return "aws-eusc"
|
||||
default:
|
||||
return "aws"
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateTags validates tags for S3 Tables.
|
||||
func ValidateTags(tags map[string]string) error {
|
||||
if len(tags) > 10 {
|
||||
return fmt.Errorf("validate tags: %d tags more than 10", len(tags))
|
||||
}
|
||||
for k, v := range tags {
|
||||
if len(k) > 128 {
|
||||
return fmt.Errorf("validate tags: tag key longer than 128")
|
||||
}
|
||||
if !tagPattern.MatchString(k) {
|
||||
return fmt.Errorf("validate tags key %s error, incorrect key", k)
|
||||
}
|
||||
if len(v) > 256 {
|
||||
return fmt.Errorf("validate tags: tag value longer than 256")
|
||||
}
|
||||
if !tagPattern.MatchString(v) {
|
||||
return fmt.Errorf("validate tags value %s error, incorrect value", v)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// isValidBucketName validates bucket name characters (kept for compatibility)
|
||||
// Deprecated: use validateBucketName instead
|
||||
func isValidBucketName(name string) bool {
|
||||
return validateBucketName(name) == nil
|
||||
}
|
||||
|
||||
// generateVersionToken generates a unique, unpredictable version token
|
||||
func generateVersionToken() string {
|
||||
b := make([]byte, 16)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
// Fallback to timestamp if crypto/rand fails
|
||||
return fmt.Sprintf("%x", time.Now().UnixNano())
|
||||
}
|
||||
return hex.EncodeToString(b)
|
||||
}
|
||||
|
||||
// splitPath splits a path into directory and name components using stdlib
|
||||
func splitPath(p string) (dir, name string) {
|
||||
dir = path.Dir(p)
|
||||
name = path.Base(p)
|
||||
return
|
||||
}
|
||||
|
||||
func validateNamespacePart(name string) error {
|
||||
if len(name) < 1 || len(name) > 255 {
|
||||
return fmt.Errorf("namespace name must be between 1 and 255 characters")
|
||||
}
|
||||
|
||||
// Prevent path traversal and multi-segment paths
|
||||
if name == "." || name == ".." {
|
||||
return fmt.Errorf("namespace name cannot be '.' or '..'")
|
||||
}
|
||||
if strings.Contains(name, "/") {
|
||||
return fmt.Errorf("namespace name cannot contain '/'")
|
||||
}
|
||||
|
||||
// Must start and end with a letter or digit
|
||||
start := name[0]
|
||||
end := name[len(name)-1]
|
||||
if !((start >= 'a' && start <= 'z') || (start >= '0' && start <= '9')) {
|
||||
return fmt.Errorf("namespace name must start with a letter or digit")
|
||||
}
|
||||
if !((end >= 'a' && end <= 'z') || (end >= '0' && end <= '9')) {
|
||||
return fmt.Errorf("namespace name must end with a letter or digit")
|
||||
}
|
||||
|
||||
// Allowed characters: a-z, 0-9, _, - (hyphen interior; start/end checked above)
|
||||
for _, ch := range name {
|
||||
if (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '_' || ch == '-' {
|
||||
continue
|
||||
}
|
||||
return fmt.Errorf("invalid namespace name: only 'a-z', '0-9', '_', and '-' are allowed")
|
||||
}
|
||||
|
||||
// Reserved prefix
|
||||
if strings.HasPrefix(name, "aws") {
|
||||
return fmt.Errorf("namespace name cannot start with reserved prefix 'aws'")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func normalizeNamespace(namespace []string) ([]string, error) {
|
||||
if len(namespace) == 0 {
|
||||
return nil, fmt.Errorf("namespace is required")
|
||||
}
|
||||
|
||||
parts := namespace
|
||||
if len(namespace) == 1 {
|
||||
parts = strings.Split(namespace[0], ".")
|
||||
}
|
||||
|
||||
normalized := make([]string, 0, len(parts))
|
||||
for _, part := range parts {
|
||||
if err := validateNamespacePart(part); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
normalized = append(normalized, part)
|
||||
}
|
||||
return normalized, nil
|
||||
}
|
||||
|
||||
// validateNamespace validates namespace identifiers and returns an internal namespace key.
|
||||
// A single dotted namespace value is interpreted as multi-level namespace for compatibility
|
||||
// with path-style APIs, for example "analytics.daily" => ["analytics", "daily"].
|
||||
func validateNamespace(namespace []string) (string, error) {
|
||||
parts, err := normalizeNamespace(namespace)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return flattenNamespace(parts), nil
|
||||
}
|
||||
|
||||
// ParseNamespace parses a namespace string into namespace parts.
|
||||
func ParseNamespace(namespace string) ([]string, error) {
|
||||
return normalizeNamespace([]string{namespace})
|
||||
}
|
||||
|
||||
// validateTableName validates a table name
|
||||
func validateTableName(name string) (string, error) {
|
||||
if len(name) < 1 || len(name) > 255 {
|
||||
return "", fmt.Errorf("table name must be between 1 and 255 characters")
|
||||
}
|
||||
if name == "." || name == ".." || strings.Contains(name, "/") {
|
||||
return "", fmt.Errorf("invalid table name: cannot be '.', '..' or contain '/'")
|
||||
}
|
||||
|
||||
// First character must be a letter or digit
|
||||
start := name[0]
|
||||
if !((start >= 'a' && start <= 'z') || (start >= '0' && start <= '9')) {
|
||||
return "", fmt.Errorf("table name must start with a letter or digit")
|
||||
}
|
||||
|
||||
// Allowed characters: a-z, 0-9, _, - (start checked above)
|
||||
for _, ch := range name {
|
||||
if (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '_' || ch == '-' {
|
||||
continue
|
||||
}
|
||||
return "", fmt.Errorf("invalid table name: only 'a-z', '0-9', '_', and '-' are allowed")
|
||||
}
|
||||
return name, nil
|
||||
}
|
||||
|
||||
// ValidateTableName is a wrapper to validate table name for other packages.
|
||||
func ValidateTableName(name string) (string, error) {
|
||||
return validateTableName(name)
|
||||
}
|
||||
|
||||
// flattenNamespace joins namespace elements into a single string (using dots as per AWS S3 Tables)
|
||||
func flattenNamespace(namespace []string) string {
|
||||
if len(namespace) == 0 {
|
||||
return ""
|
||||
}
|
||||
return strings.Join(namespace, ".")
|
||||
}
|
||||
|
||||
func expandNamespace(namespace string) []string {
|
||||
if namespace == "" {
|
||||
return nil
|
||||
}
|
||||
parts, err := ParseNamespace(namespace)
|
||||
if err != nil {
|
||||
return []string{namespace}
|
||||
}
|
||||
return parts
|
||||
}
|
||||
Reference in New Issue
Block a user