Skip to content
This repository was archived by the owner on Jul 26, 2022. It is now read-only.

Commit 4dbb6dd

Browse files
authored
feat: ✨ Introduce dataFromWithOptions (#846)
dataFromWithOptions allows to get values in bulk of a specific version
1 parent 20496ab commit 4dbb6dd

File tree

4 files changed

+113
-3
lines changed

4 files changed

+113
-3
lines changed

README.md

+25-1
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,28 @@ spec:
512512
- hello-service/credentials
513513
```
514514

515-
`data` and `dataFrom` can of course be combined, any naming conflicts will use the last defined, with `data` overriding `dataFrom`
515+
`dataFrom` by default retrieves the latest (`AWSCURRENT`) version of the backend secret, if you want to get values in bulk of a specific version, you can use `dataFromWithOptions`:
516+
517+
```yml
518+
apiVersion: kubernetes-client.io/v1
519+
kind: ExternalSecret
520+
metadata:
521+
name: hello-service
522+
spec:
523+
backendType: secretsManager
524+
# optional: specify role to assume when retrieving the data
525+
roleArn: arn:aws:iam::123456789012:role/test-role
526+
# optional: specify region
527+
region: us-east-1
528+
dataFromWithOptions:
529+
- key: hello-service/credentials
530+
versionStage: AWSPREVIOUS
531+
- key: hello-service/credentials
532+
versionId: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
533+
```
534+
535+
`data`, `dataFrom` and `dataFromWithOptions` can of course be combined, any naming conflicts will use the last defined.
536+
In the below example `data` takes precedence over `dataFromWithOptions` and `dataFrom`.
516537

517538
```yml
518539
apiVersion: kubernetes-client.io/v1
@@ -527,6 +548,9 @@ spec:
527548
region: us-east-1
528549
dataFrom:
529550
- hello-service/credentials
551+
dataFromWithOptions:
552+
- key: hello-service/credentials
553+
versionId: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
530554
data:
531555
- key: hello-service/migration-credentials
532556
name: password

charts/kubernetes-external-secrets/crds/kubernetes-client.io_externalsecrets_crd.yaml

+27
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,31 @@ spec:
7373
type: array
7474
items:
7575
type: string
76+
dataFromWithOptions:
77+
type: array
78+
items:
79+
type: object
80+
properties:
81+
key:
82+
description: Secret key in backend
83+
type: string
84+
isBinary:
85+
description: >-
86+
Whether the backend secret shall be treated as binary data
87+
represented by a base64-encoded string. You must set this to true
88+
for any base64-encoded binary data in the backend - to ensure it
89+
is not encoded in base64 again. Default is false.
90+
type: boolean
91+
versionStage:
92+
description: >-
93+
Used by: alicloudSecretsManager, secretsManager
94+
type: string
95+
versionId:
96+
description: >-
97+
Used by: secretsManager
98+
type: string
99+
required:
100+
- key
76101
data:
77102
type: array
78103
items:
@@ -180,6 +205,8 @@ spec:
180205
- data
181206
- required:
182207
- dataFrom
208+
- required:
209+
- dataFromWithOptions
183210
status:
184211
type: object
185212
properties:

lib/backends/kv-backend.js

+24-2
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,26 @@ class KVBackend extends AbstractBackend {
112112
}))
113113
}
114114

115+
/**
116+
* Fetch Kubernetes secret property values with options.
117+
* @param {Object[]} dataFromWithOptions - Array of secret keys in the backend, including extra options
118+
* @param {string} specOptions - Options set on spec level that might be interesting for the backend
119+
* @returns {Promise} Promise object representing secret property values.
120+
*/
121+
_fetchDataFromValuesWithOptions ({ dataFromWithOptions, specOptions }) {
122+
return Promise.all(dataFromWithOptions.map(async dataItem => {
123+
const { key, ...keyOptions } = dataItem
124+
const value = await this._get({ key, specOptions, keyOptions })
125+
126+
try {
127+
return JSON.parse(value)
128+
} catch (err) {
129+
this._logger.warn(`Failed to JSON.parse value for '${dataItem}',` +
130+
' please verify that your secret value is correctly formatted as JSON.')
131+
}
132+
}))
133+
}
134+
115135
/**
116136
* Get a secret property value from Key Value backend.
117137
* @param {string} key - Secret key in the backend.
@@ -162,15 +182,17 @@ class KVBackend extends AbstractBackend {
162182
properties = [],
163183
data = properties,
164184
dataFrom = [],
185+
dataFromWithOptions = [],
165186
...specOptions
166187
}
167188
}) {
168-
const [dataFromValues, dataValues] = await Promise.all([
189+
const [dataFromValues, dataFromValuesWithOptions, dataValues] = await Promise.all([
169190
this._fetchDataFromValues({ dataFrom, specOptions }),
191+
this._fetchDataFromValuesWithOptions({ dataFromWithOptions, specOptions }),
170192
this._fetchDataValues({ data, specOptions })
171193
])
172194

173-
const plainValues = dataFromValues.concat(dataValues)
195+
const plainValues = dataFromValues.concat(dataFromValuesWithOptions).concat(dataValues)
174196
.reduce((acc, parsedValue) => ({
175197
...acc,
176198
...parsedValue

lib/backends/kv-backend.test.js

+37
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,43 @@ describe('kv-backend', () => {
267267
})
268268
})
269269

270+
describe('_fetchDataFromValuesWithOptions', () => {
271+
beforeEach(() => {
272+
kvBackend._get = sinon.stub()
273+
})
274+
it('handles secrets with spec and key options', async () => {
275+
kvBackend._get.onFirstCall().resolves('{"fakePropertyName1":"fakePropertyValue1"}')
276+
kvBackend._get.onSecondCall().resolves('{"fakePropertyName2":"fakePropertyValue2"}')
277+
const dataFromValuesWithOptions = await kvBackend._fetchDataFromValuesWithOptions({
278+
dataFromWithOptions: [{ versionStage: 'AWSCURRENT', key: 'fakeKey1' },
279+
{ versionId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', key: 'fakeKey2' }],
280+
specOptions: { passMeAlong: true }
281+
})
282+
expect(dataFromValuesWithOptions).to.deep.equal([{ fakePropertyName1: 'fakePropertyValue1' }, { fakePropertyName2: 'fakePropertyValue2' }])
283+
expect(kvBackend._get.getCall(0).args[0]).to.deep.equal({
284+
key: 'fakeKey1',
285+
keyOptions: {
286+
versionStage: 'AWSCURRENT'
287+
},
288+
specOptions: { passMeAlong: true }
289+
})
290+
expect(kvBackend._get.getCall(1).args[0]).to.deep.equal({
291+
key: 'fakeKey2',
292+
keyOptions: {
293+
versionId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
294+
},
295+
specOptions: { passMeAlong: true }
296+
})
297+
})
298+
it('handles invalid JSON objects', async () => {
299+
kvBackend._get.onFirstCall().resolves('{')
300+
const dataFromValuesWithOptions = await kvBackend._fetchDataFromValuesWithOptions({
301+
dataFromWithOptions: [{ key: 'fakeKey1' }]
302+
})
303+
expect(dataFromValuesWithOptions).to.deep.equal([undefined])
304+
})
305+
})
306+
270307
describe('_get', () => {
271308
it('throws an error', () => {
272309
let error

0 commit comments

Comments
 (0)