Skip to content

Commit 491cc48

Browse files
committed
fix(middleware-sdk-s3): restore bucketEndpoint configurability
1 parent 1e2b1ae commit 491cc48

File tree

4 files changed

+93
-5
lines changed

4 files changed

+93
-5
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import {
2+
HandlerExecutionContext,
3+
MetadataBearer,
4+
RelativeMiddlewareOptions,
5+
SerializeHandler,
6+
SerializeHandlerArguments,
7+
SerializeHandlerOutput,
8+
SerializeMiddleware,
9+
} from "@smithy/types";
10+
11+
interface PreviouslyResolved {
12+
bucketEndpoint?: boolean;
13+
}
14+
15+
/**
16+
* @internal
17+
*/
18+
export function bucketEndpointMiddleware(options: PreviouslyResolved): SerializeMiddleware<any, any> {
19+
return <Output extends MetadataBearer>(
20+
next: SerializeHandler<any, Output>,
21+
context: HandlerExecutionContext
22+
): SerializeHandler<any, Output> =>
23+
async (args: SerializeHandlerArguments<any>): Promise<SerializeHandlerOutput<Output>> => {
24+
if (options.bucketEndpoint) {
25+
const endpoint = context.endpointV2;
26+
if (endpoint) {
27+
const bucket: string | undefined = args.input.Bucket;
28+
if (typeof bucket === "string") {
29+
try {
30+
const bucketEndpointUrl = new URL(bucket);
31+
endpoint.url = bucketEndpointUrl;
32+
} catch (e) {
33+
const warning = `@aws-sdk/middleware-sdk-s3: bucketEndpoint=true was set but Bucket=${bucket} could not be parsed as URL.`;
34+
if (context.logger?.constructor?.name === "NoOpLogger") {
35+
console.warn(warning);
36+
} else {
37+
context.logger?.warn?.(warning);
38+
}
39+
throw e;
40+
}
41+
}
42+
}
43+
}
44+
return next(args);
45+
};
46+
}
47+
48+
/**
49+
* @internal
50+
*/
51+
export const bucketEndpointMiddlewareOptions: RelativeMiddlewareOptions = {
52+
name: "bucketEndpointMiddleware",
53+
override: true,
54+
relation: "after",
55+
toMiddleware: "endpointV2Middleware",
56+
};

packages/middleware-sdk-s3/src/middleware-sdk-s3.integ.spec.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,5 +57,28 @@ describe("middleware-sdk-s3", () => {
5757

5858
expect.hasAssertions();
5959
});
60+
61+
it("allows using a bucket input value as the endpoint", async () => {
62+
const client = new S3({
63+
region: "us-west-2",
64+
bucketEndpoint: true,
65+
});
66+
67+
requireRequestsFrom(client).toMatch({
68+
query: { "x-id": "PutObject" },
69+
protocol: "https:",
70+
hostname: "mybucket.com",
71+
port: 8888,
72+
path: "/my-bucket-path/my-key",
73+
});
74+
75+
await client.putObject({
76+
Bucket: "https://mybucket.com:8888/my-bucket-path",
77+
Key: "my-key",
78+
Body: "abcd",
79+
});
80+
81+
expect.hasAssertions();
82+
});
6083
});
6184
});

packages/middleware-sdk-s3/src/s3Configuration.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ export interface S3InputConfig {
3232
* Identity provider for an S3 feature.
3333
*/
3434
s3ExpressIdentityProvider?: S3ExpressIdentityProvider;
35+
/**
36+
* Whether to use the bucket name as the endpoint for this client.
37+
*/
38+
bucketEndpoint?: boolean;
3539
}
3640

3741
/**
@@ -54,6 +58,7 @@ export interface S3ResolvedConfig {
5458
disableMultiregionAccessPoints: boolean;
5559
followRegionRedirects: boolean;
5660
s3ExpressIdentityProvider: S3ExpressIdentityProvider;
61+
bucketEndpoint: boolean;
5762
}
5863

5964
export const resolveS3Config = <T>(
@@ -81,5 +86,6 @@ export const resolveS3Config = <T>(
8186
})
8287
)
8388
),
89+
bucketEndpoint: input.bucketEndpoint ?? false,
8490
};
8591
};

packages/middleware-sdk-s3/src/validate-bucket-name.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,19 @@ import {
99
Pluggable,
1010
} from "@smithy/types";
1111

12+
import { bucketEndpointMiddleware, bucketEndpointMiddlewareOptions } from "./bucket-endpoint-middleware";
13+
import { S3ResolvedConfig } from "./s3Configuration";
14+
1215
/**
1316
* @internal
1417
*/
15-
export function validateBucketNameMiddleware(): InitializeMiddleware<any, any> {
18+
export function validateBucketNameMiddleware({ bucketEndpoint }: S3ResolvedConfig): InitializeMiddleware<any, any> {
1619
return <Output extends MetadataBearer>(next: InitializeHandler<any, Output>): InitializeHandler<any, Output> =>
1720
async (args: InitializeHandlerArguments<any>): Promise<InitializeHandlerOutput<Output>> => {
1821
const {
1922
input: { Bucket },
2023
} = args;
21-
if (typeof Bucket === "string" && !validateArn(Bucket) && Bucket.indexOf("/") >= 0) {
24+
if (!bucketEndpoint && typeof Bucket === "string" && !validateArn(Bucket) && Bucket.indexOf("/") >= 0) {
2225
const err = new Error(`Bucket name shouldn't contain '/', received '${Bucket}'`);
2326
err.name = "InvalidBucketName";
2427
throw err;
@@ -40,9 +43,9 @@ export const validateBucketNameMiddlewareOptions: InitializeHandlerOptions = {
4043
/**
4144
* @internal
4245
*/
43-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
44-
export const getValidateBucketNamePlugin = (unused: any): Pluggable<any, any> => ({
46+
export const getValidateBucketNamePlugin = (options: S3ResolvedConfig): Pluggable<any, any> => ({
4547
applyToStack: (clientStack) => {
46-
clientStack.add(validateBucketNameMiddleware(), validateBucketNameMiddlewareOptions);
48+
clientStack.add(validateBucketNameMiddleware(options), validateBucketNameMiddlewareOptions);
49+
clientStack.addRelativeTo(bucketEndpointMiddleware(options), bucketEndpointMiddlewareOptions);
4750
},
4851
});

0 commit comments

Comments
 (0)