@dyanet/config-aws 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +195 -0
- package/dist/cjs/config-manager.js +360 -0
- package/dist/cjs/config-manager.js.map +1 -0
- package/dist/cjs/errors/index.js +65 -0
- package/dist/cjs/errors/index.js.map +1 -0
- package/dist/cjs/index.js +37 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/interfaces/config-loader.interface.js +3 -0
- package/dist/cjs/interfaces/config-loader.interface.js.map +1 -0
- package/dist/cjs/interfaces/config-manager.interface.js +3 -0
- package/dist/cjs/interfaces/config-manager.interface.js.map +1 -0
- package/dist/cjs/interfaces/env-file-loader.interface.js +3 -0
- package/dist/cjs/interfaces/env-file-loader.interface.js.map +1 -0
- package/dist/cjs/interfaces/environment-loader.interface.js +3 -0
- package/dist/cjs/interfaces/environment-loader.interface.js.map +1 -0
- package/dist/cjs/interfaces/index.js +3 -0
- package/dist/cjs/interfaces/index.js.map +1 -0
- package/dist/cjs/interfaces/s3-loader.interface.js +3 -0
- package/dist/cjs/interfaces/s3-loader.interface.js.map +1 -0
- package/dist/cjs/interfaces/secrets-manager-loader.interface.js +3 -0
- package/dist/cjs/interfaces/secrets-manager-loader.interface.js.map +1 -0
- package/dist/cjs/interfaces/ssm-parameter-store-loader.interface.js +3 -0
- package/dist/cjs/interfaces/ssm-parameter-store-loader.interface.js.map +1 -0
- package/dist/cjs/loaders/env-file.loader.js +167 -0
- package/dist/cjs/loaders/env-file.loader.js.map +1 -0
- package/dist/cjs/loaders/environment.loader.js +83 -0
- package/dist/cjs/loaders/environment.loader.js.map +1 -0
- package/dist/cjs/loaders/index.js +14 -0
- package/dist/cjs/loaders/index.js.map +1 -0
- package/dist/cjs/loaders/s3.loader.js +141 -0
- package/dist/cjs/loaders/s3.loader.js.map +1 -0
- package/dist/cjs/loaders/secrets-manager.loader.js +156 -0
- package/dist/cjs/loaders/secrets-manager.loader.js.map +1 -0
- package/dist/cjs/loaders/ssm-parameter-store.loader.js +193 -0
- package/dist/cjs/loaders/ssm-parameter-store.loader.js.map +1 -0
- package/dist/cjs/utils/env-file-parser.util.js +98 -0
- package/dist/cjs/utils/env-file-parser.util.js.map +1 -0
- package/dist/cjs/utils/index.js +8 -0
- package/dist/cjs/utils/index.js.map +1 -0
- package/dist/cjs/utils/validation.util.js +116 -0
- package/dist/cjs/utils/validation.util.js.map +1 -0
- package/dist/esm/config-manager.js +356 -0
- package/dist/esm/config-manager.js.map +1 -0
- package/dist/esm/errors/index.js +57 -0
- package/dist/esm/errors/index.js.map +1 -0
- package/dist/esm/index.js +21 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/interfaces/config-loader.interface.js +2 -0
- package/dist/esm/interfaces/config-loader.interface.js.map +1 -0
- package/dist/esm/interfaces/config-manager.interface.js +2 -0
- package/dist/esm/interfaces/config-manager.interface.js.map +1 -0
- package/dist/esm/interfaces/env-file-loader.interface.js +2 -0
- package/dist/esm/interfaces/env-file-loader.interface.js.map +1 -0
- package/dist/esm/interfaces/environment-loader.interface.js +2 -0
- package/dist/esm/interfaces/environment-loader.interface.js.map +1 -0
- package/dist/esm/interfaces/index.js +2 -0
- package/dist/esm/interfaces/index.js.map +1 -0
- package/dist/esm/interfaces/s3-loader.interface.js +2 -0
- package/dist/esm/interfaces/s3-loader.interface.js.map +1 -0
- package/dist/esm/interfaces/secrets-manager-loader.interface.js +2 -0
- package/dist/esm/interfaces/secrets-manager-loader.interface.js.map +1 -0
- package/dist/esm/interfaces/ssm-parameter-store-loader.interface.js +2 -0
- package/dist/esm/interfaces/ssm-parameter-store-loader.interface.js.map +1 -0
- package/dist/esm/loaders/env-file.loader.js +130 -0
- package/dist/esm/loaders/env-file.loader.js.map +1 -0
- package/dist/esm/loaders/environment.loader.js +79 -0
- package/dist/esm/loaders/environment.loader.js.map +1 -0
- package/dist/esm/loaders/index.js +6 -0
- package/dist/esm/loaders/index.js.map +1 -0
- package/dist/esm/loaders/s3.loader.js +137 -0
- package/dist/esm/loaders/s3.loader.js.map +1 -0
- package/dist/esm/loaders/secrets-manager.loader.js +152 -0
- package/dist/esm/loaders/secrets-manager.loader.js.map +1 -0
- package/dist/esm/loaders/ssm-parameter-store.loader.js +189 -0
- package/dist/esm/loaders/ssm-parameter-store.loader.js.map +1 -0
- package/dist/esm/utils/env-file-parser.util.js +94 -0
- package/dist/esm/utils/env-file-parser.util.js.map +1 -0
- package/dist/esm/utils/index.js +3 -0
- package/dist/esm/utils/index.js.map +1 -0
- package/dist/esm/utils/validation.util.js +112 -0
- package/dist/esm/utils/validation.util.js.map +1 -0
- package/dist/types/config-manager.d.ts +119 -0
- package/dist/types/config-manager.d.ts.map +1 -0
- package/dist/types/errors/index.d.ts +43 -0
- package/dist/types/errors/index.d.ts.map +1 -0
- package/dist/types/index.d.ts +24 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/interfaces/config-loader.interface.d.ts +33 -0
- package/dist/types/interfaces/config-loader.interface.d.ts.map +1 -0
- package/dist/types/interfaces/config-manager.interface.d.ts +86 -0
- package/dist/types/interfaces/config-manager.interface.d.ts.map +1 -0
- package/dist/types/interfaces/env-file-loader.interface.d.ts +12 -0
- package/dist/types/interfaces/env-file-loader.interface.d.ts.map +1 -0
- package/dist/types/interfaces/environment-loader.interface.d.ts +10 -0
- package/dist/types/interfaces/environment-loader.interface.d.ts.map +1 -0
- package/dist/types/interfaces/index.d.ts +8 -0
- package/dist/types/interfaces/index.d.ts.map +1 -0
- package/dist/types/interfaces/s3-loader.interface.d.ts +14 -0
- package/dist/types/interfaces/s3-loader.interface.d.ts.map +1 -0
- package/dist/types/interfaces/secrets-manager-loader.interface.d.ts +12 -0
- package/dist/types/interfaces/secrets-manager-loader.interface.d.ts.map +1 -0
- package/dist/types/interfaces/ssm-parameter-store-loader.interface.d.ts +14 -0
- package/dist/types/interfaces/ssm-parameter-store-loader.interface.d.ts.map +1 -0
- package/dist/types/loaders/env-file.loader.d.ts +69 -0
- package/dist/types/loaders/env-file.loader.d.ts.map +1 -0
- package/dist/types/loaders/environment.loader.d.ts +46 -0
- package/dist/types/loaders/environment.loader.d.ts.map +1 -0
- package/dist/types/loaders/index.d.ts +6 -0
- package/dist/types/loaders/index.d.ts.map +1 -0
- package/dist/types/loaders/s3.loader.d.ts +62 -0
- package/dist/types/loaders/s3.loader.d.ts.map +1 -0
- package/dist/types/loaders/secrets-manager.loader.d.ts +68 -0
- package/dist/types/loaders/secrets-manager.loader.d.ts.map +1 -0
- package/dist/types/loaders/ssm-parameter-store.loader.d.ts +78 -0
- package/dist/types/loaders/ssm-parameter-store.loader.d.ts.map +1 -0
- package/dist/types/utils/env-file-parser.util.d.ts +45 -0
- package/dist/types/utils/env-file-parser.util.d.ts.map +1 -0
- package/dist/types/utils/index.d.ts +3 -0
- package/dist/types/utils/index.d.ts.map +1 -0
- package/dist/types/utils/validation.util.d.ts +53 -0
- package/dist/types/utils/validation.util.d.ts.map +1 -0
- package/package.json +97 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loader that reads configuration from process.env.
|
|
3
|
+
* Supports prefix filtering and exclusion lists.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```typescript
|
|
7
|
+
* // Load all environment variables
|
|
8
|
+
* const loader = new EnvironmentLoader();
|
|
9
|
+
*
|
|
10
|
+
* // Load only variables starting with 'APP_', stripping the prefix
|
|
11
|
+
* const loader = new EnvironmentLoader({ prefix: 'APP_' });
|
|
12
|
+
*
|
|
13
|
+
* // Load all except specific variables
|
|
14
|
+
* const loader = new EnvironmentLoader({ exclude: ['PATH', 'HOME'] });
|
|
15
|
+
*
|
|
16
|
+
* // Combine prefix and exclusion
|
|
17
|
+
* const loader = new EnvironmentLoader({
|
|
18
|
+
* prefix: 'APP_',
|
|
19
|
+
* exclude: ['APP_DEBUG']
|
|
20
|
+
* });
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export class EnvironmentLoader {
|
|
24
|
+
constructor(config = {}) {
|
|
25
|
+
this._config = config;
|
|
26
|
+
}
|
|
27
|
+
getName() {
|
|
28
|
+
return 'EnvironmentLoader';
|
|
29
|
+
}
|
|
30
|
+
async isAvailable() {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Load configuration from process.env.
|
|
35
|
+
*
|
|
36
|
+
* When a prefix is specified:
|
|
37
|
+
* - Only variables starting with the prefix are included
|
|
38
|
+
* - The prefix is stripped from the resulting key names
|
|
39
|
+
*
|
|
40
|
+
* When an exclusion list is specified:
|
|
41
|
+
* - Variables in the list are excluded from the result
|
|
42
|
+
* - Exclusion is checked against the original key (before prefix stripping)
|
|
43
|
+
*
|
|
44
|
+
* @returns Promise resolving to the loaded configuration
|
|
45
|
+
*/
|
|
46
|
+
async load() {
|
|
47
|
+
const result = {};
|
|
48
|
+
const { prefix, exclude = [] } = this._config;
|
|
49
|
+
const excludeSet = new Set(exclude);
|
|
50
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
51
|
+
// Skip undefined values
|
|
52
|
+
if (value === undefined) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
// Check exclusion list (against original key)
|
|
56
|
+
if (excludeSet.has(key)) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
// Handle prefix filtering
|
|
60
|
+
if (prefix) {
|
|
61
|
+
if (key.startsWith(prefix)) {
|
|
62
|
+
// Strip prefix from key
|
|
63
|
+
const strippedKey = key.slice(prefix.length);
|
|
64
|
+
// Only include if there's a key remaining after stripping
|
|
65
|
+
if (strippedKey) {
|
|
66
|
+
result[strippedKey] = value;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Skip keys that don't match the prefix
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
// No prefix - include all non-excluded keys
|
|
73
|
+
result[key] = value;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=environment.loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"environment.loader.js","sourceRoot":"","sources":["../../../src/loaders/environment.loader.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,OAAO,iBAAiB;IAI5B,YAAY,SAAkC,EAAE;QAC9C,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;IACxB,CAAC;IAED,OAAO;QACL,OAAO,mBAAmB,CAAC;IAC7B,CAAC;IAED,KAAK,CAAC,WAAW;QACf,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;;;;;;;;;OAYG;IACH,KAAK,CAAC,IAAI;QACR,MAAM,MAAM,GAA4B,EAAE,CAAC;QAC3C,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,EAAE,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC;QAC9C,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;QAEpC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YACvD,wBAAwB;YACxB,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;gBACxB,SAAS;YACX,CAAC;YAED,8CAA8C;YAC9C,IAAI,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBACxB,SAAS;YACX,CAAC;YAED,0BAA0B;YAC1B,IAAI,MAAM,EAAE,CAAC;gBACX,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;oBAC3B,wBAAwB;oBACxB,MAAM,WAAW,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;oBAC7C,0DAA0D;oBAC1D,IAAI,WAAW,EAAE,CAAC;wBAChB,MAAM,CAAC,WAAW,CAAC,GAAG,KAAK,CAAC;oBAC9B,CAAC;gBACH,CAAC;gBACD,wCAAwC;YAC1C,CAAC;iBAAM,CAAC;gBACN,4CAA4C;gBAC5C,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;YACtB,CAAC;QACH,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;CACF"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { EnvironmentLoader } from './environment.loader';
|
|
2
|
+
export { EnvFileLoader } from './env-file.loader';
|
|
3
|
+
export { S3Loader } from './s3.loader';
|
|
4
|
+
export { SecretsManagerLoader } from './secrets-manager.loader';
|
|
5
|
+
export { SSMParameterStoreLoader } from './ssm-parameter-store.loader';
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/loaders/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3';
|
|
2
|
+
import { fromNodeProviderChain } from '@aws-sdk/credential-providers';
|
|
3
|
+
import { EnvFileParser } from '../utils/env-file-parser.util';
|
|
4
|
+
import { AWSServiceError, ConfigurationLoadError } from '../errors';
|
|
5
|
+
/**
|
|
6
|
+
* Loader that reads configuration from S3 buckets.
|
|
7
|
+
* Supports JSON and .env file formats with auto-detection.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* // Load JSON config from S3
|
|
12
|
+
* const loader = new S3Loader({
|
|
13
|
+
* bucket: 'my-config-bucket',
|
|
14
|
+
* key: 'config/app.json',
|
|
15
|
+
* format: 'json'
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* // Load .env file from S3 with auto-detection
|
|
19
|
+
* const loader = new S3Loader({
|
|
20
|
+
* bucket: 'my-config-bucket',
|
|
21
|
+
* key: 'config/.env'
|
|
22
|
+
* });
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @see https://docs.aws.amazon.com/AmazonECS/latest/developerguide/use-environment-file.html
|
|
26
|
+
*/
|
|
27
|
+
export class S3Loader {
|
|
28
|
+
constructor(config) {
|
|
29
|
+
this._config = {
|
|
30
|
+
bucket: config.bucket,
|
|
31
|
+
key: config.key,
|
|
32
|
+
region: config.region || process.env['AWS_REGION'] || 'us-east-1',
|
|
33
|
+
format: config.format || 'auto',
|
|
34
|
+
};
|
|
35
|
+
this._client = new S3Client({
|
|
36
|
+
credentials: fromNodeProviderChain(),
|
|
37
|
+
region: this._config.region,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
getName() {
|
|
41
|
+
return `S3Loader(s3://${this._config.bucket}/${this._config.key})`;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Check if this loader is available by verifying AWS credentials.
|
|
45
|
+
* @returns Promise resolving to true if AWS credentials are available
|
|
46
|
+
*/
|
|
47
|
+
async isAvailable() {
|
|
48
|
+
try {
|
|
49
|
+
await this._client.config.credentials();
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Load configuration from S3.
|
|
58
|
+
* @returns Promise resolving to configuration key-value pairs
|
|
59
|
+
* @throws AWSServiceError if S3 operation fails
|
|
60
|
+
* @throws ConfigurationLoadError if content cannot be parsed
|
|
61
|
+
*/
|
|
62
|
+
async load() {
|
|
63
|
+
try {
|
|
64
|
+
const command = new GetObjectCommand({
|
|
65
|
+
Bucket: this._config.bucket,
|
|
66
|
+
Key: this._config.key,
|
|
67
|
+
});
|
|
68
|
+
const response = await this._client.send(command);
|
|
69
|
+
if (!response.Body) {
|
|
70
|
+
return {};
|
|
71
|
+
}
|
|
72
|
+
const content = await response.Body.transformToString();
|
|
73
|
+
if (!content || content.trim() === '') {
|
|
74
|
+
return {};
|
|
75
|
+
}
|
|
76
|
+
return this.parseContent(content);
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
if (error instanceof ConfigurationLoadError) {
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
if (error instanceof Error) {
|
|
83
|
+
if (error.name === 'NoSuchKey' || error.name === 'NoSuchBucket') {
|
|
84
|
+
// Object or bucket doesn't exist - return empty config
|
|
85
|
+
return {};
|
|
86
|
+
}
|
|
87
|
+
if (error.name === 'AccessDenied') {
|
|
88
|
+
throw new AWSServiceError(`Access denied when retrieving s3://${this._config.bucket}/${this._config.key}. Check AWS credentials and permissions.`, 'S3', 'GetObject', error);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
throw new AWSServiceError(`Failed to retrieve s3://${this._config.bucket}/${this._config.key}: ${error instanceof Error ? error.message : String(error)}`, 'S3', 'GetObject', error instanceof Error ? error : undefined);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Parse content based on format setting or auto-detection.
|
|
96
|
+
* @internal
|
|
97
|
+
*/
|
|
98
|
+
parseContent(content) {
|
|
99
|
+
const format = this._config.format === 'auto' ? this.detectFormat(content) : this._config.format;
|
|
100
|
+
if (format === 'json') {
|
|
101
|
+
return this.parseJson(content);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
return EnvFileParser.parse(content);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Detect content format based on structure.
|
|
109
|
+
* JSON content starts with '{' after trimming whitespace.
|
|
110
|
+
* @internal
|
|
111
|
+
*/
|
|
112
|
+
detectFormat(content) {
|
|
113
|
+
const trimmed = content.trim();
|
|
114
|
+
if (trimmed.startsWith('{')) {
|
|
115
|
+
return 'json';
|
|
116
|
+
}
|
|
117
|
+
return 'env';
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Parse JSON content.
|
|
121
|
+
* @internal
|
|
122
|
+
*/
|
|
123
|
+
parseJson(content) {
|
|
124
|
+
try {
|
|
125
|
+
const parsed = JSON.parse(content);
|
|
126
|
+
if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
|
|
127
|
+
return parsed;
|
|
128
|
+
}
|
|
129
|
+
// If not an object, wrap it
|
|
130
|
+
return { CONFIG_VALUE: parsed };
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
throw new ConfigurationLoadError(`Failed to parse JSON from s3://${this._config.bucket}/${this._config.key}: ${error instanceof Error ? error.message : String(error)}`, this.getName(), error instanceof Error ? error : undefined);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=s3.loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"s3.loader.js","sourceRoot":"","sources":["../../../src/loaders/s3.loader.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { GetSecretValueCommand, SecretsManagerClient } from '@aws-sdk/client-secrets-manager';
|
|
2
|
+
import { fromNodeProviderChain } from '@aws-sdk/credential-providers';
|
|
3
|
+
import { AWSServiceError, ConfigurationLoadError } from '../errors';
|
|
4
|
+
/**
|
|
5
|
+
* Loader that reads configuration from AWS Secrets Manager.
|
|
6
|
+
* Supports environment-aware path construction.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* // Basic usage
|
|
11
|
+
* const loader = new SecretsManagerLoader({
|
|
12
|
+
* secretName: '/my-app/config',
|
|
13
|
+
* region: 'us-east-1'
|
|
14
|
+
* });
|
|
15
|
+
*
|
|
16
|
+
* // With environment mapping
|
|
17
|
+
* const loader = new SecretsManagerLoader({
|
|
18
|
+
* secretName: '/my-app/config',
|
|
19
|
+
* environmentMapping: {
|
|
20
|
+
* development: 'dev',
|
|
21
|
+
* staging: 'stg',
|
|
22
|
+
* production: 'prod'
|
|
23
|
+
* }
|
|
24
|
+
* });
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export class SecretsManagerLoader {
|
|
28
|
+
constructor(config = {}) {
|
|
29
|
+
this._appEnv = process.env['APP_ENV'] || process.env['NODE_ENV'] || 'local';
|
|
30
|
+
// Set default configuration
|
|
31
|
+
this._config = {
|
|
32
|
+
secretName: config.secretName || '/nestjs-config-aws',
|
|
33
|
+
region: config.region || process.env['AWS_REGION'] || 'us-east-1',
|
|
34
|
+
environmentMapping: config.environmentMapping || {
|
|
35
|
+
development: 'dev',
|
|
36
|
+
test: 'test',
|
|
37
|
+
production: 'production',
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
// Initialize AWS Secrets Manager client
|
|
41
|
+
this._client = new SecretsManagerClient({
|
|
42
|
+
credentials: fromNodeProviderChain(),
|
|
43
|
+
region: this._config.region,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Get the name of this loader for logging and debugging.
|
|
48
|
+
* @returns The loader name with secret path
|
|
49
|
+
*/
|
|
50
|
+
getName() {
|
|
51
|
+
const secretName = this.buildSecretName();
|
|
52
|
+
return `SecretsManagerLoader(${secretName})`;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Check if this loader is available in the current environment.
|
|
56
|
+
* @returns Promise resolving to true if not in local environment and AWS credentials are available
|
|
57
|
+
*/
|
|
58
|
+
async isAvailable() {
|
|
59
|
+
// Skip in local environment
|
|
60
|
+
if (this._appEnv === 'local') {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
// Test AWS credentials by attempting to get caller identity
|
|
65
|
+
await this._client.config.credentials();
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Load configuration from AWS Secrets Manager.
|
|
74
|
+
* @returns Promise resolving to configuration key-value pairs from the secret
|
|
75
|
+
* @throws AWSServiceError if AWS operation fails
|
|
76
|
+
* @throws ConfigurationLoadError if secret cannot be parsed
|
|
77
|
+
*/
|
|
78
|
+
async load() {
|
|
79
|
+
// Skip loading in local environment
|
|
80
|
+
if (this._appEnv === 'local') {
|
|
81
|
+
return {};
|
|
82
|
+
}
|
|
83
|
+
const secretName = this.buildSecretName();
|
|
84
|
+
try {
|
|
85
|
+
const command = new GetSecretValueCommand({ SecretId: secretName });
|
|
86
|
+
const response = await this._client.send(command);
|
|
87
|
+
if (!response.SecretString) {
|
|
88
|
+
return {};
|
|
89
|
+
}
|
|
90
|
+
// Try to parse as JSON, fallback to string value
|
|
91
|
+
try {
|
|
92
|
+
const parsed = JSON.parse(response.SecretString);
|
|
93
|
+
// Ensure we return an object for configuration merging
|
|
94
|
+
if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
|
|
95
|
+
return parsed;
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
// If it's not an object, wrap it in a configuration object
|
|
99
|
+
return { SECRET_VALUE: parsed };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// If JSON parsing fails, treat as a single string value
|
|
104
|
+
return { SECRET_VALUE: response.SecretString };
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
// Handle specific AWS errors
|
|
109
|
+
if (error instanceof Error) {
|
|
110
|
+
if (error.name === 'ResourceNotFoundException') {
|
|
111
|
+
// Secret doesn't exist - this is not necessarily an error in all environments
|
|
112
|
+
return {};
|
|
113
|
+
}
|
|
114
|
+
if (error.name === 'AccessDeniedException') {
|
|
115
|
+
throw new AWSServiceError(`Access denied when retrieving secret '${secretName}'. Check AWS credentials and permissions.`, 'SecretsManager', 'GetSecretValue', error);
|
|
116
|
+
}
|
|
117
|
+
if (error.name === 'InvalidRequestException') {
|
|
118
|
+
throw new AWSServiceError(`Invalid request when retrieving secret '${secretName}'. Check secret name format.`, 'SecretsManager', 'GetSecretValue', error);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// For other errors, wrap in AWSServiceError
|
|
122
|
+
throw new AWSServiceError(`Failed to retrieve secret '${secretName}' from AWS Secrets Manager: ${error instanceof Error ? error.message : String(error)}`, 'SecretsManager', 'GetSecretValue', error instanceof Error ? error : undefined);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Build the environment-aware secret name/path.
|
|
127
|
+
* @returns The full secret name with environment prefix
|
|
128
|
+
*/
|
|
129
|
+
buildSecretName() {
|
|
130
|
+
const envPrefix = this._config.environmentMapping[this._appEnv];
|
|
131
|
+
if (!envPrefix) {
|
|
132
|
+
throw new ConfigurationLoadError(`No environment mapping found for APP_ENV '${this._appEnv}'. ` +
|
|
133
|
+
`Available environments: ${Object.keys(this._config.environmentMapping).join(', ')}`, this.getName());
|
|
134
|
+
}
|
|
135
|
+
return `/${envPrefix}${this._config.secretName}`;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Get the current app environment.
|
|
139
|
+
* @returns The current APP_ENV or NODE_ENV value
|
|
140
|
+
*/
|
|
141
|
+
getAppEnv() {
|
|
142
|
+
return this._appEnv;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Get the environment mapping configuration.
|
|
146
|
+
* @returns The environment mapping record
|
|
147
|
+
*/
|
|
148
|
+
getEnvironmentMapping() {
|
|
149
|
+
return { ...this._config.environmentMapping };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=secrets-manager.loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secrets-manager.loader.js","sourceRoot":"","sources":["../../../src/loaders/secrets-manager.loader.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { GetParametersByPathCommand, SSMClient } from '@aws-sdk/client-ssm';
|
|
2
|
+
import { fromNodeProviderChain } from '@aws-sdk/credential-providers';
|
|
3
|
+
import { AWSServiceError, ConfigurationLoadError } from '../errors';
|
|
4
|
+
/**
|
|
5
|
+
* Loader that reads configuration from AWS SSM Parameter Store.
|
|
6
|
+
* Supports environment-aware path construction, pagination, and decryption options.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* // Basic usage
|
|
11
|
+
* const loader = new SSMParameterStoreLoader({
|
|
12
|
+
* parameterPath: '/my-app/config',
|
|
13
|
+
* region: 'us-east-1'
|
|
14
|
+
* });
|
|
15
|
+
*
|
|
16
|
+
* // With environment mapping
|
|
17
|
+
* const loader = new SSMParameterStoreLoader({
|
|
18
|
+
* parameterPath: '/my-app/config',
|
|
19
|
+
* environmentMapping: {
|
|
20
|
+
* development: 'dev',
|
|
21
|
+
* staging: 'stg',
|
|
22
|
+
* production: 'prod'
|
|
23
|
+
* }
|
|
24
|
+
* });
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export class SSMParameterStoreLoader {
|
|
28
|
+
constructor(config = {}) {
|
|
29
|
+
this._appEnv = process.env['APP_ENV'] || process.env['NODE_ENV'] || 'local';
|
|
30
|
+
// Set default configuration
|
|
31
|
+
this._config = {
|
|
32
|
+
parameterPath: config.parameterPath || '/config-aws',
|
|
33
|
+
region: config.region || process.env['AWS_REGION'] || 'us-east-1',
|
|
34
|
+
environmentMapping: config.environmentMapping || {
|
|
35
|
+
development: 'dev',
|
|
36
|
+
test: 'test',
|
|
37
|
+
production: 'production',
|
|
38
|
+
},
|
|
39
|
+
withDecryption: config.withDecryption ?? true,
|
|
40
|
+
};
|
|
41
|
+
// Initialize AWS SSM client
|
|
42
|
+
this._client = new SSMClient({
|
|
43
|
+
credentials: fromNodeProviderChain(),
|
|
44
|
+
region: this._config.region,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Get the name of this loader for logging and debugging.
|
|
49
|
+
* @returns The loader name with parameter path
|
|
50
|
+
*/
|
|
51
|
+
getName() {
|
|
52
|
+
try {
|
|
53
|
+
const parameterPath = this.buildParameterPath();
|
|
54
|
+
return `SSMParameterStoreLoader(${parameterPath})`;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// Fallback if path construction fails (e.g., missing env mapping)
|
|
58
|
+
return `SSMParameterStoreLoader(${this._config.parameterPath})`;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Check if this loader is available in the current environment.
|
|
63
|
+
* @returns Promise resolving to true if not in local environment and AWS credentials are available
|
|
64
|
+
*/
|
|
65
|
+
async isAvailable() {
|
|
66
|
+
// Skip in local environment
|
|
67
|
+
if (this._appEnv === 'local') {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
// Test AWS credentials by attempting to get caller identity
|
|
72
|
+
await this._client.config.credentials();
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Load configuration from AWS SSM Parameter Store.
|
|
81
|
+
* Implements recursive parameter fetching with NextToken handling for pagination.
|
|
82
|
+
* @returns Promise resolving to configuration key-value pairs from parameters
|
|
83
|
+
* @throws AWSServiceError if AWS operation fails
|
|
84
|
+
* @throws ConfigurationLoadError if parameter path cannot be constructed
|
|
85
|
+
*/
|
|
86
|
+
async load() {
|
|
87
|
+
// Skip loading in local environment
|
|
88
|
+
if (this._appEnv === 'local') {
|
|
89
|
+
return {};
|
|
90
|
+
}
|
|
91
|
+
const parameterPath = this.buildParameterPath();
|
|
92
|
+
const result = {};
|
|
93
|
+
let nextToken;
|
|
94
|
+
try {
|
|
95
|
+
do {
|
|
96
|
+
const command = new GetParametersByPathCommand({
|
|
97
|
+
Path: parameterPath,
|
|
98
|
+
Recursive: true,
|
|
99
|
+
WithDecryption: this._config.withDecryption,
|
|
100
|
+
NextToken: nextToken,
|
|
101
|
+
});
|
|
102
|
+
const response = await this._client.send(command);
|
|
103
|
+
if (!response.Parameters) {
|
|
104
|
+
// No parameters found - this is not necessarily an error
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
// Process each parameter
|
|
108
|
+
for (const param of response.Parameters) {
|
|
109
|
+
const key = this.transformParameterName(param.Name, parameterPath);
|
|
110
|
+
if (key && param.Value !== undefined) {
|
|
111
|
+
result[key] = param.Value;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
nextToken = response.NextToken;
|
|
115
|
+
} while (nextToken);
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
// Handle specific AWS errors
|
|
120
|
+
if (error instanceof Error) {
|
|
121
|
+
if (error.name === 'ResourceNotFoundException' || error.name === 'ParameterNotFound') {
|
|
122
|
+
// No parameters found at path - this is not necessarily an error
|
|
123
|
+
return {};
|
|
124
|
+
}
|
|
125
|
+
if (error.name === 'AccessDeniedException') {
|
|
126
|
+
throw new AWSServiceError(`Access denied when retrieving parameters from path '${parameterPath}'. Check AWS credentials and permissions.`, 'SSM', 'GetParametersByPath', error);
|
|
127
|
+
}
|
|
128
|
+
if (error.name === 'InvalidFilterKey' || error.name === 'InvalidFilterValue') {
|
|
129
|
+
throw new AWSServiceError(`Invalid parameter path '${parameterPath}'. Check path format.`, 'SSM', 'GetParametersByPath', error);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// For other errors, wrap in AWSServiceError
|
|
133
|
+
throw new AWSServiceError(`Failed to retrieve parameters from path '${parameterPath}' in AWS SSM Parameter Store: ${error instanceof Error ? error.message : String(error)}`, 'SSM', 'GetParametersByPath', error instanceof Error ? error : undefined);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Build the environment-aware parameter path.
|
|
138
|
+
* @returns The full parameter path with environment prefix
|
|
139
|
+
* @throws ConfigurationLoadError if no environment mapping found
|
|
140
|
+
*/
|
|
141
|
+
buildParameterPath() {
|
|
142
|
+
const envPrefix = this._config.environmentMapping[this._appEnv];
|
|
143
|
+
if (!envPrefix) {
|
|
144
|
+
throw new ConfigurationLoadError(`No environment mapping found for APP_ENV '${this._appEnv}'. ` +
|
|
145
|
+
`Available environments: ${Object.keys(this._config.environmentMapping).join(', ')}`, this.getName());
|
|
146
|
+
}
|
|
147
|
+
return `/${envPrefix}${this._config.parameterPath}`;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Transform parameter name by removing the prefix and converting to uppercase.
|
|
151
|
+
* Example: '/dev/config-aws/database/host' -> 'DATABASE_HOST'
|
|
152
|
+
* @param parameterName The full parameter name from AWS
|
|
153
|
+
* @param pathPrefix The path prefix to remove
|
|
154
|
+
* @returns The transformed parameter name or null if invalid
|
|
155
|
+
*/
|
|
156
|
+
transformParameterName(parameterName, pathPrefix) {
|
|
157
|
+
if (!parameterName) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
// Remove the path prefix
|
|
161
|
+
let key = parameterName;
|
|
162
|
+
if (key.startsWith(pathPrefix)) {
|
|
163
|
+
key = key.substring(pathPrefix.length);
|
|
164
|
+
}
|
|
165
|
+
// Remove leading slash if present
|
|
166
|
+
if (key.startsWith('/')) {
|
|
167
|
+
key = key.substring(1);
|
|
168
|
+
}
|
|
169
|
+
// Convert slashes to underscores and uppercase
|
|
170
|
+
key = key.replace(/\//g, '_').toUpperCase();
|
|
171
|
+
// Return null for empty keys
|
|
172
|
+
return key || null;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Get the current app environment.
|
|
176
|
+
* @returns The current APP_ENV or NODE_ENV value
|
|
177
|
+
*/
|
|
178
|
+
getAppEnv() {
|
|
179
|
+
return this._appEnv;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Get the environment mapping configuration.
|
|
183
|
+
* @returns The environment mapping record
|
|
184
|
+
*/
|
|
185
|
+
getEnvironmentMapping() {
|
|
186
|
+
return { ...this._config.environmentMapping };
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=ssm-parameter-store.loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ssm-parameter-store.loader.js","sourceRoot":"","sources":["../../../src/loaders/ssm-parameter-store.loader.ts"],"names":[],"mappings":"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"}
|