ac-awssecrets 2.4.4 → 2.5.1
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/CHANGELOG.md +33 -0
- package/index.js +168 -29
- package/package.json +4 -4
- package/test/config.js +1 -1
- package/g.sh +0 -11
package/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
|
|
2
|
+
## [2.5.1](https://github.com/admiralcloud/ac-awssecrets/compare/v2.5.0..v2.5.1) (2025-05-13 08:10:25)
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
### Bug Fix
|
|
6
|
+
|
|
7
|
+
* **App:** Fixed deep merge with arrays | MP | [715c67bc5ef1c2520069d07953c6f05fd08ab37a](https://github.com/admiralcloud/ac-awssecrets/commit/715c67bc5ef1c2520069d07953c6f05fd08ab37a)
|
|
8
|
+
Fixed deep merge with arrays
|
|
9
|
+
Related issues:
|
|
10
|
+
### Chores
|
|
11
|
+
|
|
12
|
+
* **App:** Updated packages | MP | [843ab11b885697b471c74d546b40d885cc9f2844](https://github.com/admiralcloud/ac-awssecrets/commit/843ab11b885697b471c74d546b40d885cc9f2844)
|
|
13
|
+
Updated packages
|
|
14
|
+
Related issues:
|
|
15
|
+
|
|
16
|
+
# [2.5.0](https://github.com/admiralcloud/ac-awssecrets/compare/v2.4.4..v2.5.0) (2025-05-06 18:01:30)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Feature
|
|
20
|
+
|
|
21
|
+
* **App:** Allow batch fetching secret parameters | MP | [0d74ae3d45edde466aa36ca15e3f3a689676cc37](https://github.com/admiralcloud/ac-awssecrets/commit/0d74ae3d45edde466aa36ca15e3f3a689676cc37)
|
|
22
|
+
To speed up processes, this version now allows batch fetching of secret parameters
|
|
23
|
+
Related issues:
|
|
24
|
+
### Tests
|
|
25
|
+
|
|
26
|
+
* **App:** Fixed test | MP | [996dabf3f6287150da5aaa048bcb2c3b7aa9a289](https://github.com/admiralcloud/ac-awssecrets/commit/996dabf3f6287150da5aaa048bcb2c3b7aa9a289)
|
|
27
|
+
Fixed test
|
|
28
|
+
Related issues:
|
|
29
|
+
### Chores
|
|
30
|
+
|
|
31
|
+
* **App:** Updated packages | MP | [42be94ad3cb396fc40f4300eec4463d6cfe18b3a](https://github.com/admiralcloud/ac-awssecrets/commit/42be94ad3cb396fc40f4300eec4463d6cfe18b3a)
|
|
32
|
+
Updated packages
|
|
33
|
+
Related issues:
|
|
34
|
+
|
|
2
35
|
## [2.4.4](https://github.com/admiralcloud/ac-awssecrets/compare/v2.4.3..v2.4.4) (2025-04-20 06:17:50)
|
|
3
36
|
|
|
4
37
|
|
package/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const { SecretsManagerClient, GetSecretValueCommand } = require('@aws-sdk/client-secrets-manager')
|
|
2
|
-
const { SSMClient, GetParameterCommand, GetParametersByPathCommand } = require("@aws-sdk/client-ssm")
|
|
2
|
+
const { SSMClient, GetParameterCommand, GetParametersCommand, GetParametersByPathCommand } = require("@aws-sdk/client-ssm")
|
|
3
3
|
|
|
4
4
|
const testConfig = require('./test/config')
|
|
5
5
|
const functionName = 'ac-awsSecrets'.padEnd(15)
|
|
@@ -32,16 +32,26 @@ const awsSecrets = () => {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
const deepMerge = (target, source) => {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
35
|
+
if (Array.isArray(target) && Array.isArray(source)) {
|
|
36
|
+
return [...target, ...source]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (typeof source === 'object' && source !== null && typeof target === 'object' && target !== null) {
|
|
40
|
+
const result = { ...target }
|
|
41
|
+
|
|
42
|
+
for (const key in source) {
|
|
43
|
+
if (key in result) {
|
|
44
|
+
result[key] = deepMerge(result[key], source[key])
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
result[key] = source[key]
|
|
48
|
+
}
|
|
42
49
|
}
|
|
50
|
+
|
|
51
|
+
return result
|
|
43
52
|
}
|
|
44
|
-
|
|
53
|
+
|
|
54
|
+
return source
|
|
45
55
|
}
|
|
46
56
|
|
|
47
57
|
const setValue = (config, { path, value, array = false, property, merge = false }) => {
|
|
@@ -93,14 +103,127 @@ const awsSecrets = () => {
|
|
|
93
103
|
|
|
94
104
|
const loadSecretParameters = async({ secretParameters = [], config = {}, testMode = 0, debug = false, throwError = false, region = 'eu-central-1' } = {}) => {
|
|
95
105
|
const environment = config?.environment || process.env.NODE_ENV || 'development'
|
|
96
|
-
|
|
106
|
+
|
|
97
107
|
const awsConfig = {
|
|
98
108
|
region
|
|
99
109
|
}
|
|
100
110
|
const ssmClient = new SSMClient(awsConfig)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
111
|
+
|
|
112
|
+
// Process parameters in batches of 10 (AWS limit for GetParametersCommand)
|
|
113
|
+
const processBatchedParameters = async(paramList) => {
|
|
114
|
+
// Skip if no parameters
|
|
115
|
+
if (paramList.length === 0) return
|
|
116
|
+
|
|
117
|
+
// Split parameters into batches of 10
|
|
118
|
+
const batchSize = 10
|
|
119
|
+
const batches = []
|
|
120
|
+
|
|
121
|
+
for (let i = 0; i < paramList.length; i += batchSize) {
|
|
122
|
+
batches.push(paramList.slice(i, i + batchSize))
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Process each batch
|
|
126
|
+
for (const batch of batches) {
|
|
127
|
+
if (testMode === 3) {
|
|
128
|
+
// For test mode, process individually as before
|
|
129
|
+
await Promise.all(batch.map(async param => {
|
|
130
|
+
const parameterName = `/${environment}/${param.name}`
|
|
131
|
+
const found = testConfig.parameterStore.find(item => item.name === parameterName)
|
|
132
|
+
let value = found?.value
|
|
133
|
+
|
|
134
|
+
if (param.json && value) {
|
|
135
|
+
try {
|
|
136
|
+
value = JSON.parse(value)
|
|
137
|
+
}
|
|
138
|
+
catch (e) {
|
|
139
|
+
console.error('%s | %s | %s', functionName, parameterName, e?.message)
|
|
140
|
+
if (throwError) throw e
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (debug) {
|
|
145
|
+
console.warn('P %s | T %s | V %j', parameterName, typeof value, value)
|
|
146
|
+
}
|
|
147
|
+
setValue(config, {
|
|
148
|
+
path: (param.path || param.name),
|
|
149
|
+
value,
|
|
150
|
+
array: param.array || false,
|
|
151
|
+
property: param.property,
|
|
152
|
+
merge: param.merge || false
|
|
153
|
+
})
|
|
154
|
+
}))
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
// For production mode, use GetParametersCommand to fetch multiple parameters at once
|
|
158
|
+
try {
|
|
159
|
+
// Get parameter names for this batch
|
|
160
|
+
const parameterNames = batch.map(param => `/${environment}/${param.name}`)
|
|
161
|
+
|
|
162
|
+
// Fetch all parameters in this batch with a single API call
|
|
163
|
+
const command = new GetParametersCommand({
|
|
164
|
+
Names: parameterNames,
|
|
165
|
+
WithDecryption: true
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
const response = await ssmClient.send(command)
|
|
169
|
+
const parameters = response?.Parameters || []
|
|
170
|
+
|
|
171
|
+
// Process each parameter
|
|
172
|
+
await Promise.all(parameters.map(async parameter => {
|
|
173
|
+
// Find corresponding parameter config
|
|
174
|
+
const paramName = parameter.Name
|
|
175
|
+
const paramConfig = batch.find(p => `/${environment}/${p.name}` === paramName)
|
|
176
|
+
|
|
177
|
+
if (!paramConfig) return // Skip if no matching config found
|
|
178
|
+
|
|
179
|
+
let value = parameter.Value
|
|
180
|
+
|
|
181
|
+
if (paramConfig.json && value) {
|
|
182
|
+
try {
|
|
183
|
+
value = JSON.parse(value)
|
|
184
|
+
}
|
|
185
|
+
catch (e) {
|
|
186
|
+
console.error('%s | %s | %s', functionName, paramName, e?.message)
|
|
187
|
+
if (throwError) throw e
|
|
188
|
+
return // Skip this parameter if JSON parsing fails
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (debug) {
|
|
193
|
+
console.warn('P %s | T %s | V %j', paramName, typeof value, value)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
setValue(config, {
|
|
197
|
+
path: (paramConfig.path || paramConfig.name),
|
|
198
|
+
value,
|
|
199
|
+
array: paramConfig.array || false,
|
|
200
|
+
property: paramConfig.property,
|
|
201
|
+
merge: paramConfig.merge || false
|
|
202
|
+
})
|
|
203
|
+
}))
|
|
204
|
+
|
|
205
|
+
// Handle invalid parameters
|
|
206
|
+
if (response?.InvalidParameters?.length > 0) {
|
|
207
|
+
console.error('%s | Invalid parameters: %j', functionName, response.InvalidParameters)
|
|
208
|
+
if (throwError) {
|
|
209
|
+
throw new Error(`Invalid parameters: ${response.InvalidParameters.join(', ')}`)
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
catch (e) {
|
|
214
|
+
console.error('%s | Batch parameter fetch error: %s', functionName, e?.message)
|
|
215
|
+
if (throwError) throw e
|
|
216
|
+
|
|
217
|
+
// Fallback: process parameters individually if batch fails
|
|
218
|
+
await Promise.all(batch.map(param => getSecretParameter(param)))
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Keep the original getSecretParameter as fallback
|
|
225
|
+
const getSecretParameter = async(param) => {
|
|
226
|
+
const parameterName = `/${environment}/${param.name}`
|
|
104
227
|
try {
|
|
105
228
|
let value
|
|
106
229
|
if (testMode === 3) {
|
|
@@ -118,26 +241,25 @@ const awsSecrets = () => {
|
|
|
118
241
|
const response = await ssmClient.send(command)
|
|
119
242
|
value = response?.Parameter?.Value
|
|
120
243
|
}
|
|
121
|
-
|
|
244
|
+
|
|
122
245
|
// Extract and return the parameter value
|
|
123
|
-
if (json) {
|
|
246
|
+
if (param.json) {
|
|
124
247
|
value = JSON.parse(value)
|
|
125
248
|
}
|
|
126
|
-
|
|
249
|
+
|
|
127
250
|
if (debug) {
|
|
128
251
|
console.warn('P %s | T %s | V %j', parameterName, typeof value, value)
|
|
129
252
|
}
|
|
130
|
-
setValue(config, { path: (path || name), value, array, property, merge })
|
|
131
|
-
|
|
253
|
+
setValue(config, { path: (param.path || param.name), value, array: param.array, property: param.property, merge: param.merge })
|
|
132
254
|
}
|
|
133
255
|
catch (e) {
|
|
134
256
|
console.error('%s | %s | %s', functionName, parameterName, e?.message)
|
|
135
257
|
if (throwError) throw e
|
|
136
258
|
}
|
|
137
259
|
}
|
|
138
|
-
|
|
139
|
-
//
|
|
140
|
-
const getSecretParametersByPath = async({ name, json = false, array
|
|
260
|
+
|
|
261
|
+
// Keep the original getSecretParametersByPath for wildcard parameters
|
|
262
|
+
const getSecretParametersByPath = async({ path, name, json = false, array, property, merge }) => {
|
|
141
263
|
if (!path) throw new Error('pathMustBeSet')
|
|
142
264
|
const parameterName = `/${environment}/${name}`
|
|
143
265
|
try {
|
|
@@ -169,14 +291,14 @@ const awsSecrets = () => {
|
|
|
169
291
|
const response = await ssmClient.send(command)
|
|
170
292
|
valueArray = response?.Parameters
|
|
171
293
|
}
|
|
172
|
-
|
|
294
|
+
|
|
173
295
|
for (const item of valueArray) {
|
|
174
296
|
let value = item?.Value
|
|
175
297
|
// Extract and return the parameter value
|
|
176
298
|
if (json) {
|
|
177
299
|
value = JSON.parse(value)
|
|
178
300
|
}
|
|
179
|
-
|
|
301
|
+
|
|
180
302
|
if (debug) {
|
|
181
303
|
console.warn('P %s | T %s | V %j', item?.Name, typeof value, value)
|
|
182
304
|
}
|
|
@@ -188,13 +310,30 @@ const awsSecrets = () => {
|
|
|
188
310
|
if (throwError) throw e
|
|
189
311
|
}
|
|
190
312
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
else await getSecretParameter(secretParameter)
|
|
313
|
+
|
|
314
|
+
// Filter out parameters with ignoreInTestMode = true in test environment
|
|
315
|
+
let filteredParams = secretParameters
|
|
316
|
+
if (environment === 'test') {
|
|
317
|
+
filteredParams = secretParameters.filter(param => !param.ignoreInTestMode)
|
|
197
318
|
}
|
|
319
|
+
|
|
320
|
+
// Add debug if needed
|
|
321
|
+
if (debug) {
|
|
322
|
+
filteredParams.forEach(param => param.debug = true)
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Split parameters into regular and wildcard ones
|
|
326
|
+
const wildcardParams = filteredParams.filter(param => param.name.endsWith('*'))
|
|
327
|
+
const regularParams = filteredParams.filter(param => !param.name.endsWith('*'))
|
|
328
|
+
|
|
329
|
+
// Process parameters in parallel
|
|
330
|
+
await Promise.all([
|
|
331
|
+
// Process regular parameters in batches
|
|
332
|
+
processBatchedParameters(regularParams),
|
|
333
|
+
|
|
334
|
+
// Process wildcard parameters individually (using original method)
|
|
335
|
+
...wildcardParams.map(param => getSecretParametersByPath(param))
|
|
336
|
+
])
|
|
198
337
|
}
|
|
199
338
|
|
|
200
339
|
|
package/package.json
CHANGED
|
@@ -3,17 +3,17 @@
|
|
|
3
3
|
"author": "Mark Poepping (https://www.admiralcloud.com)",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": "admiralcloud/ac-awssecrets",
|
|
6
|
-
"version": "2.
|
|
6
|
+
"version": "2.5.1",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@aws-sdk/client-secrets-manager": "^3.
|
|
9
|
-
"@aws-sdk/client-ssm": "^3.
|
|
8
|
+
"@aws-sdk/client-secrets-manager": "^3.808.0",
|
|
9
|
+
"@aws-sdk/client-ssm": "^3.808.0"
|
|
10
10
|
},
|
|
11
11
|
"devDependencies": {
|
|
12
12
|
"ac-semantic-release": "^0.4.6",
|
|
13
13
|
"c8": "^10.1.3",
|
|
14
14
|
"chai": "^4.x",
|
|
15
15
|
"eslint": "9.x",
|
|
16
|
-
"mocha": "^11.
|
|
16
|
+
"mocha": "^11.2.2"
|
|
17
17
|
},
|
|
18
18
|
"scripts": {
|
|
19
19
|
"test": "NODE_ENV=test mocha --reporter spec --bail",
|
package/test/config.js
CHANGED
|
@@ -36,7 +36,7 @@ const secretParameters = [
|
|
|
36
36
|
{ name: 'configVar5.path', json: true },
|
|
37
37
|
{ name: 'configVar6', json: true },
|
|
38
38
|
{ name: 'aws', json: true, merge: true },
|
|
39
|
-
{ name: 'db/*', json: true, merge: true, path: 'db' }
|
|
39
|
+
{ name: 'db/*', json: true, merge: true, path: 'db', array: true }
|
|
40
40
|
]
|
|
41
41
|
|
|
42
42
|
const parameterStore = [
|
package/g.sh
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
aws ssm get-parameters-by-path \
|
|
3
|
-
--path "/development" \
|
|
4
|
-
--recursive \
|
|
5
|
-
--profile dev.mfa --region eu-central-1 \
|
|
6
|
-
--output json | \
|
|
7
|
-
jq '.Parameters[] |
|
|
8
|
-
{Name: .Name}'
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
{"secret":"L4w,UU3g;NAh)e6,HV(TTzUZ2G>","name":"acauth","valueHasJSON":"true","defaultApps":{"acapp":"8d09356a-042f-4d4a-9c6f-935329000969","embedlink": "5ffd8ff6-2aeb-44b0-8a6c-f413de58df3c"},"passwordAlgorithm":"aes-256-ctr","password":"8gZVqjzKN@g4XdD7Cq#9qAPh>iPdKNkr"}
|