Skip to content
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

feat(parameters): ability to set maxAge and decrypt via environment variables #1384

Merged
Merged
Show file tree
Hide file tree
Changes from 2 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
2 changes: 2 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions packages/commons/src/config/ConfigService.ts
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Housekeeping

Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,25 @@ abstract class ConfigService {
*/
public abstract getServiceName(): string;

/**
* It returns the value of the _X_AMZN_TRACE_ID environment variable.
*
* The AWS X-Ray Trace data available in the environment variable has this format:
* `Root=1-5759e988-bd862e3fe1be46a994272793;Parent=557abcec3ee5a047;Sampled=1`,
*
* The actual Trace ID is: `1-5759e988-bd862e3fe1be46a994272793`.
*
* @returns {string|undefined}
*/
public abstract getXrayTraceId(): string | undefined;

/**
* It returns true if the string value represents a boolean true value.
*
* @param {string} value
* @returns boolean
*/
public abstract isValueTrue(value: string): boolean;
}

export {
Expand Down
12 changes: 12 additions & 0 deletions packages/commons/src/config/EnvironmentVariablesService.ts
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prior to this PR this method was used only in Logger. Now it's used also by Parameters so I am extracting it to the commons package to avoid duplication.

Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,18 @@ class EnvironmentVariablesService extends ConfigService {
return xRayTraceId.split(';')[0].replace('Root=', '');
}

/**
* It returns true if the string value represents a boolean true value.
*
* @param {string} value
* @returns boolean
*/
public isValueTrue(value: string): boolean {
const truthyValues: string[] = [ '1', 'y', 'yes', 't', 'true', 'on' ];

return truthyValues.includes(value.toLowerCase());
}

}

export {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,31 @@ describe('Class: EnvironmentVariablesService', () => {

});

describe('Method: isValueTrue', () => {

const valuesToTest: Array<Array<string | boolean>> = [
[ '1', true ],
[ 'y', true ],
[ 'yes', true ],
[ 't', true ],
[ 'TRUE', true ],
[ 'on', true ],
[ '', false ],
[ 'false', false ],
[ 'fasle', false ],
[ 'somethingsilly', false ],
[ '0', false ]
];

test.each(valuesToTest)('it takes string "%s" and returns %s', (input, output) => {
// Prepare
const service = new EnvironmentVariablesService();
// Act
const value = service.isValueTrue(input as string);
// Assess
expect(value).toBe(output);
});

});

});
12 changes: 0 additions & 12 deletions packages/logger/src/config/EnvironmentVariablesService.ts
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Original file line number Diff line number Diff line change
Expand Up @@ -117,18 +117,6 @@ class EnvironmentVariablesService extends CommonEnvironmentVariablesService impl
return this.isValueTrue(value);
}

/**
* It returns true if the string value represents a boolean true value.
*
* @param {string} value
* @returns boolean
*/
public isValueTrue(value: string): boolean {
const truthyValues: string[] = [ '1', 'y', 'yes', 't', 'true', 'on' ];

return truthyValues.includes(value.toLowerCase());
}

}

export {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,31 +249,4 @@ describe('Class: EnvironmentVariablesService', () => {

});

describe('Method: isValueTrue', () => {

const valuesToTest: Array<Array<string | boolean>> = [
[ '1', true ],
[ 'y', true ],
[ 'yes', true ],
[ 't', true ],
[ 'TRUE', true ],
[ 'on', true ],
[ '', false ],
[ 'false', false ],
[ 'fasle', false ],
[ 'somethingsilly', false ],
[ '0', false ]
];

test.each(valuesToTest)('it takes string "%s" and returns %s', (input, output) => {
// Prepare
const service = new EnvironmentVariablesService();
// Act
const value = service.isValueTrue(input as string);
// Assess
expect(value).toBe(output);
});

});

});
1 change: 1 addition & 0 deletions packages/parameters/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
"aws-sdk-client-mock-jest": "^2.0.1"
},
"dependencies": {
"@aws-lambda-powertools/commons": "^1.7.0",
"@aws-sdk/util-base64-node": "^3.209.0"
}
}
14 changes: 11 additions & 3 deletions packages/parameters/src/BaseProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@ import { GetMultipleOptions } from './GetMultipleOptions';
import { ExpirableValue } from './ExpirableValue';
import { TRANSFORM_METHOD_BINARY, TRANSFORM_METHOD_JSON } from './constants';
import { GetParameterError, TransformParameterError } from './Exceptions';
import type { BaseProviderInterface, GetMultipleOptionsInterface, GetOptionsInterface, TransformOptions } from './types';
import { EnvironmentVariablesService } from './config/EnvironmentVariablesService';
import type {
BaseProviderInterface,
GetMultipleOptionsInterface,
GetOptionsInterface,
TransformOptions
} from './types';

