timonel 3.0.0-beta.1 → 3.1.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/CHANGELOG.md +192 -0
  2. package/README.md +606 -119
  3. package/SECURITY.md +25 -11
  4. package/dist/cli.js +54 -15
  5. package/dist/index.d.ts +3 -0
  6. package/dist/index.js +2 -0
  7. package/dist/lib/helm.js +28 -1
  8. package/dist/lib/helmChartWriter.js +27 -8
  9. package/dist/lib/policy/configurationLoader.d.ts +46 -0
  10. package/dist/lib/policy/configurationLoader.js +251 -0
  11. package/dist/lib/policy/errorContextGenerator.d.ts +63 -0
  12. package/dist/lib/policy/errorContextGenerator.js +302 -0
  13. package/dist/lib/policy/errors.d.ts +40 -0
  14. package/dist/lib/policy/errors.js +109 -0
  15. package/dist/lib/policy/index.d.ts +11 -0
  16. package/dist/lib/policy/index.js +10 -0
  17. package/dist/lib/policy/parallelExecutor.d.ts +58 -0
  18. package/dist/lib/policy/parallelExecutor.js +215 -0
  19. package/dist/lib/policy/pluginLoader.d.ts +41 -0
  20. package/dist/lib/policy/pluginLoader.js +220 -0
  21. package/dist/lib/policy/pluginRegistry.d.ts +14 -0
  22. package/dist/lib/policy/pluginRegistry.js +69 -0
  23. package/dist/lib/policy/policyEngine.d.ts +39 -0
  24. package/dist/lib/policy/policyEngine.js +495 -0
  25. package/dist/lib/policy/resultAggregator.d.ts +8 -0
  26. package/dist/lib/policy/resultAggregator.js +138 -0
  27. package/dist/lib/policy/resultFormatter.d.ts +25 -0
  28. package/dist/lib/policy/resultFormatter.js +217 -0
  29. package/dist/lib/policy/types.d.ts +111 -0
  30. package/dist/lib/policy/types.js +1 -0
  31. package/dist/lib/policy/validationCache.d.ts +58 -0
  32. package/dist/lib/policy/validationCache.js +289 -0
  33. package/dist/lib/resources/baseResourceProvider.js +5 -0
  34. package/dist/lib/resources/cloud/aws/awsResources.js +2 -1
  35. package/dist/lib/resources/cloud/aws/karpenterResources.js +16 -2
  36. package/dist/lib/rutter.d.ts +7 -2
  37. package/dist/lib/rutter.js +177 -8
  38. package/dist/lib/security.js +4 -4
  39. package/dist/lib/templates/flexible-subchart.js +18 -7
  40. package/dist/lib/templates/umbrella-chart.js +29 -18
  41. package/dist/lib/umbrellaRutter.d.ts +1 -1
  42. package/dist/lib/umbrellaRutter.js +21 -9
  43. package/dist/lib/utils/envVarsLoader.js +13 -7
  44. package/dist/lib/utils/helmHelpers.js +10 -2
  45. package/dist/lib/utils/helmYamlSerializer.js +19 -9
  46. package/dist/lib/utils/logger.js +54 -39
  47. package/dist/lib/utils/valuesRef.js +9 -0
  48. package/dist/lib/validation/inputValidator.d.ts +26 -0
  49. package/dist/lib/validation/inputValidator.js +176 -0
  50. package/dist/types/index.d.ts +27 -0
  51. package/dist/types/index.js +1 -0
  52. package/package.json +21 -19
