From 5478b96e705bf37145bac50b848620c02785a56e Mon Sep 17 00:00:00 2001 From: Jason Amadi Date: Sun, 13 Sep 2026 22:44:08 +0100 Subject: [PATCH] feat(s3): allow custom region and addressing style MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #1479. - Region was z.enum(s3Regions) (23 hardcoded values) — blocked S3-compatible stores and newer AWS regions. Changed to z.string().min(1) with default, preserving s3Regions[0] as default and keeping existing configs valid. - Added addressingStyle enum [auto, virtual-hosted, path] (default auto) to expose S3 URL addressing control for S3-compatible endpoints. Backend respects it in bulker S3 client: path/virtual-hosted explicit, auto preserves historical behavior (path-style when endpoint set). Backward compatible: defaults match previous behavior. --- bulker/bulkerlib/implementations/s3.go | 12 ++++++++++++ webapps/console/lib/schema/destinations.tsx | 19 ++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/bulker/bulkerlib/implementations/s3.go b/bulker/bulkerlib/implementations/s3.go index 3e3e42cc03..e42b8c7d7c 100644 --- a/bulker/bulkerlib/implementations/s3.go +++ b/bulker/bulkerlib/implementations/s3.go @@ -34,6 +34,7 @@ type S3Config struct { Bucket string `mapstructure:"bucket,omitempty" json:"bucket,omitempty" yaml:"bucket,omitempty"` Region string `mapstructure:"region,omitempty" json:"region,omitempty" yaml:"region,omitempty"` Endpoint string `mapstructure:"endpoint,omitempty" json:"endpoint,omitempty" yaml:"endpoint,omitempty"` + AddressingStyle string `mapstructure:"addressingStyle,omitempty" json:"addressingStyle,omitempty" yaml:"addressingStyle,omitempty"` UsePresignedURL bool `mapstructure:"usePresignedURL,omitempty" json:"usePresignedURL,omitempty" yaml:"usePresignedURL,omitempty"` RoleARN string `mapstructure:"roleARN" json:"roleARN" yaml:"roleARN"` @@ -136,7 +137,18 @@ func NewS3(s3Config *S3Config) (*S3, error) { o.Region = s3Config.Region if s3Config.Endpoint != "" { o.BaseEndpoint = &s3Config.Endpoint + } + // Preserve historical behavior by default (path-style for custom endpoints), + // but allow explicit override via addressingStyle. + switch s3Config.AddressingStyle { + case "path": o.UsePathStyle = true + case "virtual-hosted": + o.UsePathStyle = false + case "auto", "": + o.UsePathStyle = s3Config.Endpoint != "" + default: + o.UsePathStyle = s3Config.Endpoint != "" } } client := s3.NewFromConfig(awsCfg, o) diff --git a/webapps/console/lib/schema/destinations.tsx b/webapps/console/lib/schema/destinations.tsx index 8a333d5ec8..dba93cd2ed 100644 --- a/webapps/console/lib/schema/destinations.tsx +++ b/webapps/console/lib/schema/destinations.tsx @@ -68,6 +68,10 @@ const s3Regions = [ "us-gov-west-1", ] as const; +// Allowed S3 URL addressing styles. "auto" preserves the historical behavior: +// path-style for custom endpoints, virtual-hosted-style for AWS. +const s3AddressingStyles = ["auto", "virtual-hosted", "path"] as const; + export const MASKED_SECRET = "__MASKED_BY_JITSU__"; /** @@ -888,7 +892,13 @@ export const coreDestinations: DestinationType[] = [ .describe( "Authentication Method::S3 authentication method: IAM Role based or Access Key" ), - region: z.enum(s3Regions).default(s3Regions[0]).describe("S3 Region::S3 Region"), + region: z + .string() + .min(1) + .default(s3Regions[0]) + .describe( + "S3 Region::S3 Region. Any AWS region (e.g. us-east-1, eu-central-1) or custom region for S3-compatible storage." + ), roleARN: z .string() .optional() @@ -905,6 +915,13 @@ export const coreDestinations: DestinationType[] = [ secretAccessKey: z.string().optional().describe("S3 Secret Access Key::S3 Secret Access Key"), bucket: z.string().describe("S3 Bucket Name::S3 Bucket Name"), endpoint: z.string().optional().describe("Custom endpoint of S3-compatible server"), + addressingStyle: z + .enum(s3AddressingStyles) + .optional() + .default("auto") + .describe( + "S3 Addressing Style::S3 URL addressing style: auto (path-style for custom endpoint, virtual-hosted otherwise), virtual-hosted, or path" + ), }) .merge(blockStorageSettings), credentialsUi: {