formalize visitor, results, etc
This commit is contained in:
parent
aec3ea9e4e
commit
eb1fdd3f30
|
@ -36,6 +36,7 @@ export default {
|
|||
commonjs({
|
||||
namedExports: {
|
||||
'node_modules/deep-diff/index.js': [
|
||||
'applyDiff',
|
||||
'diff',
|
||||
],
|
||||
'node_modules/lodash/lodash.js': [
|
||||
|
@ -63,4 +64,4 @@ export default {
|
|||
rollupCommonJSResolveHack: true,
|
||||
}),
|
||||
],
|
||||
};
|
||||
};
|
||||
|
|
|
@ -56,7 +56,7 @@ rules:
|
|||
limits:
|
||||
type: object
|
||||
properties:
|
||||
cpu: &resources-cpu
|
||||
cpu: *resources-cpu
|
||||
|
||||
# ensure the limits aren't *too* low
|
||||
check:
|
||||
|
@ -99,4 +99,4 @@ rules:
|
|||
properties:
|
||||
replica:
|
||||
type: number
|
||||
minimum: 1
|
||||
minimum: 1
|
||||
|
|
27
src/index.ts
27
src/index.ts
|
@ -1,5 +1,5 @@
|
|||
import { createLogger } from 'bunyan';
|
||||
import { diff } from 'deep-diff';
|
||||
import { applyDiff, diff } from 'deep-diff';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { Options, usage } from 'yargs';
|
||||
|
||||
|
@ -117,19 +117,24 @@ export async function main(argv: Array<string>): Promise<number> {
|
|||
const activeRules = await resolveRules(rules, args as any);
|
||||
|
||||
for (const rule of activeRules) {
|
||||
const workingCopy = cloneDeep(data);
|
||||
const ruleErrors = await rule.visit(ctx, workingCopy);
|
||||
const items = await rule.pick(ctx, data);
|
||||
for (const item of items) {
|
||||
const itemCopy = cloneDeep(item);
|
||||
const itemResult = await rule.visit(ctx, itemCopy);
|
||||
|
||||
if (ruleErrors > 0) {
|
||||
logger.warn({ rule }, 'rule failed');
|
||||
} else {
|
||||
const ruleDiff = diff(data, workingCopy);
|
||||
if (Array.isArray(ruleDiff) && ruleDiff.length > 0) {
|
||||
logger.info({ diff: ruleDiff, rule }, 'rule passed with modifications');
|
||||
if (itemResult.errors.length > 0) {
|
||||
logger.warn({ count: itemResult.errors.length, rule }, 'rule failed');
|
||||
|
||||
data = workingCopy;
|
||||
ctx.mergeResult(itemResult);
|
||||
} else {
|
||||
logger.info({ rule }, 'rule passed');
|
||||
const itemDiff = diff(item, itemCopy);
|
||||
if (Array.isArray(itemDiff) && itemDiff.length > 0) {
|
||||
logger.info({ diff: itemDiff, item, rule }, 'rule passed with modifications');
|
||||
|
||||
applyDiff(item, itemDiff);
|
||||
} else {
|
||||
logger.info({ rule }, 'rule passed');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
63
src/rule.ts
63
src/rule.ts
|
@ -6,6 +6,7 @@ import { YamlParser } from 'src/parser/YamlParser';
|
|||
import { readFileSync } from 'src/source';
|
||||
import { Visitor } from 'src/visitor';
|
||||
import { VisitorContext } from 'src/visitor/context';
|
||||
import { VisitorResult } from 'src/visitor/result';
|
||||
|
||||
export interface RuleData {
|
||||
// metadata
|
||||
|
@ -92,7 +93,11 @@ export async function resolveRules(rules: Array<Rule>, selector: RuleSelector):
|
|||
return Array.from(activeRules);
|
||||
}
|
||||
|
||||
export class Rule implements RuleData, Visitor {
|
||||
export interface RuleResult extends VisitorResult {
|
||||
rule: Rule;
|
||||
}
|
||||
|
||||
export class Rule implements RuleData, Visitor<RuleResult> {
|
||||
public readonly check: any;
|
||||
public readonly desc: string;
|
||||
public readonly filter?: any;
|
||||
|
@ -115,40 +120,48 @@ export class Rule implements RuleData, Visitor {
|
|||
}
|
||||
}
|
||||
|
||||
public async visit(ctx: VisitorContext, node: any): Promise<number> {
|
||||
const check = ctx.ajv.compile(this.check);
|
||||
const filter = this.compileFilter(ctx);
|
||||
public async pick(ctx: VisitorContext, root: any): Promise<Array<any>> {
|
||||
const scopes = JSONPath({
|
||||
json: node,
|
||||
json: root,
|
||||
path: this.select,
|
||||
});
|
||||
|
||||
if (isNil(scopes) || scopes.length === 0) {
|
||||
ctx.logger.debug('no data selected');
|
||||
return 0;
|
||||
return [];
|
||||
}
|
||||
|
||||
for (const item of scopes) {
|
||||
ctx.logger.debug({ item }, 'filtering item');
|
||||
if (filter(item)) {
|
||||
ctx.logger.debug({ item }, 'checking item')
|
||||
if (!check(item)) {
|
||||
const errors = Array.from(check.errors);
|
||||
ctx.logger.warn({
|
||||
errors,
|
||||
name: this.name,
|
||||
item,
|
||||
rule: this,
|
||||
}, 'rule failed on item');
|
||||
ctx.errors.push(...errors);
|
||||
return errors.length;
|
||||
}
|
||||
} else {
|
||||
ctx.logger.debug({ errors: filter.errors, item }, 'skipping item');
|
||||
return scopes;
|
||||
}
|
||||
|
||||
public async visit(ctx: VisitorContext, node: any): Promise<RuleResult> {
|
||||
ctx.logger.debug({ item: node, rule: this}, 'visiting node');
|
||||
|
||||
const check = ctx.ajv.compile(this.check);
|
||||
const filter = this.compileFilter(ctx);
|
||||
const result: RuleResult = {
|
||||
changes: [],
|
||||
errors: [],
|
||||
rule: this,
|
||||
};
|
||||
|
||||
if (filter(node)) {
|
||||
ctx.logger.debug({ item: node }, 'checking item')
|
||||
if (!check(node)) {
|
||||
const errors = Array.from(check.errors);
|
||||
ctx.logger.warn({
|
||||
errors,
|
||||
name: this.name,
|
||||
item: node,
|
||||
rule: this,
|
||||
}, 'rule failed on item');
|
||||
result.errors.push(...errors);
|
||||
}
|
||||
} else {
|
||||
ctx.logger.debug({ errors: filter.errors, item: node }, 'skipping item');
|
||||
}
|
||||
|
||||
return 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
protected compileFilter(ctx: VisitorContext): any {
|
||||
|
@ -158,4 +171,4 @@ export class Rule implements RuleData, Visitor {
|
|||
return ctx.ajv.compile(this.filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
import * as Ajv from 'ajv';
|
||||
import { Logger } from 'noicejs';
|
||||
|
||||
import { VisitorResult } from 'src/visitor/result';
|
||||
|
||||
export interface VisitorContextOptions {
|
||||
coerce: boolean;
|
||||
defaults: boolean;
|
||||
logger: Logger;
|
||||
}
|
||||
|
||||
export class VisitorContext {
|
||||
export class VisitorContext implements VisitorContextOptions, VisitorResult {
|
||||
public readonly ajv: any;
|
||||
public readonly changes: Array<any>;
|
||||
public readonly coerce: boolean;
|
||||
|
@ -31,4 +33,10 @@ export class VisitorContext {
|
|||
this.logger.error(options, msg);
|
||||
this.errors.push(options || msg);
|
||||
}
|
||||
}
|
||||
|
||||
public mergeResult(other: VisitorResult): this {
|
||||
this.changes.push(...other.changes);
|
||||
this.errors.push(...other.errors);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,14 @@
|
|||
import { VisitorContext } from 'src/visitor/context';
|
||||
import { VisitorResult } from 'src/visitor/result';
|
||||
|
||||
export interface Visitor {
|
||||
visit(ctx: VisitorContext, node: any): Promise<number>;
|
||||
}
|
||||
export interface Visitor<TResult extends VisitorResult> {
|
||||
/**
|
||||
* Select nodes eligible to be visited.
|
||||
**/
|
||||
pick(ctx: VisitorContext, root: any): Promise<Array<any>>;
|
||||
|
||||
/**
|
||||
* Visit a node.
|
||||
*/
|
||||
visit(ctx: VisitorContext, node: any): Promise<TResult>;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
export interface VisitorResult {
|
||||
changes: Array<any>;
|
||||
errors: Array<any>;
|
||||
}
|
Loading…
Reference in New Issue