@@ -0,0 +1,217 @@
1
+ import { sortViolationsBySeverity } from './resultAggregator.js';
2
+ export class DefaultResultFormatter {
3
+ format(result) {
4
+ const lines = [];
5
+ lines.push('='.repeat(60));
6
+ lines.push('Policy Validation Results');
7
+ lines.push('='.repeat(60));
8
+ const status = result.valid ? '✅ PASSED' : '❌ FAILED';
9
+ lines.push(`Status: ${status}`);
10
+ lines.push('');
11
+ if (result.summary) {
12
+ lines.push('Summary:');
13
+ lines.push(` Errors: ${result.summary.violationsBySeverity.error}`);
14
+ lines.push(` Warnings: ${result.summary.violationsBySeverity.warning}`);
15
+ lines.push(` Info: ${result.summary.violationsBySeverity.info}`);
16
+ lines.push('');
17
+ }
18
+ lines.push('Execution Details:');
19
+ lines.push(` Plugins: ${result.metadata.pluginCount}`);
20
+ lines.push(` Manifests: ${result.metadata.manifestCount}`);
21
+ lines.push(` Duration: ${result.metadata.executionTime}ms`);
22
+ lines.push('');
23
+ if (result.violations.length > 0) {
24
+ lines.push('Violations:');
25
+ lines.push('-'.repeat(40));
26
+ const sortedViolations = sortViolationsBySeverity(result.violations);
27
+ for (const violation of sortedViolations) {
28
+ lines.push(this.formatViolation(violation));
29
+ lines.push('');
30
+ }
31
+ }
32
+ if (result.warnings.length > 0) {
33
+ lines.push('Warnings:');
34
+ lines.push('-'.repeat(40));
35
+ const sortedWarnings = sortViolationsBySeverity(result.warnings);
36
+ for (const warning of sortedWarnings) {
37
+ lines.push(this.formatViolation(warning));
38
+ lines.push('');
39
+ }
40
+ }
41
+ if (result.violations.length === 0 && result.warnings.length === 0) {
42
+ lines.push('✨ No policy violations found!');
43
+ lines.push('');
44
+ }
45
+ return lines.join('\n');
46
+ }
47
+ formatViolation(violation) {
48
+ const lines = [];
49
+ const severityIcon = this.getSeverityIcon(violation.severity);
50
+ lines.push(`${severityIcon} [${violation.plugin}] ${violation.message}`);
51
+ if (violation.resourcePath) {
52
+ lines.push(` Resource: ${violation.resourcePath}`);
53
+ }
54
+ if (violation.field) {
55
+ lines.push(` Field: ${violation.field}`);
56
+ }
57
+ if (violation.suggestion) {
58
+ lines.push(` 💡 Suggestion: ${violation.suggestion}`);
59
+ }
60
+ return lines.join('\n');
61
+ }
62
+ getSeverityIcon(severity) {
63
+ switch (severity) {
64
+ case 'error':
65
+ return '🚨';
66
+ case 'warning':
67
+ return '⚠️';
68
+ case 'info':
69
+ return 'ℹ️';
70
+ default:
71
+ return '•';
72
+ }
73
+ }
74
+ }
75
+ export class JsonResultFormatter {
76
+ constructor(indent = 2) {
77
+ this.indent = indent;
78
+ }
79
+ format(result) {
80
+ return JSON.stringify(result, null, this.indent);
81
+ }
82
+ }
83
+ export class CompactResultFormatter {
84
+ format(result) {
85
+ const status = result.valid ? 'PASS' : 'FAIL';
86
+ const errorCount = result.violations.filter((v) => v.severity === 'error').length;
87
+ const warningCount = [...result.violations, ...result.warnings].filter((v) => v.severity === 'warning' || v.severity === 'info').length;
88
+ let output = `Policy validation: ${status}`;
89
+ if (errorCount > 0 || warningCount > 0) {
90
+ output += ` (${errorCount} errors, ${warningCount} warnings)`;
91
+ }
92
+ output += ` - ${result.metadata.executionTime}ms`;
93
+ if (!result.valid && result.violations.length > 0) {
94
+ output += '\n';
95
+ const firstViolations = result.violations.slice(0, 3);
96
+ for (const violation of firstViolations) {
97
+ output += `\n • [${violation.plugin}] ${violation.message}`;
98
+ }
99
+ if (result.violations.length > 3) {
100
+ output += `\n ... and ${result.violations.length - 3} more`;
101
+ }
102
+ }
103
+ return output;
104
+ }
105
+ }
106
+ export class GitHubActionsResultFormatter {
107
+ format(result) {
108
+ const lines = [];
109
+ const status = result.valid ? 'success' : 'failure';
110
+ lines.push(`::notice title=Policy Validation::Status: ${status}`);
111
+ for (const violation of result.violations) {
112
+ const level = violation.severity === 'error' ? 'error' : 'warning';
113
+ const file = violation.resourcePath || 'unknown';
114
+ const message = `[${violation.plugin}] ${violation.message}`;
115
+ lines.push(`::${level} file=${file}::${message}`);
116
+ }
117
+ for (const warning of result.warnings) {
118
+ const level = warning.severity === 'info' ? 'notice' : 'warning';
119
+ const file = warning.resourcePath || 'unknown';
120
+ const message = `[${warning.plugin}] ${warning.message}`;
121
+ lines.push(`::${level} file=${file}::${message}`);
122
+ }
123
+ return lines.join('\n');
124
+ }
125
+ }
126
+ export class SarifResultFormatter {
127
+ format(result) {
128
+ const runs = [
129
+ {
130
+ tool: {
131
+ driver: {
132
+ name: 'Timonel Policy Engine',
133
+ version: '3.0.0',
134
+ informationUri: 'https://github.com/your-org/timonel',
135
+ },
136
+ },
137
+ results: this.convertViolationsToSarifResults([...result.violations, ...result.warnings]),
138
+ },
139
+ ];
140
+ const sarif = {
141
+ version: '2.1.0',
142
+ $schema: 'https://json.schemastore.org/sarif-2.1.0.json',
143
+ runs,
144
+ };
145
+ return JSON.stringify(sarif, null, 2);
146
+ }
147
+ convertViolationsToSarifResults(violations) {
148
+ return violations.map((violation) => ({
149
+ ruleId: `${violation.plugin}/${this.generateRuleId(violation.message)}`,
150
+ level: this.mapSeverityToSarifLevel(violation.severity),
151
+ message: {
152
+ text: violation.message,
153
+ },
154
+ locations: violation.resourcePath
155
+ ? [
156
+ {
157
+ physicalLocation: {
158
+ artifactLocation: {
159
+ uri: violation.resourcePath,
160
+ },
161
+ region: violation.field
162
+ ? {
163
+ startLine: 1,
164
+ startColumn: 1,
165
+ }
166
+ : undefined,
167
+ },
168
+ },
169
+ ]
170
+ : [],
171
+ properties: {
172
+ plugin: violation.plugin,
173
+ suggestion: violation.suggestion,
174
+ context: violation.context,
175
+ },
176
+ }));
177
+ }
178
+ generateRuleId(message) {
179
+ return message
180
+ .toLowerCase()
181
+ .replace(/[^a-z0-9\s]/g, '')
182
+ .replace(/\s+/g, '-')
183
+ .substring(0, 50);
184
+ }
185
+ mapSeverityToSarifLevel(severity) {
186
+ switch (severity) {
187
+ case 'error':
188
+ return 'error';
189
+ case 'warning':
190
+ return 'warning';
191
+ case 'info':
192
+ return 'note';
193
+ default:
194
+ return 'note';
195
+ }
196
+ }
197
+ }
198
+ export function createFormatter(name, options) {
199
+ switch (name.toLowerCase()) {
200
+ case 'default':
201
+ return new DefaultResultFormatter();
202
+ case 'json':
203
+ return new JsonResultFormatter(options?.indent);
204
+ case 'compact':
205
+ return new CompactResultFormatter();
206
+ case 'github':
207
+ case 'github-actions':
208
+ return new GitHubActionsResultFormatter();
209
+ case 'sarif':
210
+ return new SarifResultFormatter();
211
+ default:
212
+ throw new Error(`Unknown formatter: ${name}`);
213
+ }
214
+ }
215
+ export function getAvailableFormatters() {
216
+ return ['default', 'json', 'compact', 'github-actions', 'sarif'];
217
+ }
@@ -0,0 +1,111 @@
1
+ import type { TimonelLogger } from '../utils/logger.js';
2
+ import type { ChartMetadata } from '../rutter.js';
3
+ import type { ValidationOptions } from '../validation/inputValidator.js';
4
+ import type { CacheOptions } from './validationCache.js';
5
+ import type { ParallelExecutionOptions } from './parallelExecutor.js';
6
+ export interface JSONSchema {
7
+ type?: string;
8
+ properties?: Record<string, JSONSchema>;
9
+ required?: string[];
10
+ additionalProperties?: boolean | JSONSchema;
11
+ [key: string]: unknown;
12
+ }
13
+ export interface PluginMetadata {
14
+ author?: string;
15
+ license?: string;
16
+ homepage?: string;
17
+ repository?: string;
18
+ kubernetesVersions?: string[];
19
+ tags?: string[];
20
+ defaultConfig?: Record<string, unknown>;
21
+ }
22
+ export interface ValidationContext {
23
+ readonly chart: ChartMetadata;
24
+ readonly kubernetesVersion?: string;
25
+ readonly environment?: string;
26
+ readonly config?: Record<string, unknown>;
27
+ readonly logger: TimonelLogger;
28
+ }
29
+ export interface PolicyViolation {
30
+ readonly plugin: string;
31
+ readonly severity: 'error' | 'warning' | 'info';
32
+ readonly message: string;
33
+ readonly resourcePath?: string;
34
+ readonly field?: string;
35
+ readonly suggestion?: string;
36
+ readonly context?: Record<string, unknown>;
37
+ }
38
+ export interface PolicyWarning extends Omit<PolicyViolation, 'severity'> {
39
+ readonly severity: 'warning' | 'info';
40
+ }
41
+ export interface ValidationMetadata {
42
+ readonly executionTime: number;
43
+ readonly pluginCount: number;
44
+ readonly manifestCount: number;
45
+ readonly startTime?: number;
46
+ readonly [key: string]: unknown;
47
+ }
48
+ export interface PolicyResult {
49
+ readonly valid: boolean;
50
+ readonly violations: PolicyViolation[];
51
+ readonly warnings: PolicyWarning[];
52
+ readonly metadata: ValidationMetadata;
53
+ readonly summary?: ResultSummary;
54
+ }
55
+ export interface ResultSummary {
56
+ readonly violationsBySeverity: {
57
+ readonly error: number;
58
+ readonly warning: number;
59
+ readonly info: number;
60
+ };
61
+ readonly violationsByPlugin: Record<string, number>;
62
+ readonly topViolationTypes: Array<{
63
+ readonly type: string;
64
+ readonly count: number;
65
+ }>;
66
+ }
67
+ export interface PolicyPlugin {
68
+ readonly name: string;
69
+ readonly version: string;
70
+ readonly description?: string;
71
+ validate(manifests: unknown[], context: ValidationContext): Promise<PolicyViolation[]>;
72
+ readonly configSchema?: JSONSchema;
73
+ readonly metadata?: PluginMetadata;
74
+ }
75
+ export interface ResultFormatter {
76
+ format(result: PolicyResult): string;
77
+ }
78
+ export interface RetryConfig {
79
+ maxAttempts: number;
80
+ baseDelay: number;
81
+ backoffMultiplier: number;
82
+ maxDelay: number;
83
+ retryOnTimeout: boolean;
84
+ retryOnPluginError: boolean;
85
+ }
86
+ export interface ConfigurationLoaderOptions {
87
+ defaultEnvironment?: string;
88
+ validateSchemas?: boolean;
89
+ allowUnknownProperties?: boolean;
90
+ configurationFiles?: string[];
91
+ environmentPrefix?: string;
92
+ }
93
+ export interface PolicyEngineOptions {
94
+ timeout?: number;
95
+ parallel?: boolean;
96
+ failFast?: boolean;
97
+ formatter?: ResultFormatter;
98
+ pluginConfig?: Record<string, unknown>;
99
+ retryConfig?: RetryConfig;
100
+ gracefulDegradation?: boolean;
101
+ configurationLoader?: ConfigurationLoaderOptions;
102
+ environment?: string;
103
+ cacheOptions?: CacheOptions;
104
+ parallelOptions?: ParallelExecutionOptions;
105
+ validationOptions?: ValidationOptions;
106
+ }
107
+ export interface PolicyEngine {
108
+ use(plugin: PolicyPlugin): Promise<PolicyEngine>;
109
+ validate(manifests: unknown[], chartMetadata: ChartMetadata): Promise<PolicyResult>;
110
+ configure(options: PolicyEngineOptions): PolicyEngine;
111
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,58 @@
1
+ import type { PolicyResult } from './types.js';
2
+ export interface CacheEntry {
3
+ readonly result: PolicyResult;
4
+ readonly timestamp: number;
5
+ readonly manifestHash: string;
6
+ readonly pluginHash: string;
7
+ accessCount: number;
8
+ lastAccessed: number;
9
+ }
10
+ export interface CacheOptions {
11
+ maxEntries?: number;
12
+ maxAge?: number;
13
+ enableCompression?: boolean;
14
+ evictionStrategy?: 'lru' | 'lfu' | 'ttl';
15
+ cacheFailures?: boolean;
16
+ minExecutionTimeToCache?: number;
17
+ }
18
+ export interface CacheStats {
19
+ hits: number;
20
+ misses: number;
21
+ entries: number;
22
+ memoryUsage: number;
23
+ hitRatio: number;
24
+ averageAccessCount: number;
25
+ }
26
+ export declare class ValidationCache {
27
+ private readonly cache;
28
+ private readonly options;
29
+ private readonly logger;
30
+ private stats;
31
+ constructor(options?: CacheOptions);
32
+ get(manifestHash: string, pluginHash: string): PolicyResult | undefined;
33
+ set(manifestHash: string, pluginHash: string, result: PolicyResult): void;
34
+ invalidate(criteria: {
35
+ manifestHash?: string;
36
+ pluginHash?: string;
37
+ olderThan?: number;
38
+ all?: boolean;
39
+ }): number;
40
+ private invalidateAll;
41
+ private invalidateSelective;
42
+ private shouldInvalidateEntry;
43
+ getStats(): CacheStats;
44
+ clearStats(): void;
45
+ private generateCacheKey;
46
+ private isExpired;
47
+ private shouldCache;
48
+ private evictEntries;
49
+ private evictLRU;
50
+ private evictLFU;
51
+ private evictTTL;
52
+ private updateStats;
53
+ private estimateMemoryUsage;
54
+ private startPeriodicCleanup;
55
+ private cleanupExpiredEntries;
56
+ }
57
+ export declare function generateManifestHash(manifests: unknown[]): string;
58
+ export declare function generatePluginHash(plugins: string[], pluginConfigs: Record<string, unknown>): string;
@@ -0,0 +1,289 @@
1
+ import { createLogger } from '../utils/logger.js';
2
+ const DEFAULT_CACHE_OPTIONS = {
3
+ maxEntries: 1000,
4
+ maxAge: 30 * 60 * 1000,
5
+ enableCompression: false,
6
+ evictionStrategy: 'lru',
7
+ cacheFailures: true,
8
+ minExecutionTimeToCache: 10,
9
+ };
10
+ export class ValidationCache {
11
+ constructor(options = {}) {
12
+ this.cache = new Map();
13
+ this.stats = {
14
+ hits: 0,
15
+ misses: 0,
16
+ entries: 0,
17
+ memoryUsage: 0,
18
+ hitRatio: 0,
19
+ averageAccessCount: 0,
20
+ };
21
+ this.options = { ...DEFAULT_CACHE_OPTIONS, ...options };
22
+ this.logger = createLogger('validation-cache');
23
+ this.logger.debug('ValidationCache initialized', {
24
+ options: this.options,
25
+ operation: 'cache_init',
26
+ });
27
+ this.startPeriodicCleanup();
28
+ }
29
+ get(manifestHash, pluginHash) {
30
+ const cacheKey = this.generateCacheKey(manifestHash, pluginHash);
31
+ const entry = this.cache.get(cacheKey);
32
+ if (!entry) {
33
+ this.stats.misses++;
34
+ this.updateStats();
35
+ this.logger.debug('Cache miss', {
36
+ manifestHash: manifestHash.substring(0, 8),
37
+ pluginHash: pluginHash.substring(0, 8),
38
+ operation: 'cache_miss',
39
+ });
40
+ return undefined;
41
+ }
42
+ if (this.isExpired(entry)) {
43
+ this.cache.delete(cacheKey);
44
+ this.stats.misses++;
45
+ this.updateStats();
46
+ this.logger.debug('Cache entry expired', {
47
+ manifestHash: manifestHash.substring(0, 8),
48
+ pluginHash: pluginHash.substring(0, 8),
49
+ age: Date.now() - entry.timestamp,
50
+ operation: 'cache_expired',
51
+ });
52
+ return undefined;
53
+ }
54
+ entry.accessCount++;
55
+ entry.lastAccessed = Date.now();
56
+ this.stats.hits++;
57
+ this.updateStats();
58
+ this.logger.debug('Cache hit', {
59
+ manifestHash: manifestHash.substring(0, 8),
60
+ pluginHash: pluginHash.substring(0, 8),
61
+ accessCount: entry.accessCount,
62
+ age: Date.now() - entry.timestamp,
63
+ operation: 'cache_hit',
64
+ });
65
+ return entry.result;
66
+ }
67
+ set(manifestHash, pluginHash, result) {
68
+ if (!this.shouldCache(result)) {
69
+ this.logger.debug('Skipping cache storage', {
70
+ manifestHash: manifestHash.substring(0, 8),
71
+ pluginHash: pluginHash.substring(0, 8),
72
+ executionTime: result.metadata.executionTime,
73
+ hasViolations: result.violations.length > 0,
74
+ operation: 'cache_skip',
75
+ });
76
+ return;
77
+ }
78
+ const cacheKey = this.generateCacheKey(manifestHash, pluginHash);
79
+ if (this.cache.size >= this.options.maxEntries) {
80
+ this.evictEntries();
81
+ }
82
+ const entry = {
83
+ result,
84
+ timestamp: Date.now(),
85
+ manifestHash,
86
+ pluginHash,
87
+ accessCount: 0,
88
+ lastAccessed: Date.now(),
89
+ };
90
+ this.cache.set(cacheKey, entry);
91
+ this.updateStats();
92
+ this.logger.debug('Cache entry stored', {
93
+ manifestHash: manifestHash.substring(0, 8),
94
+ pluginHash: pluginHash.substring(0, 8),
95
+ executionTime: result.metadata.executionTime,
96
+ violationCount: result.violations.length,
97
+ warningCount: result.warnings.length,
98
+ operation: 'cache_store',
99
+ });
100
+ }
101
+ invalidate(criteria) {
102
+ if (criteria.all) {
103
+ return this.invalidateAll();
104
+ }
105
+ return this.invalidateSelective(criteria);
106
+ }
107
+ invalidateAll() {
108
+ const invalidatedCount = this.cache.size;
109
+ this.cache.clear();
110
+ this.logger.info('Cache cleared completely', {
111
+ invalidatedCount,
112
+ operation: 'cache_clear_all',
113
+ });
114
+ this.updateStats();
115
+ return invalidatedCount;
116
+ }
117
+ invalidateSelective(criteria) {
118
+ const keysToDelete = [];
119
+ for (const [key, entry] of this.cache.entries()) {
120
+ if (this.shouldInvalidateEntry(entry, criteria)) {
121
+ keysToDelete.push(key);
122
+ }
123
+ }
124
+ for (const key of keysToDelete) {
125
+ this.cache.delete(key);
126
+ }
127
+ this.logger.debug('Cache entries invalidated', {
128
+ invalidatedCount: keysToDelete.length,
129
+ criteria,
130
+ operation: 'cache_invalidate',
131
+ });
132
+ this.updateStats();
133
+ return keysToDelete.length;
134
+ }
135
+ shouldInvalidateEntry(entry, criteria) {
136
+ if (criteria.manifestHash && entry.manifestHash === criteria.manifestHash) {
137
+ return true;
138
+ }
139
+ if (criteria.pluginHash && entry.pluginHash === criteria.pluginHash) {
140
+ return true;
141
+ }
142
+ if (criteria.olderThan && entry.timestamp < criteria.olderThan) {
143
+ return true;
144
+ }
145
+ return false;
146
+ }
147
+ getStats() {
148
+ return { ...this.stats };
149
+ }
150
+ clearStats() {
151
+ this.stats = {
152
+ hits: 0,
153
+ misses: 0,
154
+ entries: this.cache.size,
155
+ memoryUsage: this.estimateMemoryUsage(),
156
+ hitRatio: 0,
157
+ averageAccessCount: 0,
158
+ };
159
+ this.logger.debug('Cache statistics cleared', {
160
+ operation: 'cache_stats_clear',
161
+ });
162
+ }
163
+ generateCacheKey(manifestHash, pluginHash) {
164
+ return `${manifestHash}:${pluginHash}`;
165
+ }
166
+ isExpired(entry) {
167
+ return Date.now() - entry.timestamp > this.options.maxAge;
168
+ }
169
+ shouldCache(result) {
170
+ if (result.metadata.executionTime < this.options.minExecutionTimeToCache) {
171
+ return false;
172
+ }
173
+ if (!this.options.cacheFailures && !result.valid) {
174
+ return false;
175
+ }
176
+ return true;
177
+ }
178
+ evictEntries() {
179
+ const entriesToEvict = Math.max(1, Math.floor(this.options.maxEntries * 0.1));
180
+ switch (this.options.evictionStrategy) {
181
+ case 'lru':
182
+ this.evictLRU(entriesToEvict);
183
+ break;
184
+ case 'lfu':
185
+ this.evictLFU(entriesToEvict);
186
+ break;
187
+ case 'ttl':
188
+ this.evictTTL(entriesToEvict);
189
+ break;
190
+ }
191
+ this.logger.debug('Cache entries evicted', {
192
+ strategy: this.options.evictionStrategy,
193
+ evictedCount: entriesToEvict,
194
+ remainingEntries: this.cache.size,
195
+ operation: 'cache_evict',
196
+ });
197
+ }
198
+ evictLRU(count) {
199
+ const entries = Array.from(this.cache.entries())
200
+ .sort(([, a], [, b]) => a.lastAccessed - b.lastAccessed)
201
+ .slice(0, count);
202
+ for (const [key] of entries) {
203
+ this.cache.delete(key);
204
+ }
205
+ }
206
+ evictLFU(count) {
207
+ const entries = Array.from(this.cache.entries())
208
+ .sort(([, a], [, b]) => a.accessCount - b.accessCount)
209
+ .slice(0, count);
210
+ for (const [key] of entries) {
211
+ this.cache.delete(key);
212
+ }
213
+ }
214
+ evictTTL(count) {
215
+ const entries = Array.from(this.cache.entries())
216
+ .sort(([, a], [, b]) => a.timestamp - b.timestamp)
217
+ .slice(0, count);
218
+ for (const [key] of entries) {
219
+ this.cache.delete(key);
220
+ }
221
+ }
222
+ updateStats() {
223
+ const totalRequests = this.stats.hits + this.stats.misses;
224
+ this.stats.entries = this.cache.size;
225
+ this.stats.memoryUsage = this.estimateMemoryUsage();
226
+ this.stats.hitRatio = totalRequests > 0 ? this.stats.hits / totalRequests : 0;
227
+ if (this.cache.size > 0) {
228
+ const totalAccessCount = Array.from(this.cache.values()).reduce((sum, entry) => sum + entry.accessCount, 0);
229
+ this.stats.averageAccessCount = totalAccessCount / this.cache.size;
230
+ }
231
+ else {
232
+ this.stats.averageAccessCount = 0;
233
+ }
234
+ }
235
+ estimateMemoryUsage() {
236
+ let totalSize = 0;
237
+ for (const entry of this.cache.values()) {
238
+ totalSize += JSON.stringify(entry.result).length * 2;
239
+ totalSize += entry.manifestHash.length * 2;
240
+ totalSize += entry.pluginHash.length * 2;
241
+ totalSize += 64;
242
+ }
243
+ return totalSize;
244
+ }
245
+ startPeriodicCleanup() {
246
+ const cleanupInterval = Math.min(this.options.maxAge / 4, 5 * 60 * 1000);
247
+ globalThis.setInterval(() => {
248
+ this.cleanupExpiredEntries();
249
+ }, cleanupInterval);
250
+ }
251
+ cleanupExpiredEntries() {
252
+ const keysToDelete = [];
253
+ const now = Date.now();
254
+ for (const [key, entry] of this.cache.entries()) {
255
+ if (now - entry.timestamp > this.options.maxAge) {
256
+ keysToDelete.push(key);
257
+ }
258
+ }
259
+ if (keysToDelete.length > 0) {
260
+ for (const key of keysToDelete) {
261
+ this.cache.delete(key);
262
+ }
263
+ this.updateStats();
264
+ this.logger.debug('Expired cache entries cleaned up', {
265
+ cleanedCount: keysToDelete.length,
266
+ remainingEntries: this.cache.size,
267
+ operation: 'cache_cleanup',
268
+ });
269
+ }
270
+ }
271
+ }
272
+ export function generateManifestHash(manifests) {
273
+ const manifestString = JSON.stringify(manifests, Object.keys(manifests).sort());
274
+ return hashString(manifestString);
275
+ }
276
+ export function generatePluginHash(plugins, pluginConfigs) {
277
+ const configString = JSON.stringify({
278
+ plugins: plugins.sort(),
279
+ configs: pluginConfigs,
280
+ }, Object.keys({ plugins: plugins.sort(), configs: pluginConfigs }).sort());
281
+ return hashString(configString);
282
+ }
283
+ function hashString(str) {
284
+ let hash = 5381;
285
+ for (let i = 0; i < str.length; i++) {
286
+ hash = (hash << 5) + hash + str.charCodeAt(i);
287
+ }
288
+ return Math.abs(hash).toString(36);
289
+ }
@@ -48,6 +48,11 @@ export class BaseResourceProvider {
48
48
  createRootLevelApiObject(name, apiVersion, kind, fields, labels, annotations) {
49
49
  this.validateKubernetesName(name, kind);
50
50
  this.validateLabels(labels, kind);
51
+ const reservedKeys = ['apiVersion', 'kind', 'metadata'];
52
+ const conflictingKeys = Object.keys(fields).filter((key) => reservedKeys.includes(key));
53
+ if (conflictingKeys.length > 0) {
54
+ throw new Error(`Fields object contains reserved keys: ${conflictingKeys.join(', ')}. These keys cannot be overridden.`);
55
+ }
51
56
  return new ApiObject(this.chart, name, {
52
57
  apiVersion,
53
58
  kind,
@@ -120,7 +120,8 @@ export class AWSResources extends BaseResourceProvider {
120
120
  throw new Error(`Ingress path "${path}" must start with /`);
121
121
  }
122
122
  if (normalizedPath.includes('../') || normalizedPath.includes('./')) {
123
- console.warn(`Warning: Ingress path "${path}" contains path traversal-like sequences`);
123
+ const sanitizedPath = path.replace(/[\r\n]/g, '');
124
+ console.warn(`Warning: Ingress path "${sanitizedPath}" contains path traversal-like sequences`);
124
125
  }
125
126
  }
126
127
  validatePathType(pathType) {