Skip to content

Commit 76b343d

Browse files
Merge pull request #2 from stackkit/development
Development
2 parents de4f2a0 + d9e2de9 commit 76b343d

File tree

8 files changed

+175
-41
lines changed

8 files changed

+175
-41
lines changed

README.md

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
<h3 style="color:red">This is a WIP package</h3>
2-
31
<p align="center">
42
<img src="/logo.png" width="400">
53
</p>
@@ -19,6 +17,10 @@ It only supports Artisan commands at this time due to security concerns.
1917

2018
Cloud Tasks will make a HTTP call to your application. This package adds an endpoint to your application that accepts the HTTP call and its payload (an Artisan command) and executes the command.
2119

20+
#### withoutOverlapping, before, after, onSuccess, thenPing, etc
21+
22+
All these features are supported. This package scans your console kernel (`app/Console/Kernel.php`) to see if the scheduled command in Cloud Scheduler is also scheduled in the console kernel, If it is, it will respect all configured events/hooks associated with the command. (such as withoutOverlapping)
23+
2224
# Requirements
2325

2426
This package requires Laravel 5.6 or higher.
@@ -59,10 +61,6 @@ These are the most important settings:
5961

6062
<img src="/example.png">
6163

62-
# Planned features
63-
64-
Laravel's console kernel allows to execute actions after the command has finished running (such as ping a service) and prevent overlapping. This package does not support those features at this time. The goal is to eventually read the configured settings for a command (as defined in the console kernel) and execute the command exacty as defined there so that methods like `thenPing` and `withoutOverlapping` work out of the box without the need to configure anything.
65-
6664
# Security
6765

6866
The job handler requires each request to have an OpenID token. Cloud Scheduler will generate an OpenID token and send it along with the job payload to the handler.

