From 29aaa93f17e72be18f78ba8319f4eec7e21d0299 Mon Sep 17 00:00:00 2001 From: ssube Date: Sat, 15 Jun 2019 17:38:05 -0500 Subject: [PATCH] feat: run schema, do everything but select nodes --- config/rollup.js | 4 +- package.json | 1 + rules/examples/kubernetes-resource-fail.yml | 8 ++ rules/kubernetes.yml | 40 ++++++++++ src/config/index.ts | 4 +- src/index.ts | 51 ++++++++++-- src/rule.ts | 88 +++++++++++++++++++++ src/source.ts | 12 +++ yarn.lock | 9 ++- 9 files changed, 207 insertions(+), 10 deletions(-) create mode 100644 rules/examples/kubernetes-resource-fail.yml create mode 100644 rules/kubernetes.yml create mode 100644 src/rule.ts create mode 100644 src/source.ts diff --git a/config/rollup.js b/config/rollup.js index 3ea45c1..e770b7b 100644 --- a/config/rollup.js +++ b/config/rollup.js @@ -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'], diff --git a/package.json b/package.json index 11992de..8548bf7 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/rules/examples/kubernetes-resource-fail.yml b/rules/examples/kubernetes-resource-fail.yml new file mode 100644 index 0000000..7ae0ab3 --- /dev/null +++ b/rules/examples/kubernetes-resource-fail.yml @@ -0,0 +1,8 @@ +metadata: + name: example +spec: + template: + spec: + containers: + - name: test + # missing resources \ No newline at end of file diff --git a/rules/kubernetes.yml b/rules/kubernetes.yml new file mode 100644 index 0000000..745450b --- /dev/null +++ b/rules/kubernetes.yml @@ -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 \ No newline at end of file diff --git a/src/config/index.ts b/src/config/index.ts index f96d734..4e3c496 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -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): Array { diff --git a/src/index.ts b/src/index.ts index c757d48..7f06c10 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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): Promise { 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) => { diff --git a/src/rule.ts b/src/rule.ts new file mode 100644 index 0000000..cd7a980 --- /dev/null +++ b/src/rule.ts @@ -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; +} + +export interface RuleSelector { + excludeLevel: Array; + excludeName: Array; + excludeTag: Array; + includeLevel: Array; + includeName: Array; + includeTag: Array; +} + +export async function loadRules(paths: Array): Promise> { + 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, selector: RuleSelector): Promise> { + const activeRules = new Set(); + + 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; +} \ No newline at end of file diff --git a/src/source.ts b/src/source.ts new file mode 100644 index 0000000..b44a3b8 --- /dev/null +++ b/src/source.ts @@ -0,0 +1,12 @@ +import { promisify } from 'util'; +import { readFile } from 'fs'; + +const readFileSync = promisify(readFile); + +export async function loadSource(path: string): Promise { + if (path === '-') { + return readFileSync(0, 'utf-8'); + } else { + return readFileSync(path, 'utf-8'); + } +} diff --git a/yarn.lock b/yarn.lock index fc692f0..4a36060 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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==