apps/firelens-stability/lib/utils/config-utils.ts (136 lines of code) (raw):
import { evaluateTemplateString } from "../templating/handlebars-templater.js";
import * as Path from "path"
import * as Constants from "../constants.js"
import { getJsonFromFile } from "./utils.js";
export function cascadeLists<IComponentType>(lists: IComponentType[][]): IComponentType[] {
/* An adapter to the cascadeConfigurationLists function */
const adapter = lists.map(l => ({
config: {
"lists.adapter": l
},
definitions: {},
managed: {},
}));
const cascade = cascadeConfigurationLists(adapter as any as IGenericConfig[]);
return cascade.config["lists.adapter"];
}
export function cascadeConfigurationLists(configs: IGenericConfig[]) {
/* Treat config lists differently */
/* Merge each list and override same named entries */
/* Find config lists */
const configLists = configs.map(config =>
Object.entries(config?.config ?? [])
.filter(([k, _]) => k.startsWith("lists."))
);
/* Mutate configLists into entries - overrides are last */
const entries = configLists.map(
list => list.map(([k,v]) => {
return {
"listName": k,
"items": v
}
}
)).flat();
/* Build merged lists abstract structure */
const mergedListsAbstract: {[listName: string]: {[itemName: string]: any}} = {};
entries.forEach(entry => {
entry.items.forEach(item => {
/* In most javascript implementations, order is preserved, duplicates are not */
mergedListsAbstract[entry.listName] = mergedListsAbstract[entry.listName] ?? [];
mergedListsAbstract[entry.listName][item.name ?? "null"] = item; /* preserve unnamed with random name */
});
});
/* Rebuild merged lists */
const mergedEntries = Object.entries(mergedListsAbstract).map(([listName,items]) => {
const itemsList = Object.entries(items).map(([name, item]) => item);
/* Stable sort lists by order */
const sortedItemsList = itemsList.sort((a, b) => a?.order ?? 1 - b?.order ?? 1)
return [
listName, sortedItemsList
]
})
const mergedLists = Object.fromEntries(mergedEntries);
/* Cascade the configuration objects */
let cascadedConfig: IGenericConfig = {} as IGenericConfig;
configs.forEach(config => {
cascadedConfig = {
config: {
...(cascadedConfig?.config ?? {}),
...config.config,
},
definitions: {
...(cascadedConfig?.definitions ?? {}),
...config.definitions,
},
managed: {
...(cascadedConfig?.managed ?? {}),
...config.managed,
}
};
});
/* Add merged lists to config object */
cascadedConfig = {
config: {
...(cascadedConfig?.config ?? {}),
...mergedLists,
},
definitions: cascadedConfig.definitions,
managed: cascadedConfig.managed,
}
return cascadedConfig;
}
export function cascadeConfigurationObjects(extensionConfig: IGenericConfig, baseConfig: IGenericConfig) {
return cascadeConfigurationLists([baseConfig, extensionConfig]);
}
export function cascadeConfigurationObjectsConfig(extensionConfig: IGenericConfig, baseConfig: IGenericConfig) {
const cascade = cascadeConfigurationObjects(extensionConfig, baseConfig);
return {
...baseConfig,
config: cascade.config,
}
}
export function cascadeConfigurationObjectsDefinitions(extensionConfig: IGenericConfig, baseConfig: IGenericConfig) {
const cascade = cascadeConfigurationObjects(extensionConfig, baseConfig);
return {
...baseConfig,
definitions: cascade.definitions,
}
}
export function cascadeConfigurationStringAsExtensionBasic(cascadedConfigString: string, baseConfig: IGenericConfig) {
const nextConfigLayerString = evaluateTemplateString(cascadedConfigString, baseConfig);
try {
const nextConfigLayer = JSON.parse(nextConfigLayerString);
/* Apply new configuraion layer */
return cascadeConfigurationObjects(nextConfigLayer, baseConfig);
} catch (e) {
console.log("Unable to parse templated json configuration from string:");
console.log(nextConfigLayerString);
throw e;
}
}
/*
* Cascade configuration
* Parent configuration file -> configuration file -> [conf -> def, def -> conf]
*/
export function cascadeConfigurationStringAsExtension(cascadedConfigString: string, baseConfig: IGenericConfig) {
/* Apply new configuraion layer */
const cascadeConfigBase = cascadeConfigurationStringAsExtensionBasic(cascadedConfigString, baseConfig);
/* Allow definitions to use self config variables */
const baseConfigUpdateConfig = cascadeConfigurationObjectsConfig(cascadeConfigBase, baseConfig);
const cascadeConfigUpdateDefinition = cascadeConfigurationStringAsExtensionBasic(cascadedConfigString, baseConfigUpdateConfig);
const cascadeConfigUpdate1 = cascadeConfigurationObjectsDefinitions(cascadeConfigUpdateDefinition, cascadeConfigBase);
/* Allow config to use self definition variables */
const baseConfigUpdateDefinition = cascadeConfigurationObjectsDefinitions(cascadeConfigBase, baseConfig);
const cascadeConfigUpdateConfig = cascadeConfigurationStringAsExtensionBasic(cascadedConfigString, baseConfigUpdateDefinition);
const cascadeConfigUpdate2 = cascadeConfigurationObjectsConfig(cascadeConfigUpdateConfig, cascadeConfigUpdate1);
return cascadeConfigUpdate2;
}
export function cascadeConfigurationStringAsDefault(baseString: string, extensionConfig: IGenericConfig) {
const cascadeExtended = cascadeConfigurationStringAsExtension(baseString, extensionConfig);
/* Restore pre-existing conflict fields */
return cascadeConfigurationObjects(extensionConfig, cascadeExtended);
}
export async function getTestCaseTaskDefinition(testCase: ITestCase): Promise<AWS.ECS.Types.RegisterTaskDefinitionRequest> {
const taskPath = Path.join(testCase.local.archiveLocalPath, Constants.fileNames.taskDefinition);
return await getJsonFromFile(taskPath);
}
export async function validateTestConfig(config: IGenericConfig) {
const checkNotNull = [
"template",
"cluster",
"region",
"taskCount",
"taskVpcSubnets",
"taskVpcSecurityGroups"
];
let err = false;
checkNotNull.forEach(c => {
if (config.config[c] === undefined) {
console.log(`Configuration error on test: ` +
`${config.managed.caseNameUnique}. Please ensure ${c} is set in "config"`);
}
});
if (err) {
throw "error";
}
}