Skip to content

Commit d8e690e

Browse files
authored
Merge pull request #13 from joomla-projects/feature/available-versions
Added service to fetch available versions from TUF #11
2 parents b10410e + 2e37632 commit d8e690e

19 files changed

+944
-22
lines changed

.env.example

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,5 +64,9 @@ AWS_USE_PATH_STYLE_ENDPOINT=false
6464

6565
VITE_APP_NAME="${APP_NAME}"
6666

67+
# Health interval in hours
6768
HEALTH_CHECK_INTERVAL=24
69+
# Orphan site storage period in days
6870
CLEANUP_SITE_DELAY=7
71+
# Cache time in minutes
72+
TUF_REPO_CACHETIME=5

app/Http/Controllers/Api/V1/SiteController.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
namespace App\Http\Controllers\Api\V1;
44

55
use App\Http\Controllers\Controller;
6+
use App\Http\Traits\ApiResponse;
67
use App\Jobs\CheckSiteHealth;
78
use App\Models\Site;
89
use App\RemoteSite\Connection;
9-
use App\Traits\ApiResponse;
1010
use Carbon\Carbon;
1111
use GuzzleHttp\Exception\ClientException;
1212
use GuzzleHttp\Exception\ServerException;

app/Traits/ApiResponse.php renamed to app/Http/Traits/ApiResponse.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?php
22

3-
namespace App\Traits;
3+
namespace App\Http\Traits;
44

55
use Illuminate\Http\JsonResponse;
66

app/Models/TufMetadata.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Models;
6+
7+
use Illuminate\Database\Eloquent\Model;
8+
9+
class TufMetadata extends Model
10+
{
11+
public $timestamps = false;
12+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
namespace App\Providers;
4+
5+
use App\Models\TufMetadata;
6+
use App\TUF\EloquentModelStorage;
7+
use App\TUF\HttpLoader;
8+
use GuzzleHttp\Client;
9+
use Illuminate\Support\Facades\App;
10+
use Illuminate\Support\ServiceProvider;
11+
use Tuf\Client\Updater;
12+
use Tuf\Loader\SizeCheckingLoader;
13+
use Tuf\Metadata\StorageInterface;
14+
15+
class TUFServiceProvider extends ServiceProvider
16+
{
17+
public const REPO_PATH = "https://update.joomla.org/cms/";
18+
19+
/**
20+
* Register services.
21+
*
22+
* @return void
23+
*/
24+
public function register()
25+
{
26+
$this->app->singleton(StorageInterface::class, function ($app) {
27+
// Setup loader
28+
$httpLoader = new HttpLoader(
29+
self::REPO_PATH,
30+
App::make(Client::class)
31+
);
32+
33+
$sizeCheckingLoader = new SizeCheckingLoader($httpLoader);
34+
35+
// Setup storage
36+
$storage = new EloquentModelStorage(TufMetadata::findOrFail(1));
37+
38+
// Create updater
39+
$updater = new Updater(
40+
$sizeCheckingLoader,
41+
$storage
42+
);
43+
44+
// Fetch Updates
45+
$updater->refresh();
46+
47+
$storage->persist();
48+
49+
return $storage;
50+
});
51+
}
52+
}

app/RemoteSite/Connection.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@
55
namespace App\RemoteSite;
66

77
use App\Enum\HttpMethod;
8-
use App\Enum\WebserviceEndpoint;
98
use App\RemoteSite\Responses\FinalizeUpdate as FinalizeUpdateResponse;
10-
use App\RemoteSite\Responses\HealthCheck as HealthCheckResponse;
119
use App\RemoteSite\Responses\GetUpdate as GetUpdateResponse;
10+
use App\RemoteSite\Responses\HealthCheck as HealthCheckResponse;
1211
use App\RemoteSite\Responses\PrepareUpdate as PrepareUpdateResponse;
1312
use App\RemoteSite\Responses\ResponseInterface;
1413
use GuzzleHttp\Client;

app/Enum/WebserviceEndpoint.php renamed to app/RemoteSite/WebserviceEndpoint.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
<?php
22

3-
namespace App\Enum;
3+
namespace App\RemoteSite;
44

5+
use App\Enum\HttpMethod;
56
use App\RemoteSite\Responses\FinalizeUpdate;
67
use App\RemoteSite\Responses\GetUpdate;
78
use App\RemoteSite\Responses\HealthCheck;

app/TUF/EloquentModelStorage.php

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
namespace App\TUF;
4+
5+
use App\Models\TufMetadata;
6+
use Tuf\Metadata\StorageBase;
7+
8+
class EloquentModelStorage extends StorageBase
9+
{
10+
public const METADATA_COLUMNS = ['root', 'targets', 'snapshot', 'timestamp', 'mirrors'];
11+
12+
protected TufMetadata $model;
13+
14+
/**
15+
* @var array<string, string>
16+
*/
17+
protected array $container = [];
18+
19+
public function __construct(TufMetadata $model)
20+
{
21+
$this->model = $model;
22+
23+
foreach (self::METADATA_COLUMNS as $column) {
24+
if ($this->model->$column === null) {
25+
continue;
26+
}
27+
28+
$this->write($column, $this->model->$column);
29+
}
30+
}
31+
32+
public function read(string $name): ?string
33+
{
34+
return $this->container[$name] ?? null;
35+
}
36+
37+
public function write(string $name, string $data): void
38+
{
39+
$this->container[$name] = $data;
40+
}
41+
42+
public function delete(string $name): void
43+
{
44+
unset($this->container[$name]);
45+
}
46+
47+
public function persist(): bool
48+
{
49+
foreach (self::METADATA_COLUMNS as $column) {
50+
if (!\array_key_exists($column, $this->container)) {
51+
continue;
52+
}
53+
54+
$this->model->$column = $this->container[$column];
55+
}
56+
57+
return $this->model->save();
58+
}
59+
}

app/TUF/HttpLoader.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
namespace App\TUF;
4+
5+
use GuzzleHttp\Client;
6+
use GuzzleHttp\Exception\RequestException;
7+
use GuzzleHttp\Promise\Create;
8+
use GuzzleHttp\Promise\PromiseInterface;
9+
use Tuf\Exception\RepoFileNotFound;
10+
use Tuf\Loader\LoaderInterface;
11+
12+
class HttpLoader implements LoaderInterface
13+
{
14+
public function __construct(private readonly string $repositoryPath, private readonly Client $http)
15+
{
16+
}
17+
18+
public function load(string $locator, int $maxBytes): PromiseInterface
19+
{
20+
try {
21+
$response = $this->http->get($this->repositoryPath . $locator);
22+
} catch (RequestException $e) {
23+
if ($e->getResponse()?->getStatusCode() !== 200) {
24+
throw new RepoFileNotFound();
25+
}
26+
27+
throw new HttpLoaderException($e->getMessage(), $e->getCode(), $e);
28+
}
29+
30+
// Rewind to start
31+
$response->getBody()->rewind();
32+
33+
// Return response
34+
return Create::promiseFor($response->getBody());
35+
}
36+
}

app/TUF/HttpLoaderException.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
namespace App\TUF;
4+
5+
use Tuf\Exception\TufException;
6+
7+
class HttpLoaderException extends TufException
8+
{
9+
}

0 commit comments

Comments
 (0)