1
0
Fork 0

feat: run schema, do everything but select nodes

This commit is contained in:
ssube 2019-06-15 17:38:05 -05:00
parent e8173d4be4
commit 29aaa93f17
9 changed files with 207 additions and 10 deletions

View File

@ -1,4 +1,5 @@
import commonjs from 'rollup-plugin-commonjs';
import json from 'rollup-plugin-json';
import replace from 'rollup-plugin-replace';
import resolve from 'rollup-plugin-node-resolve';
import typescript from 'rollup-plugin-typescript2';
@ -17,6 +18,7 @@ export default {
sourcemap: true,
},
plugins: [
json(),
replace({
delimiters: ['{{ ', ' }}'],
values: {
@ -33,7 +35,7 @@ export default {
}),
commonjs({
namedExports: {
'node_modules/lodash/lodash.js': ['isNil', 'isString'],
'node_modules/lodash/lodash.js': ['intersection', 'isNil', 'isString'],
'node_modules/noicejs/out/main-bundle.js': ['BaseError'],
'node_modules/js-yaml/index.js': ['DEFAULT_SAFE_SCHEMA', 'SAFE_SCHEMA', 'safeLoad', 'Schema', 'Type'],
'node_modules/yargs-parser/index.js': ['detailed'],

View File

@ -27,6 +27,7 @@
"@types/yargs-parser": "^13.0.0",
"rollup": "^1.15.5",
"rollup-plugin-commonjs": "^10.0.0",
"rollup-plugin-json": "^4.0.0",
"rollup-plugin-node-resolve": "^5.0.2",
"rollup-plugin-replace": "^2.2.0",
"rollup-plugin-typescript2": "^0.21.1",

View File

@ -0,0 +1,8 @@
metadata:
name: example
spec:
template:
spec:
containers:
- name: test
# missing resources

40
rules/kubernetes.yml Normal file
View File

@ -0,0 +1,40 @@
rules:
- name: require-resources
level: info
tags:
- cluster-health
- important
nodes:
filter: ''
select: '$.spec.template.spec.containers[*]'
schema:
type: object
additionalProperties: true
required: [resources]
properties:
resources:
type: object
required: [limits, requests]
properties:
limits:
type: object
required: [cpu, memory]
properties:
cpu:
type: string
default: 100m
memory:
type: string
default: 256Mi
requests:
type: object
required: [cpu, memory]
properties:
cpu:
type: string
default: 100m
memory:
type: string
default: 256Mi

View File

@ -9,7 +9,7 @@ import { includeSchema, includeType } from 'src/config/type/Include';
import { regexpType } from 'src/config/type/Regexp';
import { NotFoundError } from 'src/error/NotFoundError';
export const CONFIG_ENV = 'ISOLEX_HOME';
export const CONFIG_ENV = 'SALTY_HOME';
export const CONFIG_SCHEMA = Schema.create([DEFAULT_SAFE_SCHEMA], [
envType,
includeType,
@ -23,7 +23,7 @@ const readFileSync = promisify(readFile);
/**
* With the given name, generate all potential config paths in their complete, absolute form.
*
* This will include the value of `ISOLEX_HOME`, `HOME`, the current working directory, and any extra paths
* This will include the value of `SALTY_HOME`, `HOME`, the current working directory, and any extra paths
* passed as the final arguments.
*/
export function completePaths(name: string, extras: Array<string>): Array<string> {

View File

@ -1,20 +1,46 @@
import { createLogger } from 'bunyan';
import { detailed, Options } from 'yargs-parser';
import { loadConfig } from 'src/config';
import { loadConfig, CONFIG_SCHEMA } from 'src/config';
import { loadRules, resolveRules, checkRule } from 'src/rule';
import { loadSource } from 'src/source';
import { VERSION_INFO } from 'src/version';
import { safeLoad } from 'js-yaml';
const CONFIG_ARGS_NAME = 'config-name';
const CONFIG_ARGS_PATH = 'config-path';
const MAIN_ARGS: Options = {
array: [CONFIG_ARGS_PATH],
alias: {
'includeTag': ['t', 'tag'],
'mode': ['m'],
},
array: [
CONFIG_ARGS_PATH,
'excludeLevel',
'excludeName',
'excludeTag',
'includeLevel',
'includeName',
'includeTag',
'rules',
],
count: ['v'],
default: {
[CONFIG_ARGS_NAME]: `.${VERSION_INFO.app.name}.yml`,
[CONFIG_ARGS_PATH]: [],
'excludeLevel': [],
'excludeName': [],
'excludeTag': [],
'includeLevel': [],
'includeName': [],
'includeTag': [],
'mode': 'check',
'rules': [],
'source': '-',
},
envPrefix: VERSION_INFO.app.name,
string: ['mode'],
};
const STATUS_SUCCESS = 0;
@ -35,12 +61,25 @@ export async function main(argv: Array<string>): Promise<number> {
return STATUS_ERROR;
}
if (args.argv.test) {
logger.info('config is valid');
return STATUS_SUCCESS;
const rules = await loadRules(args.argv.rules);
const source = await loadSource(args.argv.source);
const data = safeLoad(source, {
schema: CONFIG_SCHEMA,
});
const activeRules = await resolveRules(rules, args.argv as any);
// run rules
let status = STATUS_SUCCESS;
for (const rule of activeRules) {
if (checkRule(rule, data)) {
logger.info({ rule }, 'passed rule');
} else {
logger.warn({ rule }, 'failed rule');
status = STATUS_ERROR;
}
}
return STATUS_SUCCESS;
return status;
}
main(process.argv).then((status) => process.exit(status)).catch((err) => {

88
src/rule.ts Normal file
View File

@ -0,0 +1,88 @@
import * as ajv from 'ajv';
import { readFile } from 'fs';
import { intersection } from 'lodash';
import { LogLevel } from 'noicejs';
import { promisify } from 'util';
import { safeLoad } from 'js-yaml';
import { CONFIG_SCHEMA } from './config';
const readFileSync = promisify(readFile);
export interface Rule {
level: LogLevel;
name: string;
nodes: {
filter: string;
select: string;
};
schema: any;
tags: Array<string>;
}
export interface RuleSelector {
excludeLevel: Array<LogLevel>;
excludeName: Array<string>;
excludeTag: Array<string>;
includeLevel: Array<LogLevel>;
includeName: Array<string>;
includeTag: Array<string>;
}
export async function loadRules(paths: Array<string>): Promise<Array<Rule>> {
const rules = [];
for (const path of paths) {
const contents = await readFileSync(path, {
encoding: 'utf-8',
});
const data = safeLoad(contents, {
schema: CONFIG_SCHEMA,
});
rules.push(...data.rules);
}
return rules;
}
export async function resolveRules(rules: Array<Rule>, selector: RuleSelector): Promise<Array<Rule>> {
const activeRules = new Set<Rule>();
for (const r of rules) {
if (selector.excludeLevel.includes(r.level)) {
continue;
}
if (selector.excludeName.includes(r.name)) {
continue;
}
const excludedTags = intersection(selector.excludeTag, r.tags);
if (excludedTags.length > 0) {
continue;
}
if (selector.includeLevel.includes(r.level)) {
activeRules.add(r);
}
if (selector.includeName.includes(r.name)) {
activeRules.add(r);
}
const includedTags = intersection(selector.includeTag, r.tags);
if (includedTags.length > 0) {
activeRules.add(r);
}
}
return Array.from(activeRules);
}
export function checkRule(rule: Rule, data: any): boolean {
const schema = new ((ajv as any).default)().compile(rule.schema);
const valid = schema(data);
console.log(data, valid);
return !!valid;
}

12
src/source.ts Normal file
View File

@ -0,0 +1,12 @@
import { promisify } from 'util';
import { readFile } from 'fs';
const readFileSync = promisify(readFile);
export async function loadSource(path: string): Promise<string> {
if (path === '-') {
return readFileSync(0, 'utf-8');
} else {
return readFileSync(path, 'utf-8');
}
}

View File

@ -798,6 +798,13 @@ rollup-plugin-commonjs@^10.0.0:
resolve "^1.10.1"
rollup-pluginutils "^2.7.0"
rollup-plugin-json@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/rollup-plugin-json/-/rollup-plugin-json-4.0.0.tgz#a18da0a4b30bf5ca1ee76ddb1422afbb84ae2b9e"
integrity sha512-hgb8N7Cgfw5SZAkb3jf0QXii6QX/FOkiIq2M7BAQIEydjHvTyxXHQiIzZaTFgx1GK0cRCHOCBHIyEkkLdWKxow==
dependencies:
rollup-pluginutils "^2.5.0"
rollup-plugin-node-resolve@^5.0.2:
version "5.0.2"
resolved "https://registry.yarnpkg.com/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-5.0.2.tgz#6475dc689934a2f85bb5c5867d03d8bd1bf859c1"
@ -835,7 +842,7 @@ rollup-pluginutils@2.6.0:
estree-walker "^0.6.0"
micromatch "^3.1.10"
rollup-pluginutils@^2.6.0, rollup-pluginutils@^2.7.0, rollup-pluginutils@^2.8.0:
rollup-pluginutils@^2.5.0, rollup-pluginutils@^2.6.0, rollup-pluginutils@^2.7.0, rollup-pluginutils@^2.8.0:
version "2.8.1"
resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.1.tgz#8fa6dd0697344938ef26c2c09d2488ce9e33ce97"
integrity sha512-J5oAoysWar6GuZo0s+3bZ6sVZAC0pfqKz68De7ZgDi5z63jOVZn1uJL/+z1jeKHNbGII8kAyHF5q8LnxSX5lQg==