Skip to content

Commit 7a9016e

Browse files
committed
chore: updated
1 parent a808ba2 commit 7a9016e

3 files changed

Lines changed: 222 additions & 4 deletions

File tree

packages/core/src/config/index.js

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -183,11 +183,12 @@ module.exports.normalize = ({ userConfig = {}, finalConfigBase = {}, defaultsOve
183183
* @param {{ userConfig?: InstanaConfig|null, defaultConfig?: InstanaConfig, finalConfig?: InstanaConfig }} [options]
184184
*/
185185
function normalizeServiceName({ userConfig = {}, defaultConfig = {}, finalConfig = {} } = {}) {
186-
finalConfig.serviceName = util.resolveStringConfig({
187-
envVar: 'INSTANA_SERVICE_NAME',
188-
configValue: userConfig.serviceName,
186+
finalConfig.serviceName = util.get({
187+
key: 'serviceName',
188+
envKey: 'INSTANA_SERVICE_NAME',
189+
inCodeValue: userConfig.serviceName,
189190
defaultValue: defaultConfig.serviceName,
190-
configPath: 'config.serviceName'
191+
type: 'STR'
191192
});
192193
}
193194

@@ -762,3 +763,17 @@ function normalizePreloadOpentelemetry({ userConfig = {}, defaultConfig = {}, fi
762763
finalConfig.preloadOpentelemetry = defaultConfig.preloadOpentelemetry;
763764
}
764765
}
766+
767+
exports.update = function update({ userConfig = {}, sourceName }) {
768+
const updatedServiceName = util.update({
769+
key: 'serviceName',
770+
newValue: userConfig.serviceName,
771+
sourceName: sourceName,
772+
type: 'STR'
773+
});
774+
775+
// Return a config object with the updated serviceName
776+
return {
777+
serviceName: updatedServiceName
778+
};
779+
};

packages/core/src/config/util.js

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,3 +229,195 @@ exports.resolveStringConfig = function resolveStringConfig({ envVar, configValue
229229
}
230230
return defaultValue;
231231
};
232+
233+
// ============================================================================
234+
// Dynamic Config Resolution
235+
// ============================================================================
236+
237+
/**
238+
* @enum {string}
239+
*/
240+
const SOURCE = {
241+
ENV: 'ENV',
242+
IN_CODE: 'IN_CODE',
243+
AGENT: 'AGENT',
244+
DEFAULT: 'DEFAULT'
245+
};
246+
247+
/**
248+
* Configuration source priority levels
249+
* Higher number = higher priority in precedence resolution
250+
*
251+
* To change precedence, simply modify the numbers here.
252+
* For example, to make AGENT higher priority than IN_CODE:
253+
* AGENT: 3, IN_CODE: 2
254+
*
255+
* @type {Record<string, number>}
256+
*/
257+
const SOURCE_PRIORITY = {
258+
[SOURCE.ENV]: 4,
259+
[SOURCE.IN_CODE]: 3,
260+
[SOURCE.AGENT]: 2,
261+
[SOURCE.DEFAULT]: 1
262+
};
263+
/**
264+
* @typedef {Object} TypeSchema
265+
* @property {function(any): any} coerce
266+
* @property {function(any): boolean} validate
267+
*/
268+
269+
/** @type {Object.<string, TypeSchema>} */
270+
const TypeSchemas = {
271+
STR: {
272+
coerce: v => (v != null ? String(v).trim() : null),
273+
validate: v => typeof v === 'string' && v.length > 0 && v !== 'null' && v !== 'undefined'
274+
},
275+
NUM: {
276+
coerce: v => (v !== '' ? Number(v) : NaN),
277+
validate: v => typeof v === 'number' && !isNaN(v)
278+
},
279+
BOOL: {
280+
coerce: v => {
281+
if (typeof v === 'boolean') return v;
282+
if (v === 'true' || v === '1') return true;
283+
if (v === 'false' || v === '0') return false;
284+
return null;
285+
},
286+
validate: v => typeof v === 'boolean'
287+
}
288+
};
289+
290+
/**
291+
* @typedef {Object} ConfigEntry
292+
* @property {any} value - The resolved configuration value
293+
* @property {string} source - The source name (ENV, IN_CODE, AGENT, DEFAULT)
294+
*/
295+
296+
/**
297+
* Central configuration state store
298+
* @type {Object.<string, ConfigEntry>}
299+
*/
300+
const configStore = {};
301+
302+
/**
303+
* Resolves a configuration value during initial normalization
304+
* Follows the priority order: ENV > IN_CODE > DEFAULT
305+
*
306+
* @param {Object} params
307+
* @param {string} params.key - Configuration key name
308+
* @param {string} params.envKey - Environment variable name
309+
* @param {any} params.inCodeValue - User-provided in-code value
310+
* @param {any} params.defaultValue - Default fallback value
311+
* @param {'STR'|'NUM'|'BOOL'} [params.type='STR'] - Value type
312+
* @returns {any} The resolved configuration value
313+
*/
314+
exports.get = function get({ key, envKey, inCodeValue, defaultValue, type = 'STR' }) {
315+
const schema = TypeSchemas[type];
316+
if (!schema) {
317+
logger.warn(`Unknown type "${type}" for config key "${key}". Defaulting to STR.`);
318+
return defaultValue;
319+
}
320+
321+
// Resolution order: ENV > IN_CODE > DEFAULT
322+
const sources = [
323+
{ name: SOURCE.ENV, value: process.env[envKey] },
324+
{ name: SOURCE.IN_CODE, value: inCodeValue },
325+
{ name: SOURCE.DEFAULT, value: defaultValue }
326+
];
327+
328+
// eslint-disable-next-line no-restricted-syntax
329+
for (const source of sources) {
330+
if (source.value === undefined || source.value === null) {
331+
continue;
332+
}
333+
334+
const coerced = schema.coerce(source.value);
335+
if (schema.validate(coerced)) {
336+
configStore[key] = {
337+
value: coerced,
338+
source: source.name
339+
};
340+
341+
logger.debug(`[config] Resolved "${key}" from ${source.name}: ${JSON.stringify(coerced)})`);
342+
343+
return coerced;
344+
}
345+
346+
logger.warn(`[config] Invalid ${type} value for "${key}" from ${source.name}: ${JSON.stringify(source.value)}`);
347+
}
348+
349+
logger.debug(`[config] No valid value found for "${key}", using default: ${JSON.stringify(defaultValue)}`);
350+
return defaultValue;
351+
};
352+
353+
/**
354+
* Updates a configuration value dynamically (e.g., from agent)
355+
* Respects the precedence hierarchy
356+
*
357+
* @param {Object} params
358+
* @param {string} params.key - Configuration key name
359+
* @param {any} params.newValue - New value to set
360+
* @param {string} params.sourceName - Source name (must be a valid SOURCE key)
361+
* @param {'STR'|'NUM'|'BOOL'} [params.type='STR'] - Value type
362+
* @returns {any} The current configuration value (may be unchanged if update was rejected)
363+
*/
364+
exports.update = function update({ key, newValue, sourceName, type = 'STR' }) {
365+
const schema = TypeSchemas[type];
366+
if (!schema) {
367+
logger.warn(`Unknown type "${type}" for config key "${key}". Update rejected.`);
368+
return configStore[key]?.value || null;
369+
}
370+
371+
const current = configStore[key];
372+
const incomingPriority = SOURCE_PRIORITY[sourceName];
373+
374+
if (incomingPriority === undefined) {
375+
logger.warn(`Invalid source name "${sourceName}" for config key "${key}". Update rejected.`);
376+
return current?.value || null;
377+
}
378+
379+
if (current) {
380+
const currentPriority = SOURCE_PRIORITY[current.source];
381+
if (incomingPriority <= currentPriority) {
382+
logger.info(
383+
`[config] Rejected "${key}" update from ${sourceName} (priority: ${incomingPriority}): ` +
384+
`${current.source} (priority: ${currentPriority}) has higher or equal precedence. ` +
385+
`Current value: ${JSON.stringify(current.value)}`
386+
);
387+
return current.value;
388+
}
389+
}
390+
391+
const coerced = schema.coerce(newValue);
392+
if (!schema.validate(coerced)) {
393+
logger.warn(
394+
`[config] Rejected "${key}" update from ${sourceName}: Invalid ${type} value: ${JSON.stringify(newValue)}`
395+
);
396+
return current?.value || null;
397+
}
398+
399+
configStore[key] = {
400+
value: coerced,
401+
source: sourceName
402+
};
403+
404+
logger.info(
405+
`[config] Updated "${key}" from ${sourceName}: ${JSON.stringify(coerced)} (priority: ${incomingPriority})`
406+
);
407+
408+
return coerced;
409+
};
410+
411+
/**
412+
* @param {string} key
413+
* @returns {ConfigEntry|null}
414+
*/
415+
exports.getConfigEntry = function getConfigEntry(key) {
416+
return configStore[key] || null;
417+
};
418+
419+
exports.clearConfigStore = function clearConfigStore() {
420+
Object.keys(configStore).forEach(key => {
421+
delete configStore[key];
422+
});
423+
};

packages/core/test/config/normalizeConfig_test.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,17 @@ describe('config.normalizeConfig', () => {
6262
expect(config.serviceName).to.equal('custom-service-name');
6363
});
6464

65+
it('should accept agent service name', () => {
66+
let config = coreConfig.normalize({ userConfig: { serviceName: 'custom-service-name' } });
67+
config = coreConfig.update({
68+
userConfig: {
69+
serviceName: 'agent-service-name',
70+
transmissionDelay: 5000
71+
},
72+
sourceName: 'AGENT'
73+
});
74+
expect(config.serviceName).to.equal('custom-service-name');
75+
});
6576
it('should accept service name from env var', () => {
6677
process.env.INSTANA_SERVICE_NAME = 'very-custom-service-name';
6778
const config = coreConfig.normalize();

0 commit comments

Comments
 (0)