1
0
Fork 0

feat: display diff when rule modifies data (fixes #3)

This commit is contained in:
ssube 2019-06-22 11:48:41 -05:00
parent e3588577ac
commit 6f15d1c621
9 changed files with 63 additions and 28 deletions

View File

@ -35,6 +35,9 @@ export default {
}), }),
commonjs({ commonjs({
namedExports: { namedExports: {
'node_modules/deep-diff/index.js': [
'diff',
],
'node_modules/lodash/lodash.js': [ 'node_modules/lodash/lodash.js': [
'cloneDeep', 'cloneDeep',
'intersection', 'intersection',

View File

@ -7,7 +7,6 @@ spec:
- name: test - name: test
resources: resources:
limits: limits:
cpu: 2m
memory: 5Mi memory: 5Mi
requests: requests:
cpu: 1m cpu: 1m

View File

@ -21,9 +21,11 @@
}, },
"devDependencies": { "devDependencies": {
"@types/bunyan": "^1.8.6", "@types/bunyan": "^1.8.6",
"@types/deep-diff": "^1.0.0",
"@types/js-yaml": "^3.12.1", "@types/js-yaml": "^3.12.1",
"@types/lodash": "^4.14.134", "@types/lodash": "^4.14.134",
"@types/yargs": "^13.0.0", "@types/yargs": "^13.0.0",
"deep-diff": "^1.0.2",
"jsonpath-plus": "^0.20.1", "jsonpath-plus": "^0.20.1",
"rollup": "^1.15.5", "rollup": "^1.15.5",
"rollup-plugin-commonjs": "^10.0.0", "rollup-plugin-commonjs": "^10.0.0",

View File

@ -22,11 +22,13 @@ rules:
required: [cpu, memory] required: [cpu, memory]
properties: properties:
cpu: &resources-cpu cpu: &resources-cpu
oneOf: default: 500m
- type: number type: string
minimum: 1 # oneOf:
- type: string # - type: number
pattern: "[1-9][0-9]*m" # minimum: 1
# - type: string
# pattern: "[1-9][0-9]*m"
memory: &resources-memory memory: &resources-memory
oneOf: oneOf:
- type: number - type: number

View File

@ -1,4 +1,6 @@
import { createLogger } from 'bunyan'; import { createLogger } from 'bunyan';
import { diff } from 'deep-diff';
import { cloneDeep } from 'lodash';
import { Options, usage } from 'yargs'; import { Options, usage } from 'yargs';
import { loadConfig } from 'src/config'; import { loadConfig } from 'src/config';
@ -11,6 +13,8 @@ import { VisitorContext } from 'src/visitor/context';
const CONFIG_ARGS_NAME = 'config-name'; const CONFIG_ARGS_NAME = 'config-name';
const CONFIG_ARGS_PATH = 'config-path'; const CONFIG_ARGS_PATH = 'config-path';
const MODES = ['check', 'fix'];
const RULE_OPTION: Options = { const RULE_OPTION: Options = {
default: [], default: [],
group: 'Rules:', group: 'Rules:',
@ -87,6 +91,11 @@ export async function main(argv: Array<string>): Promise<number> {
logger.info(VERSION_INFO, 'version info'); logger.info(VERSION_INFO, 'version info');
logger.info({ args }, 'main arguments'); logger.info({ args }, 'main arguments');
// check mode
if (!MODES.includes(args.mode)) {
logger.error({ mode: args.mode }, 'unsupported mode');
}
// const schema = new Schema(); // const schema = new Schema();
const result = { errors: [], valid: true }; // schema.match(config); const result = { errors: [], valid: true }; // schema.match(config);
if (!result.valid) { if (!result.valid) {
@ -97,9 +106,6 @@ export async function main(argv: Array<string>): Promise<number> {
const rules = await loadRules(args.rules); const rules = await loadRules(args.rules);
const source = await loadSource(args.source); const source = await loadSource(args.source);
const parser = new YamlParser();
const data = parser.parse(source);
const activeRules = await resolveRules(rules, args as any); const activeRules = await resolveRules(rules, args as any);
const ctx = new VisitorContext({ const ctx = new VisitorContext({
coerce: args.coerce, coerce: args.coerce,
@ -107,23 +113,29 @@ export async function main(argv: Array<string>): Promise<number> {
logger, logger,
}); });
switch (args.mode) { const parser = new YamlParser();
case 'check': let data = parser.parse(source);
case 'fix':
for (const rule of activeRules) { for (const rule of activeRules) {
if (rule.visit(ctx, data)) { const workingCopy = cloneDeep(data);
logger.info({ rule }, 'passed rule'); const ruleErrors = await rule.visit(ctx, workingCopy);
} else {
logger.warn({ rule }, 'failed rule'); 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');
data = workingCopy;
} else {
logger.info({ rule }, 'rule passed');
} }
break; }
default:
ctx.error({ mode: args.mode }, 'unsupported mode');
} }
if (ctx.errors.length > 0) { if (ctx.errors.length > 0) {
logger.error({ errors: ctx.errors }, 'some rules failed'); logger.error({ count: ctx.errors.length, errors: ctx.errors }, 'some rules failed');
if (args.count) { if (args.count) {
return Math.min(ctx.errors.length, 255); return Math.min(ctx.errors.length, 255);
} else { } else {

View File

@ -101,7 +101,7 @@ export class Rule implements RuleData, Visitor {
} }
} }
public async visit(ctx: VisitorContext, node: any): Promise<VisitorContext> { public async visit(ctx: VisitorContext, node: any): Promise<number> {
const check = ctx.ajv.compile(this.check); const check = ctx.ajv.compile(this.check);
const filter = this.compileFilter(ctx); const filter = this.compileFilter(ctx);
const scopes = JSONPath({ const scopes = JSONPath({
@ -111,7 +111,7 @@ export class Rule implements RuleData, Visitor {
if (isNil(scopes) || scopes.length === 0) { if (isNil(scopes) || scopes.length === 0) {
ctx.logger.debug('no data selected'); ctx.logger.debug('no data selected');
return ctx; return 0;
} }
for (const item of scopes) { for (const item of scopes) {
@ -119,19 +119,22 @@ export class Rule implements RuleData, Visitor {
if (filter(item)) { if (filter(item)) {
ctx.logger.debug({ item }, 'checking item') ctx.logger.debug({ item }, 'checking item')
if (!check(item)) { if (!check(item)) {
const errors = Array.from(check.errors);
ctx.logger.warn({ ctx.logger.warn({
errors,
name: this.name, name: this.name,
item, item,
rule: this,
}, 'rule failed on item'); }, 'rule failed on item');
ctx.errors.push(...check.errors); ctx.errors.push(...errors);
return ctx; return errors.length;
} }
} else { } else {
ctx.logger.debug({ errors: filter.errors, item }, 'skipping item'); ctx.logger.debug({ errors: filter.errors, item }, 'skipping item');
} }
} }
return ctx; return 0;
} }
protected compileFilter(ctx: VisitorContext): any { protected compileFilter(ctx: VisitorContext): any {

View File

@ -10,6 +10,8 @@ export interface VisitorContextOptions {
export class VisitorContext { export class VisitorContext {
public readonly ajv: any; public readonly ajv: any;
public readonly changes: Array<any>; public readonly changes: Array<any>;
public readonly coerce: boolean;
public readonly defaults: boolean;
public readonly errors: Array<any>; public readonly errors: Array<any>;
public readonly logger: Logger; public readonly logger: Logger;
@ -19,6 +21,8 @@ export class VisitorContext {
useDefaults: options.defaults, useDefaults: options.defaults,
}); });
this.changes = []; this.changes = [];
this.coerce = options.coerce;
this.defaults = options.defaults;
this.errors = []; this.errors = [];
this.logger = options.logger; this.logger = options.logger;
} }

View File

@ -1,5 +1,5 @@
import { VisitorContext } from 'src/visitor/context'; import { VisitorContext } from 'src/visitor/context';
export interface Visitor { export interface Visitor {
visit(ctx: VisitorContext, node: any): Promise<VisitorContext>; visit(ctx: VisitorContext, node: any): Promise<number>;
} }

View File

@ -9,6 +9,11 @@
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
"@types/deep-diff@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@types/deep-diff/-/deep-diff-1.0.0.tgz#7eba3202a99b3a207f758f351f7f86387269fc40"
integrity sha512-ENsJcujGbCU/oXhDfQ12mSo/mCBWodT2tpARZKmatoSrf8+cGRCPi0KVj3I0FORhYZfLXkewXu7AoIWqiBLkNw==
"@types/estree@0.0.39": "@types/estree@0.0.39":
version "0.0.39" version "0.0.39"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
@ -577,6 +582,11 @@ decode-uri-component@^0.2.0:
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=
deep-diff@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/deep-diff/-/deep-diff-1.0.2.tgz#afd3d1f749115be965e89c63edc7abb1506b9c26"
integrity sha512-aWS3UIVH+NPGCD1kki+DCU9Dua032iSsO43LqQpcs4R3+dVv7tX0qBGjiVHJHjplsoUM2XRO/KB92glqc68awg==
define-property@^0.2.5: define-property@^0.2.5:
version "0.2.5" version "0.2.5"
resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116"