Skip to content

tests(parameters): integration tests for SSMProvider #1257

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Feb 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 15 additions & 23 deletions packages/parameters/src/ssm/SSMProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,14 +144,11 @@ class SSMProvider extends BaseProvider {
options?: SSMGetOptionsInterface
): Promise<string | undefined> {
const sdkOptions: GetParameterCommandInput = {
...(options?.sdkOptions || {}),
Name: name,
};
if (options) {
if (options.hasOwnProperty('decrypt')) sdkOptions.WithDecryption = options.decrypt;
if (options.hasOwnProperty('sdkOptions')) {
Object.assign(sdkOptions, options.sdkOptions);
}
}
sdkOptions.WithDecryption = options?.decrypt !== undefined ?
options.decrypt : sdkOptions.WithDecryption;
const result = await this.client.send(new GetParameterCommand(sdkOptions));

return result.Parameter?.Value;
Expand All @@ -162,21 +159,18 @@ class SSMProvider extends BaseProvider {
options?: SSMGetMultipleOptionsInterface
): Promise<Record<string, string | undefined>> {
const sdkOptions: GetParametersByPathCommandInput = {
...(options?.sdkOptions || {}),
Path: path,
};
const paginationOptions: PaginationConfiguration = {
client: this.client
};
if (options) {
if (options.hasOwnProperty('decrypt')) sdkOptions.WithDecryption = options.decrypt;
if (options.hasOwnProperty('recursive')) sdkOptions.Recursive = options.recursive;
if (options.hasOwnProperty('sdkOptions')) {
Object.assign(sdkOptions, options.sdkOptions);
if (sdkOptions.MaxResults) {
paginationOptions.pageSize = sdkOptions.MaxResults;
}
}
}
sdkOptions.WithDecryption = options?.decrypt !== undefined ?
options.decrypt : sdkOptions.WithDecryption;
sdkOptions.Recursive = options?.recursive !== undefined ?
options.recursive : sdkOptions.Recursive;
paginationOptions.pageSize = sdkOptions.MaxResults !== undefined ?
sdkOptions.MaxResults : undefined;

const parameters: Record<string, string | undefined> = {};
for await (const page of paginateGetParametersByPath(paginationOptions, sdkOptions)) {
Expand Down Expand Up @@ -389,13 +383,11 @@ class SSMProvider extends BaseProvider {
const overrides = parameterOptions;
overrides.transform = overrides.transform || configs.transform;

if (!overrides.hasOwnProperty('decrypt')) {
overrides.decrypt = configs.decrypt;
}
if (!overrides.hasOwnProperty('maxAge')) {
overrides.maxAge = configs.maxAge;
}

overrides.decrypt = overrides.decrypt !== undefined ?
overrides.decrypt : configs.decrypt;
overrides.maxAge = overrides.maxAge !== undefined ?
overrides.maxAge : configs.maxAge;

if (overrides.decrypt) {
parametersToDecrypt[parameterName] = overrides;
} else {
Expand Down
3 changes: 1 addition & 2 deletions packages/parameters/src/ssm/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
export * from './SSMProvider';
export * from './getParameter';
export * from './getParameters';
export * from './getParametersByName';
export * from '../types/SSMProvider';
export * from './getParametersByName';
8 changes: 3 additions & 5 deletions packages/parameters/tests/e2e/dynamoDBProvider.class.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
* @group e2e/parameters/dynamodb/class
*/
import path from 'path';
import { Tracing } from 'aws-cdk-lib/aws-lambda';
import { AttributeType } from 'aws-cdk-lib/aws-dynamodb';
import { App, Stack, Aspects } from 'aws-cdk-lib';
import { DynamoDBClient, PutItemCommand } from '@aws-sdk/client-dynamodb';
Expand Down Expand Up @@ -138,10 +137,9 @@ describe(`parameters E2E tests (dynamoDBProvider) for runtime: ${runtime}`, () =
// Create a stack with a Lambda function
stack = createStackWithLambdaFunction({
app: integTestApp,
stackName: stackName,
functionName: functionName,
stackName,
functionName,
functionEntry: path.join(__dirname, lambdaFunctionCodeFile),
tracing: Tracing.ACTIVE,
environment: {
UUID: uuid,

Expand All @@ -154,7 +152,7 @@ describe(`parameters E2E tests (dynamoDBProvider) for runtime: ${runtime}`, () =
SORT_ATTR: sortAttr,
VALUE_ATTR: valueAttr,
},
runtime: runtime,
runtime,
});

// Create the DynamoDB tables
Expand Down
183 changes: 183 additions & 0 deletions packages/parameters/tests/e2e/ssmProvider.class.test.functionCode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import { Context } from 'aws-lambda';
import {
SSMProvider,
} from '../../src/ssm';
import {
SSMGetOptionsInterface,
SSMGetMultipleOptionsInterface,
SSMGetParametersByNameOptionsInterface
} from '../../src/types';
import { TinyLogger } from '../helpers/tinyLogger';
import { middleware } from '../helpers/sdkMiddlewareRequestCounter';
import { SSMClient } from '@aws-sdk/client-ssm';

// We use a custom logger to log pure JSON objects to stdout
const logger = new TinyLogger();

const defaultProvider = new SSMProvider();
// Provider test 8, 9
const customClient = new SSMClient({});
customClient.middlewareStack.use(middleware);
const providerWithMiddleware = new SSMProvider({
awsSdkV3Client: customClient
});

const paramA = process.env.PARAM_A ?? 'my-param';
const paramB = process.env.PARAM_B ?? 'my-param';
const paramEncryptedA = process.env.PARAM_ENCRYPTED_A ?? 'my-encrypted-param';
const paramEncryptedB = process.env.PARAM_ENCRYPTED_B ?? 'my-encrypted-param';

// Use provider specified, or default to main one & return it with cache cleared
const resolveProvider = (provider?: SSMProvider): SSMProvider => {
const resolvedProvider = provider ? provider : defaultProvider;
resolvedProvider.clearCache();

return resolvedProvider;
};

// Helper function to call get() and log the result
const _call_get = async (
paramName: string,
testName: string,
options?: SSMGetOptionsInterface,
provider?: SSMProvider
): Promise<void> => {
try {
const currentProvider = resolveProvider(provider);

const parameterValue = await currentProvider.get(paramName, options);
logger.log({
test: testName,
value: parameterValue
});
} catch (err) {
logger.log({
test: testName,
error: err.message
});
}
};

// Helper function to call getMultiple() and log the result
const _call_get_multiple = async (
paramPath: string,
testName: string,
options?: SSMGetMultipleOptionsInterface,
provider?: SSMProvider
): Promise<void> => {
try {
const currentProvider = resolveProvider(provider);

const parameterValues = await currentProvider.getMultiple(
paramPath,
options
);
logger.log({
test: testName,
value: parameterValues
});
} catch (err) {
logger.log({
test: testName,
error: err.message
});
}
};

// Helper function to call getParametersByName() and log the result
const _call_get_parameters_by_name = async (
params: Record<string, SSMGetParametersByNameOptionsInterface>,
testName: string,
options?: SSMGetParametersByNameOptionsInterface,
provider?: SSMProvider
): Promise<void> => {
try {
const currentProvider = resolveProvider(provider);

const parameterValues = await currentProvider.getParametersByName(params, options);
logger.log({
test: testName,
value: parameterValues
});
} catch (err) {
logger.log({
test: testName,
error: err.message
});
}
};

export const handler = async (_event: unknown, _context: Context): Promise<void> => {
// Test 1 - get a single parameter by name with default options
await _call_get(paramA, 'get');

// Test 2 - get a single parameter by name with decrypt
await _call_get(paramEncryptedA, 'get-decrypt', { decrypt: true });

// Test 3 - get multiple parameters by path with default options
// Get path (/param/get)
const parameterPath = paramA.substring(0, paramA.lastIndexOf('/'));
await _call_get_multiple(parameterPath, 'get-multiple');

// Test 4 - get multiple parameters by path recursively (aka. get all parameters under a path recursively)
// Get parameters root (i.e. from /param/get/a & /param/get/b to /param)
const parameterRoot = paramA.substring(
0,
paramA.substring(1, paramA.length).indexOf('/') + 1
);
await _call_get_multiple(parameterRoot, 'get-multiple-recursive', { recursive: true });

// Test 5 - get multiple parameters by path with decrypt
// Get parameters path (i.e. from /param/get/a & /param/get/b to /param/get)
const parameterPathDecrypt = paramEncryptedA.substring(0, paramEncryptedA.lastIndexOf('/'));
await _call_get_multiple(parameterPathDecrypt, 'get-multiple-decrypt', { decrypt: true });

// Test 6 - get multiple parameters by name with default options
await _call_get_parameters_by_name({
[paramA]: {},
[paramB]: {},
}, 'get-multiple-by-name');

// Test 7 - get multiple parameters by name, some of them encrypted and some not
await _call_get_parameters_by_name({
[paramA]: {},
[paramEncryptedA]: { decrypt: true },
[paramEncryptedB]: { decrypt: true },
}, 'get-multiple-by-name-mixed-decrypt');

// Test 8
// get parameter twice with middleware, which counts the number of requests, we check later if we only called SSM API once
try {
providerWithMiddleware.clearCache();
middleware.counter = 0;
await providerWithMiddleware.get(paramA);
await providerWithMiddleware.get(paramA);
logger.log({
test: 'get-cached',
value: middleware.counter // should be 1
});
} catch (err) {
logger.log({
test: 'get-cached',
error: err.message
});
}

// Test 9
// get parameter twice, but force fetch 2nd time, we count number of SDK requests and check that we made two API calls
try {
providerWithMiddleware.clearCache();
middleware.counter = 0;
await providerWithMiddleware.get(paramA);
await providerWithMiddleware.get(paramA, { forceFetch: true });
logger.log({
test: 'get-forced',
value: middleware.counter // should be 2
});
} catch (err) {
logger.log({
test: 'get-forced',
error: err.message
});
}
};
Loading