Skip to content

Commit dcf6620

Browse files
authored
feat(parameters): ability to set maxAge and decrypt via environment variables (#1384)
* feat: add env variables config for SSM/Parameters * tests: added unit tests * docs: updated docs with new env vars
1 parent 40a1a24 commit dcf6620

20 files changed

+308
-86
lines changed

Diff for: docs/index.md

+16-14
Original file line numberDiff line numberDiff line change
@@ -293,20 +293,22 @@ Core utilities such as Tracing, Logging, and Metrics will be available across al
293293
???+ info
294294
Explicit parameters take precedence over environment variables
295295

296-
| Environment variable | Description | Utility | Default |
297-
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | ------------------------- | ------------------- |
298-
| **POWERTOOLS_SERVICE_NAME** | Sets service name used for tracing namespace, metrics dimension and structured logging | All | `service_undefined` |
299-
| **POWERTOOLS_METRICS_NAMESPACE** | Sets namespace used for metrics | [Metrics](./core/metrics) | `default_namespace` |
300-
| **POWERTOOLS_TRACE_ENABLED** | Explicitly disables tracing | [Tracer](./core/tracer) | `true` |
301-
| **POWERTOOLS_TRACER_CAPTURE_RESPONSE** | Captures Lambda or method return as metadata. | [Tracer](./core/tracer) | `true` |
302-
| **POWERTOOLS_TRACER_CAPTURE_ERROR** | Captures Lambda or method exception as metadata. | [Tracer](./core/tracer) | `true` |
303-
| **POWERTOOLS_TRACER_CAPTURE_HTTPS_REQUESTS** | Captures HTTP(s) requests as segments. | [Tracer](./core/tracer) | `true` |
304-
| **POWERTOOLS_LOGGER_LOG_EVENT** | Logs incoming event | [Logger](./core/logger) | `false` |
305-
| **POWERTOOLS_LOGGER_SAMPLE_RATE** | Debug log sampling | [Logger](./core/logger) | `0` |
306-
| **POWERTOOLS_DEV** | Increase JSON indentation to ease debugging when running functions locally or in a non-production environment | [Logger](./core/logger) | `false` |
307-
| **LOG_LEVEL** | Sets logging level | [Logger](./core/logger) | `INFO` |
308-
309-
Each Utility page provides information on example values and allowed values
296+
| Environment variable | Description | Utility | Default |
297+
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | ------------------------------------ | ------------------- |
298+
| **POWERTOOLS_SERVICE_NAME** | Sets service name used for tracing namespace, metrics dimension and structured logging | All | `service_undefined` |
299+
| **POWERTOOLS_METRICS_NAMESPACE** | Sets namespace used for metrics | [Metrics](./core/metrics) | `default_namespace` |
300+
| **POWERTOOLS_TRACE_ENABLED** | Explicitly disables tracing | [Tracer](./core/tracer) | `true` |
301+
| **POWERTOOLS_TRACER_CAPTURE_RESPONSE** | Captures Lambda or method return as metadata. | [Tracer](./core/tracer) | `true` |
302+
| **POWERTOOLS_TRACER_CAPTURE_ERROR** | Captures Lambda or method exception as metadata. | [Tracer](./core/tracer) | `true` |
303+
| **POWERTOOLS_TRACER_CAPTURE_HTTPS_REQUESTS** | Captures HTTP(s) requests as segments. | [Tracer](./core/tracer) | `true` |
304+
| **POWERTOOLS_LOGGER_LOG_EVENT** | Logs incoming event | [Logger](./core/logger) | `false` |
305+
| **POWERTOOLS_LOGGER_SAMPLE_RATE** | Debug log sampling | [Logger](./core/logger) | `0` |
306+
| **POWERTOOLS_DEV** | Increase JSON indentation to ease debugging when running functions locally or in a non-production environment | [Logger](./core/logger) | `false` |
307+
| **LOG_LEVEL** | Sets logging level | [Logger](./core/logger) | `INFO` |
308+
| **POWERTOOLS_PARAMETERS_MAX_AGE** | Adjust how long values are kept in cache (in seconds) | [Parameters](./utilities/parameters) | `5` |
309+
| **POWERTOOLS_PARAMETERS_SSM_DECRYPT** | Sets whether to decrypt or not values retrieved from AWS Systems Manager Parameters Store | [Parameters](./utilities/parameters) | `false` |
310+
311+
Each Utility page provides information on example values and allowed values.
310312

311313
## Tenets
312314

Diff for: docs/snippets/parameters/adjustingCacheTTL.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ import { SSMProvider } from '@aws-lambda-powertools/parameters/ssm';
33
const parametersProvider = new SSMProvider();
44

55
export const handler = async (): Promise<void> => {
6-
// Retrieve a single parameter
7-
const parameter = await parametersProvider.get('/my/parameter', { maxAge: 60 }); // 1 minute
6+
// Retrieve a single parameter and cache it for 1 minute
7+
const parameter = await parametersProvider.get('/my/parameter', { maxAge: 60 }); // (1)
88
console.log(parameter);
99

10-
// Retrieve multiple parameters from a path prefix
11-
const parameters = await parametersProvider.getMultiple('/my/path/prefix', { maxAge: 120 }); // 2 minutes
10+
// Retrieve multiple parameters from a path prefix and cache them for 2 minutes
11+
const parameters = await parametersProvider.getMultiple('/my/path/prefix', { maxAge: 120 });
1212
for (const [ key, value ] of Object.entries(parameters || {})) {
1313
console.log(`${key}: ${value}`);
1414
}

Diff for: docs/snippets/parameters/ssmProviderDecryptAndRecursive.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { SSMProvider } from '@aws-lambda-powertools/parameters/ssm';
33
const parametersProvider = new SSMProvider();
44

55
export const handler = async (): Promise<void> => {
6-
const decryptedValue = await parametersProvider.get('/my/encrypted/parameter', { decrypt: true });
6+
const decryptedValue = await parametersProvider.get('/my/encrypted/parameter', { decrypt: true }); // (1)
77
console.log(decryptedValue);
88

99
const noRecursiveValues = await parametersProvider.getMultiple('/my/path/prefix', { recursive: false });

Diff for: docs/utilities/parameters.md

+13-3
Original file line numberDiff line numberDiff line change
@@ -127,17 +127,22 @@ The following will retrieve the latest version and store it in the cache.
127127

128128
### Adjusting cache TTL
129129

130-
???+ tip
131-
`maxAge` parameter is also available in high level functions like `getParameter`, `getSecret`, etc.
132-
133130
By default, the provider will cache parameters retrieved in-memory for 5 seconds.
134131

135132
You can adjust how long values should be kept in cache by using the param `maxAge`, when using `get()` or `getMultiple()` methods across all providers.
136133

134+
???+ tip
135+
If you want to set the same TTL for all parameters, you can set the `POWERTOOLS_PARAMETERS_MAX_AGE` environment variable. **This will override the default TTL of 5 seconds but can be overridden by the `maxAge` parameter**.
136+
137137
```typescript hl_lines="7 11" title="Caching parameters values in memory for longer than 5 seconds"
138138
--8<-- "docs/snippets/parameters/adjustingCacheTTL.ts"
139139
```
140140

141+
1. Options passed to `get()`, `getMultiple()`, and `getParametersByName()` will override the values set in `POWERTOOLS_PARAMETERS_MAX_AGE` environment variable.
142+
143+
???+ info
144+
The `maxAge` parameter is also available in high level functions like `getParameter`, `getSecret`, etc.
145+
141146
### Always fetching the latest
142147

143148
If you'd like to always ensure you fetch the latest parameter from the store regardless if already available in cache, use the `forceFetch` parameter.
@@ -166,10 +171,15 @@ The AWS Systems Manager Parameter Store provider supports two additional argumen
166171
| **decrypt** | `false` | Will automatically decrypt the parameter (see required [IAM Permissions](#iam-permissions)). |
167172
| **recursive** | `true` | For `getMultiple()` only, will fetch all parameter values recursively based on a path prefix. |
168173

174+
???+ tip
175+
If you want to always decrypt parameters, you can set the `POWERTOOLS_PARAMETERS_SSM_DECRYPT=true` environment variable. **This will override the default value of `false` but can be overridden by the `decrypt` parameter**.
176+
169177
```typescript hl_lines="6 9" title="Example with get() and getMultiple()"
170178
--8<-- "docs/snippets/parameters/ssmProviderDecryptAndRecursive.ts"
171179
```
172180

181+
1. Options passed to `get()`, `getMultiple()`, and `getParametersByName()` will override the values set in `POWERTOOLS_PARAMETERS_SSM_DECRYPT` environment variable.
182+
173183
#### SecretsProvider
174184

175185
```typescript hl_lines="4-5" title="Example with SecretsProvider for further extensibility"

Diff for: package-lock.json

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: packages/commons/src/config/ConfigService.ts

+19
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,25 @@ abstract class ConfigService {
2424
*/
2525
public abstract getServiceName(): string;
2626

27+
/**
28+
* It returns the value of the _X_AMZN_TRACE_ID environment variable.
29+
*
30+
* The AWS X-Ray Trace data available in the environment variable has this format:
31+
* `Root=1-5759e988-bd862e3fe1be46a994272793;Parent=557abcec3ee5a047;Sampled=1`,
32+
*
33+
* The actual Trace ID is: `1-5759e988-bd862e3fe1be46a994272793`.
34+
*
35+
* @returns {string|undefined}
36+
*/
37+
public abstract getXrayTraceId(): string | undefined;
38+
39+
/**
40+
* It returns true if the string value represents a boolean true value.
41+
*
42+
* @param {string} value
43+
* @returns boolean
44+
*/
45+
public abstract isValueTrue(value: string): boolean;
2746
}
2847

2948
export {

Diff for: packages/commons/src/config/EnvironmentVariablesService.ts

+12
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,18 @@ class EnvironmentVariablesService extends ConfigService {
6060
return xRayTraceId.split(';')[0].replace('Root=', '');
6161
}
6262

63+
/**
64+
* It returns true if the string value represents a boolean true value.
65+
*
66+
* @param {string} value
67+
* @returns boolean
68+
*/
69+
public isValueTrue(value: string): boolean {
70+
const truthyValues: string[] = [ '1', 'y', 'yes', 't', 'true', 'on' ];
71+
72+
return truthyValues.includes(value.toLowerCase());
73+
}
74+
6375
}
6476

6577
export {

Diff for: packages/commons/tests/unit/config/EnvironmentVariablesService.test.ts

+27
Original file line numberDiff line numberDiff line change
@@ -110,4 +110,31 @@ describe('Class: EnvironmentVariablesService', () => {
110110

111111
});
112112

113+
describe('Method: isValueTrue', () => {
114+
115+
const valuesToTest: Array<Array<string | boolean>> = [
116+
[ '1', true ],
117+
[ 'y', true ],
118+
[ 'yes', true ],
119+
[ 't', true ],
120+
[ 'TRUE', true ],
121+
[ 'on', true ],
122+
[ '', false ],
123+
[ 'false', false ],
124+
[ 'fasle', false ],
125+
[ 'somethingsilly', false ],
126+
[ '0', false ]
127+
];
128+
129+
test.each(valuesToTest)('it takes string "%s" and returns %s', (input, output) => {
130+
// Prepare
131+
const service = new EnvironmentVariablesService();
132+
// Act
133+
const value = service.isValueTrue(input as string);
134+
// Assess
135+
expect(value).toBe(output);
136+
});
137+
138+
});
139+
113140
});

Diff for: packages/logger/src/config/EnvironmentVariablesService.ts

-12
Original file line numberDiff line numberDiff line change
@@ -117,18 +117,6 @@ class EnvironmentVariablesService extends CommonEnvironmentVariablesService impl
117117
return this.isValueTrue(value);
118118
}
119119

120-
/**
121-
* It returns true if the string value represents a boolean true value.
122-
*
123-
* @param {string} value
124-
* @returns boolean
125-
*/
126-
public isValueTrue(value: string): boolean {
127-
const truthyValues: string[] = [ '1', 'y', 'yes', 't', 'true', 'on' ];
128-
129-
return truthyValues.includes(value.toLowerCase());
130-
}
131-
132120
}
133121

134122
export {

Diff for: packages/logger/tests/unit/config/EnvironmentVariablesService.test.ts

-27
Original file line numberDiff line numberDiff line change
@@ -249,31 +249,4 @@ describe('Class: EnvironmentVariablesService', () => {
249249

250250
});
251251

252-
describe('Method: isValueTrue', () => {
253-
254-
const valuesToTest: Array<Array<string | boolean>> = [
255-
[ '1', true ],
256-
[ 'y', true ],
257-
[ 'yes', true ],
258-
[ 't', true ],
259-
[ 'TRUE', true ],
260-
[ 'on', true ],
261-
[ '', false ],
262-
[ 'false', false ],
263-
[ 'fasle', false ],
264-
[ 'somethingsilly', false ],
265-
[ '0', false ]
266-
];
267-
268-
test.each(valuesToTest)('it takes string "%s" and returns %s', (input, output) => {
269-
// Prepare
270-
const service = new EnvironmentVariablesService();
271-
// Act
272-
const value = service.isValueTrue(input as string);
273-
// Assess
274-
expect(value).toBe(output);
275-
});
276-
277-
});
278-
279252
});

Diff for: packages/parameters/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
"aws-sdk-client-mock-jest": "^2.0.1"
6666
},
6767
"dependencies": {
68+
"@aws-lambda-powertools/commons": "^1.7.0",
6869
"@aws-sdk/util-base64-node": "^3.209.0"
6970
}
7071
}

Diff for: packages/parameters/src/BaseProvider.ts

+11-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@ import { GetMultipleOptions } from './GetMultipleOptions';
44
import { ExpirableValue } from './ExpirableValue';
55
import { TRANSFORM_METHOD_BINARY, TRANSFORM_METHOD_JSON } from './constants';
66
import { GetParameterError, TransformParameterError } from './Exceptions';
7-
import type { BaseProviderInterface, GetMultipleOptionsInterface, GetOptionsInterface, TransformOptions } from './types';
7+
import { EnvironmentVariablesService } from './config/EnvironmentVariablesService';
8+
import type {
9+
BaseProviderInterface,
10+
GetMultipleOptionsInterface,
11+
GetOptionsInterface,
12+
TransformOptions
13+
} from './types';
814

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

3441
public constructor() {
3542
this.store = new Map();
43+
this.envVarsService = new EnvironmentVariablesService();
3644
}
3745

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

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

103111
if (!configs.forceFetch && !this.hasKeyExpiredInCache(key)) {

Diff for: packages/parameters/src/GetMultipleOptions.ts

+11-10
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
1-
import { DEFAULT_MAX_AGE_SECS } from './constants';
2-
import type { GetMultipleOptionsInterface, TransformOptions } from './types';
1+
import { GetOptions } from './GetOptions';
2+
import { EnvironmentVariablesService } from './config/EnvironmentVariablesService';
3+
import type { GetMultipleOptionsInterface } from './types';
34

45
/**
56
* Options for the `getMultiple` method.
67
*
7-
* It merges the default options with the provided options.
8+
* Extends the `GetOptions` class and adds the `throwOnTransformError` option.
89
*/
9-
class GetMultipleOptions implements GetMultipleOptionsInterface {
10-
public forceFetch: boolean = false;
11-
public maxAge: number = DEFAULT_MAX_AGE_SECS;
12-
public sdkOptions?: unknown;
10+
class GetMultipleOptions extends GetOptions implements GetMultipleOptionsInterface {
1311
public throwOnTransformError: boolean = false;
14-
public transform?: TransformOptions;
1512

16-
public constructor(options: GetMultipleOptionsInterface) {
17-
Object.assign(this, options);
13+
public constructor(options: GetMultipleOptionsInterface = {}, envVarsService: EnvironmentVariablesService) {
14+
super(options, envVarsService);
15+
16+
if (options.throwOnTransformError !== undefined) {
17+
this.throwOnTransformError = options.throwOnTransformError;
18+
}
1819
}
1920
}
2021

Diff for: packages/parameters/src/GetOptions.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { DEFAULT_MAX_AGE_SECS } from './constants';
2+
import { EnvironmentVariablesService } from './config/EnvironmentVariablesService';
23
import type { GetOptionsInterface, TransformOptions } from './types';
34

45
/**
@@ -8,12 +9,16 @@ import type { GetOptionsInterface, TransformOptions } from './types';
89
*/
910
class GetOptions implements GetOptionsInterface {
1011
public forceFetch: boolean = false;
11-
public maxAge: number = DEFAULT_MAX_AGE_SECS;
12+
public maxAge!: number;
1213
public sdkOptions?: unknown;
1314
public transform?: TransformOptions;
1415

15-
public constructor(options: GetOptionsInterface = {}) {
16+
public constructor(options: GetOptionsInterface = {}, envVarsService: EnvironmentVariablesService) {
1617
Object.assign(this, options);
18+
19+
if (options.maxAge === undefined) {
20+
this.maxAge = envVarsService.getParametersMaxAge() ?? DEFAULT_MAX_AGE_SECS;
21+
}
1722
}
1823
}
1924

Diff for: packages/parameters/src/appconfig/AppConfigProvider.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,8 @@ import type {
165165
*/
166166
class AppConfigProvider extends BaseProvider {
167167
public client: AppConfigDataClient;
168-
protected configurationTokenStore: Map<string, string> = new Map();
169-
protected valueStore: Map<string, Uint8Array> = new Map();
168+
protected configurationTokenStore = new Map<string, string>();
169+
protected valueStore = new Map<string, Uint8Array>();
170170
private application?: string;
171171
private environment: string;
172172

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

190-
if (!options?.application && !process.env['POWERTOOLS_SERVICE_NAME']) {
190+
this.application = options?.application || this.envVarsService.getServiceName();
191+
if (!this.application || this.application.trim().length === 0) {
191192
throw new Error(
192193
'Application name is not defined or POWERTOOLS_SERVICE_NAME is not set'
193194
);
194195
}
195-
this.application =
196-
options.application || process.env['POWERTOOLS_SERVICE_NAME'];
197196
this.environment = options.environment;
198197
}
199198

0 commit comments

Comments
 (0)