Răsfoiți Sursa

Section out API from the pgupgrade plugin

George S. Baugh 5 ani în urmă
părinte
comite
0eecbb8f6b
10 a modificat fișierele cu 428 adăugiri și 0 ștergeri
  1. 4 0
      .gitignore
  2. 53 0
      Makefile
  3. 47 0
      SPECS/TroglodyneApi.spec
  4. 81 0
      cgi/api.cgi
  5. 1 0
      configure
  6. 117 0
      lib/Troglodyne/CGI.pm
  7. 81 0
      lib/Troglodyne/CGI/API.pm
  8. 4 0
      plugin/troglodyne_api.conf
  9. 24 0
      t/Troglodyne-CGI.t
  10. 16 0
      t/lib/Cpanel/Template.pm

+ 4 - 0
.gitignore

@@ -0,0 +1,4 @@
+*.swp
+*.swo
+*.bak
+SOURCES/Troglodyne-API*

+ 53 - 0
Makefile

@@ -0,0 +1,53 @@
+ulc  = /usr/local/cpanel
+tmpl = /whostmgr/docroot/templates/troglodyne
+cgi  = /whostmgr/docroot/cgi/troglodyne
+vcp  = /var/cpanel/perl
+vca  = /var/cpanel/apps
+vct  = /var/cpanel/templates
+pwd  = $(shell pwd)
+ 
+.PHONY: all install register test uninstall rpm test-depend
+all: install register
+
+install: test
+	mkdir -p  $(DESTDIR)$(ulc)$(cgi) $(DESTDIR)$(vca) $(DESTDIR)$(vcp)/Troglodyne/CGI $(DESTDIR)$(vcp)/Troglodyne/API $(DESTDIR)$(ulc)/whostmgr/docroot/addon_plugins
+	install $(pwd)/cgi/api.cgi $(DESTDIR)$(ulc)$(cgi)
+	install $(pwd)/lib/Troglodyne/CGI/API.pm $(DESTDIR)$(vcp)/Troglodyne/CGI
+	install $(pwd)/lib/Troglodyne/CGI.pm $(DESTDIR)$(vcp)/Troglodyne
+	install $(pwd)/plugin/troglodyne_api.conf $(DESTDIR)$(vca)
+	chmod 0755 $(DESTDIR)$(vca)
+	chmod +x $(DESTDIR)$(ulc)$(cgi)/api.cgi
+
+register:
+	$(ulc)/bin/register_appconfig ./plugin/troglodyne_api.conf
+
+uninstall:
+	$(ulc)/bin/unregister_appconfig troglodyne_api
+	rm -f $(vcp)/Troglodyne/CGI/API.pm
+	rm -f $(vcp)/Troglodyne/CGI.pm
+	rm -f $(ulc)$(cgi)/api.cgi
+
+test-depend:
+	perl -MTest2::V0 -MTest::MockModule -MFile::Temp -MCapture::Tiny -e 'exit 0' || sudo cpan -i Test2::V0 Test::MockModule File::Temp Capture::Tiny
+
+test: test-depend
+	prove -mv t/*.t
+
+rpm:
+	rm -rf SOURCES/*
+	mkdir -p SOURCES/Troglodyne-API-1.0
+	ln -s $(pwd)/bin SOURCES/Troglodyne-API-1.0/bin
+	ln -s $(pwd)/cgi SOURCES/Troglodyne-API-1.0/cgi
+	ln -s $(pwd)/img SOURCES/Troglodyne-API-1.0/img
+	ln -s $(pwd)/install SOURCES/Troglodyne-API-1.0/install
+	ln -s $(pwd)/js SOURCES/Troglodyne-API-1.0/js
+	ln -s $(pwd)/lib SOURCES/Troglodyne-API-1.0/lib
+	ln -s $(pwd)/plugin SOURCES/Troglodyne-API-1.0/plugin
+	ln -s $(pwd)/t SOURCES/Troglodyne-API-1.0/t
+	ln -s $(pwd)/templates SOURCES/Troglodyne-API-1.0/templates
+	cp $(pwd)/Makefile SOURCES/Troglodyne-API-1.0/Makefile
+	cp $(pwd)/configure SOURCES/Troglodyne-API-1.0/configure
+	mkdir -p ~/rpmbuild/SOURCES
+	cd SOURCES && tar --exclude="*.swp" --exclude="*.swn" --exclude="*.swo" -ch Troglodyne-API-1.0 | gzip > ~/rpmbuild/SOURCES/Troglodyne-API-1.0.tar.gz
+	rpmbuild -ba --clean --target noarch SPECS/TroglodyneApi.spec
+	rm -rf SOURCES/*

+ 47 - 0
SPECS/TroglodyneApi.spec

@@ -0,0 +1,47 @@
+Name: Troglodyne-API
+Version: 1.0
+Release:	1%{?dist}
+Summary: Common code for Troglodyne cPanel & WHM Plugins
+
+Group: Plugins
+License: Troglodyne
+URL: https://troglodyne.net/troglodyne-api
+Source0: Troglodyne-API-%{version}.tar.gz
+
+# No real way to tell it what version of cpanel-perl libs it needs,
+# as each of these are named like cpanel-perl-5xx-Module-Name.
+# This makes your RPM break every time they upgrade perl.
+# As such, just require the symlink to the binary and "pray it goes ok"
+AutoReqProv: no
+BuildRequires: make
+Requires: /usr/local/cpanel/3rdparty/bin/perl
+
+%description
+Troglodyne common code plugin
+
+%prep
+%setup -q
+
+%build
+
+%install
+make install DESTDIR=%{buildroot}
+
+%files
+%defattr(0700,root,root,-)
+/usr/local/cpanel/whostmgr/docroot/cgi/troglodyne/api.cgi
+%defattr(0600,root,root,-)
+/var/cpanel/perl/Troglodyne/CGI/API.pm
+/var/cpanel/perl/Troglodyne/CGI.pm
+%defattr(0755,root,root,0755)
+/var/cpanel/apps/troglodyne_api.conf
+
+%preun
+/usr/local/cpanel/bin/unregister_appconfig troglodyne_api
+
+%post
+/usr/local/cpanel/bin/register_appconfig /var/cpanel/apps/troglodyne_api.conf
+
+%changelog
+* Tue Jul 21 2020 George S. Baugh <george@troglodyne.net> - 1.0.1
+- Initial Release

+ 81 - 0
cgi/api.cgi

@@ -0,0 +1,81 @@
+#!/usr/local/cpanel/3rdparty/bin/perl
+#ACLS:all
+
+package Troglodyne::CGI::API;
+
+use strict;
+use warnings;
+
+use Cpanel::LoadModule::Custom ();
+use JSON::XS                   ();
+
+exit run() unless caller();
+
+sub run {
+    # Load up CGI processing modules
+    Cpanel::LoadModule::Custom::load_perl_module("Troglodyne::CGI");
+
+    my $ret = { 'metadata' => {} };
+
+    # Process the args
+    my $args;
+    my $err;
+    {
+        local $@;
+        eval { $args = Troglodyne::CGI::get_args() };
+        $err = $@;
+    }
+
+    if(!$args || !scalar(keys(%$args))) {
+        $ret->{'result'} = 0;
+        $ret->{'error'} = "No args detected! $err";
+        return emit($ret);
+    }
+    $err = '';
+    $ret->{'metadata'}{'input_args'} = $args;
+
+    # XXX Validation plz
+
+    # Load route code
+    my $namespace = "Troglodyne::API::$args->{'module'}";
+    my ( $loaded, $coderef );
+    {
+        local $@;
+        $loaded = eval {
+            Cpanel::LoadModule::Custom::load_perl_module($namespace);
+        };
+        $err = $@;
+        $coderef = $namespace->can($args->{'function'});
+    }
+
+    # Get back the datastruct from the called module.
+    # Yeah, yeah, I know. String eval. XXX
+    if( $loaded && $coderef ) {
+        local $@;
+        my $data = eval { $coderef->($args) };
+        my $error = $@;
+        if($data) {
+            $ret->{'data'} = $data;
+            $ret->{'result'} = 1;
+        } else {
+            $ret->{'result'} = 0;
+            $ret->{'error'} = $error;
+        }
+    } elsif( !$coderef ) {
+        $ret->{'error'} = "No such function '$args->{'function'}' in $namespace";
+        $ret->{'result'} = 0;
+    } else {
+        $ret->{'error'} = $err;
+        $ret->{'result'} = 0;
+    }
+
+    return emit($ret);
+}
+
+sub emit {
+    print "Content-type: application/json\r\n\r\n";
+    print JSON::XS::encode_json($_[0]);
+    return 0;
+}
+
+1;

+ 1 - 0
configure

@@ -0,0 +1 @@
+/bin/true

+ 117 - 0
lib/Troglodyne/CGI.pm

@@ -0,0 +1,117 @@
+package Troglodyne::CGI;
+
+use strict;
+use warnings;
+
+# Need to also get POST args, etc.
+sub get_args {
+    my $meth = $ENV{'REQUEST_METHOD'};
+    die "Unsupported request method: '$meth'" if !grep{ $meth eq $_ } qw{GET POST};
+    my $args_hr = {};
+    my %handlers = (
+        'GET'  => sub { return { map { split( /=/, $_ ) } split( /&/, $ENV{'QUERY_STRING'} ) } },
+        'POST' => sub {
+            die "Content length longer than 4096 bytes on a POST. Get lost!" if $ENV{'CONTENT_LENGTH'} > 4096;
+            sysread( STDIN, my $line, $ENV{'CONTENT_LENGTH'} );
+            return { map { split( /=/, $_ ) } split( /&/, $line ) };
+        },
+    );
+    $args_hr = $handlers{$meth}->();
+    return $args_hr;
+}
+
+our $CP_SECURITY_TOKEN = $ENV{'cp_security_token'} || 'cpsess0000000000';
+our $CP_USER = $ENV{'REMOTE_USER'} || 'nobody';
+
+# XXX TODO TODO TODO Need to make API calls return 304 unmodified if the data is
+# unchanged! hehehe
+
+# TODO delete cached templates via cron or hook or something, as the cache files
+# will otherwise begin to pile up.
+# Suggest using Cpanel::Session::is_active_security_token_for_user($user,$token)
+# in a loop and trashing inactive templates.
+
+# Yay. I now have ~250ms pageloads instead of 350+ms pageloads.
+# XXX Need to check when the chrome updates and pop cache then too?
+# Maybe upcp hook instead?
+sub render_cached_or_process_template {
+    my ( $service, $input_hr ) = @_;
+    if( $input_hr->{'troglodyne_do_static_render'} ) {
+
+        # Try to print from cache, as Cpanel::Template is slow AF
+        my ( $cached, $cache_dir ) = cached( $service, $input_hr->{'template_file'} );
+        return if( $cached && render_from_cache($cache_dir) );
+
+        # OK, so no cache. Let's fix that.
+        $input_hr->{'print'} = 0;
+        require Cpanel::Template;
+        my ( $success, $output_sr ) = Cpanel::Template::process_template( $service, $input_hr );
+
+        # TODO: Investigate whether I can send to two filehandles simultaneously -- $output_sr and STDOUT (as we are a CGI).
+        # Then we could cache & print without the below step.
+        if( $success ) {
+            return if render_to_cache_and_print( $cache_dir, $output_sr );
+        }
+    }
+
+    # Crap, everything failed. Just try to print it, sigh
+    require Cpanel::Template;
+    Cpanel::Template::process_template( $service, $input_hr );
+    return;
+}
+
+sub render_to_cache_and_print {
+    my ( $cache_dir, $content_sr ) = @_;
+    local $@;
+    require Cpanel::Mkdir;
+    Cpanel::Mkdir::ensure_directory_existence_and_mode($cache_dir, 0711);
+    my $worked = eval {
+        open( my $fh, '>', "$cache_dir/$CP_SECURITY_TOKEN" ) or die "Couldn't open cache file \"$cache_dir/$CP_SECURITY_TOKEN\" for writing: $!";
+        print $fh $$content_sr;
+        print STDOUT $$content_sr;
+    };
+    if(my $err = $@) {
+
+        # Require bashes $@, so assign first
+        require Cpanel::Debug;
+        Cpanel::Debug::log_error($err);
+    }
+    return $worked;
+}
+
+sub render_from_cache {
+    my ( $cache_dir ) = @_;
+    local $@;
+    my $worked = eval {
+        open( my $fh, '<', "$cache_dir/$CP_SECURITY_TOKEN" ) or die "Couldn't open cache file \"$cache_dir/$CP_SECURITY_TOKEN\" for reading: $!";
+        while( <$fh> ) { print $_; }
+        1;
+    };
+    if(my $err = $@) {
+
+        # Require bashes $@, so assign first
+        require Cpanel::Debug;
+        Cpanel::Debug::log_error($err);
+    }
+    return $worked;
+}
+
+# These MUST be indexed by cp_security_token... sadly
+our $ULC = '/usr/local/cpanel';
+our %TMPL_DIRS_BY_SVC = (
+    'whostmgr' => 'whostmgr/docroot/templates',
+    'cpanel'   => 'base/frontend/paper_lantern',
+    'webmail'  => 'base/webmail/paper_lantern',
+);
+sub cached {
+    my ( $service, $tmpl_file ) = @_;
+    my $tmpl_path  = "$ULC/$TMPL_DIRS_BY_SVC{$service}/$tmpl_file";
+    my $cache_dir = "${tmpl_path}_caches/$CP_USER";
+    my $cache_path = "$cache_dir/$CP_SECURITY_TOKEN";
+
+    # If cache mtime is older than template mtime, we are fine to use the cache.
+    my $cached = ( -s $cache_path && ( (stat(_))[9] > (stat($tmpl_path))[9] ) );
+    return ( $cached, $cache_dir );
+}
+
+1;

+ 81 - 0
lib/Troglodyne/CGI/API.pm

@@ -0,0 +1,81 @@
+#!/usr/local/cpanel/3rdparty/bin/perl
+#ACLS:all
+
+package Troglodyne::CGI::API;
+
+use strict;
+use warnings;
+
+use Cpanel::LoadModule::Custom ();
+use JSON::XS                   ();
+
+exit run() unless caller();
+
+sub run {
+    # Load up CGI processing modules
+    Cpanel::LoadModule::Custom::load_perl_module("Troglodyne::CGI");
+
+    my $ret = { 'metadata' => {} };
+
+    # Process the args
+    my $args;
+    my $err;
+    {
+        local $@;
+        eval { $args = Troglodyne::CGI::get_args() };
+        $err = $@;
+    }
+
+    if(!$args || !scalar(keys(%$args))) {
+        $ret->{'result'} = 0;
+        $ret->{'error'} = "No args detected! $err";
+        return emit($ret);
+    }
+    $err = '';
+    $ret->{'metadata'}{'input_args'} = $args;
+
+    # XXX Validation plz
+
+    # Load route code
+    my $namespace = "Troglodyne::API::$args->{'module'}";
+    my ( $loaded, $coderef );
+    {
+        local $@;
+        $loaded = eval {
+            Cpanel::LoadModule::Custom::load_perl_module($namespace);
+        };
+        $err = $@;
+        $coderef = $namespace->can($args->{'function'});
+    }
+
+    # Get back the datastruct from the called module.
+    # Yeah, yeah, I know. String eval. XXX
+    if( $loaded && $coderef ) {
+        local $@;
+        my $data = eval { $coderef->($args) };
+        my $error = $@;
+        if($data) {
+            $ret->{'data'} = $data;
+            $ret->{'result'} = 1;
+        } else {
+            $ret->{'result'} = 0;
+            $ret->{'error'} = $error;
+        }
+    } elsif( !$coderef ) {
+        $ret->{'error'} = "No such function '$args->{'function'}' in $namespace";
+        $ret->{'result'} = 0;
+    } else {
+        $ret->{'error'} = $err;
+        $ret->{'result'} = 0;
+    }
+
+    return emit($ret);
+}
+
+sub emit {
+    print "Content-type: application/json\r\n\r\n";
+    print JSON::XS::encode_json($_[0]);
+    return 0;
+}
+
+1;

+ 4 - 0
plugin/troglodyne_api.conf

@@ -0,0 +1,4 @@
+service=whostmgr
+user=root
+url=/cgi/troglodyne/api.cgi
+acls=all

+ 24 - 0
t/Troglodyne-CGI.t

@@ -0,0 +1,24 @@
+use Test2::V0;
+use File::Temp;
+use FindBin;
+use Capture::Tiny qw{capture_stdout};
+
+use lib "$FindBin::Bin/lib"; # Test libraries
+use lib "$FindBin::Bin/../lib"; # Code under test
+
+use Troglodyne::CGI  ();
+use Cpanel::Template ();
+
+plan 1;
+
+subtest "render_cached_or_process_template" => sub {
+    my $tmp_obj = File::Temp->newdir();
+    local $Troglodyne::CGI::ULC = $tmp_obj->dirname();
+    my $input_hr = { 'template_file' => 'bogusbogus', 'print' => 1 };
+    my $printed = capture_stdout {
+        Troglodyne::CGI::render_cached_or_process_template( 'whostmongler', $input_hr );
+    };
+    my $test_str = "# [whostmongler] This is a test of Troglodyne::CGI. Please Ignore";
+    is( $printed, $test_str, "Got the expected output when troglodyne_do_static_render invoked" );
+    $input_hr->{'troglodyne_do_static_render'} = 1;
+};

+ 16 - 0
t/lib/Cpanel/Template.pm

@@ -0,0 +1,16 @@
+package Cpanel::Template;
+use strict;
+use warnings;
+
+use FindBin;
+
+sub process_template {
+    my ( $svc, $args_hr ) = @_;
+    my $content = "# [$svc] This is a test of Troglodyne::CGI. Please Ignore";
+    if($args_hr->{'print'}) {
+        print STDOUT $content;
+    }
+    return ( 1, \$content ); 
+};
+
+1;