Skip to content

Commit

Permalink
feat(client-s3): add useArnRegion config (#1420)
Browse files Browse the repository at this point in the history
* feat(node-config-provider): add a loader for configs from config files and env

* chore(node-config-provider): add preferred config file to load

* chore(node-config-provider): env and config option can only be a getter function

* feat(middleware-bucket-endppoint): add useArnRegion config to bucket endpoint middleware

* chore: codegen s3 useArnRegion config

* feat(client-s3): codegen S3 client with useArnAgent config
  • Loading branch information
AllanZhengYP committed Aug 11, 2020
1 parent f6f700d commit 40e600e
Show file tree
Hide file tree
Showing 24 changed files with 898 additions and 14 deletions.
5 changes: 5 additions & 0 deletions clients/client-s3/S3Client.ts
Expand Up @@ -259,6 +259,7 @@ import {
SmithyResolvedConfiguration as __SmithyResolvedConfiguration,
} from "@aws-sdk/smithy-client";
import {
Provider,
RegionInfoProvider,
Credentials as __Credentials,
Decoder as __Decoder,
Expand Down Expand Up @@ -550,6 +551,10 @@ export interface ClientDefaults extends Partial<__SmithyResolvedConfiguration<__
*/
signingEscapePath?: boolean;

/**
* Whether to override the request region with the region inferred from requested resource's ARN. Defaults to false.
*/
useArnRegion?: boolean | Provider<boolean>;
/**
* The function that provides necessary utilities for generating and parsing event stream
*/
Expand Down
1 change: 1 addition & 0 deletions clients/client-s3/package.json
Expand Up @@ -71,6 +71,7 @@
"@aws-sdk/util-utf8-browser": "1.0.0-gamma.5",
"@aws-sdk/util-utf8-node": "1.0.0-gamma.5",
"@aws-sdk/xml-builder": "1.0.0-gamma.5",
"@aws-sdk/node-config-provider": "1.0.0-gamma.0",
"fast-xml-parser": "^3.16.0",
"tslib": "^2.0.0"
},
Expand Down
1 change: 1 addition & 0 deletions clients/client-s3/runtimeConfig.shared.ts
Expand Up @@ -6,4 +6,5 @@ export const ClientSharedValues = {
regionInfoProvider: defaultRegionInfoProvider,
signingEscapePath: false,
signingName: "s3",
useArnRegion: false,
};
3 changes: 3 additions & 0 deletions clients/client-s3/runtimeConfig.ts
Expand Up @@ -3,6 +3,8 @@ import { defaultProvider as credentialDefaultProvider } from "@aws-sdk/credentia
import { eventStreamSerdeProvider } from "@aws-sdk/eventstream-serde-node";
import { Hash } from "@aws-sdk/hash-node";
import { fileStreamHasher as streamHasher } from "@aws-sdk/hash-stream-node";
import { NODE_USE_ARN_REGION_CONFIG_OPTIONS } from "@aws-sdk/middleware-bucket-endpoint";
import { loadConfig as loadNodeConfig } from "@aws-sdk/node-config-provider";
import { NodeHttpHandler, streamCollector } from "@aws-sdk/node-http-handler";
import { defaultProvider as regionDefaultProvider } from "@aws-sdk/region-provider";
import { maxAttemptsProvider as maxAttemptsDefaultProvider } from "@aws-sdk/retry-config-provider";
Expand Down Expand Up @@ -32,6 +34,7 @@ export const ClientDefaultValues: Required<ClientDefaults> = {
streamCollector,
streamHasher,
urlParser: parseUrl,
useArnRegion: loadNodeConfig(NODE_USE_ARN_REGION_CONFIG_OPTIONS),
utf8Decoder: fromUtf8,
utf8Encoder: toUtf8,
};
Expand Up @@ -24,6 +24,7 @@
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.shapes.ServiceShape;
import software.amazon.smithy.typescript.codegen.LanguageTarget;
import software.amazon.smithy.typescript.codegen.TypeScriptDependency;
import software.amazon.smithy.typescript.codegen.TypeScriptSettings;
import software.amazon.smithy.typescript.codegen.TypeScriptWriter;
import software.amazon.smithy.typescript.codegen.integration.TypeScriptIntegration;
Expand All @@ -35,33 +36,40 @@
public final class AddS3Config implements TypeScriptIntegration {

@Override
public void addConfigInterfaceFields(
TypeScriptSettings settings,
Model model,
SymbolProvider symbolProvider,
TypeScriptWriter writer
) {
public void addConfigInterfaceFields(TypeScriptSettings settings, Model model, SymbolProvider symbolProvider,
TypeScriptWriter writer) {
if (!needsS3Config(settings.getService(model))) {
return;
}
writer.writeDocs("Whether to escape request path when signing the request.")
.write("signingEscapePath?: boolean;\n");
writer.writeDocs(
"Whether to override the request region with the region inferred from requested resource's ARN. Defaults to false.")
.addImport("Provider", "Provider", TypeScriptDependency.AWS_SDK_TYPES.packageName)
.write("useArnRegion?: boolean | Provider<boolean>;");
}

@Override
public Map<String, Consumer<TypeScriptWriter>> getRuntimeConfigWriters(
TypeScriptSettings settings,
Model model,
SymbolProvider symbolProvider,
LanguageTarget target
) {
public Map<String, Consumer<TypeScriptWriter>> getRuntimeConfigWriters(TypeScriptSettings settings, Model model,
SymbolProvider symbolProvider, LanguageTarget target) {
if (!needsS3Config(settings.getService(model))) {
return Collections.emptyMap();
}
switch (target) {
case SHARED:
return MapUtils.of("signingEscapePath", writer -> {
writer.write("signingEscapePath: false,");
}, "useArnRegion", writer -> {
writer.write("useArnRegion: false");
});
case NODE:
return MapUtils.of("useArnRegion", writer -> {
writer.addDependency(AwsDependency.NODE_CONFIG_PROVIDER)
.addImport("loadConfig", "loadNodeConfig", AwsDependency.NODE_CONFIG_PROVIDER.packageName)
.addDependency(AwsDependency.BUCKET_ENDPOINT_MIDDLEWARE)
.addImport("NODE_USE_ARN_REGION_CONFIG_OPTIONS", "NODE_USE_ARN_REGION_CONFIG_OPTIONS",
AwsDependency.BUCKET_ENDPOINT_MIDDLEWARE.packageName)
.write("useArnRegion: loadNodeConfig(NODE_USE_ARN_REGION_CONFIG_OPTIONS),");
});
default:
return Collections.emptyMap();
Expand Down
Expand Up @@ -60,7 +60,8 @@ public enum AwsDependency implements SymbolDependencyContainer {
AWS_SDK_EVENTSTREAM_HANDLER_NODE(NORMAL_DEPENDENCY, "@aws-sdk/eventstream-handler-node", "^1.0.0-beta.0"),
TRANSCRIBE_STREAMING_MIDDLEWARE(NORMAL_DEPENDENCY, "@aws-sdk/middleware-sdk-transcribe-streaming",
"^1.0.0-gamma.0"),
RETRY_CONFIG_PROVIDER(NORMAL_DEPENDENCY, "@aws-sdk/retry-config-provider", "^1.0.0-gamma.0");
RETRY_CONFIG_PROVIDER(NORMAL_DEPENDENCY, "@aws-sdk/retry-config-provider", "^1.0.0-gamma.0"),
NODE_CONFIG_PROVIDER(NORMAL_DEPENDENCY, "@aws-sdk/node-config-provider", "^1.0.0-gamma.0");

public final String packageName;
public final String version;
Expand Down
3 changes: 2 additions & 1 deletion packages/middleware-bucket-endpoint/package.json
Expand Up @@ -24,8 +24,9 @@
},
"devDependencies": {
"@aws-sdk/middleware-stack": "1.0.0-gamma.5",
"@aws-sdk/node-config-provider": "1.0.0-gamma.0",
"@types/jest": "^26.0.4",
"jest": "^26.1.0",
"typescript": "~3.9.3"
}
}
}
68 changes: 68 additions & 0 deletions packages/middleware-bucket-endpoint/src/configuration.spec.ts
@@ -0,0 +1,68 @@
import {
NODE_USE_ARN_REGION_CONFIG_OPTIONS as loadOptions,
NODE_USE_ARN_REGION_ENV_NAME,
NODE_USE_ARN_REGION_INI_NAME,
} from "./configurations";
describe("Node useArnRegion config loader", () => {
describe("environment variable selector", () => {
const env: { [NODE_USE_ARN_REGION_ENV_NAME]: any } = {} as any;

beforeEach(() => {
delete env[NODE_USE_ARN_REGION_ENV_NAME];
});

it(`should return undefined if the environment variable doesn't have ${NODE_USE_ARN_REGION_ENV_NAME}`, () => {
expect(loadOptions.environmentVariableSelector(env)).toBeUndefined();
});

[
{ envValue: "true", parsed: true },
{ envValue: "false", parsed: false },
].forEach(({ envValue, parsed }) => {
it(`should return boolean if the environment variable ${NODE_USE_ARN_REGION_ENV_NAME} is ${envValue}`, () => {
env[NODE_USE_ARN_REGION_ENV_NAME] = envValue;
expect(loadOptions.environmentVariableSelector(env)).toBe(parsed);
});
});

["0", "1", "yes", "no", undefined, null, void 0, ""].forEach((envValue) => {
it(`should throw if the environment variable ${NODE_USE_ARN_REGION_ENV_NAME} is ${envValue}`, () => {
env[NODE_USE_ARN_REGION_ENV_NAME] = envValue as any;
expect(() => loadOptions.environmentVariableSelector(env)).toThrow(
`Cannot load env ${NODE_USE_ARN_REGION_ENV_NAME}. Expected "true" or "false", got ${envValue}.`
);
});
});
});

describe("shared INI files selector", () => {
const profileContent: { [NODE_USE_ARN_REGION_INI_NAME]: any } = {} as any;

beforeEach(() => {
delete profileContent[NODE_USE_ARN_REGION_INI_NAME];
});

it(`should return undefined if shared config profile doesn't have ${NODE_USE_ARN_REGION_INI_NAME}`, () => {
expect(loadOptions.configFileSelector(profileContent)).toBeUndefined();
});

[
{ value: "true", parsed: true },
{ value: "false", parsed: false },
].forEach(({ value, parsed }) => {
it(`should return boolean if the shared config profile ${NODE_USE_ARN_REGION_INI_NAME} is ${value}`, () => {
profileContent[NODE_USE_ARN_REGION_INI_NAME] = value;
expect(loadOptions.configFileSelector(profileContent)).toBe(parsed);
});
});

["0", "1", "yes", "no", undefined, null, void 0, ""].forEach((value) => {
it(`should throw if the shared config profile ${NODE_USE_ARN_REGION_INI_NAME} is ${value}`, () => {
profileContent[NODE_USE_ARN_REGION_INI_NAME] = value as any;
expect(() => loadOptions.configFileSelector(profileContent)).toThrow(
`Cannot load shared config entry ${NODE_USE_ARN_REGION_INI_NAME}. Expected "true" or "false", got ${value}.`
);
});
});
});
});
38 changes: 38 additions & 0 deletions packages/middleware-bucket-endpoint/src/configurations.ts
@@ -1,3 +1,6 @@
import { LoadedConfigSelectors } from "@aws-sdk/node-config-provider";
import { Provider } from "@aws-sdk/types";

export interface BucketEndpointInputConfig {
/**
* Whether the provided endpoint addresses an individual bucket.
Expand All @@ -15,13 +18,18 @@ export interface BucketEndpointInputConfig {
* Enables IPv6/IPv4 dualstack endpoint. When a DNS lookup is performed on an endpoint of this type, it returns an “A” record with an IPv4 address and an “AAAA” record with an IPv6 address. In most cases the network stack in the client environment will automatically prefer the AAAA record and make a connection using the IPv6 address. Note, however, that currently on Windows, the IPv4 address will be preferred.
*/
useDualstackEndpoint?: boolean;
/**
* Whether to override the request region with the region inferred from requested resource's ARN. Defaults to false
*/
useArnRegion?: boolean | Provider<boolean>;
}

export interface BucketEndpointResolvedConfig {
bucketEndpoint: boolean;
forcePathStyle: boolean;
useAccelerateEndpoint: boolean;
useDualstackEndpoint: boolean;
useArnRegion: Provider<boolean>;
}

export function resolveBucketEndpointConfig<T>(input: T & BucketEndpointInputConfig): T & BucketEndpointResolvedConfig {
Expand All @@ -30,12 +38,42 @@ export function resolveBucketEndpointConfig<T>(input: T & BucketEndpointInputCon
forcePathStyle = false,
useAccelerateEndpoint = false,
useDualstackEndpoint = false,
useArnRegion = false,
} = input;
return {
...input,
bucketEndpoint,
forcePathStyle,
useAccelerateEndpoint,
useDualstackEndpoint,
useArnRegion: typeof useArnRegion === "boolean" ? () => Promise.resolve(useArnRegion) : useArnRegion,
};
}

export const NODE_USE_ARN_REGION_ENV_NAME = "AWS_S3_USE_ARN_REGION";
export const NODE_USE_ARN_REGION_INI_NAME = "s3_use_arn_region";

/**
* Config to load useArnRegion from environment variables and shared INI files
*
* @api private
*/
export const NODE_USE_ARN_REGION_CONFIG_OPTIONS: LoadedConfigSelectors<boolean> = {
environmentVariableSelector: (env: NodeJS.ProcessEnv) => {
if (!Object.prototype.hasOwnProperty.call(env, NODE_USE_ARN_REGION_ENV_NAME)) return undefined;
if (env[NODE_USE_ARN_REGION_ENV_NAME] === "true") return true;
if (env[NODE_USE_ARN_REGION_ENV_NAME] === "false") return false;
throw new Error(
`Cannot load env ${NODE_USE_ARN_REGION_ENV_NAME}. Expected "true" or "false", got ${env[NODE_USE_ARN_REGION_ENV_NAME]}.`
);
},
configFileSelector: (profile) => {
if (!Object.prototype.hasOwnProperty.call(profile, NODE_USE_ARN_REGION_INI_NAME)) return undefined;
if (profile[NODE_USE_ARN_REGION_INI_NAME] === "true") return true;
if (profile[NODE_USE_ARN_REGION_INI_NAME] === "false") return false;
throw new Error(
`Cannot load shared config entry ${NODE_USE_ARN_REGION_INI_NAME}. Expected "true" or "false", got ${profile[NODE_USE_ARN_REGION_INI_NAME]}.`
);
},
default: false,
};

0 comments on commit 40e600e

Please sign in to comment.