From f51d2129c62da1b8e2b39532c93e85185c7f43bf Mon Sep 17 00:00:00 2001 From: Katie Dai Date: Thu, 11 Aug 2022 13:53:01 -0400 Subject: [PATCH 1/7] update request options to add keys from config map to headers --- lib/BaseDownloadController.js | 4 + lib/ReqOpts.js | 161 ++++++++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 lib/ReqOpts.js diff --git a/lib/BaseDownloadController.js b/lib/BaseDownloadController.js index 5e8b2c2..a224f3f 100644 --- a/lib/BaseDownloadController.js +++ b/lib/BaseDownloadController.js @@ -22,6 +22,7 @@ const clone = require('clone'); const CompositeController = require('./CompositeController'); +const ReqOpts = require('./ReqOpts'); module.exports = class BaseDownloadController extends CompositeController { constructor(params) { @@ -195,6 +196,9 @@ module.exports = class BaseDownloadController extends CompositeController { } } } + + let reqopt = new ReqOpts(this); + requestOptions = await reqopt.get(requestOptions); return requestOptions; } diff --git a/lib/ReqOpts.js b/lib/ReqOpts.js new file mode 100644 index 0000000..aa3bc2b --- /dev/null +++ b/lib/ReqOpts.js @@ -0,0 +1,161 @@ +/* + * Copyright 2020 IBM Corp. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const clone = require('clone'); + +const ERR_NODATA = 'make sure your data exists in the correct location and is in the expected format.'; +const KIND_MAP = new Map([ + ['secretKeyRef', 'Secret'], + ['secretMapRef', 'Secret'], + ['configMapRef', 'ConfigMap'], + ['configMapKeyRef', 'ConfigMap'] +]); + +module.exports = class ReqOpts { + + get [Symbol.toStringTag]() { + return 'ReqOpts'; + } + + constructor(controllerObject) { + if (!controllerObject) { + throw Error('ReqOpts must have: controller object instance'); + } + this.data = controllerObject.data; + this.namespace = this.data?.object?.metadata?.namespace; + this.kubeResourceMeta = controllerObject.kubeResourceMeta; + this.kubeClass = controllerObject.kubeClass; + this.api = this.kubeResourceMeta.request.bind(this.kubeResourceMeta); + this.updateRazeeLogs = controllerObject.updateRazeeLogs ? + ((logLevel, log) => { controllerObject.updateRazeeLogs(logLevel, log); }) : + (() => { log.debug('\'updateRazeeLogs()\' not passed to fetchEnvs. will not update razeeLogs on failure to fetch envs'); }); + } + + #secretMapRef(conf) { + return this.#genericMapRef(conf, 'secretMapRef', true); + } + + #configMapRef(conf) { + return this.#genericMapRef(conf, 'configMapRef'); + } + + async #genericMapRef(conf, valueFrom = 'genericMapRef', decode = false) { + let resource; + let kubeError = ERR_NODATA; + const ref = conf[valueFrom]; + const optional = !!conf.optional; + + const { + apiVersion = 'v1', + kind = KIND_MAP.get(valueFrom), + namespace = this.namespace, + name + } = ref; + + const krm = await this.kubeClass.getKubeResourceMeta(apiVersion, kind, 'update'); + + if (krm) { + try { + resource = await krm.get(name, namespace); + } catch (error) { + kubeError = error.message; + } + } + + const data = resource?.data; + + if (!data) { + const msg = `failed to get envFrom: ${JSON.stringify(conf)}. ${kubeError}`; + if (!optional) throw new Error(msg); + log.warn(msg); + this.updateRazeeLogs('warn', { controller: 'ReqOpts', message: msg }); + return { ...conf, data }; + } + + if (decode) { + for (const [key, value] of Object.entries(data)) { + data[key] = Buffer.from(value, 'base64').toString(); + } + } + let ret = { ...conf, data }; + + return ret; + } + + #processEnvFrom(envFrom) { + return Promise.all(envFrom.map((element) => { + const { configMapRef, secretMapRef, genericMapRef } = element; + + if (!configMapRef && !secretMapRef && !genericMapRef) { + throw new Error(`oneOf configMapRef, secretMapRef, genericMapRef must be defined. Got: ${JSON.stringify(element)}`); + } + + if (configMapRef) return this.#configMapRef(element); + if (secretMapRef) return this.#secretMapRef(element); + return this.#genericMapRef(element); + })); + } + + async get(requestOptions) { + + requestOptions = clone(requestOptions); + + let envFrom = objectPath.get(requestOptions, 'envFrom'); + + let envFromTemp = await this.#processEnvFrom(envFrom); + let headers = objectPath.get(requestOptions, 'headers'); + + for (const env of envFromTemp) { + const envdata = env?.data; + headers = { ...headers, ...envdata }; + } + + requestOptions = { ...requestOptions, headers }; + + return requestOptions; + + } +}; + +const objectPath = { + get: function (obj, path, def) { + if (typeof path === 'string') { + const output = []; + path.split('.').forEach(function (item) { + // Split to an array with bracket notation + item.split(/\[([^}]+)\]/g).forEach(function (key) { + // Push to the new array + if (key.length > 0) { + output.push(key); + } + }); + }); + path = output; + } + + // Cache the current object + var current = obj; + // For each item in the path, dig into the object + for (var i = 0; i < path.length; i++) { + // If the item isn't found, return the default (or null) + if (!current[path[i]]) return def; + // Otherwise, update the current value + current = current[path[i]]; + } + + return current; + } +}; From 5155caafaf5d818607d2c2803a7b03ef3fd5b335 Mon Sep 17 00:00:00 2001 From: Katie Dai Date: Thu, 11 Aug 2022 14:04:29 -0400 Subject: [PATCH 2/7] check for envFrom --- lib/ReqOpts.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/ReqOpts.js b/lib/ReqOpts.js index aa3bc2b..72d714a 100644 --- a/lib/ReqOpts.js +++ b/lib/ReqOpts.js @@ -115,15 +115,18 @@ module.exports = class ReqOpts { let envFrom = objectPath.get(requestOptions, 'envFrom'); - let envFromTemp = await this.#processEnvFrom(envFrom); - let headers = objectPath.get(requestOptions, 'headers'); + if (envFrom) { + let envFromTemp = await this.#processEnvFrom(envFrom); + let headers = objectPath.get(requestOptions, 'headers'); - for (const env of envFromTemp) { - const envdata = env?.data; - headers = { ...headers, ...envdata }; - } + for (const env of envFromTemp) { + const envdata = env?.data; + headers = { ...headers, ...envdata }; + } - requestOptions = { ...requestOptions, headers }; + requestOptions = { ...requestOptions, headers }; + } + return requestOptions; From 02dfbed6df908c127a958585d074e5bce5b2d659 Mon Sep 17 00:00:00 2001 From: Katie Dai Date: Thu, 11 Aug 2022 14:26:28 -0400 Subject: [PATCH 3/7] add log --- lib/ReqOpts.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/ReqOpts.js b/lib/ReqOpts.js index 72d714a..73371a9 100644 --- a/lib/ReqOpts.js +++ b/lib/ReqOpts.js @@ -15,6 +15,7 @@ */ const clone = require('clone'); +const log = require('./bunyan-api').createLogger('reqOpts'); const ERR_NODATA = 'make sure your data exists in the correct location and is in the expected format.'; const KIND_MAP = new Map([ From db82f31ad960b447925fef80553824c193922e01 Mon Sep 17 00:00:00 2001 From: Katie Dai Date: Thu, 11 Aug 2022 15:11:28 -0400 Subject: [PATCH 4/7] change get headers to use reuse fetchEnvs --- lib/BaseDownloadController.js | 6 +- lib/FetchEnvs.js | 19 ++++ lib/ReqOpts.js | 165 ---------------------------------- 3 files changed, 22 insertions(+), 168 deletions(-) delete mode 100644 lib/ReqOpts.js diff --git a/lib/BaseDownloadController.js b/lib/BaseDownloadController.js index a224f3f..7f57b70 100644 --- a/lib/BaseDownloadController.js +++ b/lib/BaseDownloadController.js @@ -22,7 +22,7 @@ const clone = require('clone'); const CompositeController = require('./CompositeController'); -const ReqOpts = require('./ReqOpts'); +const FetchEnvs = require('./FetchEnvs'); module.exports = class BaseDownloadController extends CompositeController { constructor(params) { @@ -197,8 +197,8 @@ module.exports = class BaseDownloadController extends CompositeController { } } - let reqopt = new ReqOpts(this); - requestOptions = await reqopt.get(requestOptions); + let reqopt = new FetchEnvs(this); + requestOptions = await reqopt.getHeaders(requestOptions); return requestOptions; } diff --git a/lib/FetchEnvs.js b/lib/FetchEnvs.js index ca6dbb3..9f5379b 100644 --- a/lib/FetchEnvs.js +++ b/lib/FetchEnvs.js @@ -16,6 +16,7 @@ const merge = require('deepmerge'); const log = require('./bunyan-api').createLogger('fetchEnvs'); +const clone = require('clone'); const STRING = 'string'; const OBJECT = 'object'; @@ -227,6 +228,24 @@ module.exports = class FetchEnvs { const env = objectPath.get(this.data, `object.${path}.env`, []); return (await this.#processEnv(env)).reduce(reduceEnv, result); } + + async getHeaders(requestOptions) { + + requestOptions = clone(requestOptions); + + let headersFrom = objectPath.get(requestOptions, 'headersFrom'); + if (headersFrom) { + let headersFromTemp = await this.#processEnvFrom(headersFrom); + let headers = objectPath.get(requestOptions, 'headers'); + for (const env of headersFromTemp) { + const envdata = env?.data; + headers = { ...headers, ...envdata }; + } + requestOptions = { ...requestOptions, headers }; + } + + return requestOptions; + } }; function reduceItemList(ref, strategy, decode) { diff --git a/lib/ReqOpts.js b/lib/ReqOpts.js deleted file mode 100644 index 73371a9..0000000 --- a/lib/ReqOpts.js +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright 2020 IBM Corp. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -const clone = require('clone'); -const log = require('./bunyan-api').createLogger('reqOpts'); - -const ERR_NODATA = 'make sure your data exists in the correct location and is in the expected format.'; -const KIND_MAP = new Map([ - ['secretKeyRef', 'Secret'], - ['secretMapRef', 'Secret'], - ['configMapRef', 'ConfigMap'], - ['configMapKeyRef', 'ConfigMap'] -]); - -module.exports = class ReqOpts { - - get [Symbol.toStringTag]() { - return 'ReqOpts'; - } - - constructor(controllerObject) { - if (!controllerObject) { - throw Error('ReqOpts must have: controller object instance'); - } - this.data = controllerObject.data; - this.namespace = this.data?.object?.metadata?.namespace; - this.kubeResourceMeta = controllerObject.kubeResourceMeta; - this.kubeClass = controllerObject.kubeClass; - this.api = this.kubeResourceMeta.request.bind(this.kubeResourceMeta); - this.updateRazeeLogs = controllerObject.updateRazeeLogs ? - ((logLevel, log) => { controllerObject.updateRazeeLogs(logLevel, log); }) : - (() => { log.debug('\'updateRazeeLogs()\' not passed to fetchEnvs. will not update razeeLogs on failure to fetch envs'); }); - } - - #secretMapRef(conf) { - return this.#genericMapRef(conf, 'secretMapRef', true); - } - - #configMapRef(conf) { - return this.#genericMapRef(conf, 'configMapRef'); - } - - async #genericMapRef(conf, valueFrom = 'genericMapRef', decode = false) { - let resource; - let kubeError = ERR_NODATA; - const ref = conf[valueFrom]; - const optional = !!conf.optional; - - const { - apiVersion = 'v1', - kind = KIND_MAP.get(valueFrom), - namespace = this.namespace, - name - } = ref; - - const krm = await this.kubeClass.getKubeResourceMeta(apiVersion, kind, 'update'); - - if (krm) { - try { - resource = await krm.get(name, namespace); - } catch (error) { - kubeError = error.message; - } - } - - const data = resource?.data; - - if (!data) { - const msg = `failed to get envFrom: ${JSON.stringify(conf)}. ${kubeError}`; - if (!optional) throw new Error(msg); - log.warn(msg); - this.updateRazeeLogs('warn', { controller: 'ReqOpts', message: msg }); - return { ...conf, data }; - } - - if (decode) { - for (const [key, value] of Object.entries(data)) { - data[key] = Buffer.from(value, 'base64').toString(); - } - } - let ret = { ...conf, data }; - - return ret; - } - - #processEnvFrom(envFrom) { - return Promise.all(envFrom.map((element) => { - const { configMapRef, secretMapRef, genericMapRef } = element; - - if (!configMapRef && !secretMapRef && !genericMapRef) { - throw new Error(`oneOf configMapRef, secretMapRef, genericMapRef must be defined. Got: ${JSON.stringify(element)}`); - } - - if (configMapRef) return this.#configMapRef(element); - if (secretMapRef) return this.#secretMapRef(element); - return this.#genericMapRef(element); - })); - } - - async get(requestOptions) { - - requestOptions = clone(requestOptions); - - let envFrom = objectPath.get(requestOptions, 'envFrom'); - - if (envFrom) { - let envFromTemp = await this.#processEnvFrom(envFrom); - let headers = objectPath.get(requestOptions, 'headers'); - - for (const env of envFromTemp) { - const envdata = env?.data; - headers = { ...headers, ...envdata }; - } - - requestOptions = { ...requestOptions, headers }; - } - - - return requestOptions; - - } -}; - -const objectPath = { - get: function (obj, path, def) { - if (typeof path === 'string') { - const output = []; - path.split('.').forEach(function (item) { - // Split to an array with bracket notation - item.split(/\[([^}]+)\]/g).forEach(function (key) { - // Push to the new array - if (key.length > 0) { - output.push(key); - } - }); - }); - path = output; - } - - // Cache the current object - var current = obj; - // For each item in the path, dig into the object - for (var i = 0; i < path.length; i++) { - // If the item isn't found, return the default (or null) - if (!current[path[i]]) return def; - // Otherwise, update the current value - current = current[path[i]]; - } - - return current; - } -}; From e359f7ebd2804067e305b6577f04558d2281cb93 Mon Sep 17 00:00:00 2001 From: Katie Dai Date: Fri, 12 Aug 2022 09:40:31 -0400 Subject: [PATCH 5/7] move to baseDownloadController --- lib/BaseDownloadController.js | 12 +++++++++++- lib/FetchEnvs.js | 23 ++--------------------- 2 files changed, 13 insertions(+), 22 deletions(-) diff --git a/lib/BaseDownloadController.js b/lib/BaseDownloadController.js index 7f57b70..34f49cd 100644 --- a/lib/BaseDownloadController.js +++ b/lib/BaseDownloadController.js @@ -198,7 +198,17 @@ module.exports = class BaseDownloadController extends CompositeController { } let reqopt = new FetchEnvs(this); - requestOptions = await reqopt.getHeaders(requestOptions); + let headersFrom = objectPath.get(requestOptions, 'headersFrom'); + if (headersFrom) { + let headersFromTemp = await reqopt.processEnvFrom(headersFrom); + let headers = objectPath.get(requestOptions, 'headers'); + for (const header of headersFromTemp) { + const data = header?.data; + headers = { ...headers, ...data }; + } + requestOptions = { ...requestOptions, headers }; + } + return requestOptions; } diff --git a/lib/FetchEnvs.js b/lib/FetchEnvs.js index 9f5379b..397ed55 100644 --- a/lib/FetchEnvs.js +++ b/lib/FetchEnvs.js @@ -16,7 +16,6 @@ const merge = require('deepmerge'); const log = require('./bunyan-api').createLogger('fetchEnvs'); -const clone = require('clone'); const STRING = 'string'; const OBJECT = 'object'; @@ -179,7 +178,7 @@ module.exports = class FetchEnvs { } - #processEnvFrom(envFrom) { + processEnvFrom(envFrom) { return Promise.all(envFrom.map((element) => { const { configMapRef, secretMapRef, genericMapRef } = element; @@ -219,7 +218,7 @@ module.exports = class FetchEnvs { path = path.replace(/^\.*|\.*$|(\.envFrom\.*$)|(\.env\.*$)/g, ''); let envFrom = objectPath.get(this.data, `object.${path}.envFrom`, []); - envFrom = await this.#processEnvFrom(envFrom); + envFrom = await this.processEnvFrom(envFrom); for (const env of envFrom) { const data = env?.data ?? {}; result = { ...result, ...data }; @@ -228,24 +227,6 @@ module.exports = class FetchEnvs { const env = objectPath.get(this.data, `object.${path}.env`, []); return (await this.#processEnv(env)).reduce(reduceEnv, result); } - - async getHeaders(requestOptions) { - - requestOptions = clone(requestOptions); - - let headersFrom = objectPath.get(requestOptions, 'headersFrom'); - if (headersFrom) { - let headersFromTemp = await this.#processEnvFrom(headersFrom); - let headers = objectPath.get(requestOptions, 'headers'); - for (const env of headersFromTemp) { - const envdata = env?.data; - headers = { ...headers, ...envdata }; - } - requestOptions = { ...requestOptions, headers }; - } - - return requestOptions; - } }; function reduceItemList(ref, strategy, decode) { From ed1d376f0737f26e042df04f05932272c0b99b0e Mon Sep 17 00:00:00 2001 From: Katie Dai Date: Fri, 12 Aug 2022 12:38:28 -0400 Subject: [PATCH 6/7] use const change name --- lib/BaseDownloadController.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/BaseDownloadController.js b/lib/BaseDownloadController.js index 34f49cd..cb1cfd1 100644 --- a/lib/BaseDownloadController.js +++ b/lib/BaseDownloadController.js @@ -197,16 +197,16 @@ module.exports = class BaseDownloadController extends CompositeController { } } - let reqopt = new FetchEnvs(this); - let headersFrom = objectPath.get(requestOptions, 'headersFrom'); + const reqopt = new FetchEnvs(this); + const headersFrom = objectPath.get(requestOptions, 'headersFrom'); if (headersFrom) { - let headersFromTemp = await reqopt.processEnvFrom(headersFrom); - let headers = objectPath.get(requestOptions, 'headers'); + const headersFromTemp = await reqopt.processEnvFrom(headersFrom); + let mergedHeaders = { ...headers } for (const header of headersFromTemp) { const data = header?.data; - headers = { ...headers, ...data }; + mergedHeaders = { ...mergedHeaders, ...data }; } - requestOptions = { ...requestOptions, headers }; + requestOptions = { ...requestOptions, headers: mergedHeaders }; } return requestOptions; From a8e59ac2607f5e3f12aea3a057daf3c172f8dd4b Mon Sep 17 00:00:00 2001 From: Katie Dai Date: Fri, 12 Aug 2022 13:00:33 -0400 Subject: [PATCH 7/7] fix lint error --- lib/BaseDownloadController.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/BaseDownloadController.js b/lib/BaseDownloadController.js index cb1cfd1..9db75c8 100644 --- a/lib/BaseDownloadController.js +++ b/lib/BaseDownloadController.js @@ -201,7 +201,7 @@ module.exports = class BaseDownloadController extends CompositeController { const headersFrom = objectPath.get(requestOptions, 'headersFrom'); if (headersFrom) { const headersFromTemp = await reqopt.processEnvFrom(headersFrom); - let mergedHeaders = { ...headers } + let mergedHeaders = { ...headers }; for (const header of headersFromTemp) { const data = header?.data; mergedHeaders = { ...mergedHeaders, ...data };