From: Robert 'phaylon' Sedlacek Date: Wed, 25 Jul 2012 21:12:38 +0000 (+0000) Subject: mediawiki report publishing X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=0331d9cd8aa9da26fee6a810145f4b3a3a7af297;p=scpubgit%2FSystem-Introspector-Report.git mediawiki report publishing --- diff --git a/Makefile.PL b/Makefile.PL index 1c30f73..61488b2 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -16,11 +16,13 @@ WriteMakefile( 'strictures' => '1', 'JSON::PP' => '2.53', 'Object::Remote' => '0.001001', + 'MediaWiki::API' => '0.39', 'MRO::Compat' => 0, 'Class::C3' => 0, 'Getopt::Long' => 0, 'Pod::Usage' => 0, 'aliased' => 0, + 'Try::Tiny' => 0, }, EXE_FILES => [ 'bin/system-introspector-report', diff --git a/bin/system-introspector-report b/bin/system-introspector-report index deb074a..9243b68 100755 --- a/bin/system-introspector-report +++ b/bin/system-introspector-report @@ -2,6 +2,7 @@ use strictures; use Getopt::Long; use Pod::Usage; +use Try::Tiny; use System::Introspector::Report::Source; use System::Introspector::Report::Config; @@ -32,7 +33,12 @@ my @types = $all_reports my @reports = $source->generate(@types); for my $publisher ($config->publishers(@publish_types)) { - $publisher->publish(\@reports); + try { + $publisher->publish(\@reports); + } + catch { + print "Error during publish: $_\n"; + }; } __END__ diff --git a/lib/System/Introspector/Report/Builder/Perls.pm b/lib/System/Introspector/Report/Builder/Perls.pm index ce3e03c..ac5b724 100644 --- a/lib/System/Introspector/Report/Builder/Perls.pm +++ b/lib/System/Introspector/Report/Builder/Perls.pm @@ -48,6 +48,7 @@ sub render_reports { title => 'Perl Installations', id => 'perls', rowid => [qw( remote location )], + meta => {}, columns => [ { key => 'remote', label => 'Remote' }, { key => 'hostname', label => 'Hostname' }, diff --git a/lib/System/Introspector/Report/Publish/MediaWiki.pm b/lib/System/Introspector/Report/Publish/MediaWiki.pm index edbead7..878beea 100644 --- a/lib/System/Introspector/Report/Publish/MediaWiki.pm +++ b/lib/System/Introspector/Report/Publish/MediaWiki.pm @@ -1,5 +1,6 @@ package System::Introspector::Report::Publish::MediaWiki; use Moo; +use Try::Tiny; use aliased 'System::Introspector::Report::Publish::MediaWiki::Connection'; has page_options => ( @@ -13,13 +14,35 @@ has connection => (is => 'ro', lazy => 1, builder => 1, handles => { put_page => 'put', }); -sub _build_producer { Producer->new } -sub _build_connection { Connection->new } +has api_uri => (is => 'ro', required => 1); +has username => (is => 'ro', required => 1); +has password => (is => 'ro', required => 1); +has create => (is => 'ro', default => sub { 0 }); +has http_auth => (is => 'ro'); +has http_realm => (is => 'ro'); + +sub _build_producer { Producer->new } + +sub _build_connection { + my ($self) = @_; + return Connection->new( + api_uri => $self->api_uri, + username => $self->username, + password => $self->password, + create => $self->create, + $self->http_auth ? ( + http_auth => 1, + http_realm => $self->http_realm, + ) : (), + ); +} sub publish { my ($self, $reports) = @_; my $pages = $self->page_options; + printf "Pushing reports to MediaWiki at '%s'\n", $self->api_uri; for my $page (sort keys %$pages) { + print "Updating page '$page'\n"; $self->_publish_page($reports, $page, $pages->{$page}); } return 1; @@ -45,9 +68,14 @@ sub _sort_reports { sub _publish_page { my ($self, $reports, $page_name, $options) = @_; my $sorted = $self->_sort_reports($reports, $options->{report} || []); - my $page = $self->get_page($page_name); - $page->update($sorted); - $self->put_page($page); + try { + my $page = $self->get_page($page_name); + $page->update($sorted); + $self->put_page($page); + } + catch { + print "Error during page update: $_\n"; + }; return 1; } diff --git a/lib/System/Introspector/Report/Publish/MediaWiki/Connection.pm b/lib/System/Introspector/Report/Publish/MediaWiki/Connection.pm index 8871f64..d308d5b 100644 --- a/lib/System/Introspector/Report/Publish/MediaWiki/Connection.pm +++ b/lib/System/Introspector/Report/Publish/MediaWiki/Connection.pm @@ -1,12 +1,80 @@ package System::Introspector::Report::Publish::MediaWiki::Connection; use Moo; +use MediaWiki::API; +use URI; +use aliased 'System::Introspector::Report::Publish::MediaWiki::Page'; + +has api => (is => 'lazy'); +has api_uri => (is => 'ro', required => 1); +has username => (is => 'ro', required => 1); +has password => (is => 'ro', required => 1); +has create => (is => 'ro', default => sub { 0 }); +has http_auth => (is => 'ro'); +has http_realm => (is => 'ro'); + +my $_api_fail = sub { + my $api = shift; + die sprintf "MediaWiki Error %s: %s\n", + $api->{error}{code}, + $api->{error}{details}; +}; + +sub _build_api { + my ($self) = @_; + my $api_uri = URI->new($self->api_uri); + my $api = MediaWiki::API->new({ api_url => $self->api_uri }); + my $passwd = $self->password; + if ($ENV{TEST_SI_MEDIAWIKI_GETPWD}) { + system "stty -echo"; + printf "MediaWiki password for '%s': ", $self->username; + $passwd = ; + chomp $passwd; + system "stty echo"; + print "\n"; + } + $api->{ua}->credentials( + join(':', $api_uri->host, $api_uri->port), + $self->http_realm || 'Wiki', + $self->username, + $passwd, + ) if $self->http_auth; + $api->login({ + lgname => $self->username, + lgpassword => $passwd, + }) or $api->$_api_fail; + return $api; +} sub get { my ($self, $page_name) = @_; + my $page = $self->api->get_page({ title => $page_name }) + or $self->api->$_api_fail; + if (defined $page->{missing}) { + die "MediaWiki page '$page_name' does not exist\n" + unless $self->create; + return Page->new( + name => $page_name, + content => '', + ); + } + return Page->new( + name => $page_name, + timestamp => $page->{timestamp}, + content => $page->{'*'} || '', + ); } sub put { my ($self, $page) = @_; + $self->api->edit({ + action => 'edit', + title => $page->name, + text => $page->content, + $page->has_timestamp + ? (basetimestamp => $page->timestamp) + : (), + }) or $self->api->$_api_fail; + return 1; } 1; diff --git a/lib/System/Introspector/Report/Publish/MediaWiki/Page.pm b/lib/System/Introspector/Report/Publish/MediaWiki/Page.pm index b39b6c7..4ac1748 100644 --- a/lib/System/Introspector/Report/Publish/MediaWiki/Page.pm +++ b/lib/System/Introspector/Report/Publish/MediaWiki/Page.pm @@ -6,7 +6,7 @@ use aliased 'System::Introspector::Report::Publish::MediaWiki::Producer'; use aliased 'System::Introspector::Report::Updater'; has name => (is => 'ro', required => 1); -has timestamp => (is => 'ro', required => 1); +has timestamp => (is => 'ro', predicate => 'has_timestamp'); has content => (is => 'rwp', required => 1); my $_producer = Producer->new; diff --git a/t/builder_packages-apt-byhost.t b/t/builder_packages-apt-byhost.t new file mode 100644 index 0000000..0fed0a1 --- /dev/null +++ b/t/builder_packages-apt-byhost.t @@ -0,0 +1,57 @@ +use strictures 1; +use Test::More; +use FindBin; +use aliased 'System::Introspector::Report::Source'; + +my $source = Source->new(root => "$FindBin::Bin/data/json/"); +my @reports = $source->generate(['Packages::Apt::ByHost', {}]); + +is scalar(@reports), 2, 'two reports received'; + +my $_test_columns = sub { + my ($rep, @columns) = @_; + my %cols = map { ($_->{key}, 1) } @{$rep->{columns}}; + ok $cols{$_}, "$_ column exists" + for @columns; +}; + +my $_test_rows = sub { + my ($rep, @rows) = @_; + is scalar(@{$rep->{rows}}), scalar(@rows), 'matching number of rows'; + for my $idx (0 .. $#rows) { + is $rep->{rows}[$idx]{$_}, $rows[$idx]{$_}, "row $idx $_ value" + for sort keys %{$rows[$idx]}; + } +}; + +subtest "first report" => sub { + my $rep = $reports[0]; + is_deeply $rep->{id}, ['packages-apt-by-remote', 'hostA'], 'report id'; + like $rep->{title}, qr{hostA}, 'report title contains remote'; + is_deeply $rep->{meta}, { remote => 'hostA' }, 'meta data'; + is_deeply $rep->{rowid}, ['package'], 'correct rowid'; + $rep->$_test_columns(qw( package version upgrade )); + $rep->$_test_rows( + { package => 'bar', version => 17, upgrade => '18' }, + { package => 'baz', version => undef, upgrade => undef, }, + { package => 'foo', version => 23, upgrade => undef }, + { package => 'qux', version => 77, upgrade => '80' }, + ); +}; + +subtest "second report" => sub { + my $rep = $reports[1]; + is_deeply $rep->{id}, ['packages-apt-by-remote', 'hostB'], 'report id'; + like $rep->{title}, qr{hostB}, 'report title contains remote'; + is_deeply $rep->{meta}, { remote => 'hostB' }, 'meta data'; + is_deeply $rep->{rowid}, ['package'], 'correct rowid'; + $rep->$_test_columns(qw( package version upgrade )); + $rep->$_test_rows( + { package => 'bar', version => 18, upgrade => undef }, + { package => 'baz', version => 99, upgrade => undef, }, + { package => 'foo', version => 23, upgrade => 'unparsable' }, + { package => 'qux', version => undef, upgrade => undef }, + ); +}; + +done_testing; diff --git a/t/builder_packages-apt-bypackage.t b/t/builder_packages-apt-bypackage.t new file mode 100644 index 0000000..6751d8d --- /dev/null +++ b/t/builder_packages-apt-bypackage.t @@ -0,0 +1,101 @@ +use strictures 1; +use Test::More; +use FindBin; +use aliased 'System::Introspector::Report::Source'; + +my $source = Source->new(root => "$FindBin::Bin/data/json/"); +my @reports = $source->generate(['Packages::Apt::ByPackage', {}]); + +is scalar(@reports), 4, 'four reports received'; + +my $_test_columns = sub { + my ($rep, @columns) = @_; + my %cols = map { ($_->{key}, 1) } @{$rep->{columns}}; + ok $cols{$_}, "$_ column exists" + for @columns; +}; + +my $_test_rows = sub { + my ($rep, @rows) = @_; + is scalar(@{$rep->{rows}}), scalar(@rows), 'matching number of rows'; + for my $idx (0 .. $#rows) { + is $rep->{rows}[$idx]{$_}, $rows[$idx]{$_}, "row $idx $_ value" + for sort keys %{$rows[$idx]}; + } +}; + +subtest "first report" => sub { + my $rep = shift @reports; + is_deeply $rep->{id}, ['packages-apt-by-package', 'bar'], 'report id'; + like $rep->{title}, qr{bar}, 'report title contains package'; + is_deeply $rep->{meta}, { package => 'bar' }, 'meta data'; + is_deeply $rep->{rowid}, ['remote'], 'correct rowid'; + $rep->$_test_columns(qw( hostname remote version upgrade )); + $rep->$_test_rows( + { remote => 'hostA', + hostname => 'a.example.com', + version => 17, + upgrade => 18, + }, + { remote => 'hostB', + hostname => 'b.example.com', + version => 18, + upgrade => undef, + }, + ); +}; + +subtest "second report" => sub { + my $rep = shift @reports; + is_deeply $rep->{id}, ['packages-apt-by-package', 'baz'], 'report id'; + like $rep->{title}, qr{baz}, 'report title contains package'; + is_deeply $rep->{meta}, { package => 'baz' }, 'meta data'; + is_deeply $rep->{rowid}, ['remote'], 'correct rowid'; + $rep->$_test_columns(qw( hostname remote version upgrade )); + $rep->$_test_rows( + { remote => 'hostB', + hostname => 'b.example.com', + version => 99, + upgrade => undef, + }, + ); +}; + +subtest "third report" => sub { + my $rep = shift @reports; + is_deeply $rep->{id}, ['packages-apt-by-package', 'foo'], 'report id'; + like $rep->{title}, qr{foo}, 'report title contains package'; + is_deeply $rep->{meta}, { package => 'foo' }, 'meta data'; + is_deeply $rep->{rowid}, ['remote'], 'correct rowid'; + $rep->$_test_columns(qw( hostname remote version upgrade )); + $rep->$_test_rows( + { remote => 'hostA', + hostname => 'a.example.com', + version => 23, + upgrade => undef, + }, + { remote => 'hostB', + hostname => 'b.example.com', + version => 23, + upgrade => 'unparsable', + }, + ); +}; + +subtest "fourth report" => sub { + my $rep = shift @reports; + is_deeply $rep->{id}, ['packages-apt-by-package', 'qux'], 'report id'; + like $rep->{title}, qr{qux}, 'report title contains package'; + is_deeply $rep->{meta}, { package => 'qux' }, 'meta data'; + is_deeply $rep->{rowid}, ['remote'], 'correct rowid'; + $rep->$_test_columns(qw( hostname remote version upgrade )); + $rep->$_test_rows( + { remote => 'hostA', + hostname => 'a.example.com', + version => 77, + upgrade => 80, + }, + ); +}; + +done_testing; diff --git a/t/builder_perls.t b/t/builder_perls.t new file mode 100644 index 0000000..c051018 --- /dev/null +++ b/t/builder_perls.t @@ -0,0 +1,46 @@ +use strictures 1; +use Test::More; +use FindBin; +use aliased 'System::Introspector::Report::Source'; + +my $source = Source->new(root => "$FindBin::Bin/data/json/"); +my @reports = $source->generate(['Perls', {}]); + +is scalar(@reports), 1, 'single report generated'; +my $rep = $reports[0]; + +is $rep->{id}, 'perls', 'report id'; +like $rep->{title}, qr{perl}i, 'report title contains perl'; +is_deeply $rep->{meta}, {}, 'no metadata in this report'; +is_deeply $rep->{rowid}, [qw( remote location )], 'correct rowid'; +my %cols = map { ($_->{key}, 1) } @{$rep->{columns}}; +ok $cols{$_}, "$_ column exists" + for qw( remote hostname location version ); +my @rows = ( + { hostname => 'a.example.com', + remote => 'hostA', + location => '/foo/bar/perl', + version => '5.10.0', + }, + { hostname => 'a.example.com', + remote => 'hostA', + location => '/foo/baz/perl', + version => '5.14.1', + }, + { hostname => 'b.example.com', + remote => 'hostB', + location => '/foo/bar/perl', + version => '5.10.0', + }, + { hostname => 'b.example.com', + remote => 'hostB', + location => '/foo/qux/perl', + version => '5.14.1', + }, +); +for my $idx (0 .. $#rows) { + is $rep->{rows}[$idx]{$_}, $rows[$idx]{$_}, "row $idx $_ value" + for sort keys %{$rows[$idx]}; +} + +done_testing; diff --git a/t/data/json/hostA/groupA/host.json b/t/data/json/hostA/groupA/host.json new file mode 100644 index 0000000..4599107 --- /dev/null +++ b/t/data/json/hostA/groupA/host.json @@ -0,0 +1 @@ +{ "hostname": "a.example.com" } diff --git a/t/data/json/hostA/groupA/packages/apt.json b/t/data/json/hostA/groupA/packages/apt.json new file mode 100644 index 0000000..d731fc8 --- /dev/null +++ b/t/data/json/hostA/groupA/packages/apt.json @@ -0,0 +1,23 @@ +{ + "installed": { + "packages": { + "foo": { + "version": 23, + }, + "bar": { + "version": 17, + }, + "qux": { + "version": 77, + }, + }, + }, + "upgradable": { + "actions": { + "inst": { + "bar": "[17] (18)", + "qux": "[77] (80)", + }, + }, + }, +} diff --git a/t/data/json/hostA/groupA/perls.json b/t/data/json/hostA/groupA/perls.json new file mode 100644 index 0000000..6ab9d58 --- /dev/null +++ b/t/data/json/hostA/groupA/perls.json @@ -0,0 +1,14 @@ +{ "perls": { + "/foo/bar": { + "config": { + "version": "5.10.0", + }, + "executable": "/foo/bar/perl", + }, + "/foo/baz": { + "config": { + "version": "5.14.1", + }, + "executable": "/foo/baz/perl", + }, +} } diff --git a/t/data/json/hostB/groupB/host.json b/t/data/json/hostB/groupB/host.json new file mode 100644 index 0000000..540f6ba --- /dev/null +++ b/t/data/json/hostB/groupB/host.json @@ -0,0 +1 @@ +{ "hostname": "b.example.com" } diff --git a/t/data/json/hostB/groupB/packages/apt.json b/t/data/json/hostB/groupB/packages/apt.json new file mode 100644 index 0000000..4b2c70a --- /dev/null +++ b/t/data/json/hostB/groupB/packages/apt.json @@ -0,0 +1,23 @@ +{ + "installed": { + "packages": { + "foo": { + "version": 23, + }, + "bar": { + "version": 18, + }, + "baz": { + "version": 99, + }, + }, + }, + "upgradable": { + "actions": { + "inst": { + "foo": "unparsable", + }, + }, + }, +} + diff --git a/t/data/json/hostB/groupB/perls.json b/t/data/json/hostB/groupB/perls.json new file mode 100644 index 0000000..24a955b --- /dev/null +++ b/t/data/json/hostB/groupB/perls.json @@ -0,0 +1,14 @@ +{ "perls": { + "/foo/bar": { + "config": { + "version": "5.10.0", + }, + "executable": "/foo/bar/perl", + }, + "/foo/qux": { + "config": { + "version": "5.14.1", + }, + "executable": "/foo/qux/perl", + }, +} } diff --git a/t/updater.t b/t/updater.t new file mode 100644 index 0000000..21d0dd6 --- /dev/null +++ b/t/updater.t @@ -0,0 +1,32 @@ +use strictures 1; +use Test::More; + +use aliased 'System::Introspector::Report::Updater'; + +my $updater = Updater->new; + +my $content = $updater->merge( + [], + [ + { id => "foo", + title => "Foo", + columns => [{ key => "a", label => "A" }, { key => "b", label => "B" }], + rows => [ + { a => 17, b => 23 }, + { a => 23, b => 17 }, + ], + }, + { id => "bar", + title => "Bar", + columns => [{ key => "a", label => "A" }, { key => "b", label => "B" }], + rows => [ + { a => 17, b => 23 }, + { a => 23, b => 17 }, + ], + }, + ], +); + +ok scalar(@$content), 'received content'; + +done_testing;