From 4f27f3ea59baa6cfca0ac1df96b1dfedbd83706c Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Fri, 2 Jan 2015 14:00:56 +0100 Subject: initial --- Makefile | 61 +++ README.md | 9 + ca.pem | 1 + contrib/zsh/_mon.zsh | 46 ++ debian/README | 6 + debian/changelog | 5 + debian/compat | 1 + debian/control | 16 + debian/copyright | 30 ++ debian/files | 1 + debian/mon.debhelper.log | 45 ++ debian/mon.manpages | 1 + debian/mon.substvars | 2 + debian/mon/DEBIAN/control | 12 + debian/mon/DEBIAN/md5sums | 21 + debian/mon/usr/bin/m | 133 ++++++ debian/mon/usr/bin/mi | 3 + debian/mon/usr/bin/mon | 133 ++++++ debian/mon/usr/share/doc/mon/changelog.gz | Bin 0 -> 170 bytes debian/mon/usr/share/doc/mon/copyright | 30 ++ debian/mon/usr/share/man/man1/mon.1.gz | Bin 0 -> 6617 bytes debian/mon/usr/share/mon/contrib/zsh/_mon.zsh | 46 ++ debian/mon/usr/share/mon/examples/mon.conf.sample | 23 + debian/mon/usr/share/mon/itca.pem | 23 + debian/mon/usr/share/mon/lib/MAPI/Cache.pm | 55 +++ debian/mon/usr/share/mon/lib/MAPI/Config.pm | 176 +++++++ debian/mon/usr/share/mon/lib/MAPI/Display.pm | 360 ++++++++++++++ debian/mon/usr/share/mon/lib/MAPI/Filter.pm | 166 +++++++ debian/mon/usr/share/mon/lib/MAPI/JSON.pm | 51 ++ debian/mon/usr/share/mon/lib/MAPI/Options.pm | 163 +++++++ debian/mon/usr/share/mon/lib/MAPI/Query.pm | 557 ++++++++++++++++++++++ debian/mon/usr/share/mon/lib/MAPI/QueryBase.pm | 232 +++++++++ debian/mon/usr/share/mon/lib/MAPI/RESTlos.pm | 471 ++++++++++++++++++ debian/mon/usr/share/mon/lib/MAPI/Syslogger.pm | 77 +++ debian/mon/usr/share/mon/lib/MAPI/Utils.pm | 80 ++++ debian/mon/usr/share/mon/version | 1 + debian/rules | 13 + docs/README.txt | 1 + docs/mon.1 | 546 +++++++++++++++++++++ docs/mon.1.gz | Bin 0 -> 6625 bytes docs/mon.pod | 409 ++++++++++++++++ docs/mon.txt | 394 +++++++++++++++ lib/MON/Cache.pm | 55 +++ lib/MON/Config.pm | 176 +++++++ lib/MON/Display.pm | 360 ++++++++++++++ lib/MON/Filter.pm | 166 +++++++ lib/MON/JSON.pm | 51 ++ lib/MON/Options.pm | 163 +++++++ lib/MON/Query.pm | 557 ++++++++++++++++++++++ lib/MON/QueryBase.pm | 232 +++++++++ lib/MON/RESTlos.pm | 471 ++++++++++++++++++ lib/MON/Syslogger.pm | 77 +++ lib/MON/Utils.pm | 80 ++++ mi | 3 + mon | 133 ++++++ mon.conf | 23 + redhat-specs-add.txt | 2 + t/Makefile | 2 + t/delete.t | 28 ++ t/edit.t | 26 + t/get.t | 25 + t/get_interactive.t | 25 + t/get_where.t | 48 ++ t/getfmt.t | 23 + t/insert.t | 29 ++ t/options_debug_verbose_quiet.t | 43 ++ t/options_version.t | 28 ++ t/post.t | 41 ++ t/post_as_array.t | 39 ++ t/post_interactive.t | 41 ++ t/put.t | 60 +++ t/update.t | 49 ++ t/update_format.t | 37 ++ t/view.t | 22 + 74 files changed, 7515 insertions(+) create mode 100644 Makefile create mode 100644 README.md create mode 100644 ca.pem create mode 100644 contrib/zsh/_mon.zsh create mode 100644 debian/README create mode 100644 debian/changelog create mode 100644 debian/compat create mode 100644 debian/control create mode 100644 debian/copyright create mode 100644 debian/files create mode 100644 debian/mon.debhelper.log create mode 100644 debian/mon.manpages create mode 100644 debian/mon.substvars create mode 100644 debian/mon/DEBIAN/control create mode 100644 debian/mon/DEBIAN/md5sums create mode 100755 debian/mon/usr/bin/m create mode 100755 debian/mon/usr/bin/mi create mode 100755 debian/mon/usr/bin/mon create mode 100644 debian/mon/usr/share/doc/mon/changelog.gz create mode 100644 debian/mon/usr/share/doc/mon/copyright create mode 100644 debian/mon/usr/share/man/man1/mon.1.gz create mode 100644 debian/mon/usr/share/mon/contrib/zsh/_mon.zsh create mode 100644 debian/mon/usr/share/mon/examples/mon.conf.sample create mode 100644 debian/mon/usr/share/mon/itca.pem create mode 100644 debian/mon/usr/share/mon/lib/MAPI/Cache.pm create mode 100644 debian/mon/usr/share/mon/lib/MAPI/Config.pm create mode 100644 debian/mon/usr/share/mon/lib/MAPI/Display.pm create mode 100644 debian/mon/usr/share/mon/lib/MAPI/Filter.pm create mode 100644 debian/mon/usr/share/mon/lib/MAPI/JSON.pm create mode 100644 debian/mon/usr/share/mon/lib/MAPI/Options.pm create mode 100644 debian/mon/usr/share/mon/lib/MAPI/Query.pm create mode 100644 debian/mon/usr/share/mon/lib/MAPI/QueryBase.pm create mode 100644 debian/mon/usr/share/mon/lib/MAPI/RESTlos.pm create mode 100644 debian/mon/usr/share/mon/lib/MAPI/Syslogger.pm create mode 100644 debian/mon/usr/share/mon/lib/MAPI/Utils.pm create mode 100644 debian/mon/usr/share/mon/version create mode 100755 debian/rules create mode 100644 docs/README.txt create mode 100644 docs/mon.1 create mode 100644 docs/mon.1.gz create mode 100644 docs/mon.pod create mode 100644 docs/mon.txt create mode 100644 lib/MON/Cache.pm create mode 100644 lib/MON/Config.pm create mode 100644 lib/MON/Display.pm create mode 100644 lib/MON/Filter.pm create mode 100644 lib/MON/JSON.pm create mode 100644 lib/MON/Options.pm create mode 100644 lib/MON/Query.pm create mode 100644 lib/MON/QueryBase.pm create mode 100644 lib/MON/RESTlos.pm create mode 100644 lib/MON/Syslogger.pm create mode 100644 lib/MON/Utils.pm create mode 100755 mi create mode 100755 mon create mode 100644 mon.conf create mode 100644 redhat-specs-add.txt create mode 100644 t/Makefile create mode 100644 t/delete.t create mode 100644 t/edit.t create mode 100644 t/get.t create mode 100644 t/get_interactive.t create mode 100644 t/get_where.t create mode 100644 t/getfmt.t create mode 100644 t/insert.t create mode 100644 t/options_debug_verbose_quiet.t create mode 100644 t/options_version.t create mode 100644 t/post.t create mode 100644 t/post_as_array.t create mode 100644 t/post_interactive.t create mode 100644 t/put.t create mode 100644 t/update.t create mode 100644 t/update_format.t create mode 100644 t/view.t diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7c3dd66 --- /dev/null +++ b/Makefile @@ -0,0 +1,61 @@ +NAME=mon +SHORTNAME=m +all: version documentation perltidy +version: + cut -d' ' -f2 debian/changelog | head -n 1 | sed 's/(//;s/)//' > .version +perltidy: + find . -name \*.pm | xargs perltidy -i 2 -b + perltidy -b $(NAME) + find . -name \*.bak -delete +documentation: + pod2man --release="$(NAME) $$(cat .version)" \ + --center="User Commands" ./docs/$(NAME).pod > ./docs/$(NAME).1 + pod2text ./docs/$(NAME).pod > ./docs/$(NAME).txt + gzip -c ./docs/$(NAME).1 > ./docs/$(NAME).1.gz +install: deinstall + test ! -d $(DESTDIR)/usr/bin && mkdir -p $(DESTDIR)/usr/bin || exit 0 + test ! -d $(DESTDIR)/usr/share/$(NAME) && mkdir -p $(DESTDIR)/usr/share/$(NAME) || exit 0 + test ! -d $(DESTDIR)/usr/share/$(NAME)/examples && mkdir -p $(DESTDIR)/usr/share/$(NAME)/examples || exit 0 + test ! -d $(DESTDIR)/usr/share/$(NAME)/contrib && mkdir -p $(DESTDIR)/usr/share/$(NAME)/contrib || exit 0 + test ! -d $(DESTDIR)/usr/share/man/man1 && mkdir -p $(DESTDIR)/usr/share/man/man1 || exit 0 + cp $(NAME) $(DESTDIR)/usr/bin + cp $(NAME) $(DESTDIR)/usr/bin/$(SHORTNAME) + cp ./mi $(DESTDIR)/usr/bin + cp -r ./lib $(DESTDIR)/usr/share/$(NAME)/ + cp ./.version $(DESTDIR)/usr/share/$(NAME)/version + cp ./$(NAME).conf $(DESTDIR)/usr/share/$(NAME)/examples/$(NAME).conf.sample + cp ./ca.pem $(DESTDIR)/usr/share/$(NAME)/ca.pem + cp -R ./contrib/* $(DESTDIR)/usr/share/$(NAME)/contrib + cp ./docs/$(NAME).1.gz $(DESTDIR)/usr/share/man/man1/$(NAME).1.gz + test ! -z "$(DESTDIR)" && find $(DESTDIR) -name .\*.swp -delete || exit 0 +deinstall: + test -f $(DESTDIR)/usr/bin/$(NAME) && rm $(DESTDIR)/usr/bin/$(NAME) && rm $(DESTDIR)/usr/bin/$(SHORTNAME) && rm $(DESTDIR)/usr/bin/mi || exit 0 + test -d $(DESTDIR)/usr/share/$(NAME) && rm -r $(DESTDIR)/usr/share/$(NAME) || exit 0 + test -f $(DESTDIR)/usr/share/man/man1/$(NAME).1.gz && rm -f $(DESTDIR)/usr/share/man/man1/$(NAME).1.gz || exit 0 +uninstall: deinstall +clean: + test -d debian/$(NAME)/usr && rm -Rf debian/$(NAME)/usr || exit 0 +dch: + dch -i +deb: + dpkg-buildpackage -uc -us +rpm: deb + alien -r --generate ../$(NAME)_$$(cat ./.version)_all.deb + cp redhat-specs-add.txt rpm.specs + sed "s/%%{ARCH}/noarch/" $$(pwd)/$(NAME)-$$(cat ./.version)/*.spec >> rpm.specs + mv rpm.specs $$(pwd)/$(NAME)-$$(cat ./.version)/*.spec + rpmbuild --buildroot $$(pwd)/$(NAME)-$$(cat ./.version) -ba $$(pwd)/$(NAME)-$$(cat ./.version)/*.spec +release: dch version + git commit -a -m 'New release' + bash -c "git tag $$(cat .version)" + git push --tags + git push origin master +clean-top: + rm -f ../$(NAME)_*.tar.gz || exit 0 + rm -f ../$(NAME)_*.dsc || exit 0 + rm -f ../$(NAME)_*.changes || exit 0 + rm -f ../$(NAME)_*.deb || exit 0 + rm -f ../$(NAME)-*.rpm || exit 0 +dotest: + sh -c 'cd ./t;make' + diff --git a/README.md b/README.md new file mode 100644 index 0000000..2b213c1 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +mon +==== + +The humble monitoring API tool. + +This tool assists you to manage monitoring configurations via the +RESTlos API. + +Please checkout manual pages at ./docs or man mon. diff --git a/ca.pem b/ca.pem new file mode 100644 index 0000000..7fe007a --- /dev/null +++ b/ca.pem @@ -0,0 +1 @@ +I am invalid, fill me diff --git a/contrib/zsh/_mon.zsh b/contrib/zsh/_mon.zsh new file mode 100644 index 0000000..3770c60 --- /dev/null +++ b/contrib/zsh/_mon.zsh @@ -0,0 +1,46 @@ +#compdef m mon + +get_options() { + elements=`${words[1,$[${CURRENT}-1]]} --dry --nocolor 2>&1` + if [ $? -ne 0 ]; then + elements="" + fi + + echo `echo $elements | tr '\n' ' '` +} + +_mon() { + local curcontext="$curcontext" state line + typeset -A opt_args + + case $words[2] in + getfmt) + if [ $CURRENT -eq 5 ]; then + compadd "$@" "where" + return + fi + ;; + post|put) + if [ $CURRENT -eq 4 ]; then + compadd "$@" "from" + return + fi + ;; + delete|edit|get|insert|update|view) + if [ $CURRENT -eq 4 ]; then + compadd "$@" "where" + return + fi + ;; + esac + + VALUES="`get_options`" + if [ -z "${VALUES}" ]; then + _files + else + ARGS=(${=VALUES}) + compadd "$@" ${ARGS[@]} + fi +} + +_mon "$@" diff --git a/debian/README b/debian/README new file mode 100644 index 0000000..41fc005 --- /dev/null +++ b/debian/README @@ -0,0 +1,6 @@ +The Debian Package mon +---------------------------- + +This is just a humble monitoring api tool. + + -- Paul Buetow Sun, 08 Apr 2012 15:23:53 +0200 diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..22d063a --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +mon (1.0.0) stable; urgency=low + + * Initial + + -- Paul Buetow Fri, 02 Jan 2015 13:50:50 +0100 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..45a4fb7 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +8 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..9d5d0e0 --- /dev/null +++ b/debian/control @@ -0,0 +1,16 @@ +Source: mon +Section: utils +Priority: optional +Maintainer: Paul Buetow +Build-Depends: perl, perltidy, alien, rpm +Standards-Version: 3.9.2 +Homepage: http://mon.buetow.org +Vcs-Git: https://github.com/rantanplan/mon +Vcs-Browser: https://github.com/rantanplan/mon + +Package: mon +Architecture: all +Depends: ${perl:Depends}, libwww-perl, libtime-modules-perl, libconfig-json-perl, libio-socket-ssl-perl, libunix-syslog-perl, libjson-xs-perl, libterm-readline-gnu-perl +Recommends: ca-root-cert, vim +Description: A simple monitoring API tool + Uses the RESTlos monitoring API to configure monitoring stuff. diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..9be5c7f --- /dev/null +++ b/debian/copyright @@ -0,0 +1,30 @@ +Format: http://dep.debian.net/deps/dep5 +Upstream-Name: mon +Source: http://mon.buetow.org + +Files: * +Copyright: 2012 Paul Buetow +License: GPL-3.0+ + +Files: debian/* +Copyright: 2012 Paul Buetow +License: GPL-3.0+ + +License: GPL-3.0+ + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + . + This package is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + . + You should have received a copy of the GNU General Public License + along with this program. If not, see . + . + On Debian systems, the complete text of the GNU General + Public License version 3 can be found in "/usr/share/common-licenses/GPL-3". + + diff --git a/debian/files b/debian/files new file mode 100644 index 0000000..1e9ea35 --- /dev/null +++ b/debian/files @@ -0,0 +1 @@ +mon_1.0.0_all.deb utils optional diff --git a/debian/mon.debhelper.log b/debian/mon.debhelper.log new file mode 100644 index 0000000..545a50f --- /dev/null +++ b/debian/mon.debhelper.log @@ -0,0 +1,45 @@ +dh_auto_configure +dh_auto_build +dh_auto_test +dh_prep +dh_installdirs +dh_auto_install +dh_install +dh_installdocs +dh_installchangelogs +dh_installexamples +dh_installman +dh_installcatalogs +dh_installcron +dh_installdebconf +dh_installemacsen +dh_installifupdown +dh_installinfo +dh_pysupport +dh_installinit +dh_installmenu +dh_installmime +dh_installmodules +dh_installlogcheck +dh_installlogrotate +dh_installpam +dh_installppp +dh_installudev +dh_installwm +dh_installxfonts +dh_installgsettings +dh_bugfiles +dh_ucf +dh_lintian +dh_gconf +dh_icons +dh_perl +dh_usrlocal +dh_link +dh_compress +dh_fixperms +dh_installdeb +dh_gencontrol +dh_md5sums +dh_builddeb +dh_builddeb diff --git a/debian/mon.manpages b/debian/mon.manpages new file mode 100644 index 0000000..202840a --- /dev/null +++ b/debian/mon.manpages @@ -0,0 +1 @@ +docs/mon.1 diff --git a/debian/mon.substvars b/debian/mon.substvars new file mode 100644 index 0000000..bcb0957 --- /dev/null +++ b/debian/mon.substvars @@ -0,0 +1,2 @@ +perl:Depends=perl +misc:Depends= diff --git a/debian/mon/DEBIAN/control b/debian/mon/DEBIAN/control new file mode 100644 index 0000000..35e9094 --- /dev/null +++ b/debian/mon/DEBIAN/control @@ -0,0 +1,12 @@ +Package: mon +Version: 3.0.1 +Architecture: all +Maintainer: Paul Buetow +Installed-Size: 126 +Depends: perl, libwww-perl, libtime-modules-perl, libconfig-json-perl, libio-socket-ssl-perl, libunix-syslog-perl, libjson-xs-perl, libterm-readline-gnu-perl +Recommends: ca-root-cert, vim +Section: utils +Priority: optional +Homepage: https://mamat-git0101.infra.server.lan/mon +Description: A simple monitoring API tool + Uses the RESTlos monitoring API to configure monitoring stuff. diff --git a/debian/mon/DEBIAN/md5sums b/debian/mon/DEBIAN/md5sums new file mode 100644 index 0000000..54cd36a --- /dev/null +++ b/debian/mon/DEBIAN/md5sums @@ -0,0 +1,21 @@ +8397dbe590d346eb3b925331eb5c2db1 usr/bin/m +8397dbe590d346eb3b925331eb5c2db1 usr/bin/mon +504f6e72ac6e115d63d3e9a1ccd1288c usr/bin/mi +e7abd63037f4be7013488c671a4716dd usr/share/doc/mon/changelog.gz +f125cc11bbc34b49bbfc518d07a96213 usr/share/doc/mon/copyright +9d4f77923a3c90cd6c5e2fe2136db9b6 usr/share/man/man1/mon.1.gz +a93997a0ddabf9ca06e602cca2134ea4 usr/share/mon/contrib/zsh/_mon.zsh +fc1844f15efdefc185d67518ae4857c2 usr/share/mon/examples/mon.conf.sample +7b124b0a3bc0bc716d3434f994fa7498 usr/share/mon/ca.pem +84722dc1495b5dba22d013eacba87ee8 usr/share/mon/lib/MON/Cache.pm +2c1750e42e80ba05c59af0b7a15f2407 usr/share/mon/lib/MON/Config.pm +752936b2d93398123c8d2238f8f9c9db usr/share/mon/lib/MON/Display.pm +442663ada73b39675e6e3a0012ad268a usr/share/mon/lib/MON/Filter.pm +330f96c524cd1c0dc012d94c7e216201 usr/share/mon/lib/MON/JSON.pm +f6dab5e42b4367a1af832cd76fc2b92b usr/share/mon/lib/MON/Options.pm +581f3d813169e433953fa761efb555fd usr/share/mon/lib/MON/Query.pm +ce6a905ad17647d4b7a3bfe268ac9b73 usr/share/mon/lib/MON/QueryBase.pm +5baa39a120e8a8d66d38e9490e93dd17 usr/share/mon/lib/MON/RESTlos.pm +6c8278360c0676c7c0f208b1d884128c usr/share/mon/lib/MON/Syslogger.pm +b62cea4e2ffd5b449f10e8b209fc151d usr/share/mon/lib/MON/Utils.pm +662f52dba3387be714becad581d0ac87 usr/share/mon/version diff --git a/debian/mon/usr/bin/m b/debian/mon/usr/bin/m new file mode 100755 index 0000000..162f71a --- /dev/null +++ b/debian/mon/usr/bin/m @@ -0,0 +1,133 @@ +#!/usr/bin/perl +# +# (c) 2013, 2014 1&1 Internet AG +# Paul C. Buetow + +use strict; +use warnings; +use v5.10; +use autodie; + +use Data::Dumper; +use Term::ReadLine; + +$| = 1; +my $lib; + +BEGIN { + if ( -d './lib/MON' ) { + $lib = './lib'; + + } + else { + $lib = '/usr/share/mon/lib'; + } + + unless ( exists $ENV{HTTPS_CA_FILE} ) { + if ( -f 'ca.pem' ) { + $ENV{HTTPS_CA_FILE} = 'ca.pem'; + } + elsif ( -f '/etc/ssl/certs/ca.pem' ) { + $ENV{HTTPS_CA_FILE} = '/etc/ssl/certs/ca.pem'; + } + else { + $ENV{HTTPS_CA_FILE} = '/usr/share/mon/ca.pem'; + } + } +} + +use lib $lib; + +use MON::RESTlos; +use MON::Config; +use MON::Options; +use MON::Query; +use MON::Syslogger; +use MON::Utils; +use MON::Display; + +sub main (@) { + my @args = @_; + my @opts; + + # Only interpret OPTIONS if they are at the beginning + push @opts, shift @args while @args and $args[0] =~ /^-/; + + # .. or at the end + push @opts, pop @args while @args and $args[-1] =~ /^-/; + + my $options = MON::Options->new( opts_passed => \@opts ); + my $logger = MON::Syslogger->new( options => $options ); + my $config = MON::Config->new( options => $options, logger => $logger ); + + if ( $config->{'version'} ) { + print get_version(), "\n"; + exit 0; + } + + $logger->logg( 'info', "Invoked by $ENV{USER} (params: @ARGV)" ); + + if ( $config->{interactive} ) { + my $term = Term::ReadLine->new('Monitoring API tool'); + my $prompt = '>> '; + my $out = $term->OUT || \*STDOUT; + + say $out "Welcome to the Monitoring API Tool v" . get_version(); + say $out "Press Ctrl+D to exit; Prefix cmd with ! to run via shell"; + + while ( defined( $_ = $term->readline($prompt) ) ) { + $term->addhistory($_) if /\S/; + + if (s/^!//) { + system($_); + } + else { + + my $line = $_; + + my @args = split / +/, $line; + + my $api = MON::RESTlos->new( config => $config ); + my $query = MON::Query->new( + config => $config, + api => $api, + options => $options, + args => \@args + ); + $query->parse(); + } + } + + } + else { + my $api = MON::RESTlos->new( config => $config ); + my $query = MON::Query->new( + config => $config, + api => $api, + options => $options, + args => \@args + ); + + $query->parse(); + $query->verbose("Good bye"); + + if ( $api->{had_error} != 0 ) { + + # Needed by Puppet to re-try operation the next Puppet run + if ( $config->{errfile} ne '' ) { + open my $fh, $config->{errfile} or die "$!: $config->{errfile}"; + print $fh "Exited with an error\n"; + close $fh; + } + + exit 2; + } + else { + unlink $config->{errfile} + if $config->{errfile} ne '' and -f $config->{errfile}; + exit 0; + } + } +} + +main @ARGV; diff --git a/debian/mon/usr/bin/mi b/debian/mon/usr/bin/mi new file mode 100755 index 0000000..49efcc0 --- /dev/null +++ b/debian/mon/usr/bin/mi @@ -0,0 +1,3 @@ +#!/bin/sh + +exec mon --meta --interactive $@ diff --git a/debian/mon/usr/bin/mon b/debian/mon/usr/bin/mon new file mode 100755 index 0000000..162f71a --- /dev/null +++ b/debian/mon/usr/bin/mon @@ -0,0 +1,133 @@ +#!/usr/bin/perl +# +# (c) 2013, 2014 1&1 Internet AG +# Paul C. Buetow + +use strict; +use warnings; +use v5.10; +use autodie; + +use Data::Dumper; +use Term::ReadLine; + +$| = 1; +my $lib; + +BEGIN { + if ( -d './lib/MON' ) { + $lib = './lib'; + + } + else { + $lib = '/usr/share/mon/lib'; + } + + unless ( exists $ENV{HTTPS_CA_FILE} ) { + if ( -f 'ca.pem' ) { + $ENV{HTTPS_CA_FILE} = 'ca.pem'; + } + elsif ( -f '/etc/ssl/certs/ca.pem' ) { + $ENV{HTTPS_CA_FILE} = '/etc/ssl/certs/ca.pem'; + } + else { + $ENV{HTTPS_CA_FILE} = '/usr/share/mon/ca.pem'; + } + } +} + +use lib $lib; + +use MON::RESTlos; +use MON::Config; +use MON::Options; +use MON::Query; +use MON::Syslogger; +use MON::Utils; +use MON::Display; + +sub main (@) { + my @args = @_; + my @opts; + + # Only interpret OPTIONS if they are at the beginning + push @opts, shift @args while @args and $args[0] =~ /^-/; + + # .. or at the end + push @opts, pop @args while @args and $args[-1] =~ /^-/; + + my $options = MON::Options->new( opts_passed => \@opts ); + my $logger = MON::Syslogger->new( options => $options ); + my $config = MON::Config->new( options => $options, logger => $logger ); + + if ( $config->{'version'} ) { + print get_version(), "\n"; + exit 0; + } + + $logger->logg( 'info', "Invoked by $ENV{USER} (params: @ARGV)" ); + + if ( $config->{interactive} ) { + my $term = Term::ReadLine->new('Monitoring API tool'); + my $prompt = '>> '; + my $out = $term->OUT || \*STDOUT; + + say $out "Welcome to the Monitoring API Tool v" . get_version(); + say $out "Press Ctrl+D to exit; Prefix cmd with ! to run via shell"; + + while ( defined( $_ = $term->readline($prompt) ) ) { + $term->addhistory($_) if /\S/; + + if (s/^!//) { + system($_); + } + else { + + my $line = $_; + + my @args = split / +/, $line; + + my $api = MON::RESTlos->new( config => $config ); + my $query = MON::Query->new( + config => $config, + api => $api, + options => $options, + args => \@args + ); + $query->parse(); + } + } + + } + else { + my $api = MON::RESTlos->new( config => $config ); + my $query = MON::Query->new( + config => $config, + api => $api, + options => $options, + args => \@args + ); + + $query->parse(); + $query->verbose("Good bye"); + + if ( $api->{had_error} != 0 ) { + + # Needed by Puppet to re-try operation the next Puppet run + if ( $config->{errfile} ne '' ) { + open my $fh, $config->{errfile} or die "$!: $config->{errfile}"; + print $fh "Exited with an error\n"; + close $fh; + } + + exit 2; + } + else { + unlink $config->{errfile} + if $config->{errfile} ne '' and -f $config->{errfile}; + exit 0; + } + } +} + +main @ARGV; diff --git a/debian/mon/usr/share/doc/mon/changelog.gz b/debian/mon/usr/share/doc/mon/changelog.gz new file mode 100644 index 0000000..89f05ae Binary files /dev/null and b/debian/mon/usr/share/doc/mon/changelog.gz differ diff --git a/debian/mon/usr/share/doc/mon/copyright b/debian/mon/usr/share/doc/mon/copyright new file mode 100644 index 0000000..9be5c7f --- /dev/null +++ b/debian/mon/usr/share/doc/mon/copyright @@ -0,0 +1,30 @@ +Format: http://dep.debian.net/deps/dep5 +Upstream-Name: mon +Source: http://mon.buetow.org + +Files: * +Copyright: 2012 Paul Buetow +License: GPL-3.0+ + +Files: debian/* +Copyright: 2012 Paul Buetow +License: GPL-3.0+ + +License: GPL-3.0+ + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + . + This package is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + . + You should have received a copy of the GNU General Public License + along with this program. If not, see . + . + On Debian systems, the complete text of the GNU General + Public License version 3 can be found in "/usr/share/common-licenses/GPL-3". + + diff --git a/debian/mon/usr/share/man/man1/mon.1.gz b/debian/mon/usr/share/man/man1/mon.1.gz new file mode 100644 index 0000000..1639eb5 Binary files /dev/null and b/debian/mon/usr/share/man/man1/mon.1.gz differ diff --git a/debian/mon/usr/share/mon/contrib/zsh/_mon.zsh b/debian/mon/usr/share/mon/contrib/zsh/_mon.zsh new file mode 100644 index 0000000..3770c60 --- /dev/null +++ b/debian/mon/usr/share/mon/contrib/zsh/_mon.zsh @@ -0,0 +1,46 @@ +#compdef m mon + +get_options() { + elements=`${words[1,$[${CURRENT}-1]]} --dry --nocolor 2>&1` + if [ $? -ne 0 ]; then + elements="" + fi + + echo `echo $elements | tr '\n' ' '` +} + +_mon() { + local curcontext="$curcontext" state line + typeset -A opt_args + + case $words[2] in + getfmt) + if [ $CURRENT -eq 5 ]; then + compadd "$@" "where" + return + fi + ;; + post|put) + if [ $CURRENT -eq 4 ]; then + compadd "$@" "from" + return + fi + ;; + delete|edit|get|insert|update|view) + if [ $CURRENT -eq 4 ]; then + compadd "$@" "where" + return + fi + ;; + esac + + VALUES="`get_options`" + if [ -z "${VALUES}" ]; then + _files + else + ARGS=(${=VALUES}) + compadd "$@" ${ARGS[@]} + fi +} + +_mon "$@" diff --git a/debian/mon/usr/share/mon/examples/mon.conf.sample b/debian/mon/usr/share/mon/examples/mon.conf.sample new file mode 100644 index 0000000..3cb6460 --- /dev/null +++ b/debian/mon/usr/share/mon/examples/mon.conf.sample @@ -0,0 +1,23 @@ +# Mandatory +restlos.api.host: mamat-monitoringapi.infra.server.lan + +# Optional, default value is 'https' +restlos.api.protocol: https + +# Optional, default value is '443' +restlos.api.port: 443 + +# Optional, default value is $USER +restlos.auth.username: USERNAME + +# Mandatory +restlos.auth.password.enc: PASSWORDBASE64ENCODED + +# Optional, default value is '1' +backups.disable: 1 + +# Optional, default value is '7' +backups.keep.days: 0.1 + +# Optional, default value is '~/.mon' +backups.dir: ~/.mon diff --git a/debian/mon/usr/share/mon/itca.pem b/debian/mon/usr/share/mon/itca.pem new file mode 100644 index 0000000..5aaef20 --- /dev/null +++ b/debian/mon/usr/share/mon/itca.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIID0DCCArgCAQAwDQYJKoZIhvcNAQEEBQAwga0xGjAYBgNVBAcTEUQtNzYxMzUg +S2FybHNydWhlMQ4wDAYDVQQDEwVJVC1DQTEbMBkGA1UEChMSU2NobHVuZCtQYXJ0 +bmVyIEFHMRIwEAYDVQQLEwlJVCBXRUIuREUxJDAiBgkqhkiG9w0BCQEWFWl0LW9w +ZXJhdGluZ0B3ZWJkZS5kZTELMAkGA1UEBhMCREUxGzAZBgNVBAgTEkJhZGVuLVd1 +ZXJ0dGVtYmVyZzAeFw0wNjA5MDUxMTQzMDFaFw0xNjA5MDIxMTQzMDFaMIGtMRow +GAYDVQQHExFELTc2MTM1IEthcmxzcnVoZTEOMAwGA1UEAxMFSVQtQ0ExGzAZBgNV +BAoTElNjaGx1bmQrUGFydG5lciBBRzESMBAGA1UECxMJSVQgV0VCLkRFMSQwIgYJ +KoZIhvcNAQkBFhVpdC1vcGVyYXRpbmdAd2ViZGUuZGUxCzAJBgNVBAYTAkRFMRsw +GQYDVQQIExJCYWRlbi1XdWVydHRlbWJlcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQC9BUNmQbH7A6AYm5ABESPxROOSd5MrXPbo8V+Lfc0whZMc4x/I +6TmMl1t5yk5SyOQ6YGm5k86Q6YUgz1kWd4xfiUMUgd6skiUmcpR1vs/UkTqK1T3t +szwm3jLUFA9oFwi5SWLOlP4r1bwIJcP/EAgT5roLuIfruV9G6AnEconRnnnI0FcM +hSyta0saLsSBFzfU24NVh/zbzs1DV03POxd/xvc222bnsWM++5a7gMbMHTVwb73Y +JpqEVQLA8klU4Ko0GZPc6OFFgrAKl4rcj7JryK5iFbTKFRk45huPciBhkQ5XlVhr +gKn3xD7ZQr40hMIdIbc5lLAELeifVQcsdbCBAgMBAAEwDQYJKoZIhvcNAQEEBQAD +ggEBAJUC5MF121nZh/3/2YCgi7HedafMDg9sjKiBG9gWlpRVTw4hRSBlQq5V32sC ++GPL9VWD8IN+U3Vjiw5ja8j0mza2EomEdpL52EeWcGmQtSjOGnQ1MsXJiCZk+Q/P +qGwgJpgwdubcnTmCH332sWTurgO53M4v61+VPj+N3MGMtaehzIo062lzXZyFdCS6 +SjTF1WdWbi/5nybNs8ffnG+p+uf37trJtopgndiVDi9k8WzS1HzWQf8Cnvy9X/U2 +5kZ4T6eVtrHujuP4PvG6PpfrZqEXENngsYHG0jtNTH06Ewtb7y/VDuqwYGsvSGm6 +RbB+lGFa1z1MzlWbBpPGckUgSHU= +-----END CERTIFICATE----- diff --git a/debian/mon/usr/share/mon/lib/MAPI/Cache.pm b/debian/mon/usr/share/mon/lib/MAPI/Cache.pm new file mode 100644 index 0000000..21b59f5 --- /dev/null +++ b/debian/mon/usr/share/mon/lib/MAPI/Cache.pm @@ -0,0 +1,55 @@ +package MON::Cache; + +use strict; +use warnings; +use v5.10; +use autodie; + +use Data::Dumper; + +use MON::Display; +use MON::Config; +use MON::Utils; + +our @ISA = ('MON::Display'); + +sub new { + my ( $class, %opts ) = @_; + + my $self = bless \%opts, $class; + + $self->init(); + + return $self; +} + +sub init { + my ($self) = @_; + + $self->clear(); + + return undef; +} + +sub clear { + my ($self) = @_; + + $self->{cache} = {}; + + return undef; +} + +sub magic { + my ( $self, $key, $sub ) = @_; + + my $cache = $self->{cache}; + + if ( exists $cache->{$key} ) { + $self->verbose("Delivering '$key' from cache"); + return $cache->{$key}; + } + + return $cache->{$key} = $sub->(); +} + +1; diff --git a/debian/mon/usr/share/mon/lib/MAPI/Config.pm b/debian/mon/usr/share/mon/lib/MAPI/Config.pm new file mode 100644 index 0000000..dc83911 --- /dev/null +++ b/debian/mon/usr/share/mon/lib/MAPI/Config.pm @@ -0,0 +1,176 @@ +package MON::Config; + +use strict; +use warnings; +use v5.10; +use autodie; + +use IO::File; +use Data::Dumper; + +use MON::Display; +use MON::Utils; + +#use MON::Options; + +use MIME::Base64 qw( decode_base64 ); + +our @ISA = ('MON::Display'); + +sub new { + my ( $class, %opts ) = @_; + + my $self = bless \%opts, $class; + my $options = $self->{options}; + + $options->store_first($self); + + $self->SUPER::init(%opts); + + for ( @{ $options->{unknown} } ) { + $self->error("Unknown option: $_"); + } + + if ( $self->{'config'} ne '' ) { + $self->read_config( $self->{'config'} ); + + } + elsif ( exists $ENV{MON_CONFIG} ) { + $self->read_config( $ENV{MON_CONFIG} ); + + } + else { + $self->read_config('/etc/mon.conf'); + $self->read_config($_) for sort glob("/etc/mon.d/*.conf"); + + $self->read_config("$ENV{HOME}/.mon.conf"); + $self->read_config($_) for sort glob("$ENV{HOME}/.mon.d/*.conf"); + } + + $options->store_after($self); + + unless ( exists $self->{config_was_read} ) { + $self->verbose("No config file found, but this might be OK"); + } + + $self->_set_defaults(); + + return $self; +} + +sub _set_defaults { + my ($self) = @_; + + my $set_default = sub { + my ( $key, $val ) = @_; + + unless ( exists $self->{$key} ) { + $self->{$key} = $val; + $self->verbose( + "Since $key is not specified setting its default value to $val"); + } + }; + + $set_default->( 'backups.dir' => "$ENV{HOME}/.mon" ); + $set_default->( 'backups.disable' => 1 ); + $set_default->( 'backups.keep.days' => 7 ); + $set_default->( 'restlos.api.port' => '443' ); + $set_default->( 'restlos.api.protocol' => 'https' ); + $set_default->( 'restlos.auth.realm' => 'Login Required' ); + $set_default->( 'restlos.auth.username' => $ENV{USER} ); +} + +sub read_config { + my ( $self, $config_file ) = @_; + + return undef if not defined $config_file or not -f $config_file; + + my $fh = IO::File->new( $config_file, 'r' ); + $self->error("Could not open file $config_file") unless defined $fh; + + $self->verbose("Reading config $config_file"); + + while ( my $line = $fh->getline() ) { + next if $line =~ /^#/; + + # Ignore comments + $line =~ s/(.*);.*/$1/; + + # Parse only matching lines + if ( $line =~ /^(.*):(.*)/ ) { + my ( $key, $val ) = ( lc trim $1, trim $2); + $self->verbose("Reading conf value $key"); + + # Handle ~ + $val =~ s/~/$ENV{HOME}/g; + $self->set( $key, $val ); + } + } + + $fh->close(); + $self->{config_was_read} = 1; + + return undef; +} + +sub get { + my ( $self, $key ) = @_; + $key = lc $key; + + $self->{$key} //= do { + my $key = uc $key; + $key =~ s/\./_/g; + + exists $ENV{$key} ? $ENV{$key} : undef; + }; + + if ( not exists $self->{$key} + or not defined $self->{$key} + or $self->{$key} eq '' ) + { + $self->error("$key not configured"); + } + + return $self->{$key}; +} + +sub get_maybe_encoded { + my ( $self, $key ) = @_; + + return $self->get($key) if exists $self->{$key}; + + $self->error("$key or $key.enc not configured") + unless exists $self->{"$key.enc"}; + + my $enc = $self->get("$key.enc"); + + return decode_base64($enc); +} + +sub bool { + my ( $self, $key ) = @_; + + my $val = $self->get($key); + + return $val != 0; +} + +sub array { + my ( $self, $key ) = @_; + + my $val = $self->get($key); + + return map { trim $_ } split ',', $val; +} + +sub set { + my ( $self, $key, $val ) = @_; + $key = lc $key; + + $self->verbose("$key already configured, overwriting it with its new value") + if exists $self->{$key}; + + return $self->{$key} = $val; +} + +1; diff --git a/debian/mon/usr/share/mon/lib/MAPI/Display.pm b/debian/mon/usr/share/mon/lib/MAPI/Display.pm new file mode 100644 index 0000000..9bf8115 --- /dev/null +++ b/debian/mon/usr/share/mon/lib/MAPI/Display.pm @@ -0,0 +1,360 @@ +package MON::Display; + +use strict; +use warnings; +use v5.10; +use autodie; + +use Data::Dumper; +use Term::ANSIColor; + +use MON::Config; +use MON::JSON; +use MON::Utils; + +our $VERBOSE = 0; +our $DEBUG = 0; +our $COLORFUL = 0; +our $QUIET = 0; +our $LOGGER = undef; +our $INTERACTIVE = undef; + +sub init { + my ( $self, %opts ) = @_; + + $VERBOSE = $self->{'verbose'} == 1; + $DEBUG = $self->{'debug'} == 1; + $QUIET = $self->{'quiet'} == 1; + $LOGGER = $opts{logger}; + $INTERACTIVE = $opts{interactive}; + + $self->{logglevel} = 'info'; + + if ( $self->{'nocolor'} == 1 ) { + $COLORFUL = 0; + } + else { + $COLORFUL = $ENV{MON_COLORFUL} // 1; + } + + $VERBOSE = $DEBUG = $COLORFUL = 0 if $QUIET == 1; + + return undef; +} + +sub is_verbose { + my ($self) = @_; + + return $VERBOSE == 1; +} + +sub is_debug { + my ($self) = @_; + + return $DEBUG == 1; +} + +sub is_quiet { + my ($self) = @_; + + return $QUIET == 1; +} + +sub _display { + my ( $self, $msg, $fh, $level ) = @_; + + return undef unless defined $msg; + + $LOGGER->logg( $self->{logglevel}, $msg ) if defined $LOGGER; + + return undef if $QUIET; + + $fh = *STDERR unless defined $fh; + + print $fh $msg; + + return undef; +} + +sub info_no_nl { + my ( $self, $msg ) = @_; + + print STDERR color 'bold blue' if $COLORFUL; + $self->_display($msg); + print STDERR color 'reset' if $COLORFUL; + + return undef; +} + +sub out_json { + my ( $self, $out ) = @_; + + return undef unless defined $out; + my $config = $self->{config}; + + local $, = "\n"; + + my $json = MON::JSON->new()->decode($out); + my $num_results = ref $json eq 'ARRAY' ? @$json : undef; + + # Don't _display meta aka custom variables unless -m or --meta is specified + unless ( $config->{'meta'} ) { + if ( ref $json eq 'ARRAY' ) { + @$json = map { + if ( ref $_ eq 'HASH' ) + { + my $h = $_; + delete $h->{$_} for grep /^_/, keys %$h; + $h; + } + else { + $_; + } + } @$json; + } + } + + # Sort and pretty print all the JSON pretty pretty please + unless ( defined $config->{outfile} ) { + print MON::JSON->new()->encode_canonical($json) unless $QUIET; + } + else { + my $outfile = $config->{outfile}; + print $outfile MON::JSON->new()->encode_canonical($json); + print STDERR color 'bold green' if $COLORFUL; + $self->_display("Wrote JSON to file\n"); + print STDERR color 'reset' if $COLORFUL; + } + + $LOGGER->logg( 'info', JSON->new()->encode($json) ) if defined $LOGGER; + + print STDERR color 'bold green' if $COLORFUL; + $self->_display("Found $num_results entries\n") if defined $num_results; + print STDERR color 'reset' if $COLORFUL; + + return undef; +} + +sub out_format { + my ( $self, $format, $out ) = @_; + + return undef unless defined $out; + + my $config = $self->{config}; + my $options = $self->{options}; + my $json = MON::JSON->new()->decode($out); + my $num_results = ref $json eq 'ARRAY' ? @$json : undef; + + $self->error("Expected an JSON Array") if ref $json ne 'ARRAY'; + + my @vars1 = $format =~ /\$(\w+)/g; + my @vars2 = $format =~ /\$\{(\w+)\}/g; + my @vars3 = $format =~ /\@(\w+)/g; + my @vars4 = $format =~ /\@\{(\w+)\}/g; + + my %vars; + $vars{$_} = '' for @vars1, @vars2, @vars3, @vars4; + my @out; + my %empty; + + for my $obj (@$json) { + my %obj_vars = %vars; + my $obj_format = $format; + + for my $var ( keys %obj_vars ) { + if ( $var eq 'HOSTNAME' ) { + my $val = exists $obj->{host_name} ? $obj->{host_name} : ''; + + if ( $val eq '' ) { + $empty{$var} = 1; + } + else { + $val =~ s/\..*//; + } + + $obj_format =~ s/\$$var/$val/g; + + } + else { + my $val = exists $obj->{$var} ? $obj->{$var} : ''; + $empty{$var} = 1 if $val eq ''; + + $obj_format =~ s/\$$var/$val/g; + $obj_format =~ s/\$\{$var\}/$val/g; + $obj_format =~ s/\@$var/$val/g; + $obj_format =~ s/\@\{$var\}/$val/g; + } + } + + push @out, $obj_format if $obj_format =~ /^.*\w+.*$/; + } + + if (@out) { + + if ( $config->{'unique'} ) { + my %lines; + @out = grep { exists $lines{$_} ? 0 : ( $lines{$_} = 1 ) } sort @out; + $num_results = @out; + } + else { + @out = sort @out; + } + + if ( $QUIET == 0 ) { + local $, = "\n"; + print @out; + say ''; + } + elsif ( defined $LOGGER ) { + $LOGGER->logg( 'info', $_ ) for @out; + } + } + + $self->warning( "Some objects dont have such a field or have empty strings: " + . join( ' ', sort keys %empty ) ) + if keys %empty; + + print STDERR color 'bold green' if $COLORFUL; + $self->_display("Found $num_results entries\n") if defined $num_results; + print STDERR color 'reset' if $COLORFUL; + + return undef; +} + +sub info { + my ( $self, $msg ) = @_; + + my $str = "$msg\n"; + $self->{logglevel} = 'info'; + + print STDERR color 'bold blue' if $COLORFUL; + $self->_display($str); + print STDERR color 'reset' if $COLORFUL; + + return undef; +} + +sub nl { + my ($self) = @_; + + $self->_display("\n"); + + return undef; +} + +sub error { + my ( $self, $msg ) = @_; + + $self->error_no_exit($msg); + + exit 3 unless $INTERACTIVE; +} + +sub error_no_exit { + my ( $self, $msg ) = @_; + + $self->{logglevel} = 'warning'; + print STDERR color 'bold red' if $COLORFUL; + $self->_display( "! ERROR: $msg\n", *STDERR ); + print STDERR color 'reset' if $COLORFUL; + + return undef; +} + +sub possible { + my ( $self, @params ) = @_; + + my $config = $self->{config}; + my $options = $self->{options}; + + push @params, $options->get_keys() + if $config->{'help'}; + + my $msg = ''; + + if (@params) { + for ( grep !/^V_ALIAS/, @params ) { + if ( ref $_ eq 'ARRAY' ) { + $msg .= join "\n", @$_; + $msg .= "\n"; + } + else { + $msg .= "$_\n"; + } + } + } + else { + $msg .= "\n"; + } + + $self->{logglevel} = 'info'; + $self->_display($msg); + + exit 0 unless $INTERACTIVE; +} + +sub warning { + my ( $self, $msg ) = @_; + + my $str = "! $msg\n"; + + print STDERR color 'red' if $COLORFUL; + $self->_display( $str, *STDERR ); + print STDERR color 'reset' if $COLORFUL; + + return undef; +} + +sub verbose { + my ( $self, @msgs ) = @_; + + print STDERR color 'cyan' if $COLORFUL; + $self->{logglevel} = 'info'; + + if ( $self->is_verbose() ) { + for my $msg (@msgs) { + if ( $self->is_debug() ) { + my @caller = caller; + $self->_display("@caller: $msg\n"); + } + else { + $self->_display("$msg\n"); + } + } + } + + print STDERR color 'reset' if $COLORFUL; + + return undef; +} + +sub dump { + my ( $self, $msg ) = @_; + + $self->{logglevel} = 'warning'; + $self->_display( Dumper $msg ); + + return undef; +} + +sub debug { + my ( $self, @msgs ) = @_; + + my @caller = caller; + + if ( $self->is_debug() ) { + for my $msg (@msgs) { + $msg = Dumper $msg if ref $msg ne ''; + + my $str = "@caller: $msg\n"; + + $self->{logglevel} = 'debug'; + $self->_display($str); + } + } + + return undef; +} + +1; + diff --git a/debian/mon/usr/share/mon/lib/MAPI/Filter.pm b/debian/mon/usr/share/mon/lib/MAPI/Filter.pm new file mode 100644 index 0000000..d16d1c5 --- /dev/null +++ b/debian/mon/usr/share/mon/lib/MAPI/Filter.pm @@ -0,0 +1,166 @@ +package MON::Filter; + +use strict; +use warnings; +use v5.10; +use autodie; + +use Data::Dumper; + +use MON::Display; +use MON::Config; +use MON::Utils; + +our @ISA = ('MON::Display'); + +sub new { + my ( $class, %opts ) = @_; + + my $self = bless \%opts, $class; + + $self->init(); + + return $self; +} + +sub init { + my ($self) = @_; + + $self->{query_string} = ''; + $self->{filters} = {}; + $self->{num_filters} = 0; + $self->{is_computed} = 0; + $self->{or} = []; + + return undef; +} + +# Create filters with params +sub compute { + my ( $self, $params ) = @_; + + $self->debug( 'Computing filter using', $params ); + return undef if $self->{is_computed}; + + my %likes; + + if ( defined $params and ref $params eq 'ARRAY' ) { + while (@$params) { + my $op_token = pop @$params; + given ($op_token) { + when (/^OP_LIKE$/) { + my $arg2 = pop @$params; + my $arg1 = pop @$params; + + if ( exists $likes{$arg1} ) { + $self->error( +"Can not run multiple 'like's on '$arg1', since it is used for the API query_string" + ); + } + else { + $likes{$arg1} = "$arg1=$arg2"; + } + + } + when (/^OP_/) { + $self->{filters}{$_} = [] unless exists $self->{filters}{$_}; + my $arg2 = pop @$params; + my $arg1 = pop @$params; + push @{ $self->{filters}{$_} }, [ $arg1, $arg2 ]; + $self->{num_filters}++; + } + default { + $self->error("Inernal error: Operator expected instead of $_"); + } + } + } + } + + $self->{query_string} = '?' . join( '&', values %likes ); + $self->{is_computed} = 1; + + $self->debug( 'Computed filter:', $self->{filters} ); + $self->verbose( "Computed query string is: " . $self->{query_string} ); + + return undef; +} + +sub filter { + my ( $self, $objects ) = @_; + + my $config = $self->{config}; + my $json = $self->{json}; + + return $objects unless $self->{num_filters}; + + my $num = sub { + my $str = shift; + $str =~ s/\D//g; + $str = 0 if $str eq ''; + return int $str; + }; + + while ( my ( $op, $vals ) = each %{ $self->{filters} } ) { + for my $val (@$vals) { + my ( $key, $val ) = @$val; + + @$objects = grep { + my $object = $_; + + if ( exists $object->{$key} ) { + if ( $op eq 'OP_MATCHES' and $object->{$key} =~ /$val/ ) { + 1; + + } + elsif ( $op eq 'OP_NMATCHES' and $object->{$key} !~ /$val/ ) { + 1; + + } + elsif ( $op eq 'OP_EQ' and $object->{$key} eq $val ) { + 1; + + } + elsif ( $op eq 'OP_NE' and $object->{$key} ne $val ) { + 1; + + } + elsif ( $op eq 'OP_LT' + and $num->( $object->{$key} ) < $num->($val) ) + { + 1; + + } + elsif ( $op eq 'OP_LE' + and $num->( $object->{$key} ) <= $num->($val) ) + { + 1; + + } + elsif ( $op eq 'OP_GT' + and $num->( $object->{$key} ) > $num->($val) ) + { + 1; + + } + elsif ( $op eq 'OP_GE' + and $num->( $object->{$key} ) >= $num->($val) ) + { + 1; + + } + else { + 0; + } + } + else { + 0; + } + } @$objects; + } + } + + return $objects; +} + +1; + diff --git a/debian/mon/usr/share/mon/lib/MAPI/JSON.pm b/debian/mon/usr/share/mon/lib/MAPI/JSON.pm new file mode 100644 index 0000000..e12b1ce --- /dev/null +++ b/debian/mon/usr/share/mon/lib/MAPI/JSON.pm @@ -0,0 +1,51 @@ +package MON::JSON; + +use strict; +use warnings; +use v5.10; +use autodie; + +use JSON; + +use MON::Display; +use MON::Utils; + +our @ISA = ('MON::Display'); + +our $JSON_XS = JSON::XS->new(); + +sub new { + my ( $class, %opts ) = @_; + + my $self = bless \%opts, $class; + + $self->init(); + + return $self; +} + +sub init { + my ($self) = @_; + + return undef; +} + +sub decode { + my ( $self, $json ) = @_; + + return $JSON_XS->allow_nonref()->decode($json); +} + +sub encode { + my ( $self, $vals ) = @_; + + return $JSON_XS->pretty()->encode($vals); +} + +sub encode_canonical { + my ( $self, $vals ) = @_; + + return $JSON_XS->canonical()->pretty()->encode($vals); +} + +1; diff --git a/debian/mon/usr/share/mon/lib/MAPI/Options.pm b/debian/mon/usr/share/mon/lib/MAPI/Options.pm new file mode 100644 index 0000000..b798f56 --- /dev/null +++ b/debian/mon/usr/share/mon/lib/MAPI/Options.pm @@ -0,0 +1,163 @@ +package MON::Options; + +use strict; +use warnings; +use v5.10; +use autodie; + +use Data::Dumper; +use Scalar::Util qw(looks_like_number); + +use MON::Utils; + +sub new { + my ( $class, %opts ) = @_; + + my $self = bless \%opts, $class; + + $self->init(); + $self->parse(); + + return $self; +} + +sub init { + my ($self) = @_; + + my %opts = ( + opts => { + config => '', + debug => 0, + dry => 0, + help => 0, + interactive => 0, + meta => 0, + nocolor => 0, + quiet => 0, + syslog => 0, + unique => 0, + verbose => 0, + version => 0, + errfile => '', + }, + opts_short => { + c => 'config', + D => 'debug', + d => 'dry', + i => 'interactive', + h => 'help', + m => 'meta', + n => 'nocolor', + q => 'quiet', + s => 'syslog', + u => 'unique', + v => 'verbose', + V => 'version', + R => 'errfile', + }, + unknown => [], + ); + + $self->{$_} = $opts{$_} for keys %opts; + + return undef; +} + +sub parse { + my ($self) = @_; + + my $opts_passed = $self->{opts_passed}; + + for my $opt (@$opts_passed) { + my ( $k, $v ) = split /=/, $opt; + + # Longopt + if ( $k =~ s/^--// && isin $k, keys %{ $self->{opts} } ) { + if ( defined $v ) { + $self->{opts}{$k} = $v; + } + else { + $self->{opts}{$k} = 1; + } + } + + # Shortopt + elsif ( $k =~ s/^-// && isin $k, keys %{ $self->{opts_short} } ) { + if ( defined $v ) { + $self->{opts}{ $self->{opts_short}{$k} } = $v; + } + else { + $self->{opts}{ $self->{opts_short}{$k} } = 1; + } + + } + elsif ( $k !~ /\./ ) { + + # If key is not separated by dot, it is unknown + push @{ $self->{unknown} }, $opt; + + } + else { + + # Otherise it might overwrite a value of mon.conf + $self->{opts}{$k} = $v; + } + } + + # Help implies dry mode + $self->{opts}{dry} = 1 if $self->{opts}{help}; + + # Debug implies verbose mode + $self->{opts}{verbose} = 1 if $self->{opts}{debug}; + + return undef; +} + +sub get_keys { + my ($self) = @_; + my @keys; + + while ( my ( $k, $v ) = each %{ $self->{opts_short} } ) { + if ( looks_like_number( $self->{opts}{$v} ) ) { + push @keys, "--$v -$k"; + } + else { + push @keys, "--$v=VAL -$k=VAL"; + } + } + + return @keys; +} + +sub store { + my ( $self, $config ) = @_; + + $self->store_first($config); + $self->store_after($config); + + return undef; +} + +# Only store values which are not separated by dots +sub store_first { + my ( $self, $config ) = @_; + + for ( grep !/\./, keys %{ $self->{opts} } ) { + $config->{$_} = $self->{opts}{$_}; + } + + return undef; +} + +# Only store values which are separated by dots +sub store_after { + my ( $self, $config ) = @_; + + for ( grep /\./, keys %{ $self->{opts} } ) { + $config->{$_} = $self->{opts}{$_}; + } + + return undef; +} + +1; diff --git a/debian/mon/usr/share/mon/lib/MAPI/Query.pm b/debian/mon/usr/share/mon/lib/MAPI/Query.pm new file mode 100644 index 0000000..d7e223b --- /dev/null +++ b/debian/mon/usr/share/mon/lib/MAPI/Query.pm @@ -0,0 +1,557 @@ +package MON::Query; + +use strict; +use warnings; +use v5.10; + +use Data::Dumper; + +use MON::Display; +use MON::Config; +use MON::Utils; +use MON::QueryBase; + +our @ISA = ('MON::QueryBase'); + +sub new { + my ( $class, %opts ) = @_; + + my $self = bless \%opts, $class; + + $self->init(); + + return $self; +} + +sub init { + my ($self) = @_; + + $self->{querystack} = []; + $self->{args} = [ map { s/^V_/:V_/; $_ } @{ $self->{args} } ]; + + return undef; +} + +sub tree { + my ($self) = @_; + + my $api = $self->{api}; + my $paths = $api->get_possible_paths(); + + my ( $s, $r ) = ( $self, $api ); + + my $arr = sub { + my ( $keys, $vals ) = @_; + map { $_ => shift @$vals } @$keys; + }; + +# _ => By default to run anonymous sub if no other key is specified in command line +# __ => Always to run anonymous sub in the beginning of the current recursion +# ___ => Always to run anonymous sub before next recursion +# __DO => Process recursion right away, only do __ if exists +# V_FOO => Declare variable FOO + + my $where = { + _ => sub { + my $d = shift; + $s->possible( $r->get_path_params( $d->{V_PATH} ) ); + }, + V_KEY => { + __ => sub { + my $d = shift; + $s->check_has( $d->{V_KEY}, $r->get_path_params( $d->{V_PATH} ) ); + }, + like => { + V_VAL => { + _ => sub { + my $d = shift; + my $path = $d->{V_PATH}; + $s->out_json( $d->{where_action}($path) ); + }, + }, + ___ => sub { $s->push_querystack('OP_LIKE') } + }, + matches => { + V_VAL => { + _ => sub { + my $d = shift; + my $path = $d->{V_PATH}; + $s->out_json( $d->{where_action}($path) ); + }, + }, + ___ => sub { $s->push_querystack('OP_MATCHES') } + }, + nmatches => { + V_VAL => { + _ => sub { + my $d = shift; + my $path = $d->{V_PATH}; + $s->out_json( $d->{where_action}($path) ); + }, + }, + ___ => sub { $s->push_querystack('OP_NMATCHES') } + }, + eq => { + V_VAL => { + _ => sub { + my $d = shift; + my $path = $d->{V_PATH}; + $s->out_json( $d->{where_action}($path) ); + }, + }, + ___ => sub { $s->push_querystack('OP_EQ') } + }, + ne => { + V_VAL => { + _ => sub { + my $d = shift; + my $path = $d->{V_PATH}; + $s->out_json( $d->{where_action}($path) ); + }, + }, + ___ => sub { $s->push_querystack('OP_NE') } + }, + lt => { + V_VAL => { + _ => sub { + my $d = shift; + my $path = $d->{V_PATH}; + $s->out_json( $d->{where_action}($path) ); + }, + }, + ___ => sub { $s->push_querystack('OP_LT') } + }, + le => { + V_VAL => { + _ => sub { + my $d = shift; + my $path = $d->{V_PATH}; + $s->out_json( $d->{where_action}($path) ); + }, + }, + ___ => sub { $s->push_querystack('OP_LE') } + }, + gt => { + V_VAL => { + _ => sub { + my $d = shift; + my $path = $d->{V_PATH}; + $s->out_json( $d->{where_action}($path) ); + }, + }, + ___ => sub { $s->push_querystack('OP_GT') } + }, + ge => { + V_VAL => { + _ => sub { + my $d = shift; + my $path = $d->{V_PATH}; + $s->out_json( $d->{where_action}($path) ); + }, + }, + ___ => sub { $s->push_querystack('OP_GE') } + }, + }, + }; + + for my $op ( sort qw(like matches nmatches eq ne lt le gt ge) ) { + $where->{V_KEY}{$op}{V_VAL}{and}{__DO} = $where; + $where->{V_KEY}{$op}{V_VAL}{'V_ALIAS:a'} = $where->{V_KEY}{$op}{V_VAL}{and}; + } + + $where->{V_KEY}{'V_ALIAS:l'} = $where->{V_KEY}{like}; + $where->{V_KEY}{'V_ALIAS:~'} = $where->{V_KEY}{like}; + $where->{V_KEY}{'V_ALIAS:=='} = $where->{V_KEY}{eq}; + $where->{V_KEY}{'V_ALIAS:!='} = $where->{V_KEY}{ne}; + $where->{V_KEY}{'V_ALIAS:=~'} = $where->{V_KEY}{matches}; + $where->{V_KEY}{'V_ALIAS:!~'} = $where->{V_KEY}{nmatches}; + + my $set_where = { + _ => sub { + my $d = shift; + $s->possible( $r->get_path_params( $d->{V_PATH} ) ); + }, + V_SETKEY => { + '=' => { + V_SETVAL => { + __ => sub { + my $d = shift; + $d->{where_action} = $d->{set_action}; + }, + where => $where, + }, + }, + }, + }; + $set_where->{V_SETKEY}{'='}{V_SETVAL}{and} = $set_where; + $set_where->{V_SETKEY}{'='}{V_SETVAL}{'V_ALIAS::'} = + $set_where->{V_SETKEY}{'='}{V_SETVAL}{where}; + $set_where->{V_SETKEY}{'='}{V_SETVAL}{'V_ALIAS:a'} = + $set_where->{V_SETKEY}{'='}{V_SETVAL}{and}; + + my $set = { + _ => sub { + my $d = shift; + $s->possible( $r->get_path_params( $d->{V_PATH} ) ); + }, + V_SETKEY => { + '=' => { + V_SETVAL => { + _ => sub { + my $d = shift; + my $path = $d->{V_PATH}; + + $s->out_json( $d->{set_action}($path) ); + }, + }, + }, + }, + }; + $set->{V_SETKEY}{'='}{V_SETVAL}{and} = $set; + $set->{V_SETKEY}{'='}{V_SETVAL}{'V_ALIAS:a'} = + $set->{V_SETKEY}{'='}{V_SETVAL}{and}; + + my $remove = { + _ => sub { + my $d = shift; + $s->possible( $r->get_path_params( $d->{V_PATH} ) ); + }, + V_REMOVEKEY => { + __ => sub { + my $d = shift; + $d->{where_action} = $d->{remove_action}; + }, + where => $where, + }, + }; + $remove->{V_REMOVEKEY}{and} = $remove; + $remove->{V_REMOVEKEY}{'V_ALIAS:a'} = $remove->{V_REMOVEKEY}{and}; + + my $tree = { + get => { + _ => sub { $s->possible(@$paths) }, + V_PATH => { + __ => sub { + my $d = shift; + $s->check_has( $d->{V_PATH}, $paths ); + $d->{where_action} = sub { + my ($path) = @_; + $r->fetch_path_json( $path, $s->get_querystack() ); + }; + }, + _ => sub { + my $d = shift; + $s->out_json( + $r->fetch_path_json( $d->{V_PATH}, $s->get_querystack() ) ); + }, + where => $where, + }, + }, + getfmt => { + V_FORMAT => { + _ => sub { $s->possible(@$paths) }, + V_PATH => { + __ => sub { + my $d = shift; + $s->check_has( $d->{V_PATH}, $paths ); + $d->{where_action} = sub { + my ($path) = @_; + $s->out_format( $d->{V_FORMAT}, + $r->fetch_path_json( $path, $s->get_querystack() ) ); + }; + }, + _ => sub { + my $d = shift; + $s->out_format( $d->{V_FORMAT}, + $r->fetch_path_json( $d->{V_PATH}, $s->get_querystack() ) ); + }, + where => $where, + }, + }, + }, + edit => { + _ => sub { $s->possible(@$paths) }, + V_PATH => { + __ => sub { + my $d = shift; + $s->check_has( $d->{V_PATH}, $paths ); + $d->{where_action} = sub { + my ($path) = @_; + $s->edit_path_data( $path, + $r->fetch_path_json( $path, $s->get_querystack() ) ); + }; + }, + _ => sub { + my $d = shift; + $s->edit_path_data( $d->{V_PATH}, + $r->fetch_path_json( $d->{V_PATH} ) ); + }, + where => $where, + }, + }, + view => { + _ => sub { $s->possible(@$paths) }, + V_PATH => { + __ => sub { + my $d = shift; + $s->check_has( $d->{V_PATH}, $paths ); + $d->{where_action} = sub { + my ($path) = @_; + $s->view_data( $path, + $r->fetch_path_json( $path, $s->get_querystack() ) ); + }; + }, + _ => sub { + my $d = shift; + $s->view_data( $d->{V_PATH}, $r->fetch_path_json( $d->{V_PATH} ) ); + }, + where => $where, + }, + }, + delete => { + _ => sub { $s->possible(@$paths) }, + V_PATH => { + __ => sub { + my $d = shift; + $s->check_has( $d->{V_PATH}, $paths ); + $d->{where_action} = sub { + my ($path) = @_; + $s->out_json( $r->delete_path_json( $path, $s->get_querystack() ) ); + }; + }, + _ => sub { + my $d = shift; + $s->out_json( $r->fetch_path_json( $d->{V_PATH} ) ); + }, + where => $where, + }, + }, + update => { + _ => sub { $s->possible(@$paths) }, + V_PATH => { + __ => sub { + my $d = shift; + $s->check_has( $d->{V_PATH}, $paths ); + $d->{set_action} = sub { + my ($path) = @_; + my %set = $arr->( $d->{ALL_V_SETKEY}, $d->{ALL_V_SETVAL} ); + $s->out_json( + $r->update_path_json( $path, $s->get_querystack(), \%set ) ); + }; + $d->{remove_action} = sub { + my ($path) = @_; + my $remove = $d->{ALL_V_REMOVEKEY}; + $s->out_json( + $r->update_remove_path_json( + $path, $s->get_querystack(), $remove + ) + ); + }; + }, + set => $set_where, + 'delete' => $remove, + }, + }, + insert => { + _ => sub { $s->possible(@$paths) }, + V_PATH => { + __ => sub { + my $d = shift; + $s->check_has( $d->{V_PATH}, $paths ); + $d->{set_action} = sub { + my ($path) = @_; + my %set = $arr->( $d->{ALL_V_SETKEY}, $d->{ALL_V_SETVAL} ); + $s->out_json( $self->insert_data( $path, \%set ) ); + }; + }, + set => $set, + }, + }, + post => { + _ => sub { $s->possible(@$paths) }, + V_PATH => { + __ => sub { + my $d = shift; + $s->check_has( $d->{V_PATH}, $paths ); + }, + _ => sub { + my $d = shift; + $s->send_data( $d->{V_PATH}, 'POST' ); + }, + from => { + V_FILEPATH => { + _ => sub { + my $d = shift; + $s->send_data( $d->{V_PATH}, 'POST', $d->{V_FILEPATH} ); + } + } + } + }, + }, + put => { + _ => sub { $s->possible(@$paths) }, + V_PATH => { + __ => sub { + my $d = shift; + $s->check_has( $d->{V_PATH}, $paths ); + }, + _ => sub { + my $d = shift; + $s->send_data( $d->{V_PATH}, 'PUT' ); + }, + from => { + V_FILEPATH => { + _ => sub { + my $d = shift; + $s->send_data( $d->{V_PATH}, 'PUT', $d->{V_FILEPATH} ); + } + } + } + }, + }, + verify => sub { $s->verify() }, + restart => sub { $s->restart() }, + reload => sub { $s->restart() }, + 'V_ALIAS:y' => sub { $s->verify() }, + 'V_ALIAS:r' => sub { $s->restart() }, + }; + + $tree->{delete}{V_PATH}{'V_ALIAS::'} = $tree->{delete}{V_PATH}{where}; + $tree->{'V_ALIAS:d'} = $tree->{delete}; + + $tree->{edit}{V_PATH}{'V_ALIAS::'} = $tree->{edit}{V_PATH}{where}; + $tree->{'V_ALIAS:e'} = $tree->{edit}; + + $tree->{get}{V_PATH}{'V_ALIAS::'} = $tree->{get}{V_PATH}{where}; + $tree->{'V_ALIAS:g'} = $tree->{get}; + + $tree->{getfmt}{V_FORMAT}{V_PATH}{'V_ALIAS::'} = + $tree->{getfmt}{V_FORMAT}{V_PATH}{where}; + $tree->{'V_ALIAS:f'} = $tree->{getfmt}; + + $tree->{insert}{V_PATH}{'V_ALIAS:s'} = $tree->{insert}{V_PATH}{set}; + $tree->{'V_ALIAS:i'} = $tree->{insert}; + + $tree->{'V_ALIAS:p'} = $tree->{post}; + + $tree->{'V_ALIAS:t'} = $tree->{put}; + + $tree->{update}{V_PATH}{'V_ALIAS:d'} = $tree->{update}{V_PATH}{delete}; + $tree->{update}{V_PATH}{'V_ALIAS:s'} = $tree->{update}{V_PATH}{set}; + $tree->{'V_ALIAS:u'} = $tree->{update}; + + $tree->{view}{V_PATH}{'V_ALIAS::'} = $tree->{view}{V_PATH}{where}; + $tree->{'V_ALIAS:v'} = $tree->{view}; + + $self->debug( 'Abstract syntax tree:', $tree ); + + return $tree; +} + +sub parse { + my ($self) = @_; + + my $config = $self->{config}; + my $args = $self->{args}; + + # Get > and < operators (only needed by interactive mode) + $config->{outfile} = $config->{infile} = undef; + if ( defined $args->[-2] ) { + given ( $args->[-2] ) { + when ('>') { + my ( undef, $file ) = splice @$args, -2, 2; + open $config->{outfile}, '>', $file or $self->warning("$file: $!"); + } + when ('<') { + my ( undef, $file ) = splice @$args, -2, 2; + open $config->{infile}, '<', $file or $self->warning("$file: $!"); + } + } + } + + my $ret = $self->traverse( $args, $self->tree(), {} ); + + close $config->{infile} if defined $config->{infile}; + close $config->{outfile} if defined $config->{outfile}; + + return $ret; +} + +sub traverse { + my ( $self, $args, $tree, $data ) = @_; + + $self->debug( 'Traversing args: ' . Dumper $args); + $self->debug( 'Traversing data: ' . Dumper $data); + + if ( ref $tree eq 'CODE' ) { + $tree->($data); + return undef; + } + + $tree->{__}->($data) if exists $tree->{__}; + + if ( exists $tree->{__DO} ) { +