@syntropysoft/praetorian
Version:
Praetorian CLI – A universal multi-environment configuration validator for DevSecOps teams. Validate, compare, and secure YAML/ENV files with ease.
474 lines • 17.7 kB
JavaScript
;
/**
* @file src/application/services/rule-loading/DevSecOpsRuleConnector.ts
* @description DevSecOps Rule Connector - Multi-source rule loading (SOLID SRP + Functional Programming)
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DevSecOpsRuleConnector = void 0;
const ultra_simple_core_rules_1 = require("../../../shared/rules/ultra-simple-core-rules");
const UltraSimpleRuleDictionary_1 = require("./UltraSimpleRuleDictionary");
const promises_1 = __importDefault(require("fs/promises"));
const path_1 = __importDefault(require("path"));
const js_yaml_1 = __importDefault(require("js-yaml"));
const promises_2 = require("fs/promises");
const path_2 = require("path");
/**
* @class DevSecOpsRuleConnector
* @description Connects rule dictionary with multiple sources (SOLID SRP)
*/
class DevSecOpsRuleConnector {
constructor(workingDirectory = process.cwd()) {
this.workingDirectory = workingDirectory;
}
/**
* Loads rules from DevSecOps configuration (Main Orchestrator Method)
* @param config - DevSecOps configuration
* @param context - Loading context
* @returns Promise with loaded rules and metadata
*/
async loadRulesFromConfig(config, context) {
const startTime = Date.now();
// Guard clause: no config
if (!config) {
return this.createEmptyResult(['Configuration is required']);
}
// Guard clause: no environment
if (!context.environment) {
return this.createEmptyResult(['Environment is required']);
}
try {
// Validate configuration
const validationErrors = this.validateConfig(config);
if (validationErrors.length > 0) {
return this.createEmptyResult(validationErrors);
}
// Get environment configuration
const envConfig = this.getEnvironmentConfig(config, context.environment);
// Load rules from all sources
const sourceResults = await this.loadFromSources(config.sources, envConfig, context);
// Merge all results
const mergedResult = this.mergeSourceResults(sourceResults);
// Apply environment-specific overrides
const finalResult = this.applyEnvironmentOverrides(mergedResult, envConfig, config);
return {
rules: finalResult.dictionary,
errors: this.extractErrors(sourceResults),
warnings: this.extractWarnings(sourceResults),
metadata: {
sourcesLoaded: sourceResults.map(r => r.sourceName),
environmentsProcessed: [context.environment],
totalRules: Object.keys(finalResult.dictionary).length,
duplicateRules: this.extractWarnings(sourceResults).filter(w => w.includes('already exists')).length,
loadDuration: Date.now() - startTime,
},
};
}
catch (error) {
return this.createEmptyResult([`Failed to load rules: ${error.message}`]);
}
}
/**
* Loads rules from a specific source (Pure Function with Guard Clauses)
* @param source - Source configuration
* @param sourceName - Name of the source
* @param context - Loading context
* @returns Promise with source load result
*/
async loadFromSource(source, sourceName, context) {
// Guard clause: source disabled
if (source.enabled === false) {
return {
sourceName,
rules: {},
errors: [],
warnings: [`Source '${sourceName}' is disabled`],
success: true,
};
}
try {
switch (source.type) {
case 'core':
return this.loadCoreRules(source, sourceName);
case 'local':
return await this.loadLocalRules(source, sourceName, context);
case 'remote':
return await this.loadRemoteRules(source, sourceName, context);
case 'package':
return await this.loadPackageRules(source, sourceName, context);
case 'git':
return await this.loadGitRules(source, sourceName, context);
default:
return {
sourceName,
rules: {},
errors: [`Unknown source type: ${source.type}`],
warnings: [],
success: false,
};
}
}
catch (error) {
return {
sourceName,
rules: {},
errors: [`Failed to load from source '${sourceName}': ${error.message}`],
warnings: [],
success: false,
};
}
}
/**
* Loads core rules (Pure Function)
* @param source - Source configuration
* @param sourceName - Source name
* @returns Source load result
*/
loadCoreRules(source, sourceName) {
// Guard clause: core rules disabled in context
if (source.enabled === false) {
return {
sourceName,
rules: {},
errors: [],
warnings: [`Core rules disabled for source '${sourceName}'`],
success: true,
};
}
return {
sourceName,
rules: ultra_simple_core_rules_1.ALL_CORE_RULES,
errors: [],
warnings: [],
success: true,
};
}
/**
* Loads local rules from file system (Pure Function with Guard Clauses)
* @param source - Source configuration
* @param sourceName - Source name
* @param context - Loading context
* @returns Promise with source load result
*/
async loadLocalRules(source, sourceName, context) {
// Guard clause: no path specified
if (!source.path) {
return {
sourceName,
rules: {},
errors: [`Local source '${sourceName}' has no path specified`],
warnings: [],
success: false,
};
}
try {
const fullPath = path_1.default.isAbsolute(source.path)
? source.path
: path_1.default.join(context.workingDirectory, source.path);
// Check if it's a glob pattern
if (source.path.includes('*') || source.path.includes('?')) {
return await this.loadLocalGlobRules(fullPath, sourceName);
}
else {
return await this.loadLocalSingleFile(fullPath, sourceName);
}
}
catch (error) {
return {
sourceName,
rules: {},
errors: [`Failed to load local rules from '${source.path}': ${error.message}`],
warnings: [],
success: false,
};
}
}
/**
* Loads rules from directory pattern (Pure Function)
* @param patternPath - Pattern path (simplified for now)
* @param sourceName - Source name
* @returns Promise with source load result
*/
async loadLocalGlobRules(patternPath, sourceName) {
try {
// For now, handle simple directory patterns
const dirPath = patternPath.replace('/*.yaml', '').replace('/*.yml', '');
const files = await this.getRuleFilesFromDirectory(dirPath);
if (files.length === 0) {
return {
sourceName,
rules: {},
errors: [],
warnings: [`No rule files found in directory '${dirPath}'`],
success: true,
};
}
const allRules = {};
const allErrors = [];
const allWarnings = [];
for (const file of files) {
try {
const content = await promises_1.default.readFile(file, 'utf-8');
const parsed = this.parseRuleFile(content, (0, path_2.extname)(file));
// Merge rules from this file
const result = (0, UltraSimpleRuleDictionary_1.addRulesToDictionary)(allRules, parsed, file);
Object.assign(allRules, result.dictionary);
allErrors.push(...result.warnings.map(w => `${file}: ${w}`));
allWarnings.push(...result.warnings);
}
catch (error) {
allErrors.push(`Failed to load ${file}: ${error.message}`);
}
}
return {
sourceName,
rules: allRules,
errors: allErrors,
warnings: allWarnings,
success: allErrors.length === 0,
};
}
catch (error) {
return {
sourceName,
rules: {},
errors: [`Failed to process directory pattern '${patternPath}': ${error.message}`],
warnings: [],
success: false,
};
}
}
/**
* Gets rule files from directory (Pure Function)
* @param dirPath - Directory path
* @returns Promise with array of file paths
*/
async getRuleFilesFromDirectory(dirPath) {
try {
const entries = await (0, promises_2.readdir)(dirPath);
const ruleFiles = [];
for (const entry of entries) {
const fullPath = (0, path_2.join)(dirPath, entry);
const stats = await (0, promises_2.stat)(fullPath);
if (stats.isFile() && (entry.endsWith('.yaml') || entry.endsWith('.yml') || entry.endsWith('.json'))) {
ruleFiles.push(fullPath);
}
}
return ruleFiles;
}
catch (error) {
return [];
}
}
/**
* Loads rules from single file (Pure Function)
* @param filePath - File path
* @param sourceName - Source name
* @returns Promise with source load result
*/
async loadLocalSingleFile(filePath, sourceName) {
try {
const content = await promises_1.default.readFile(filePath, 'utf-8');
const rules = this.parseRuleFile(content, path_1.default.extname(filePath));
return {
sourceName,
rules,
errors: [],
warnings: [],
success: true,
};
}
catch (error) {
return {
sourceName,
rules: {},
errors: [`Failed to load file '${filePath}': ${error.message}`],
warnings: [],
success: false,
};
}
}
// Placeholder methods for other source types (to be implemented)
async loadRemoteRules(source, sourceName, context) {
return {
sourceName,
rules: {},
errors: ['Remote rule loading not implemented yet'],
warnings: [],
success: false,
};
}
async loadPackageRules(source, sourceName, context) {
return {
sourceName,
rules: {},
errors: ['Package rule loading not implemented yet'],
warnings: [],
success: false,
};
}
async loadGitRules(source, sourceName, context) {
return {
sourceName,
rules: {},
errors: ['Git rule loading not implemented yet'],
warnings: [],
success: false,
};
}
// Helper methods (Pure Functions with Guard Clauses)
validateConfig(config) {
const errors = [];
// Guard clause: no sources
if (!config.sources || Object.keys(config.sources).length === 0) {
errors.push('No rule sources defined');
}
// Guard clause: no environments
if (!config.environments || Object.keys(config.environments).length === 0) {
errors.push('No environments defined');
}
return errors;
}
getEnvironmentConfig(config, environment) {
// Guard clause: environment not found
if (!config.environments[environment]) {
throw new Error(`Environment '${environment}' not found in configuration`);
}
return config.environments[environment];
}
async loadFromSources(sources, envConfig, context) {
const loadPromises = envConfig.sources.map(sourceName => {
const source = sources[sourceName];
if (!source) {
return Promise.resolve({
sourceName,
rules: {},
errors: [`Source '${sourceName}' not found in configuration`],
warnings: [],
success: false,
});
}
return this.loadFromSource(source, sourceName, context);
});
return Promise.all(loadPromises);
}
mergeSourceResults(sourceResults) {
const dictionaries = sourceResults
.filter(result => result.success)
.map(result => result.rules);
const sources = sourceResults
.filter(result => result.success)
.map(result => result.sourceName);
return (0, UltraSimpleRuleDictionary_1.mergeRuleDictionaries)(dictionaries, sources);
}
applyEnvironmentOverrides(mergedResult, envConfig, config) {
let finalDictionary = mergedResult.dictionary;
// Apply environment-specific overrides
if (envConfig.overrides) {
const overrideResult = (0, UltraSimpleRuleDictionary_1.addRulesToDictionary)(finalDictionary, envConfig.overrides, `environment-${envConfig.name}-overrides`);
finalDictionary = overrideResult.dictionary;
}
// Apply global overrides
if (config.globalOverrides) {
const globalOverrideResult = (0, UltraSimpleRuleDictionary_1.addRulesToDictionary)(finalDictionary, config.globalOverrides, 'global-overrides');
finalDictionary = globalOverrideResult.dictionary;
}
// Remove disabled rules
const disabledRules = [
...(envConfig.disabledRules || []),
...(config.globalDisabled || [])
];
for (const ruleId of disabledRules) {
delete finalDictionary[ruleId];
}
return {
dictionary: finalDictionary,
added: mergedResult.added,
skipped: mergedResult.skipped,
warnings: mergedResult.warnings,
};
}
parseRuleFile(content, extension) {
try {
let parsed;
switch (extension.toLowerCase()) {
case '.yaml':
case '.yml':
parsed = js_yaml_1.default.load(content);
break;
case '.json':
parsed = JSON.parse(content);
break;
default:
throw new Error(`Unsupported file extension: ${extension}`);
}
return this.extractRulesFromParsedContent(parsed);
}
catch (error) {
throw new Error(`Failed to parse rule file: ${error.message}`);
}
}
extractRulesFromParsedContent(content) {
// Guard clause: no content
if (!content) {
return {};
}
// Handle different content structures
if (Array.isArray(content)) {
return this.arrayToRuleDictionary(content);
}
else if (content.rules && Array.isArray(content.rules)) {
return this.arrayToRuleDictionary(content.rules);
}
else if (typeof content === 'object') {
// Check if it's already a rule dictionary
if (this.isRuleDictionary(content)) {
return content;
}
// Treat as single rule if it has id and name
if (content.id && content.name) {
return { [content.id]: content.name };
}
}
return {};
}
arrayToRuleDictionary(rules) {
const dictionary = {};
for (const rule of rules) {
if (rule.id && rule.name) {
dictionary[rule.id] = rule.name;
}
}
return dictionary;
}
isRuleDictionary(obj) {
return typeof obj === 'object' &&
obj !== null &&
!Array.isArray(obj) &&
Object.values(obj).every(value => typeof value === 'string');
}
extractErrors(sourceResults) {
return sourceResults.flatMap(result => result.errors);
}
extractWarnings(sourceResults) {
return sourceResults.flatMap(result => result.warnings);
}
createEmptyResult(errors) {
return {
rules: {},
errors,
warnings: [],
metadata: {
sourcesLoaded: [],
environmentsProcessed: [],
totalRules: 0,
duplicateRules: 0,
loadDuration: 0,
},
};
}
}
exports.DevSecOpsRuleConnector = DevSecOpsRuleConnector;
//# sourceMappingURL=DevSecOpsRuleConnector.js.map