// These providers are dinamycally intialized on first use of the helper functions
const DEFAULT_PROVIDERS: Record<string, BaseProvider> = {};
Expand All @@ -29,10 +35,12 @@ const DEFAULT_PROVIDERS: Record<string, BaseProvider> = {};
* this should be an acceptable tradeoff.
*/
abstract class BaseProvider implements BaseProviderInterface {
public envVarsService: EnvironmentVariablesService;
protected store: Map<string, ExpirableValue>;

public constructor() {
this.store = new Map();
this.envVarsService = new EnvironmentVariablesService();
}

/**
Expand Down Expand Up @@ -62,7 +70,7 @@ abstract class BaseProvider implements BaseProviderInterface {
* @param {GetOptionsInterface} options - Options to configure maximum age, trasformation, AWS SDK options, or force fetch
*/
public async get(name: string, options?: GetOptionsInterface): Promise<undefined | string | Uint8Array | Record<string, unknown>> {
const configs = new GetOptions(options);
const configs = new GetOptions(options, this.envVarsService);
const key = [ name, configs.transform ].toString();

if (!configs.forceFetch && !this.hasKeyExpiredInCache(key)) {
Expand Down Expand Up @@ -97,7 +105,7 @@ abstract class BaseProvider implements BaseProviderInterface {
* @returns
*/
public async getMultiple(path: string, options?: GetMultipleOptionsInterface): Promise<undefined | Record<string, unknown>> {
const configs = new GetMultipleOptions(options || {});
const configs = new GetMultipleOptions(options, this.envVarsService);
const key = [ path, configs.transform ].toString();

if (!configs.forceFetch && !this.hasKeyExpiredInCache(key)) {
Expand Down
21 changes: 11 additions & 10 deletions packages/parameters/src/GetMultipleOptions.ts
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was a lot of duplication with the GetOptions class here, so opted for inheritance.

Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import { DEFAULT_MAX_AGE_SECS } from './constants';
import type { GetMultipleOptionsInterface, TransformOptions } from './types';
import { GetOptions } from './GetOptions';
import { EnvironmentVariablesService } from './config/EnvironmentVariablesService';
import type { GetMultipleOptionsInterface } from './types';

/**
* Options for the `getMultiple` method.
*
* It merges the default options with the provided options.
* Extends the `GetOptions` class and adds the `throwOnTransformError` option.
*/
class GetMultipleOptions implements GetMultipleOptionsInterface {
public forceFetch: boolean = false;
public maxAge: number = DEFAULT_MAX_AGE_SECS;
public sdkOptions?: unknown;
class GetMultipleOptions extends GetOptions implements GetMultipleOptionsInterface {
public throwOnTransformError: boolean = false;
public transform?: TransformOptions;

public constructor(options: GetMultipleOptionsInterface) {
Object.assign(this, options);
public constructor(options: GetMultipleOptionsInterface = {}, envVarsService: EnvironmentVariablesService) {
super(options, envVarsService);

if (options.throwOnTransformError !== undefined) {
this.throwOnTransformError = options.throwOnTransformError;
}
}
}

Expand Down
9 changes: 7 additions & 2 deletions packages/parameters/src/GetOptions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { DEFAULT_MAX_AGE_SECS } from './constants';
import { EnvironmentVariablesService } from './config/EnvironmentVariablesService';
import type { GetOptionsInterface, TransformOptions } from './types';

/**
Expand All @@ -8,12 +9,16 @@ import type { GetOptionsInterface, TransformOptions } from './types';
*/
class GetOptions implements GetOptionsInterface {
public forceFetch: boolean = false;
public maxAge: number = DEFAULT_MAX_AGE_SECS;
public maxAge!: number;
public sdkOptions?: unknown;
public transform?: TransformOptions;

public constructor(options: GetOptionsInterface = {}) {
public constructor(options: GetOptionsInterface = {}, envVarsService: EnvironmentVariablesService) {
Object.assign(this, options);

if (options.maxAge === undefined) {
this.maxAge = envVarsService.getParametersMaxAge() ?? DEFAULT_MAX_AGE_SECS;
}
}
}

Expand Down
9 changes: 4 additions & 5 deletions packages/parameters/src/appconfig/AppConfigProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,8 @@ import type {
*/
class AppConfigProvider extends BaseProvider {
public client: AppConfigDataClient;
protected configurationTokenStore: Map<string, string> = new Map();
protected valueStore: Map<string, Uint8Array> = new Map();
protected configurationTokenStore = new Map<string, string>();
protected valueStore = new Map<string, Uint8Array>();
private application?: string;
private environment: string;

Expand All @@ -187,13 +187,12 @@ class AppConfigProvider extends BaseProvider {
this.client = new AppConfigDataClient(options.clientConfig || {});
}

if (!options?.application && !process.env['POWERTOOLS_SERVICE_NAME']) {
this.application = options?.application || this.envVarsService.getServiceName();
if (!this.application || this.application.trim().length === 0) {
throw new Error(
'Application name is not defined or POWERTOOLS_SERVICE_NAME is not set'
);
}
this.application =
options.application || process.env['POWERTOOLS_SERVICE_NAME'];
this.environment = options.environment;
}

Expand Down
15 changes: 15 additions & 0 deletions packages/parameters/src/config/ConfigServiceInterface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
interface ConfigServiceInterface {

get?(name: string): string

getServiceName(): string

getParametersMaxAge(): number | undefined

getSSMDecrypt(): string

}

export {
ConfigServiceInterface,
};
34 changes: 34 additions & 0 deletions packages/parameters/src/config/EnvironmentVariablesService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { ConfigServiceInterface } from './ConfigServiceInterface';
import { DEFAULT_MAX_AGE_SECS } from '../constants';
import { EnvironmentVariablesService as CommonEnvironmentVariablesService } from '@aws-lambda-powertools/commons';

class EnvironmentVariablesService extends CommonEnvironmentVariablesService implements ConfigServiceInterface {

// Environment variables
private parametersMaxAgeVariable = 'POWERTOOLS_PARAMETERS_MAX_AGE';
private ssmDecryptVariable = 'POWERTOOLS_PARAMETERS_SSM_DECRYPT';

public getParametersMaxAge(): number | undefined {
const maxAge = this.get(this.parametersMaxAgeVariable);

if (maxAge.length === 0) return undefined;

const maxAgeAsNumber = parseInt(maxAge, 10);
if (isNaN(maxAgeAsNumber)) {
console.warn(
`Invalid value for ${this.parametersMaxAgeVariable} environment variable: [${maxAge}], using default value of ${DEFAULT_MAX_AGE_SECS} seconds`
);
} else {
return maxAgeAsNumber;
}
}

public getSSMDecrypt(): string {
return this.get(this.ssmDecryptVariable);
}

}

export {
EnvironmentVariablesService,
};
21 changes: 16 additions & 5 deletions packages/parameters/src/ssm/SSMProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ class SSMProvider extends BaseProvider {
options?: SSMGetParametersByNameOptionsInterface
): Promise<Record<string, unknown>> {
const configs = { ...{
decrypt: false,
decrypt: this.resolveDecryptionConfigValue({}) || false,
maxAge: DEFAULT_MAX_AGE_SECS,
throwOnError: true,
}, ...options };
Expand Down Expand Up @@ -477,8 +477,7 @@ class SSMProvider extends BaseProvider {
...(options?.sdkOptions || {}),
Name: name,
};
sdkOptions.WithDecryption = options?.decrypt !== undefined ?
options.decrypt : sdkOptions.WithDecryption;
sdkOptions.WithDecryption = this.resolveDecryptionConfigValue(options, sdkOptions);
const result = await this.client.send(new GetParameterCommand(sdkOptions));

return result.Parameter?.Value;
Expand All @@ -501,8 +500,7 @@ class SSMProvider extends BaseProvider {
const paginationOptions: PaginationConfiguration = {
client: this.client
};
sdkOptions.WithDecryption = options?.decrypt !== undefined ?
options.decrypt : sdkOptions.WithDecryption;
sdkOptions.WithDecryption = this.resolveDecryptionConfigValue(options, sdkOptions);
sdkOptions.Recursive = options?.recursive !== undefined ?
options.recursive : sdkOptions.Recursive;
paginationOptions.pageSize = sdkOptions.MaxResults !== undefined ?
Expand Down Expand Up @@ -734,6 +732,19 @@ class SSMProvider extends BaseProvider {
return errors;
}

protected resolveDecryptionConfigValue(
options: SSMGetOptionsInterface | SSMGetMultipleOptionsInterface = {},
sdkOptions?: GetParameterCommandInput | GetParametersByPathCommandInput
): boolean | undefined {
if (options?.decrypt !== undefined) return options.decrypt;
if (sdkOptions?.WithDecryption !== undefined) return sdkOptions.WithDecryption;
if (this.envVarsService.getSSMDecrypt() !== '') {
return this.envVarsService.isValueTrue(this.envVarsService.getSSMDecrypt());
}

return undefined;
}

/**
* Split parameters that can be fetched by GetParameters vs GetParameter.
*
Expand Down
Loading