composer.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
},
1818
"require-dev": {
1919
"mockery/mockery": "^1.2",
20-
"symfony/console": "^4.4|^5.0"
20+
"orchestra/testbench": "^3.5 || ^3.6 || ^3.7 || ^3.8 || ^4.0 || ^5.0",
21+
"symfony/console": "^4.4|^5.0",
22+
"timacdonald/log-fake": "^1.6"
2123
},
2224
"autoload": {
2325
"psr-4": {

src/TaskHandler.php

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
namespace Stackkit\LaravelGoogleCloudScheduler;
44

5+
use Illuminate\Console\Scheduling\Schedule;
6+
use Illuminate\Container\Container;
7+
use Illuminate\Contracts\Console\Kernel;
58
use Illuminate\Http\Request;
69
use Illuminate\Support\Facades\Artisan;
710

@@ -10,12 +13,24 @@ class TaskHandler
1013
private $command;
1114
private $request;
1215
private $openId;
16+
private $kernel;
17+
private $schedule;
18+
private $container;
1319

14-
public function __construct(Command $command, Request $request, OpenIdVerificator $openId)
15-
{
20+
public function __construct(
21+
Command $command,
22+
Request $request,
23+
OpenIdVerificator $openId,
24+
Kernel $kernel,
25+
Schedule $schedule,
26+
Container $container
27+
) {
1628
$this->command = $command;
1729
$this->request = $request;
1830
$this->openId = $openId;
31+
$this->kernel = $kernel;
32+
$this->schedule = $schedule;
33+
$this->container = $container;
1934
}
2035

2136
/**
@@ -27,9 +42,7 @@ public function handle()
2742

2843
set_time_limit(0);
2944

30-
Artisan::call($this->command->captureWithoutArtisan());
31-
32-
$output = Artisan::output();
45+
$output = $this->runCommand($this->command->captureWithoutArtisan());
3346

3447
return $this->cleanOutput($output);
3548
}
@@ -50,6 +63,54 @@ private function authorizeRequest()
5063
$this->openId->guardAgainstInvalidOpenIdToken($decodedToken);
5164
}
5265

66+
private function runCommand($command)
67+
{
68+
if ($this->isScheduledCommand($command)) {
69+
$scheduledCommand = $this->getScheduledCommand($command);
70+
71+
if ($scheduledCommand->withoutOverlapping && ! $scheduledCommand->mutex->create($scheduledCommand)) {
72+
return null;
73+
}
74+
75+
$scheduledCommand->callBeforeCallbacks($this->container);
76+
77+
Artisan::call($command);
78+
79+
$scheduledCommand->callAfterCallbacks($this->container);
80+
} else {
81+
Artisan::call($command);
82+
}
83+
84+
return Artisan::output();
85+
}
86+
87+
private function isScheduledCommand($command)
88+
{
89+
return !is_null($this->getScheduledCommand($command));
90+
}
91+
92+
private function getScheduledCommand($command)
93+
{
94+
$events = $this->schedule->events();
95+
96+
foreach ($events as $event) {
97+
$eventCommand = $this->commandWithoutArtisan($event->command);
98+
99+
if ($command === $eventCommand) {
100+
return $event;
101+
}
102+
}
103+
104+
return null;
105+
}
106+
107+
private function commandWithoutArtisan($command)
108+
{
109+
$parts = explode('artisan', $command);
110+
111+
return substr($parts[1], 2, strlen($parts[1]));
112+
}
113+
53114
private function cleanOutput($output)
54115
{
55116
return trim($output);

tests/Support/Kernel.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
namespace Tests\Support;
4+
5+
use Illuminate\Console\Scheduling\Schedule;
6+
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
7+
use Illuminate\Support\Facades\Log;
8+
9+
class Kernel extends ConsoleKernel
10+
{
11+
protected $commands = [
12+
TestCommand::class,
13+
TestCommand2::class,
14+
];
15+
16+
protected function schedule(Schedule $schedule)
17+
{
18+
$schedule->command('test:command')->withoutOverlapping();
19+
$schedule->command('test:command2')->before(function () {
20+
Log::info('log after');
21+
})->after(function () {
22+
Log::warning('log before');
23+
});
24+
}
25+
}

tests/Support/TestCommand.php

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,40 +3,15 @@
33
namespace Tests\Support;
44

55
use Illuminate\Console\Command;
6+
use Illuminate\Support\Facades\Log;
67

78
class TestCommand extends Command
89
{
9-
/**
10-
* The name and signature of the console command.
11-
*
12-
* @var string
13-
*/
1410
protected $signature = 'test:command';
15-
16-
/**
17-
* The console command description.
18-
*
19-
* @var string
20-
*/
2111
protected $description = 'Do some testy stuff';
2212

23-
/**
24-
* Create a new command instance.
25-
*
26-
* @return void
27-
*/
28-
public function __construct()
29-
{
30-
parent::__construct();
31-
}
32-
33-
/**
34-
* Execute the console command.
35-
*
36-
* @return mixed
37-
*/
3813
public function handle()
3914
{
40-
logger('did something testy');
15+
Log::debug('did something testy');
4116
}
4217
}

tests/Support/TestCommand2.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace Tests\Support;
4+
5+
use Illuminate\Console\Command;
6+
use Illuminate\Support\Facades\Log;
7+
8+
class TestCommand2 extends Command
9+
{
10+
protected $signature = 'test:command2';
11+
12+
protected $description = 'Do some testy stuff';
13+
14+
public function handle()
15+
{
16+
Log::debug('did something testy');
17+
}
18+
}

tests/TaskHandlerTest.php

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,18 @@
33
namespace Tests;
44

55
use Firebase\JWT\JWT;
6+
use Illuminate\Console\Application as ConsoleApplication;
67
use Illuminate\Console\Scheduling\Schedule;
8+
use Illuminate\Container\Container;
9+
use Illuminate\Contracts\Console\Kernel;
710
use Illuminate\Http\Request;
811
use Illuminate\Support\Facades\Log;
912
use Stackkit\LaravelGoogleCloudScheduler\CloudSchedulerException;
1013
use Stackkit\LaravelGoogleCloudScheduler\Command;
1114
use Stackkit\LaravelGoogleCloudScheduler\OpenIdVerificator;
1215
use Stackkit\LaravelGoogleCloudScheduler\TaskHandler;
1316
use Throwable;
17+
use TiMacDonald\Log\LogFake;
1418

1519
class TaskHandlerTest extends TestCase
1620
{
@@ -35,7 +39,9 @@ public function setUp(): void
3539
$this->fakeCommand,
3640
$this->request,
3741
$this->openId,
38-
app(JWT::class)
42+
app(Kernel::class),
43+
app(Schedule::class),
44+
Container::getInstance()
3945
);
4046
}
4147

@@ -132,4 +138,46 @@ public function the_aud_claim_must_be_the_same_as_the_app_id()
132138

133139
$this->taskHandler->handle();
134140
}
141+
142+
/** @test */
143+
public function it_prevents_overlapping_if_the_command_is_scheduled_without_overlapping()
144+
{
145+
$this->fakeCommand->shouldReceive('capture')->andReturn('test:command');
146+
$this->openId->shouldReceive('guardAgainstInvalidOpenIdToken')->andReturnNull();
147+
$this->openId->shouldReceive('decodeToken')->andReturnNull();
148+
149+
cache()->clear();
150+
151+
Log::shouldReceive('debug')->twice();
152+
153+
$this->taskHandler->handle();
154+
155+
$expression = '* * * * *';
156+
$command = ConsoleApplication::formatCommandString('test:command');
157+
$mutex = 'framework'.DIRECTORY_SEPARATOR.'schedule-'.sha1($expression.$command);
158+
159+
cache()->add($mutex, true, 60);
160+
161+
$this->taskHandler->handle();
162+
163+
cache()->delete($mutex);
164+
165+
$this->taskHandler->handle();
166+
}
167+
168+
/** @test */
169+
public function it_runs_the_before_and_after_callbacks()
170+
{
171+
$this->fakeCommand->shouldReceive('capture')->andReturn('test:command2');
172+
$this->openId->shouldReceive('guardAgainstInvalidOpenIdToken')->andReturnNull();
173+
$this->openId->shouldReceive('decodeToken')->andReturnNull();
174+
175+
Log::swap(new LogFake());
176+
177+
$this->taskHandler->handle();
178+
179+
Log::assertLoggedMessage('info', 'log after');
180+
Log::assertLoggedMessage('warning', 'log before');
181+
Log::assertLoggedMessage('debug', 'did something testy');
182+
}
135183
}

tests/TestCase.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
namespace Tests;
44

5+
use Illuminate\Console\Scheduling\Schedule;
6+
use Tests\Support\Kernel;
7+
use Tests\Support\TestCommand;
8+
59
class TestCase extends \Orchestra\Testbench\TestCase
610
{
711
/**
@@ -29,6 +33,9 @@ protected function getPackageProviders($app)
2933
*/
3034
protected function getEnvironmentSetUp($app)
3135
{
32-
//
36+
$app->singleton(
37+
\Illuminate\Contracts\Console\Kernel::class,
38+
Kernel::class
39+
);
3340
}
3441
}

0 commit comments

Comments
 (0)