Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a reporters API, and provide Nagios and Syslog reporting #7

Merged
merged 6 commits into from Nov 12, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions META6.json
Expand Up @@ -3,11 +3,17 @@
"auth": "Oetiker+Partner",
"depends": [
"JSON::Fast",
"Log::Syslog::Native",
"Terminal::ANSIColor",
"JSON::Path"
],
"provides": {
"JsonHound::Reporter::Compound": "lib/JsonHound/Reporter/Compound.pm6",
"JsonHound::Reporter::Syslog": "lib/JsonHound/Reporter/Syslog.pm6",
"JsonHound::Reporter::CLI": "lib/JsonHound/Reporter/CLI.pm6",
"JsonHound::Reporter::Nagios": "lib/JsonHound/Reporter/Nagios.pm6",
"JsonHound::Violation": "lib/JsonHound/Violation.pm6",
"JsonHound::Reporter": "lib/JsonHound/Reporter.pm6",
"JsonHound::RuleSet": "lib/JsonHound/RuleSet.pm6",
"JsonHound::PathMixin": "lib/JsonHound/PathMixin.pm6",
"JsonHound": "lib/JsonHound.pm6"
Expand Down
67 changes: 37 additions & 30 deletions bin/jsonhound
@@ -1,59 +1,66 @@
#!/usr/bin/env perl6
use JSON::Fast;
use Terminal::ANSIColor;
use JsonHound::Reporter::Compound;
use JsonHound::Reporter::CLI;
use JsonHound::Reporter::Nagios;
use JsonHound::Reporter::Syslog;
use JsonHound::RuleSet;

sub MAIN(
$validations, #= Path to module specifying the validation to apply
*@json-files #= The JSON file(s) to validate
*@json-files, #= The JSON file(s) to validate
Str :$reporter #= Which reporter(s) to use, comma-separated (nagios,cli,syslog)
) {
my $*JSON-HOUND-RULESET = JsonHound::RuleSet.new;
my $rule-file = $validations.IO;
CompUnit::RepositoryRegistry.use-repository:
CompUnit::RepositoryRegistry.repository-for-spec($rule-file.parent.absolute);
require "$rule-file.basename()";

my $failed = False;
race for @json-files.race(:1batch) -> $file {
# We process the input files in parallel, but then do reporting of those
# results one at a time. The `map` produces closures that are invoked with
# the reporter; the `for` enforces one-at-a-time behavior. This means that
# reporters don't need to account for concurrency.
my $reporter-object = build-reporter($reporter);
.($reporter-object) for @json-files.race(:1batch).map: -> $file {
with slurp($file) -> $json {
with try from-json($json) -> $parsed {
if $*JSON-HOUND-RULESET.validate($parsed) -> @violations {
validation-error($file, @violations);
*.validation-error($file, @violations);
}
else {
ok($file);
*.ok($file);
}
}
else {
file-error($file, "invalid JSON: $!");
*.file-error($file, "invalid JSON: $!");
}
}
else {
file-error($file, "file not found");
*.file-error($file, "file not found");
}
}
exit($failed ?? 1 !! 0);

sub ok($file) {
say "$file: " ~ colored("passed validation", "green");
}

sub file-error($file, $message) {
say "$file: " ~ colored($message, "red");
$failed = True;
}
};
$reporter-object.finalize;
exit($reporter-object.exit-code // 0);
}

sub validation-error($file, @violations) {
my $message = "$file: " ~ colored("failed validation\n", "red");
for @violations -> $v {
$message ~= " " ~ colored("$v.name()", "underline") ~
" $v.file.IO.basename():$v.line()\n";
for $v.arguments.sort(*.key).map(|*.kv) -> $name, $json {
$message ~= colored(" $name: ", "bold");
$message ~= colored("$json.path()\n", "blue");
$message ~= to-json($json).indent(6) ~ "\n";
}
sub build-reporter($reporter) {
my @reporters = do for ($reporter // 'cli').split(',') {
when 'cli' {
JsonHound::Reporter::CLI.new
}
when 'nagios' {
JsonHound::Reporter::Nagios.new
}
when 'syslog' {
JsonHound::Reporter::Syslog.new
}
default {
note "No such reporter '$_'";
exit 1;
}
say $message;
}
return @reporters == 1
?? @reporters[0]
!! JsonHound::Reporter::Compound.new(:@reporters);
}
22 changes: 22 additions & 0 deletions lib/JsonHound/Reporter.pm6
@@ -0,0 +1,22 @@
#| A reporter presents validation results in some way. That might be output
#| to stdout/stderr, writing to syslog, or something else. A reporter may
#| also provide an exit code for the program.
role JsonHound::Reporter {
#| Called when a file is validated successfully.
method ok(Str $file --> Nil) { ... }

#| Called when there is a problem with the file that prevents validation
#| even being attempted.
method file-error(Str $file, Str $problem --> Nil) { ... }

#| Called when a file has validation errors.
method validation-error(Str $file, @violations --> Nil) { ... }

#| Called when all files have been processed, to finalize the results.
#| By default, does nothing.
method finalize(--> Nil) { }

#| Called to obtain an exit code. If this reporter does not wish to
#| contribute an exist code, it should simply not implement this method.
method exit-code(--> Int) { Int }
}
34 changes: 34 additions & 0 deletions lib/JsonHound/Reporter/CLI.pm6
@@ -0,0 +1,34 @@
use JSON::Fast;
use JsonHound::Reporter;
use Terminal::ANSIColor;

class JsonHound::Reporter::CLI does JsonHound::Reporter {
has $!failed = False;

method ok(Str $file --> Nil) {
note "$file: " ~ colored("passed validation", "green");
}

method file-error(Str $file, Str $problem --> Nil) {
note "$file: " ~ colored($problem, "red");
$!failed = True;
}

method validation-error(Str $file, @violations --> Nil) {
my $message = "$file: " ~ colored("failed validation\n", "red");
for @violations -> $v {
$message ~= " " ~ colored("$v.name()", "underline") ~
" $v.file.IO.basename():$v.line()\n";
for $v.arguments.sort(*.key).map(|*.kv) -> $name, $json {
$message ~= colored(" $name: ", "bold");
$message ~= colored("$json.path()\n", "blue");
$message ~= to-json($json).indent(6) ~ "\n";
}
}
note $message;
}

method exit-code(--> Int) {
$!failed ?? 1 !! 0
}
}
30 changes: 30 additions & 0 deletions lib/JsonHound/Reporter/Compound.pm6
@@ -0,0 +1,30 @@
use JsonHound::Reporter;

#| Allows the use for multiple reporters, by delegating to them in turn.
#| For exit code, the first one providing an exit code wins.
class JsonHound::Reporter::Compound does JsonHound::Reporter {
has JsonHound::Reporter @.reporters;

method ok(Str $file --> Nil) {
.ok($file) for @!reporters;
}

method file-error(Str $file, Str $problem --> Nil) {
.file-error($file, $problem) for @!reporters;
}

method validation-error(Str $file, @violations --> Nil) {
.validation-error($file, @violations) for @!reporters;
}

method finalize(--> Nil) {
.finalize for @!reporters;
}

method exit-code(--> Int) {
for @!reporters -> $reporter {
.return with $reporter.exit-code;
}
return Int;
}
}
33 changes: 33 additions & 0 deletions lib/JsonHound/Reporter/Nagios.pm6
@@ -0,0 +1,33 @@
use JsonHound::Reporter;

class JsonHound::Reporter::Nagios does JsonHound::Reporter {
has @!problems;

method ok(Str $file --> Nil) {
# Nothing to do
}

method file-error(Str $file, Str $problem --> Nil) {
push @!problems, "$file: $problem";
}

method validation-error(Str $file, @violations --> Nil) {
for @violations -> $v {
push @!problems, "$file: $v.name()";
}
}

method finalize(--> Nil) {
if @!problems {
say "Validation failed";
.say for @!problems;
}
else {
say "Validation passed";
}
}

method exit-code(--> Int) {
@!problems ?? 1 !! 0
}
}
18 changes: 18 additions & 0 deletions lib/JsonHound/Reporter/Syslog.pm6
@@ -0,0 +1,18 @@
use JsonHound::Reporter;
use Log::Syslog::Native;

class JsonHound::Reporter::Syslog does JsonHound::Reporter {
has Log::Syslog::Native $!syslog .= new(:ident<jsonhound>);

method ok(Str $file --> Nil) { }

method file-error(Str $file, Str $problem --> Nil) {
$!syslog.warning("$file: $problem")
}

method validation-error(Str $file, @violations --> Nil) {
for @violations -> $v {
$!syslog.warning("$file: $v.name()");
}
}
}