timonel 3.0.0 → 3.1.0

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 (40) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +432 -1
  3. package/SECURITY.md +2 -2
  4. package/dist/index.d.ts +3 -0
  5. package/dist/index.js +2 -0
  6. package/dist/lib/policy/configurationLoader.d.ts +46 -0
  7. package/dist/lib/policy/configurationLoader.js +251 -0
  8. package/dist/lib/policy/errorContextGenerator.d.ts +63 -0
  9. package/dist/lib/policy/errorContextGenerator.js +302 -0
  10. package/dist/lib/policy/errors.d.ts +40 -0
  11. package/dist/lib/policy/errors.js +109 -0
  12. package/dist/lib/policy/index.d.ts +11 -0
  13. package/dist/lib/policy/index.js +10 -0
  14. package/dist/lib/policy/parallelExecutor.d.ts +58 -0
  15. package/dist/lib/policy/parallelExecutor.js +215 -0
  16. package/dist/lib/policy/pluginLoader.d.ts +41 -0
  17. package/dist/lib/policy/pluginLoader.js +220 -0
  18. package/dist/lib/policy/pluginRegistry.d.ts +14 -0
  19. package/dist/lib/policy/pluginRegistry.js +69 -0
  20. package/dist/lib/policy/policyEngine.d.ts +39 -0
  21. package/dist/lib/policy/policyEngine.js +495 -0
  22. package/dist/lib/policy/resultAggregator.d.ts +8 -0
  23. package/dist/lib/policy/resultAggregator.js +138 -0
  24. package/dist/lib/policy/resultFormatter.d.ts +25 -0
  25. package/dist/lib/policy/resultFormatter.js +217 -0
  26. package/dist/lib/policy/types.d.ts +111 -0
  27. package/dist/lib/policy/types.js +1 -0
  28. package/dist/lib/policy/validationCache.d.ts +58 -0
  29. package/dist/lib/policy/validationCache.js +289 -0
  30. package/dist/lib/rutter.d.ts +7 -2
  31. package/dist/lib/rutter.js +177 -8
  32. package/dist/lib/templates/flexible-subchart.js +4 -2
  33. package/dist/lib/templates/umbrella-chart.js +8 -8
  34. package/dist/lib/umbrellaRutter.d.ts +1 -1
  35. package/dist/lib/umbrellaRutter.js +2 -2
  36. package/dist/lib/validation/inputValidator.d.ts +26 -0
  37. package/dist/lib/validation/inputValidator.js +176 -0
  38. package/dist/types/index.d.ts +27 -0
  39. package/dist/types/index.js +1 -0
  40. package/package.json +22 -20
