2021-04-08 12:54:49 +02:00
< ? php
2022-04-27 14:24:48 +02:00
namespace App\Repository ;
2021-04-08 12:54:49 +02:00
2022-05-13 15:24:05 +02:00
use App\Entity\Job ;
2022-04-27 14:24:48 +02:00
use App\Entity\Run ;
use App\Service\Secret ;
2021-04-13 14:07:11 +02:00
use DateTime ;
2022-04-27 14:24:48 +02:00
use Doctrine\ORM\EntityRepository ;
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-24 18:36:16 +02:00
use phpseclib3\Crypt\PublicKeyLoader ;
use phpseclib3\Net\SSH2 ;
2021-04-08 12:54:49 +02:00
2022-04-27 14:24:48 +02:00
class JobRepository extends EntityRepository
2021-04-08 12:54:49 +02:00
{
2021-11-19 15:13:20 +01:00
public function getFailingJobs ()
{
2022-04-27 14:24:48 +02:00
$runRepo = $this -> getEntityManager () -> getRepository ( Run :: class );
2022-05-16 18:00:44 +02:00
/** @var Job[] $jobs */
$jobs = $this -> getAllJobs ();
2021-11-19 15:13:20 +01:00
2022-05-16 18:00:44 +02:00
$return = [];
foreach ( $jobs as $job ) {
2022-05-17 16:06:46 +02:00
if ( $job -> getData ( 'needschecking' )) {
2022-05-16 18:00:44 +02:00
$return [] = $job ;
2021-11-19 15:13:20 +01:00
}
}
2022-05-16 18:00:44 +02:00
return $return ;
2021-11-19 15:13:20 +01:00
}
2022-05-16 18:00:44 +02:00
public function getRunningJobs ( bool $idiskey = false ) : array
2022-02-04 14:21:42 +01:00
{
2022-05-16 18:00:44 +02:00
$qb = $this -> createQueryBuilder ( 'job' );
2022-05-17 16:06:46 +02:00
return $qb
-> where ( 'job.running != 0' )
-> getQuery () -> getResult ();
2022-05-16 18:00:44 +02:00
}
2022-02-04 14:21:42 +01:00
2022-05-16 18:00:44 +02:00
public function getAllJobs ( bool $idiskey = false )
{
$qb = $this -> createQueryBuilder ( 'job' );
/** @var Job[] $jobs */
$jobs = $qb
-> orderBy ( 'job.name' )
-> where ( 'job.id = job.id' )
-> addOrderBy ( " JSON_VALUE(job.data, ' $ .host') " )
-> addOrderBy ( " JSON_VALUE(job.data, ' $ .service') " )
-> getQuery () -> getResult ();
return $this -> parseJobs ( $jobs );
2022-02-04 14:21:42 +01:00
}
2022-05-16 18:00:44 +02:00
public function parseJobs ( array $jobs ) : array
2021-04-08 12:54:49 +02:00
{
2022-04-27 14:24:48 +02:00
$runRepo = $this -> getEntityManager () -> getRepository ( Run :: class );
2022-05-13 15:24:05 +02:00
2021-04-08 12:54:49 +02:00
foreach ( $jobs as $key =>& $job ) {
2022-05-13 15:24:05 +02:00
$jobData = $job -> getData ();
$job -> addData ( 'host-displayname' , $jobData [ 'host' ]);
$job -> addData ( 'host' , $jobData [ 'host' ]);
$job -> addData ( 'service' , $jobData [ 'service' ] ? ? '' );
$job -> addData ( 'norun' , $job -> getLastrun () !== null && $job -> getNextrun () > $job -> getLastrun ());
$job -> addData ( 'running' , $job -> getRunning () != 0 );
2022-05-16 18:00:44 +02:00
$failedruns = $runRepo -> getRunsForJob ( $job -> getId (), true , $jobData [ 'fail-days' ]);
$failed = count ( $failedruns );
2022-05-13 15:24:05 +02:00
$all = count ( $runRepo -> getRunsForJob ( $job -> getId (), false , $jobData [ 'fail-days' ]));
2022-05-16 18:00:44 +02:00
$job -> addData ( 'lastfail' , $failedruns [ 0 ] ? ? NULL );
2022-05-13 15:24:05 +02:00
$job -> addData ( 'needschecking' , $all > 0 && (( $failed / $all ) * 100 ) > $jobData [ 'fail-pct' ]);
if ( ! empty ( $jobData [ 'containertype' ]) && $jobData [ 'containertype' ] != 'none' ) {
$job -> addData ( 'host-displayname' , $jobData [ 'service' ] . ' on ' . $jobData [ 'host' ]);
2021-05-28 12:25:22 +02:00
}
2021-04-08 12:54:49 +02:00
}
2021-07-20 16:29:03 +02:00
2021-04-08 12:54:49 +02:00
return $jobs ;
}
2021-04-13 14:07:11 +02:00
2021-05-24 18:36:16 +02:00
public function getJobsDue ()
{
2022-05-17 16:06:46 +02:00
$qb = $this -> createQueryBuilder ( 'job' );
return $qb
-> where (
$qb -> expr () -> andX (
$qb -> expr () -> lte ( 'job.nextrun' , ':timestamp' ),
$qb -> expr () -> orX (
$qb -> expr () -> isNull ( 'job.lastrun' ),
$qb -> expr () -> gt ( 'job.lastrun' , ':timestamp' )
),
$qb -> expr () -> in ( 'job.running' , [ 0 , 2 ])
)
)
-> orWhere (
$qb -> expr () -> andX (
$qb -> expr () -> notIn ( 'job.running' , [ 0 , 1 , 2 ]),
$qb -> expr () -> lt ( 'job.running' , ':timestamp' )
)
)
-> orWhere ( 'job.running = 2' )
-> orderBy ( 'job.running' , 'DESC' )
-> addOrderBy ( 'job.nextrun' , 'ASC' )
-> setParameter ( ':timestamp' , time ())
-> getQuery () -> getResult ();
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 " ;
2022-04-27 14:24:48 +02:00
$jobsStmt = $this -> getEntityManager () -> getConnection () -> prepare ( $jobsSql );
2021-10-22 12:16:21 +02:00
$jobsRslt = $jobsStmt -> executeQuery ([ ':time' => time ()]);
$nextjob = $jobsRslt -> fetchAssociative ();
$jobsSql = " SELECT nextrun
FROM job
WHERE running = 2
ORDER BY nextrun
LIMIT 1 " ;
2022-04-27 14:24:48 +02:00
$jobsStmt = $this -> getEntityManager () -> getConnection () -> prepare ( $jobsSql );
2021-10-22 12:16:21 +02:00
$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 " ;
2022-04-27 14:24:48 +02:00
$jobsStmt = $this -> getEntityManager () -> getConnection () -> prepare ( $jobsSql );
2021-10-22 12:16:21 +02:00
$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) " ;
2022-04-27 14:24:48 +02:00
$jobsStmt = $this -> getEntityManager () -> getConnection () -> prepare ( $jobsSql );
2021-05-24 18:36:16 +02:00
$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 " ;
2022-04-27 14:24:48 +02:00
$jobsStmt = $this -> getEntityManager () -> getConnection () -> prepare ( $jobsSql );
2021-05-28 18:19:12 +02:00
$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 " ;
2022-04-27 14:24:48 +02:00
$jobsStmt = $this -> getEntityManager () -> getConnection () -> prepare ( $jobsSql );
2021-05-28 18:19:12 +02:00
$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 " ;
2022-04-27 14:24:48 +02:00
$jobsStmt = $this -> getEntityManager () -> getConnection () -> prepare ( $jobsSql );
2021-05-28 18:19:12 +02:00
$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 " ;
2022-04-27 14:24:48 +02:00
$jobsStmt = $this -> getEntityManager () -> getConnection () -> prepare ( $jobsSql );
2021-05-28 18:19:12 +02:00
$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 " ;
2022-04-27 14:24:48 +02:00
$jobsStmt = $this -> getEntityManager () -> getConnection () -> prepare ( $jobsSql );
2021-05-28 18:19:12 +02:00
$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
}
2022-05-17 16:06:46 +02:00
private function runHttpJob ( Job $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
2022-05-17 16:06:46 +02:00
if ( ! empty ( $job -> getData ( 'vars' ))) {
foreach ( $job -> getData ( 'vars' ) as $key => $var ) {
if ( ! empty ( $job -> getData ( 'basicauth-username' ))) $job -> addData ( 'basicauth-username' , str_replace ( '{' . $key . '}' , $var [ 'value' ], $job -> getData ( 'basicauth-username' )));
$job -> addData ( 'url' , str_replace ( '{' . $key . '}' , $var [ 'value' ], $job -> getData ( '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
2022-05-17 16:06:46 +02:00
$url = $job -> getData ( 'url' );
2021-05-28 09:59:55 +02:00
$options [ 'http_errors' ] = false ;
2022-05-17 16:06:46 +02:00
$options [ 'auth' ] = ! empty ( $job -> getData ( 'basicauth-username' )) ? [ $job -> getData ( 'basicauth-username' ), $job -> getData ( '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 ();
2022-05-17 16:06:46 +02:00
$return [ 'failed' ] = ! in_array ( $return [ 'exitcode' ], $job -> getData ( '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
2022-05-17 16:06:46 +02:00
private function runCommandJob ( Job $job ) : array
2021-05-28 09:59:55 +02:00
{
2022-05-17 16:06:46 +02:00
if ( ! empty ( $job -> getData ( 'vars' ))) {
foreach ( $job -> getData ( 'vars' ) as $key => $var ) {
$job -> addData ( 'command' , str_replace ( '{' . $key . '}' , $var [ 'value' ], $job -> getData ( '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
2022-05-17 16:06:46 +02:00
$command = $job -> getData ( 'command' );
if ( $job -> getData ( 'containertype' ) == 'docker' ) {
$command = $this -> prepareDockerCommand ( $command , $job -> getData ( 'service' ), $job -> getData ( 'container-user' ));
2021-05-28 09:59:55 +02:00
}
2021-09-22 10:26:52 +02:00
try {
2022-05-17 16:06:46 +02:00
if ( $job -> getData ( 'hosttype' ) == 'local' ) {
2021-09-22 10:26:52 +02:00
$return = $this -> runLocalCommand ( $command );
2022-05-17 16:06:46 +02:00
} elseif ( $job -> getData ( 'hosttype' ) == 'ssh' ) {
$return = $this -> runSshCommand ( $command , $job -> getData ( 'host' ), $job -> getData ( 'user' ), $job -> getData ( 'ssh-privkey' ), $job -> getData ( 'privkey-password' ));
2021-09-22 10:26:52 +02:00
}
2022-05-17 16:06:46 +02:00
$return [ 'failed' ] = ! in_array ( $return [ 'exitcode' ], $job -> getData ( 'response' ));
2021-09-22 10:26:52 +02:00
} 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
2022-05-17 16:06:46 +02:00
private function runSshCommand ( string $command , string $host , string $user , ? string $privkey , ? string $password ) : array
2021-05-28 09:59:55 +02:00
{
$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 " ;
2022-04-27 14:24:48 +02:00
$jobsStmt = $this -> getEntityManager () -> getConnection () -> 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
2022-04-27 14:24:48 +02:00
try {
if ( $job [ 'data' ][ 'hosttype' ] == 'local' ) {
$this -> runLocalCommand ( $job [ 'data' ][ 'reboot-command' ]);
} elseif ( $job [ 'data' ][ 'hosttype' ] == 'ssh' ) {
$this -> runSshCommand ( $job [ 'data' ][ 'reboot-command' ], $job [ 'data' ][ 'host' ], $job [ 'data' ][ 'user' ], $job [ 'data' ][ 'ssh-privkey' ] ? ? '' , $job [ 'data' ][ 'privkey-password' ] ? ? '' );
}
} catch ( \RuntimeException $exception ) {
$return [ 'exitcode' ] = $exception -> getCode ();
$return [ 'output' ] = $exception -> getMessage ();
$return [ 'failed' ] = true ;
return $return ;
2021-05-28 09:59:55 +02:00
}
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 " ;
2022-04-27 14:24:48 +02:00
$jobsStmt = $this -> getEntityManager () -> getConnection () -> prepare ( $jobsSql );
2021-05-28 19:16:25 +02:00
$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
}
}
2022-04-27 14:24:48 +02:00
try {
if ( $job [ 'data' ][ 'hosttype' ] == 'local' ) {
$return = $this -> runLocalCommand ( $job [ 'data' ][ 'getservices-command' ]);
} elseif ( $job [ 'data' ][ 'hosttype' ] == 'ssh' ) {
$return = $this -> runSshCommand ( $job [ 'data' ][ 'getservices-command' ], $job [ 'data' ][ 'host' ], $job [ 'data' ][ 'user' ], $job [ 'data' ][ 'ssh-privkey' ] ? ? '' , $job [ 'data' ][ 'privkey-password' ] ? ? '' );
}
} catch ( \RuntimeException $exception ) {
$return [ 'exitcode' ] = $exception -> getCode ();
$return [ 'output' ] = $exception -> getMessage ();
$return [ 'failed' ] = true ;
return $return ;
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 );
2022-04-27 14:24:48 +02:00
$runRepo = $this -> getEntityManager () -> getRepository ( Run :: class );
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) " ;
2022-04-27 14:24:48 +02:00
$jobsStmt = $this -> getEntityManager () -> getConnection () -> prepare ( $jobsSql );
2021-06-01 17:41:10 +02:00
$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' ],
2022-04-27 14:24:48 +02:00
'title' => ! str_contains ( $output [ 'flags' ], RunRepository :: FAILED ) ? 'Cronjob successfully ran' : 'Cronjob failed. Please check output below' ,
'success' => ! str_contains ( $output [ 'flags' ], RunRepository :: FAILED )
2021-06-01 17:41:10 +02:00
];
}
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 );
2022-05-17 16:06:46 +02:00
if ( $job -> getData ( 'crontype' ) == 'http' ) {
2021-05-28 09:59:55 +02:00
$result = $this -> runHttpJob ( $job );
2022-05-17 16:06:46 +02:00
} elseif ( $job -> getData ( 'crontype' ) == 'command' ) {
2021-05-28 09:59:55 +02:00
$result = $this -> runCommandJob ( $job );
2022-05-17 16:06:46 +02:00
} elseif ( $job -> getData ( '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 ) {
2022-04-27 14:24:48 +02:00
$flags [] = RunRepository :: FAILED ;
2021-06-01 13:45:57 +02:00
} else {
2022-04-27 14:24:48 +02:00
$flags [] = RunRepository :: SUCCESS ;
2021-06-01 13:45:57 +02:00
}
2021-05-24 18:36:16 +02:00
2021-06-01 17:41:10 +02:00
if ( $manual === true ) {
2022-04-27 14:24:48 +02:00
$flags [] = RunRepository :: MANUAL ;
2021-06-01 13:45:57 +02:00
}
2021-11-30 13:54:17 +01:00
// Remove secrets from output
2022-05-17 16:06:46 +02:00
if ( ! empty ( $job -> getData ( 'vars' ))) {
foreach ( $job -> getData ( 'vars' ) as $key => $var ) {
2021-12-04 10:25:13 +01:00
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
2022-04-27 14:24:48 +02:00
$this -> getEntityManager () -> getConnection () -> close ();
$runRepo = $this -> getEntityManager () -> getRepository ( Run :: class );
2022-05-17 16:06:46 +02:00
$runRepo -> addRun ( $job -> getId (), $result [ 'exitcode' ], floor ( $starttime ), $runtime , $result [ 'output' ], $flags );
2021-06-01 17:41:10 +02:00
if ( ! $manual ){
// setting nextrun to next run
2022-05-17 16:06:46 +02:00
$nextrun = $job -> getNextrun ();
2021-06-01 17:41:10 +02:00
do {
2022-05-17 16:06:46 +02:00
$nextrun = $nextrun + $job -> getInterval ();
2021-06-01 17:41:10 +02:00
} 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' ;
2022-04-27 14:24:48 +02:00
$addRunStmt = $this -> getEntityManager () -> getConnection () -> prepare ( $addRunSql );
2022-05-17 16:06:46 +02:00
$addRunStmt -> executeQuery ([ ':id' => $job -> getId (), ':nextrun' => $nextrun ]);
2021-06-01 17:41:10 +02:00
}
2022-05-17 16:06:46 +02:00
return [ 'job_id' => $job -> getId (), '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 ;
}
2022-04-27 14:24:48 +02:00
$jobsStmt = $this -> getEntityManager () -> getConnection () -> prepare ( $jobsSql );
2021-05-29 14:20:05 +02:00
$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 ];
2022-04-27 14:24:48 +02:00
return count ( $this -> getEntityManager () -> getConnection () -> prepare ( $jobsSql ) -> executeQuery ( $params ) -> fetchAllAssociative ()) > 0 ;
2021-07-15 13:22:52 +02:00
}
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' ]);
2022-03-21 11:30:30 +01: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
2022-04-27 14:24:48 +02:00
$addJobStmt = $this -> getEntityManager () -> getConnection () -> 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' ]);
2022-03-21 11:30:30 +01: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
2022-04-27 14:24:48 +02:00
$editJobStmt = $this -> getEntityManager () -> getConnection () -> prepare ( $editJobSql );
2021-05-21 13:09:48 +02:00
$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' ];
2022-03-21 11:30:30 +01:00
if ( ! empty ( $values [ 'reboot-delay' ]) || $values [ 'reboot-delay' ] == 0 ) {
2021-05-06 14:30:35 +02:00
$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' ])) {
2022-03-21 11:30:30 +01:00
foreach ( $values [ 'var-value' ] as $key => $value ) {
if ( ! empty ( $value ) || $value == 0 ) {
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 ) {
2022-05-17 16:06:46 +02:00
$job = $this -> find ( $id );
2021-04-13 14:44:58 +02:00
2022-05-17 16:06:46 +02:00
if ( ! empty ( $job -> getData ( 'vars' ))) {
foreach ( $job -> getData ( 'vars' ) as $key => & $value ) {
2021-05-06 13:30:12 +02:00
if ( $value [ 'issecret' ]) {
2022-05-17 16:06:46 +02:00
$job -> addData ( 'vars.' . $key . '.value' , ( $withSecrets ) ? Secret :: decrypt ( base64_decode ( $value [ 'value' ])) : '' );
2021-05-06 13:30:12 +02:00
}
2021-04-13 14:44:58 +02:00
}
}
2022-05-17 16:06:46 +02:00
switch ( $job -> getData ( 'crontype' )) {
2021-05-21 13:09:48 +02:00
case 'http' :
2022-05-17 16:06:46 +02:00
if ( $job -> hasData ( 'vars.basicauth-password.value' )) {
$job -> addData ( 'basicauth-password' , $job -> getData ( 'vars.basicauth-password.value' ));
$job -> removeData ( 'vars.basicauth-password' );
2021-05-21 13:09:48 +02:00
}
break ;
2021-05-24 12:28:47 +02:00
case 'reboot' :
2022-05-17 16:06:46 +02:00
$job -> addData ( 'reboot-delay' , $job -> getData ( 'vars.reboot-delay.value' ));
$job -> addData ( 'reboot-delay-secs' , $job -> getData ( 'vars.reboot-delay-secs.value' ));
$job -> removeData ( 'vars.reboot-delay' );
$job -> removeData ( 'vars.reboot-delay-secs' );
2021-05-24 12:28:47 +02:00
break ;
}
2022-05-17 16:06:46 +02:00
switch ( $job -> getData ( 'hosttype' )) {
2021-05-24 12:28:47 +02:00
case 'ssh' :
2022-05-17 16:06:46 +02:00
if ( $job -> hasData ( 'vars.ssh-privkey.value' )) {
$job -> addData ( 'ssh-privkey' , $job -> getData ( 'vars.ssh-privkey.value' ));
$job -> removeData ( 'vars.ssh-privkey' );
2021-05-24 12:28:47 +02:00
}
2022-05-17 16:06:46 +02:00
if ( $job -> hasData ( 'vars.privkey-password.value' )) {
$job -> addData ( 'privkey-password' , $job -> getData ( 'vars.privkey-password.value' ));
$job -> removeData ( 'vars.privkey-password' );
2021-05-24 12:28:47 +02:00
}
break ;
2021-05-21 13:09:48 +02:00
}
2022-05-17 16:06:46 +02:00
if ( $job -> getData ( 'crontype' ) == 'http' ) {
2021-05-21 13:09:48 +02:00
}
2022-05-17 16:06:46 +02:00
return $job ;
2021-04-13 14:44:58 +02:00
}
2021-05-24 14:08:30 +02:00
public function deleteJob ( int $id )
{
2022-05-18 11:12:35 +02:00
$em = $this -> getEntityManager ();
$job = $this -> find ( $id );
$em -> remove ( $job );
$em -> flush ();
2021-05-24 14:08:30 +02:00
return [ 'success' => true , 'message' => 'Cronjob succesfully deleted' ];
}
2021-04-08 12:54:49 +02:00
}