2021-04-08 12:54:49 +02:00
< ? php
namespace JeroenED\Webcron\Repository ;
2021-04-13 14:07:11 +02:00
use DateTime ;
2021-05-24 18:36:16 +02:00
use GuzzleHttp\Client ;
2021-10-18 15:17:36 +02:00
use GuzzleHttp\Exception\GuzzleException ;
2021-05-27 11:46:30 +02:00
use JeroenED\Framework\Repository ;
2021-05-24 18:36:16 +02:00
use phpseclib3\Crypt\PublicKeyLoader ;
use phpseclib3\Net\SSH2 ;
2021-04-08 12:54:49 +02:00
2021-05-27 11:46:30 +02:00
class Job extends Repository
2021-04-08 12:54:49 +02:00
{
2021-11-19 15:13:20 +01:00
public function getFailingJobs ()
{
$runRepo = new Run ( $this -> dbcon );
$jobsSql = " SELECT * FROM job " ;
$jobsStmt = $this -> dbcon -> prepare ( $jobsSql );
$jobsRslt = $jobsStmt -> executeQuery ();
$jobs = $jobsRslt -> fetchAllAssociative ();
foreach ( $jobs as $key =>& $job ) {
$job [ 'data' ] = json_decode ( $job [ 'data' ], true );
$job [ 'host-displayname' ] = $job [ 'data' ][ 'host' ];
$job [ 'host' ] = $job [ 'data' ][ 'host' ];
$job [ 'service' ] = $job [ 'data' ][ 'service' ] ? ? '' ;
$failedruns = $runRepo -> getRunsForJob ( $job [ 'id' ], true , $job [ 'data' ][ 'fail-days' ]);
$failed = count ( $failedruns );
$all = count ( $runRepo -> getRunsForJob ( $job [ 'id' ], false , $job [ 'data' ][ 'fail-days' ]));
$job [ 'lastfail' ] = $failedruns [ 0 ] ? ? NULL ;
$job [ 'needschecking' ] = $all > 0 && (( $failed / $all ) * 100 ) > $job [ 'data' ][ 'fail-pct' ];
if ( ! empty ( $job [ 'data' ][ 'containertype' ]) && $job [ 'data' ][ 'containertype' ] != 'none' ) {
$job [ 'host-displayname' ] = $job [ 'data' ][ 'service' ] . ' on ' . $job [ 'data' ][ 'host' ];
}
if ( $job [ 'needschecking' ]) $failingjobs [] = $job ;
}
if ( empty ( $failingjobs )) return [];
array_multisort (
array_column ( $failingjobs , 'name' ), SORT_ASC ,
array_column ( $failingjobs , 'host' ), SORT_ASC ,
array_column ( $failingjobs , 'service' ), SORT_ASC ,
$failingjobs );
return $failingjobs ;
}
2021-07-20 16:29:03 +02:00
public function getAllJobs ( bool $idiskey = false )
2021-04-08 12:54:49 +02:00
{
2021-08-02 17:07:41 +02:00
$runRepo = new Run ( $this -> dbcon );
2021-05-28 19:57:18 +02:00
$jobsSql = " SELECT * FROM job " ;
2021-04-08 12:54:49 +02:00
$jobsStmt = $this -> dbcon -> prepare ( $jobsSql );
2021-05-24 18:36:16 +02:00
$jobsRslt = $jobsStmt -> executeQuery ();
2021-04-08 12:54:49 +02:00
$jobs = $jobsRslt -> fetchAllAssociative ();
2021-07-20 16:29:03 +02:00
$returnbyid = [];
2021-04-08 12:54:49 +02:00
foreach ( $jobs as $key =>& $job ) {
$job [ 'data' ] = json_decode ( $job [ 'data' ], true );
2021-05-28 12:25:22 +02:00
$job [ 'host-displayname' ] = $job [ 'data' ][ 'host' ];
2021-05-28 19:57:18 +02:00
$job [ 'host' ] = $job [ 'data' ][ 'host' ];
$job [ 'service' ] = $job [ 'data' ][ 'service' ] ? ? '' ;
2021-07-14 13:10:12 +02:00
$job [ 'norun' ] = isset ( $job [ 'lastrun' ]) && $job [ 'nextrun' ] > $job [ 'lastrun' ];
$job [ 'running' ] = $job [ 'running' ] != 0 ;
2021-08-03 17:23:41 +02:00
$failed = count ( $runRepo -> getRunsForJob ( $job [ 'id' ], true , $job [ 'data' ][ 'fail-days' ]));
$all = count ( $runRepo -> getRunsForJob ( $job [ 'id' ], false , $job [ 'data' ][ 'fail-days' ]));
$job [ 'needschecking' ] = $all > 0 && (( $failed / $all ) * 100 ) > $job [ 'data' ][ 'fail-pct' ];
2021-05-28 12:25:22 +02:00
if ( ! empty ( $job [ 'data' ][ 'containertype' ]) && $job [ 'data' ][ 'containertype' ] != 'none' ) {
$job [ 'host-displayname' ] = $job [ 'data' ][ 'service' ] . ' on ' . $job [ 'data' ][ 'host' ];
}
2021-07-20 16:29:03 +02:00
if ( $idiskey ) $returnbyid [ $job [ 'id' ]] = $job ;
2021-04-08 12:54:49 +02:00
}
2021-07-20 16:29:03 +02:00
if ( $idiskey ) return $returnbyid ;
2021-05-28 19:57:18 +02:00
array_multisort (
array_column ( $jobs , 'name' ), SORT_ASC ,
array_column ( $jobs , 'host' ), SORT_ASC ,
array_column ( $jobs , 'service' ), SORT_ASC ,
$jobs );
2021-04-08 12:54:49 +02:00
return $jobs ;
}
2021-04-13 14:07:11 +02:00
2021-08-02 17:07:41 +02:00
public function getErrorRatio ( int $jobId ) : bool
{
$errorSql = " SELECT count(*) as count FROM job WHERE id = :id " ;
$errorStmt = $this -> dbcon -> prepare ( $errorSql );
$errorRslt = $errorStmt -> executeQuery ([ ':timestamp' => time (), ':timestamplastrun' => time (), ':timestamprun' => time ()]);
$error = $errorRslt -> fetchAllAssociative ();
$errorSql = " SELECT count(*) as count FROM job WHERE id = :id " ;
$errorStmt = $this -> dbcon -> prepare ( $errorSql );
$errorRslt = $errorStmt -> executeQuery ([ ':timestamp' => time (), ':timestamplastrun' => time (), ':timestamprun' => time ()]);
$error = $errorRslt -> fetchAllAssociative ();
}
2021-05-24 18:36:16 +02:00
public function getJobsDue ()
{
2021-06-01 13:45:57 +02:00
$jobsSql = " SELECT id, running
2021-05-29 14:51:19 +02:00
FROM job
WHERE (
nextrun <= : timestamp
AND ( lastrun IS NULL OR lastrun > : timestamplastrun )
AND running IN ( 0 , 2 )
)
2021-06-01 20:56:16 +02:00
OR ( running NOT IN ( 0 , 1 , 2 ) AND running < : timestamprun )
OR ( running == 2 ) " ;
2021-05-24 18:36:16 +02:00
$jobsStmt = $this -> dbcon -> prepare ( $jobsSql );
2021-05-29 14:51:19 +02:00
$jobsRslt = $jobsStmt -> executeQuery ([ ':timestamp' => time (), ':timestamplastrun' => time (), ':timestamprun' => time ()]);
2021-05-24 18:36:16 +02:00
$jobs = $jobsRslt -> fetchAllAssociative ();
2021-06-01 13:45:57 +02:00
return $jobs ;
2021-05-24 18:36:16 +02:00
}
2021-10-22 12:16:21 +02:00
public function getTimeOfNextRun ()
{
$jobsSql = " SELECT nextrun
FROM job
WHERE running = 0 and nextrun != : time
ORDER BY nextrun
LIMIT 1 " ;
$jobsStmt = $this -> dbcon -> prepare ( $jobsSql );
$jobsRslt = $jobsStmt -> executeQuery ([ ':time' => time ()]);
$nextjob = $jobsRslt -> fetchAssociative ();
$jobsSql = " SELECT nextrun
FROM job
WHERE running = 2
ORDER BY nextrun
LIMIT 1 " ;
$jobsStmt = $this -> dbcon -> prepare ( $jobsSql );
$jobsRslt = $jobsStmt -> executeQuery ();
$manualjob = $jobsRslt -> fetchAssociative ();
if ( $nextjob == false && $manualjob == false ) {
return PHP_INT_MAX ;
}
if ( $manualjob != false ) {
return 100 ;
}
$jobsSql = " SELECT running
FROM job
WHERE running > 2
ORDER BY nextrun DESC
LIMIT 1 " ;
$jobsStmt = $this -> dbcon -> prepare ( $jobsSql );
$jobsRslt = $jobsStmt -> executeQuery ();
$running = $jobsRslt -> fetchAssociative ();
if ( $running == false ) {
return ( int ) $nextjob [ 'nextrun' ];
}
return $nextjob < $running ? ( int ) $running [ 'running' ] : ( int ) $nextjob [ 'nextrun' ];
}
2021-05-24 18:36:16 +02:00
public function setJobRunning ( int $job , bool $status ) : void
{
2021-05-29 14:25:02 +02:00
$jobsSql = " UPDATE job SET running = :status WHERE id = :id AND running IN (0,1,2) " ;
2021-05-24 18:36:16 +02:00
$jobsStmt = $this -> dbcon -> prepare ( $jobsSql );
$jobsStmt -> executeQuery ([ ':id' => $job , ':status' => $status ? 1 : 0 ]);
return ;
}
2021-05-28 18:19:12 +02:00
public function setTempVar ( int $job , string $name , mixed $value ) : void
{
2021-05-29 14:25:02 +02:00
$jobsSql = " SELECT data FROM job WHERE id = :id " ;
2021-05-28 18:19:12 +02:00
$jobsStmt = $this -> dbcon -> prepare ( $jobsSql );
$result = $jobsStmt -> executeQuery ([ ':id' => $job ]) -> fetchAssociative ();
$result = json_decode ( $result [ 'data' ], true );
$result [ 'temp_vars' ][ $name ] = $value ;
2021-05-29 14:25:02 +02:00
$jobsSql = " UPDATE job SET data = :data WHERE id = :id " ;
2021-05-28 18:19:12 +02:00
$jobsStmt = $this -> dbcon -> prepare ( $jobsSql );
$jobsStmt -> executeQuery ([ ':id' => $job , ':data' => json_encode ( $result )]);
return ;
}
public function deleteTempVar ( int $job , string $name ) : void
{
2021-05-29 14:25:02 +02:00
$jobsSql = " SELECT data FROM job WHERE id = :id " ;
2021-05-28 18:19:12 +02:00
$jobsStmt = $this -> dbcon -> prepare ( $jobsSql );
$result = $jobsStmt -> executeQuery ([ ':id' => $job ]) -> fetchAssociative ();
$result = json_decode ( $result [ 'data' ], true );
unset ( $result [ 'temp_vars' ][ $name ]);
2021-05-29 14:25:02 +02:00
$jobsSql = " UPDATE job SET data = :data WHERE id = :id " ;
2021-05-28 18:19:12 +02:00
$jobsStmt = $this -> dbcon -> prepare ( $jobsSql );
$jobsStmt -> executeQuery ([ ':id' => $job , ':data' => json_encode ( $result )]);
return ;
}
2021-10-18 15:17:36 +02:00
public function getTempVar ( int $job , string $name , mixed $default = NULL ) : mixed
2021-05-28 18:19:12 +02:00
{
2021-05-29 14:25:02 +02:00
$jobsSql = " SELECT data FROM job WHERE id = :id " ;
2021-05-28 18:19:12 +02:00
$jobsStmt = $this -> dbcon -> prepare ( $jobsSql );
$result = $jobsStmt -> executeQuery ([ ':id' => $job ]) -> fetchAssociative ();
$result = json_decode ( $result [ 'data' ], true );
2021-10-18 15:17:36 +02:00
return $result [ 'temp_vars' ][ $name ] ? ? $default ;
2021-05-28 18:19:12 +02:00
}
2021-05-28 09:59:55 +02:00
private function runHttpJob ( array $job ) : array
2021-05-24 18:36:16 +02:00
{
2021-05-28 09:59:55 +02:00
$client = new Client ();
2021-05-24 18:36:16 +02:00
2021-05-28 09:59:55 +02:00
if ( ! empty ( $job [ 'data' ][ 'vars' ])) {
foreach ( $job [ 'data' ][ 'vars' ] as $key => $var ) {
2021-05-31 09:48:09 +02:00
if ( ! empty ( $job [ 'data' ][ 'basicauth-username' ])) $job [ 'data' ][ 'basicauth-username' ] = str_replace ( '{' . $key . '}' , $var [ 'value' ], $job [ 'data' ][ 'basicauth-username' ]);
2021-05-28 09:59:55 +02:00
$job [ 'data' ][ 'url' ] = str_replace ( '{' . $key . '}' , $var [ 'value' ], $job [ 'data' ][ 'url' ]);
2021-05-24 18:36:16 +02:00
}
2021-05-28 09:59:55 +02:00
}
2021-05-24 18:36:16 +02:00
2021-05-28 09:59:55 +02:00
$url = $job [ 'data' ][ 'url' ];
$options [ 'http_errors' ] = false ;
2021-05-31 09:48:09 +02:00
$options [ 'auth' ] = ! empty ( $job [ 'data' ][ 'basicauth-username' ]) ? [ $job [ 'data' ][ 'basicauth-username' ], $job [ 'data' ][ 'basicauth-password' ]] : NULL ;
2021-09-21 09:37:22 +02:00
try {
$res = $client -> request ( 'GET' , $url , $options );
$return [ 'exitcode' ] = $res -> getStatusCode ();
$return [ 'output' ] = $res -> getBody ();
$return [ 'failed' ] = ! in_array ( $return [ 'exitcode' ], $job [ 'data' ][ 'http-status' ]);
2021-10-18 15:17:36 +02:00
} catch ( GuzzleException $exception ) {
2021-09-21 09:37:22 +02:00
$return [ 'exitcode' ] = $exception -> getCode ();
$return [ 'output' ] = $exception -> getMessage ();
$return [ 'failed' ] = true ;
}
2021-05-28 09:59:55 +02:00
return $return ;
}
2021-05-27 20:24:03 +02:00
2021-05-28 09:59:55 +02:00
private function runCommandJob ( array $job ) : array
{
if ( ! empty ( $job [ 'data' ][ 'vars' ])) {
foreach ( $job [ 'data' ][ 'vars' ] as $key => $var ) {
$job [ 'data' ][ 'command' ] = str_replace ( '{' . $key . '}' , $var [ 'value' ], $job [ 'data' ][ 'command' ]);
2021-05-27 20:24:03 +02:00
}
2021-05-28 09:59:55 +02:00
}
2021-05-24 18:36:16 +02:00
2021-05-28 09:59:55 +02:00
$command = $job [ 'data' ][ 'command' ];
if ( $job [ 'data' ][ 'containertype' ] == 'docker' ) {
$command = $this -> prepareDockerCommand ( $command , $job [ 'data' ][ 'service' ], $job [ 'data' ][ 'container-user' ]);
}
2021-09-22 10:26:52 +02:00
try {
if ( $job [ 'data' ][ 'hosttype' ] == 'local' ) {
$return = $this -> runLocalCommand ( $command );
} elseif ( $job [ 'data' ][ 'hosttype' ] == 'ssh' ) {
$return = $this -> runSshCommand ( $command , $job [ 'data' ][ 'host' ], $job [ 'data' ][ 'user' ], $job [ 'data' ][ 'ssh-privkey' ], $job [ 'data' ][ 'privkey-password' ]);
}
$return [ 'failed' ] = ! in_array ( $return [ 'exitcode' ], $job [ 'data' ][ 'response' ]);
} catch ( \RuntimeException $exception ) {
$return [ 'exitcode' ] = $exception -> getCode ();
$return [ 'output' ] = $exception -> getMessage ();
$return [ 'failed' ] = true ;
2021-05-28 09:59:55 +02:00
}
2021-09-22 10:26:52 +02:00
2021-07-14 13:09:23 +02:00
return $return ;
2021-05-28 09:59:55 +02:00
}
2021-05-27 21:17:10 +02:00
2021-05-28 09:59:55 +02:00
private function runLocalCommand ( string $command ) : array
{
2021-07-14 13:46:56 +02:00
if ( function_exists ( 'pcntl_signal' )) pcntl_signal ( SIGCHLD , SIG_DFL );
2021-05-28 09:59:55 +02:00
$return [ 'exitcode' ] = NULL ;
$return [ 'output' ] = NULL ;
exec ( $command . ' 2>&1' , $return [ 'output' ], $return [ 'exitcode' ]);
2021-07-14 13:46:56 +02:00
if ( function_exists ( 'pcntl_signal' )) pcntl_signal ( SIGCHLD , SIG_IGN );
2021-05-28 09:59:55 +02:00
$return [ 'output' ] = implode ( " \n " , $return [ 'output' ]);
return $return ;
}
2021-05-27 21:17:10 +02:00
2021-05-28 09:59:55 +02:00
private function runSshCommand ( string $command , string $host , string $user , string $privkey , string $password ) : array
{
$ssh = new SSH2 ( $host );
$key = null ;
if ( ! empty ( $privkey )) {
if ( ! empty ( $password )) {
$key = PublicKeyLoader :: load ( base64_decode ( $privkey ), $password );
} else {
$key = PublicKeyLoader :: load ( base64_decode ( $privkey ));
}
} elseif ( ! empty ( $password )) {
2021-05-28 17:28:02 +02:00
$key = $password ;
2021-05-28 09:59:55 +02:00
}
if ( ! $ssh -> login ( $user , $key )) {
2021-05-28 11:45:30 +02:00
$return [ 'output' ] = " Login failed " ;
$return [ 'exitcode' ] = 255 ;
return $return ;
2021-05-28 09:59:55 +02:00
}
2021-07-02 22:06:18 +02:00
$ssh -> setTimeout ( 0 );
2021-05-29 15:50:19 +02:00
$return [ 'output' ] = $ssh -> exec ( $command );
$return [ 'exitcode' ] = $ssh -> getExitStatus ();
$return [ 'exitcode' ] = ( empty ( $return [ 'exitcode' ])) ? 0 : $return [ 'exitcode' ];
2021-05-28 09:59:55 +02:00
return $return ;
}
2021-05-27 21:17:10 +02:00
2021-06-01 13:45:57 +02:00
private function runRebootJob ( array $job , float & $starttime , bool & $manual ) : array
2021-05-28 09:59:55 +02:00
{
if ( $job [ 'running' ] == 1 ) {
2021-05-28 18:19:12 +02:00
$this -> setTempVar ( $job [ 'id' ], 'starttime' , $starttime );
2021-06-01 13:45:57 +02:00
$this -> setTempVar ( $job [ 'id' ], 'manual' , $manual );
2021-05-28 09:59:55 +02:00
$job [ 'data' ][ 'reboot-command' ] = str_replace ( '{reboot-delay}' , $job [ 'data' ][ 'reboot-delay' ], $job [ 'data' ][ 'reboot-command' ]);
$job [ 'data' ][ 'reboot-command' ] = str_replace ( '{reboot-delay-secs}' , $job [ 'data' ][ 'reboot-delay-secs' ], $job [ 'data' ][ 'reboot-command' ]);
2021-05-27 21:17:10 +02:00
2021-05-28 09:59:55 +02:00
if ( ! empty ( $job [ 'data' ][ 'vars' ])) {
foreach ( $job [ 'data' ][ 'vars' ] as $key => $var ) {
$job [ 'data' ][ 'reboot-command' ] = str_replace ( '{' . $key . '}' , $var [ 'value' ], $job [ 'data' ][ 'reboot-command' ]);
2021-05-27 21:17:10 +02:00
}
2021-05-28 09:59:55 +02:00
}
2021-05-27 21:17:10 +02:00
2021-05-28 09:59:55 +02:00
$jobsSql = " UPDATE job SET running = :status WHERE id = :id " ;
$jobsStmt = $this -> dbcon -> prepare ( $jobsSql );
2021-05-28 17:28:02 +02:00
$jobsStmt -> executeQuery ([ ':id' => $job [ 'id' ], ':status' => time () + $job [ 'data' ][ 'reboot-delay-secs' ] + ( $job [ 'data' ][ 'reboot-duration' ] * 60 )]);
2021-05-27 21:17:10 +02:00
2021-05-28 09:59:55 +02:00
if ( $job [ 'data' ][ 'hosttype' ] == 'ssh' ) {
2021-05-28 17:28:02 +02:00
$this -> runSshCommand ( $job [ 'data' ][ 'reboot-command' ], $job [ 'data' ][ 'host' ], $job [ 'data' ][ 'user' ], $job [ 'data' ][ 'ssh-privkey' ] ? ? '' , $job [ 'data' ][ 'privkey-password' ] ? ? '' );
2021-05-27 21:17:10 +02:00
2021-05-28 09:59:55 +02:00
} elseif ( $job [ 'data' ][ 'hosttype' ] == 'local' ) {
$this -> runLocalCommand ( $job [ 'data' ][ 'reboot-command' ]);
}
2021-07-15 13:14:36 +02:00
return [ 'status' => 'deferred' ];
2021-05-28 09:59:55 +02:00
} elseif ( $job [ 'running' ] != 0 ) {
if ( $job [ 'running' ] > time ()) {
2021-07-15 13:14:36 +02:00
return [ 'status' => 'deferred' ];
2021-05-28 09:59:55 +02:00
}
2021-05-29 11:56:29 +02:00
$starttime = ( float ) $this -> getTempVar ( $job [ 'id' ], 'starttime' );
$this -> deleteTempVar ( $job [ 'id' ], 'starttime' );
2021-06-01 21:26:25 +02:00
$manual = $this -> getTempVar ( $job [ 'id' ], 'manual' );
2021-06-01 13:45:57 +02:00
$this -> deleteTempVar ( $job [ 'id' ], 'manual' );
2021-05-29 11:56:29 +02:00
2021-05-28 19:16:25 +02:00
$jobsSql = " UPDATE job SET running = :status WHERE id = :id " ;
$jobsStmt = $this -> dbcon -> prepare ( $jobsSql );
$jobsStmt -> executeQuery ([ ':id' => $job [ 'id' ], ':status' => 1 ]);
2021-05-27 21:17:10 +02:00
2021-05-28 09:59:55 +02:00
if ( ! empty ( $job [ 'data' ][ 'vars' ])) {
foreach ( $job [ 'data' ][ 'vars' ] as $key => $var ) {
$job [ 'data' ][ 'getservices-command' ] = str_replace ( '{' . $key . '}' , $var [ 'value' ], $job [ 'data' ][ 'getservices-command' ]);
2021-05-27 21:17:10 +02:00
}
}
2021-05-28 09:59:55 +02:00
if ( $job [ 'data' ][ 'hosttype' ] == 'ssh' ) {
2021-05-28 18:19:12 +02:00
$return = $this -> runSshCommand ( $job [ 'data' ][ 'getservices-command' ], $job [ 'data' ][ 'host' ], $job [ 'data' ][ 'user' ], $job [ 'data' ][ 'ssh-privkey' ], $job [ 'data' ][ 'privkey-password' ]);
2021-05-28 09:59:55 +02:00
} elseif ( $job [ 'data' ][ 'hosttype' ] == 'local' ) {
2021-05-28 18:19:12 +02:00
$return = $this -> runLocalCommand ( $job [ 'data' ][ 'getservices-command' ]);
2021-05-28 09:59:55 +02:00
}
2021-07-14 13:09:23 +02:00
$return [ 'failed' ] = ! in_array ( $return [ 'exitcode' ], $job [ 'data' ][ 'getservices-response' ]);
2021-05-28 18:19:12 +02:00
return $return ;
2021-05-28 09:59:55 +02:00
}
}
2021-05-28 18:19:12 +02:00
2021-07-15 12:41:34 +02:00
public function runNow ( $job , $console = false ) {
2021-06-01 17:41:10 +02:00
$job = $this -> getJob ( $job , true );
$runRepo = new Run ( $this -> dbcon );
2021-08-02 10:54:50 +02:00
if ( $console == false && ( $runRepo -> isSlowJob ( $job [ 'id' ]) || count ( $runRepo -> getRunsForJob ( $job [ 'id' ])) == 0 || $job [ 'data' ][ 'crontype' ] === 'reboot' )) {
2021-06-01 17:41:10 +02:00
$jobsSql = " UPDATE job SET running = :status WHERE id = :id AND running IN (0,1,2) " ;
$jobsStmt = $this -> dbcon -> prepare ( $jobsSql );
$jobsStmt -> executeQuery ([ ':id' => $job [ 'id' ], ':status' => 2 ]);
} else {
2021-07-15 12:41:34 +02:00
$output = $this -> runJob ( $job [ 'id' ], true );
2021-07-15 13:14:36 +02:00
if ( ! ( isset ( $output [ 'status' ]) && $output [ 'status' ] == 'deferred' ))
2021-06-01 17:41:10 +02:00
return [
2021-06-01 20:21:47 +02:00
'status' => 'ran' ,
2021-07-15 12:41:34 +02:00
'output' => ( $console ) ? $output [ 'output' ] : htmlentities ( $output [ 'output' ]),
2021-06-01 17:41:10 +02:00
'exitcode' => $output [ 'exitcode' ],
2021-07-02 21:03:21 +02:00
'runtime' => ( float ) $output [ 'runtime' ],
2021-06-01 20:21:47 +02:00
'title' => ! str_contains ( $output [ 'flags' ], Run :: FAILED ) ? 'Cronjob successfully ran' : 'Cronjob failed. Please check output below' ,
2021-06-01 17:41:10 +02:00
'success' => ! str_contains ( $output [ 'flags' ], Run :: FAILED )
];
}
2021-07-15 13:14:36 +02:00
return [ 'success' => true , 'status' => 'deferred' , 'title' => 'Cronjob has been scheduled' , 'message' => 'Job was scheduled to be run. You will find the output soon in the job details' ];
2021-06-01 17:41:10 +02:00
}
2021-05-28 09:59:55 +02:00
private function prepareDockerCommand ( string $command , string $service , string | NULL $user ) : string
{
$prepend = 'docker exec ' ;
$prepend .= ( ! empty ( $user )) ? ' --user=' . $user . ' ' : '' ;
2021-05-28 10:16:08 +02:00
$prepend .= $service . ' ' ;
2021-05-28 09:59:55 +02:00
return $prepend . $command ;
}
2021-07-15 12:41:34 +02:00
public function runJob ( int $job , bool $manual ) : array
2021-05-28 09:59:55 +02:00
{
2021-05-28 14:24:33 +02:00
$starttime = microtime ( true );
2021-05-28 09:59:55 +02:00
$job = $this -> getJob ( $job , true );
2021-06-01 17:41:10 +02:00
if ( $job [ 'data' ][ 'crontype' ] == 'http' ) {
2021-05-28 09:59:55 +02:00
$result = $this -> runHttpJob ( $job );
2021-06-01 17:41:10 +02:00
} elseif ( $job [ 'data' ][ 'crontype' ] == 'command' ) {
2021-05-28 09:59:55 +02:00
$result = $this -> runCommandJob ( $job );
2021-06-01 17:41:10 +02:00
} elseif ( $job [ 'data' ][ 'crontype' ] == 'reboot' ) {
2021-06-01 13:45:57 +02:00
$result = $this -> runRebootJob ( $job , $starttime , $manual );
2021-07-15 13:14:36 +02:00
if ( isset ( $result [ 'status' ]) && $result [ 'status' ] == 'deferred' ) return $result ;
2021-05-24 18:36:16 +02:00
}
2021-05-28 14:24:33 +02:00
$endtime = microtime ( true );
2021-05-29 11:51:34 +02:00
$runtime = $endtime - $starttime ;
2021-05-24 18:36:16 +02:00
2021-06-01 13:45:57 +02:00
// setting flags
$flags = [];
2021-06-01 17:41:10 +02:00
if ( $result [ 'failed' ] === true ) {
2021-06-01 13:45:57 +02:00
$flags [] = Run :: FAILED ;
} else {
$flags [] = Run :: SUCCESS ;
}
2021-05-24 18:36:16 +02:00
2021-06-01 17:41:10 +02:00
if ( $manual === true ) {
2021-06-01 13:45:57 +02:00
$flags [] = Run :: MANUAL ;
}
2021-11-30 13:54:17 +01:00
// Remove secrets from output
2021-12-04 10:25:13 +01:00
if ( ! empty ( $job [ 'data' ][ 'vars' ])) {
foreach ( $job [ 'data' ][ 'vars' ] as $key => $var ) {
if ( $var [ 'issecret' ]) {
$result [ 'output' ] = str_replace ( $var [ 'value' ], '{' . $key . '}' , $result [ 'output' ]);
}
2021-11-30 13:54:17 +01:00
}
}
2021-06-01 13:45:57 +02:00
// saving to database
$runRepo = new Run ( $this -> dbcon );
$runRepo -> addRun ( $job [ 'id' ], $result [ 'exitcode' ], floor ( $starttime ), $runtime , $result [ 'output' ], $flags );
2021-06-01 17:41:10 +02:00
if ( ! $manual ){
// setting nextrun to next run
$nextrun = $job [ 'nextrun' ];
do {
$nextrun = $nextrun + $job [ 'interval' ];
} while ( $nextrun < time ());
2021-05-24 18:36:16 +02:00
2021-06-01 17:41:10 +02:00
$addRunSql = 'UPDATE job SET nextrun = :nextrun WHERE id = :id' ;
$addRunStmt = $this -> dbcon -> prepare ( $addRunSql );
$addRunStmt -> executeQuery ([ ':id' => $job [ 'id' ], ':nextrun' => $nextrun ]);
}
2021-07-15 12:41:34 +02:00
return [ 'job_id' => $job [ 'id' ], 'exitcode' => $result [ 'exitcode' ], 'timestamp' => floor ( $starttime ), 'runtime' => $runtime , 'output' => ( string ) $result [ 'output' ], 'flags' => implode ( " " , $flags )];
2021-05-24 18:36:16 +02:00
}
2021-05-29 14:20:05 +02:00
public function unlockJob ( int $id = 0 ) : void
{
2021-06-01 13:45:57 +02:00
$jobsSql = " UPDATE job SET running = :status WHERE running = 1 " ;
2021-05-29 14:20:05 +02:00
$params = [ ':status' => 0 ];
if ( $id != 0 ) {
$jobsSql .= " AND id = :id " ;
$params [ ':id' ] = $id ;
}
$jobsStmt = $this -> dbcon -> prepare ( $jobsSql );
$jobsStmt -> executeQuery ( $params );
return ;
}
2021-07-15 13:22:52 +02:00
public function isLockedJob ( int $id = 0 ) : bool
{
$jobsSql = " SELECT id FROM job WHERE id = :id AND running != :status " ;
$params = [ ':status' => 0 , ':id' => $id ];
return count ( $this -> dbcon -> prepare ( $jobsSql ) -> executeQuery ( $params ) -> fetchAllAssociative ()) > 0 ;
}
2021-04-13 14:07:11 +02:00
public function addJob ( array $values )
{
2021-05-19 13:24:38 +02:00
if ( empty ( $values [ 'crontype' ]) ||
2021-04-13 14:07:11 +02:00
empty ( $values [ 'name' ]) ||
2021-05-06 15:53:21 +02:00
empty ( $values [ 'interval' ]) ||
2021-04-13 14:07:11 +02:00
empty ( $values [ 'nextrun' ])
) {
2021-05-21 13:09:48 +02:00
throw new \InvalidArgumentException ( 'Some fields are empty' );
2021-04-13 14:07:11 +02:00
}
2021-05-21 13:09:48 +02:00
$data = $this -> prepareJob ( $values );
$data [ 'data' ] = json_encode ( $data [ 'data' ]);
2021-05-24 18:36:16 +02:00
$addJobSql = " INSERT INTO job(name, data, interval, nextrun, lastrun, running) VALUES (:name, :data, :interval, :nextrun, :lastrun, :running) " ;
2021-05-21 13:09:48 +02:00
$addJobStmt = $this -> dbcon -> prepare ( $addJobSql );
2021-05-24 18:36:16 +02:00
$addJobStmt -> executeQuery ([ ':name' => $data [ 'name' ], ':data' => $data [ 'data' ], ':interval' => $data [ 'interval' ], ':nextrun' => $data [ 'nextrun' ], ':lastrun' => $data [ 'lastrun' ], ':running' => 0 ]);
2021-05-21 13:09:48 +02:00
return [ 'success' => true , 'message' => 'Cronjob succesfully added' ];
}
public function editJob ( int $id , array $values )
{
if ( empty ( $values [ 'crontype' ]) ||
empty ( $values [ 'name' ]) ||
empty ( $values [ 'interval' ]) ||
empty ( $values [ 'nextrun' ])
) {
throw new \InvalidArgumentException ( 'Some fields are empty' );
}
$data = $this -> prepareJob ( $values );
$data [ 'data' ] = json_encode ( $data [ 'data' ]);
2021-05-29 14:25:02 +02:00
$editJobSql = " UPDATE job SET name = :name, data = :data, interval = :interval, nextrun = :nextrun, lastrun = :lastrun WHERE id = :id " ;
2021-05-21 13:09:48 +02:00
$editJobStmt = $this -> dbcon -> prepare ( $editJobSql );
$editJobStmt -> executeQuery ([ ':name' => $data [ 'name' ], ':data' => $data [ 'data' ], ':interval' => $data [ 'interval' ], ':nextrun' => $data [ 'nextrun' ], ':lastrun' => $data [ 'lastrun' ], ':id' => $id ]);
return [ 'success' => true , 'message' => 'Cronjob succesfully edited' ];
}
public function prepareJob ( array $values ) : array
{
2021-05-26 13:34:19 +02:00
if ( empty ( $values [ 'lastrun' ]) || ( isset ( $values [ 'lastrun-eternal' ]) && $values [ 'lastrun-eternal' ] == 'true' )) {
2021-04-13 14:07:11 +02:00
$values [ 'lastrun' ] = NULL ;
} else {
2021-05-24 12:28:47 +02:00
$values [ 'lastrun' ] = DateTime :: createFromFormat ( 'd/m/Y H:i:s' , $values [ 'lastrun' ]) -> getTimestamp ();
2021-04-13 14:07:11 +02:00
}
2021-05-24 12:28:47 +02:00
$values [ 'nextrun' ] = DateTime :: createFromFormat ( 'd/m/Y H:i:s' , $values [ 'nextrun' ]) -> getTimestamp ();
2021-12-23 11:11:01 +01:00
$values [ 'data' ][ 'retention' ] = ! empty ( $values [ 'retention' ]) ? ( int ) $values [ 'retention' ] : NULL ;
2021-07-20 16:29:03 +02:00
2021-05-21 13:09:48 +02:00
$values [ 'data' ][ 'crontype' ] = $values [ 'crontype' ];
$values [ 'data' ][ 'hosttype' ] = $values [ 'hosttype' ];
$values [ 'data' ][ 'containertype' ] = $values [ 'containertype' ];
2021-12-23 11:11:01 +01:00
$values [ 'data' ][ 'fail-pct' ] = ! empty ( $values [ 'fail-pct' ]) ? ( int ) $values [ 'fail-pct' ] : 50 ;
$values [ 'data' ][ 'fail-days' ] = ! empty ( $values [ 'fail-days' ]) ? ( int ) $values [ 'fail-days' ] : 7 ;
2021-04-13 14:07:11 +02:00
2021-05-24 12:28:47 +02:00
if ( empty ( $values [ 'data' ][ 'crontype' ])) {
throw new \InvalidArgumentException ( " Crontype cannot be empty " );
}
2021-05-21 13:09:48 +02:00
switch ( $values [ 'data' ][ 'crontype' ])
2021-04-13 14:07:11 +02:00
{
2021-05-19 13:24:38 +02:00
case 'command' :
2021-05-21 13:09:48 +02:00
$values [ 'data' ][ 'command' ] = $values [ 'command' ];
2021-05-28 15:00:24 +02:00
$values [ 'data' ][ 'response' ] = explode ( ',' , $values [ 'response' ]);
2021-04-15 13:52:27 +02:00
break ;
2021-05-06 14:30:35 +02:00
case 'reboot' :
2021-05-21 13:09:48 +02:00
$values [ 'data' ][ 'reboot-command' ] = $values [ 'reboot-command' ];
$values [ 'data' ][ 'getservices-command' ] = $values [ 'getservices-command' ];
2021-07-14 13:09:23 +02:00
$values [ 'data' ][ 'getservices-response' ] = explode ( ',' , $values [ 'getservices-response' ]);
2021-05-21 13:09:48 +02:00
$values [ 'data' ][ 'reboot-duration' ] = $values [ 'reboot-duration' ];
2021-05-06 14:30:35 +02:00
if ( ! empty ( $values [ 'reboot-delay' ])) {
$newsecretkey = count ( $values [ 'var-value' ]);
$values [ 'var-id' ][ $newsecretkey ] = 'reboot-delay' ;
$values [ 'var-issecret' ][ $newsecretkey ] = false ;
$values [ 'var-value' ][ $newsecretkey ] = ( int ) $values [ 'reboot-delay' ];
$newsecretkey = count ( $values [ 'var-value' ]);
$values [ 'var-id' ][ $newsecretkey ] = 'reboot-delay-secs' ;
$values [ 'var-issecret' ][ $newsecretkey ] = false ;
$values [ 'var-value' ][ $newsecretkey ] = ( int ) $values [ 'reboot-delay' ] * 60 ;
}
break ;
2021-04-13 14:07:11 +02:00
case 'http' :
$parsedUrl = parse_url ( $values [ 'url' ]);
2021-05-21 13:09:48 +02:00
$values [ 'data' ][ 'url' ] = $values [ 'url' ];
2021-05-28 15:00:24 +02:00
$values [ 'data' ][ 'http-status' ] = explode ( ',' , $values [ 'http-status' ]);
2021-05-21 13:09:48 +02:00
$values [ 'data' ][ 'basicauth-username' ] = $values [ 'basicauth-username' ];
2021-04-13 14:07:11 +02:00
if ( empty ( $parsedUrl [ 'host' ])) {
2021-05-21 13:09:48 +02:00
throw new \InvalidArgumentException ( 'Some data was invalid' );
2021-04-13 14:07:11 +02:00
}
2021-05-06 13:30:12 +02:00
if ( ! empty ( $values [ 'basicauth-password' ])) {
$newsecretkey = count ( $values [ 'var-value' ]);
$values [ 'var-id' ][ $newsecretkey ] = 'basicauth-password' ;
$values [ 'var-issecret' ][ $newsecretkey ] = true ;
$values [ 'var-value' ][ $newsecretkey ] = $values [ 'basicauth-password' ];
}
2021-05-21 13:09:48 +02:00
$values [ 'data' ][ 'host' ] = $parsedUrl [ 'host' ];
2021-04-13 14:07:11 +02:00
break ;
}
2021-05-21 13:09:48 +02:00
switch ( $values [ 'data' ][ 'hosttype' ]) {
2021-05-24 12:28:47 +02:00
default :
2021-05-25 19:44:57 +02:00
if ( $values [ 'data' ][ 'crontype' ] == 'http' ) break ;
$values [ 'data' ][ 'hosttype' ] = 'local' ;
2021-05-19 13:24:38 +02:00
case 'local' :
2021-05-21 13:09:48 +02:00
$values [ 'data' ][ 'host' ] = 'localhost' ;
2021-05-19 13:24:38 +02:00
break ;
case 'ssh' :
2021-05-21 13:09:48 +02:00
$values [ 'data' ][ 'host' ] = $values [ 'host' ];
$values [ 'data' ][ 'user' ] = $values [ 'user' ];
2021-05-19 13:24:38 +02:00
if ( ! empty ( $values [ 'privkey-password' ])) {
2021-05-21 13:09:48 +02:00
$newsecretkey = count ( $values [ 'var-value' ]);
$values [ 'var-id' ][ $newsecretkey ] = 'privkey-password' ;
$values [ 'var-issecret' ][ $newsecretkey ] = true ;
$values [ 'var-value' ][ $newsecretkey ] = $values [ 'privkey-password' ];
2021-05-19 13:24:38 +02:00
}
2021-05-24 12:28:47 +02:00
$privkeyid = NULL ;
2021-05-19 13:24:38 +02:00
if ( ! empty ( $_FILES [ 'privkey' ][ 'tmp_name' ])) {
$newsecretkey = count ( $values [ 'var-value' ]);
2021-05-24 12:28:47 +02:00
$privkeyid = $newsecretkey ;
2021-05-19 13:24:38 +02:00
$values [ 'var-id' ][ $newsecretkey ] = 'ssh-privkey' ;
$values [ 'var-issecret' ][ $newsecretkey ] = true ;
$values [ 'var-value' ][ $newsecretkey ] = base64_encode ( file_get_contents ( $_FILES [ 'privkey' ][ 'tmp_name' ]));
}
2021-05-28 17:28:02 +02:00
if ( isset ( $values [ 'privkey-keep' ]) && $values [ 'privkey-keep' ] == true ) {
2021-05-24 12:28:47 +02:00
$privkeyid = ( $privkeyid === NULL ) ? count ( $values [ 'var-value' ]) : $privkeyid ;
$values [ 'var-id' ][ $privkeyid ] = 'ssh-privkey' ;
$values [ 'var-issecret' ][ $privkeyid ] = true ;
$values [ 'var-value' ][ $privkeyid ] = $values [ 'privkey-orig' ];
}
2021-05-19 13:24:38 +02:00
break ;
}
2021-05-20 13:06:53 +02:00
2021-05-21 13:09:48 +02:00
switch ( $values [ 'data' ][ 'containertype' ]) {
2021-05-24 12:28:47 +02:00
default :
2021-05-28 12:25:22 +02:00
if ( $values [ 'data' ][ 'crontype' ] == 'http' || $values [ 'data' ][ 'crontype' ] == 'reboot' ) break ;
2021-05-25 19:44:57 +02:00
$values [ 'data' ][ 'containertype' ] = 'none' ;
2021-05-24 12:28:47 +02:00
case 'none' :
// No options for no container
break ;
2021-05-20 13:06:53 +02:00
case 'docker' :
2021-05-21 13:09:48 +02:00
$values [ 'data' ][ 'service' ] = $values [ 'service' ];
2021-05-24 12:28:47 +02:00
$values [ 'data' ][ 'container-user' ] = $values [ 'container-user' ];
2021-05-20 13:06:53 +02:00
break ;
}
2021-05-06 13:30:12 +02:00
if ( ! empty ( $values [ 'var-value' ])) {
foreach ( $values [ 'var-value' ] as $key => $name ) {
if ( ! empty ( $name )) {
2021-05-06 14:30:35 +02:00
if ( isset ( $values [ 'var-issecret' ][ $key ]) && $values [ 'var-issecret' ][ $key ] != false ) {
2021-05-21 13:09:48 +02:00
$values [ 'data' ][ 'vars' ][ $values [ 'var-id' ][ $key ]][ 'issecret' ] = true ;
$values [ 'data' ][ 'vars' ][ $values [ 'var-id' ][ $key ]][ 'value' ] = base64_encode ( Secret :: encrypt ( $values [ 'var-value' ][ $key ]));
2021-05-06 13:30:12 +02:00
} else {
2021-05-21 13:09:48 +02:00
$values [ 'data' ][ 'vars' ][ $values [ 'var-id' ][ $key ]][ 'issecret' ] = false ;
$values [ 'data' ][ 'vars' ][ $values [ 'var-id' ][ $key ]][ 'value' ] = $values [ 'var-value' ][ $key ];
2021-05-06 13:30:12 +02:00
}
}
2021-04-13 14:44:58 +02:00
}
}
2021-05-21 13:09:48 +02:00
return $values ;
2021-04-13 14:07:11 +02:00
}
2021-04-13 14:44:58 +02:00
public function getJob ( int $id , bool $withSecrets = false ) {
$jobSql = " SELECT * FROM job WHERE id = :id " ;
$jobStmt = $this -> dbcon -> prepare ( $jobSql );
2021-05-29 15:43:27 +02:00
$jobRslt = $jobStmt -> executeQuery ([ ':id' => $id ]) -> fetchAssociative ();
2021-04-13 14:44:58 +02:00
$jobRslt [ 'data' ] = json_decode ( $jobRslt [ 'data' ], true );
2021-05-21 13:09:48 +02:00
2021-05-06 13:30:12 +02:00
if ( ! empty ( $jobRslt [ 'data' ][ 'vars' ])) {
foreach ( $jobRslt [ 'data' ][ 'vars' ] as $key => & $value ) {
if ( $value [ 'issecret' ]) {
$value [ 'value' ] = ( $withSecrets ) ? Secret :: decrypt ( base64_decode ( $value [ 'value' ])) : '' ;
}
2021-04-13 14:44:58 +02:00
}
}
2021-05-21 13:09:48 +02:00
switch ( $jobRslt [ 'data' ][ 'crontype' ]) {
case 'http' :
if ( isset ( $jobRslt [ 'data' ][ 'vars' ][ 'basicauth-password' ][ 'value' ])) {
$jobRslt [ 'data' ][ 'basicauth-password' ] = $jobRslt [ 'data' ][ 'vars' ][ 'basicauth-password' ][ 'value' ];
unset ( $jobRslt [ 'data' ][ 'vars' ][ 'basicauth-password' ]);
}
break ;
2021-05-24 12:28:47 +02:00
case 'reboot' :
$jobRslt [ 'data' ][ 'reboot-delay' ] = $jobRslt [ 'data' ][ 'vars' ][ 'reboot-delay' ][ 'value' ];
2021-05-27 21:17:10 +02:00
$jobRslt [ 'data' ][ 'reboot-delay-secs' ] = $jobRslt [ 'data' ][ 'vars' ][ 'reboot-delay-secs' ][ 'value' ];
2021-05-24 12:28:47 +02:00
unset ( $jobRslt [ 'data' ][ 'vars' ][ 'reboot-delay' ]);
unset ( $jobRslt [ 'data' ][ 'vars' ][ 'reboot-delay-secs' ]);
break ;
}
switch ( $jobRslt [ 'data' ][ 'hosttype' ]) {
case 'ssh' :
if ( isset ( $jobRslt [ 'data' ][ 'vars' ][ 'ssh-privkey' ][ 'value' ])) {
$jobRslt [ 'data' ][ 'ssh-privkey' ] = $jobRslt [ 'data' ][ 'vars' ][ 'ssh-privkey' ][ 'value' ];
unset ( $jobRslt [ 'data' ][ 'vars' ][ 'ssh-privkey' ]);
}
if ( isset ( $jobRslt [ 'data' ][ 'vars' ][ 'privkey-password' ][ 'value' ])) {
$jobRslt [ 'data' ][ 'privkey-password' ] = $jobRslt [ 'data' ][ 'vars' ][ 'privkey-password' ][ 'value' ];
unset ( $jobRslt [ 'data' ][ 'vars' ][ 'privkey-password' ]);
}
break ;
2021-05-21 13:09:48 +02:00
}
if ( $jobRslt [ 'data' ][ 'crontype' ] == 'http' ) {
}
2021-04-13 14:44:58 +02:00
return $jobRslt ;
}
2021-05-24 14:08:30 +02:00
public function deleteJob ( int $id )
{
2021-07-20 16:29:03 +02:00
$this -> dbcon -> prepare ( " DELETE FROM job WHERE id = :id " ) -> executeStatement ([ ':id' => $id ]);
$this -> dbcon -> prepare ( " DELETE FROM run WHERE job_id = :id " ) -> executeStatement ([ ':id' => $id ]);
2021-05-24 14:08:30 +02:00
return [ 'success' => true , 'message' => 'Cronjob succesfully deleted' ];
}
2021-04-08 12:54:49 +02:00
}