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 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
- const result = { ...target }
36
- for (const key in source) {
37
- if (typeof source[key] === 'object' && !Array.isArray(source[key]) && typeof result[key] === 'object' && !Array.isArray(result[key])) {
38
- result[key] = deepMerge(result[key], source[key])
39
- }
40
- else {
41
- result[key] = source[key]
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
- return result
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
- const getSecretParameter = async({ name, json = false, array = false, path, property, debug, merge }) => {
103
- const parameterName = `/${environment}/${name}`
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
- // pushes multiple paramters into the given path (e.g. params /dev/db/1 and /dev/db/2 (name = /dev/db/*) will be objects in array databases (path))
140
- const getSecretParametersByPath = async({ name, json = false, array = true, path, property, debug, merge }) => {
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
- for (const secretParameter of secretParameters) {
193
- if (environment === 'test' && secretParameter?.ignoreInTestMode) continue
194
- if (debug) secretParameter.debug = true
195
- if (secretParameter.name.endsWith('*')) await getSecretParametersByPath(secretParameter)
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.4.4",
6
+ "version": "2.5.1",
7
7
  "dependencies": {
8
- "@aws-sdk/client-secrets-manager": "^3.787.0",
9
- "@aws-sdk/client-ssm": "^3.787.0"
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.1.0"
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"}