@@ -0,0 +1,138 @@
1
+ export function aggregateResults(results) {
2
+ if (results.length === 0) {
3
+ return createEmptyResult();
4
+ }
5
+ if (results.length === 1) {
6
+ const result = results[0];
7
+ return result || createEmptyResult();
8
+ }
9
+ const violations = [];
10
+ const warnings = [];
11
+ let totalExecutionTime = 0;
12
+ let totalPluginCount = 0;
13
+ let totalManifestCount = 0;
14
+ let earliestStartTime;
15
+ for (const result of results) {
16
+ violations.push(...result.violations);
17
+ warnings.push(...result.warnings);
18
+ totalExecutionTime += result.metadata.executionTime;
19
+ totalPluginCount += result.metadata.pluginCount;
20
+ totalManifestCount = Math.max(totalManifestCount, result.metadata.manifestCount);
21
+ if (result.metadata.startTime &&
22
+ (!earliestStartTime || result.metadata.startTime < earliestStartTime)) {
23
+ earliestStartTime = result.metadata.startTime;
24
+ }
25
+ }
26
+ const metadata = {
27
+ executionTime: totalExecutionTime,
28
+ pluginCount: totalPluginCount,
29
+ manifestCount: totalManifestCount,
30
+ ...(earliestStartTime && { startTime: earliestStartTime }),
31
+ aggregatedResults: results.length,
32
+ };
33
+ const aggregatedResult = {
34
+ valid: violations.length === 0,
35
+ violations,
36
+ warnings,
37
+ metadata,
38
+ summary: generateResultSummary(violations, warnings),
39
+ };
40
+ return aggregatedResult;
41
+ }
42
+ export function generateResultSummary(violations, warnings, allPlugins) {
43
+ const allViolations = [...violations, ...warnings];
44
+ const violationsBySeverity = {
45
+ error: violations.filter((v) => v.severity === 'error').length,
46
+ warning: allViolations.filter((v) => v.severity === 'warning').length,
47
+ info: allViolations.filter((v) => v.severity === 'info').length,
48
+ };
49
+ const violationsByPlugin = Object.create(null);
50
+ if (allPlugins) {
51
+ for (const pluginName of allPlugins) {
52
+ violationsByPlugin[pluginName] = 0;
53
+ }
54
+ }
55
+ for (const violation of allViolations) {
56
+ const pluginName = violation.plugin;
57
+ violationsByPlugin[pluginName] = (violationsByPlugin[pluginName] || 0) + 1;
58
+ }
59
+ const violationTypeCount = Object.create(null);
60
+ for (const violation of allViolations) {
61
+ const type = extractViolationType(violation.message);
62
+ violationTypeCount[type] = (violationTypeCount[type] || 0) + 1;
63
+ }
64
+ const topViolationTypes = Object.entries(violationTypeCount)
65
+ .map(([type, count]) => ({ type, count }))
66
+ .sort((a, b) => b.count - a.count)
67
+ .slice(0, 5);
68
+ return {
69
+ violationsBySeverity,
70
+ violationsByPlugin,
71
+ topViolationTypes,
72
+ };
73
+ }
74
+ export function filterViolationsBySeverity(violations, severity) {
75
+ return violations.filter((v) => v.severity === severity);
76
+ }
77
+ export function groupViolationsByPlugin(violations) {
78
+ const grouped = Object.create(null);
79
+ for (const violation of violations) {
80
+ const pluginName = violation.plugin;
81
+ if (!grouped[pluginName]) {
82
+ grouped[pluginName] = [];
83
+ }
84
+ grouped[pluginName].push(violation);
85
+ }
86
+ return grouped;
87
+ }
88
+ export function sortViolationsBySeverity(violations) {
89
+ const severityOrder = { error: 0, warning: 1, info: 2 };
90
+ return [...violations].sort((a, b) => {
91
+ const orderA = severityOrder[a.severity];
92
+ const orderB = severityOrder[b.severity];
93
+ if (orderA !== orderB) {
94
+ return orderA - orderB;
95
+ }
96
+ if (a.plugin !== b.plugin) {
97
+ return a.plugin.localeCompare(b.plugin);
98
+ }
99
+ return a.message.localeCompare(b.message);
100
+ });
101
+ }
102
+ export function createEmptyResult() {
103
+ const metadata = {
104
+ executionTime: 0,
105
+ pluginCount: 0,
106
+ manifestCount: 0,
107
+ };
108
+ return {
109
+ valid: true,
110
+ violations: [],
111
+ warnings: [],
112
+ metadata,
113
+ summary: {
114
+ violationsBySeverity: { error: 0, warning: 0, info: 0 },
115
+ violationsByPlugin: {},
116
+ topViolationTypes: [],
117
+ },
118
+ };
119
+ }
120
+ function extractViolationType(message) {
121
+ const sentences = message.split('.');
122
+ const firstSentence = sentences.length > 0 ? sentences[0] : message;
123
+ if (firstSentence && firstSentence.length <= 50) {
124
+ return firstSentence.trim();
125
+ }
126
+ return message.substring(0, 50).trim() + '...';
127
+ }
128
+ export function mergeViolationContexts(contexts) {
129
+ const merged = Object.create(null);
130
+ for (const context of contexts) {
131
+ if (context && typeof context === 'object') {
132
+ for (const [key, value] of Object.entries(context)) {
133
+ merged[key] = value;
134
+ }
135
+ }
136
+ }
137
+ return merged;
138
+ }
@@ -0,0 +1,25 @@
1
+ import type { PolicyResult, ResultFormatter } from './types.js';
2
+ export declare class DefaultResultFormatter implements ResultFormatter {
3
+ format(result: PolicyResult): string;
4
+ private formatViolation;
5
+ private getSeverityIcon;
6
+ }
7
+ export declare class JsonResultFormatter implements ResultFormatter {
8
+ private indent;
9
+ constructor(indent?: number);
10
+ format(result: PolicyResult): string;
11
+ }
12
+ export declare class CompactResultFormatter implements ResultFormatter {
13
+ format(result: PolicyResult): string;
14
+ }
15
+ export declare class GitHubActionsResultFormatter implements ResultFormatter {
16
+ format(result: PolicyResult): string;
17
+ }
18
+ export declare class SarifResultFormatter implements ResultFormatter {
19
+ format(result: PolicyResult): string;
20
+ private convertViolationsToSarifResults;
21
+ private generateRuleId;
22
+ private mapSeverityToSarifLevel;
23
+ }
24
+ export declare function createFormatter(name: string, options?: Record<string, unknown>): ResultFormatter;
25
+ export declare function getAvailableFormatters(): string[];
@@ -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;