diff --git a/Makefile b/Makefile index bb78a74..0845043 100755 --- a/Makefile +++ b/Makefile @@ -123,7 +123,7 @@ run-rules: ## validate the rules directory --config-name config-stderr.yml \ --rules $(ROOT_PATH)/rules/salty-dog.yml \ --source $${file} \ - --tag important > /dev/null; \ + --tag important > /dev/null || exit 1; \ done run-stream: ## validate stdin and write it to stdout, errors to stderr diff --git a/rules/ansible.yml b/rules/ansible.yml index 965b2a9..2972faf 100644 --- a/rules/ansible.yml +++ b/rules/ansible.yml @@ -1,3 +1,4 @@ +name: salty-dog-ansible rules: - name: ansible-playbook desc: ensure plays have important properties diff --git a/rules/gitlab-ci.yml b/rules/gitlab-ci.yml index 839a5e2..5d87925 100644 --- a/rules/gitlab-ci.yml +++ b/rules/gitlab-ci.yml @@ -1,3 +1,4 @@ +name: salty-dog-gitlab-ci rules: - name: gitlab-stages desc: should specify stages diff --git a/rules/kubernetes.yml b/rules/kubernetes.yml index 139bed2..ec5b9e8 100644 --- a/rules/kubernetes.yml +++ b/rules/kubernetes.yml @@ -1,3 +1,4 @@ +name: salty-dog-kubernetes rules: - name: kubernetes-resources desc: containers must have complete resources specified diff --git a/rules/salty-dog.yml b/rules/salty-dog.yml index f68e2f8..d1a3f42 100644 --- a/rules/salty-dog.yml +++ b/rules/salty-dog.yml @@ -1,3 +1,45 @@ +name: salty-dog-meta +definitions: + rule: + type: object + additionalProperties: false + required: + # metadata + - name + - desc + - level + - tags + # data + - select + - check + properties: + name: + type: string + pattern: "[-a-z0-9]+" + desc: + type: string + minLength: 8 + maxLength: 255 + level: + type: string + enum: + - debug + - info + - warn + - error + tags: + type: array + items: + type: string + pattern: "[-:a-z0-9]+" + select: + type: string + minLength: 1 + filter: + type: object + check: + type: object + rules: - name: salty-dog-rule desc: rules must be complete @@ -7,42 +49,32 @@ rules: - salty-dog select: '$.rules[*]' + check: + $ref: "salty-dog-meta#/definitions/rule" + + - name: salty-dog-source + desc: source files must have rules + level: info + tags: + - important + - salty-dog + + select: '$' check: type: object additionalProperties: false - required: - # metadata - - name - - desc - - level - - tags - # data - - select - - check + required: [name, rules] properties: + definitions: + type: object + additionalProperties: false + patternProperties: + "[-a-z]+": + type: object name: type: string - pattern: "[-a-z0-9]+" - desc: - type: string - minLength: 8 - maxLength: 255 - level: - type: string - enum: - - debug - - info - - warn - - error - tags: + rules: type: array + minItems: 1 items: - type: string - pattern: "[-:a-z0-9]+" - select: - type: string - minLength: 1 - filter: - type: object - check: - type: object \ No newline at end of file + $ref: "salty-dog-meta#/definitions/rule" \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 57d2df0..1013b4e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -103,10 +103,6 @@ export async function main(argv: Array): Promise { return STATUS_ERROR; } - const rules = await loadRules(args.rules); - const source = await loadSource(args.source); - - const activeRules = await resolveRules(rules, args as any); const ctx = new VisitorContext({ coerce: args.coerce, defaults: args.mode === 'fix', @@ -114,8 +110,12 @@ export async function main(argv: Array): Promise { }); const parser = new YamlParser(); + const source = await loadSource(args.source); let data = parser.parse(source); + const rules = await loadRules(args.rules, ctx.ajv); + const activeRules = await resolveRules(rules, args as any); + for (const rule of activeRules) { const workingCopy = cloneDeep(data); const ruleErrors = await rule.visit(ctx, workingCopy); diff --git a/src/rule.ts b/src/rule.ts index 83a8dd8..670c7a1 100644 --- a/src/rule.ts +++ b/src/rule.ts @@ -28,7 +28,13 @@ export interface RuleSelector { includeTag: Array; } -export async function loadRules(paths: Array): Promise> { +export interface RuleSource { + definitions?: Array; + name: string; + rules: Array; +} + +export async function loadRules(paths: Array, ajv: any): Promise> { const parser = new YamlParser(); const rules = []; @@ -37,7 +43,15 @@ export async function loadRules(paths: Array): Promise> { encoding: 'utf-8', }); - const data = parser.parse(contents); + const data = parser.parse(contents) as RuleSource; + + if (!isNil(data.definitions)) { + ajv.addSchema({ + '$id': data.name, + definitions: data.definitions, + }); + } + rules.push(...data.rules.map((data: any) => new Rule(data))); }