Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion app/Models/Egg.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,16 @@ class Egg extends Model implements Identifiable
*/
public const RESOURCE_NAME = 'egg';

/**
* Every egg version the panel can import sorted by newest first.
* Add to this list to release a new egg version.
*/
public const VERSIONS = ['RCYL_v26', 'PTDL_v2', 'PTDL_v1'];

/**
* Defines the current egg export version.
*/
public const EXPORT_VERSION = 'RCYL_v26';
public const EXPORT_VERSION = self::VERSIONS[0];

/**
* Different features that can be enabled on any given egg. These are used internally
Expand Down
6 changes: 3 additions & 3 deletions app/Services/Eggs/EggParserService.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public function handle(UploadedFile $file): array

/** @var array $parsed */
$parsed = json_decode($file->openFile()->fread($file->getSize()), true, 512, JSON_THROW_ON_ERROR);
if (! in_array(Arr::get($parsed, 'meta.version') ?? '', ['PTDL_v1', 'PTDL_v2', 'RCYL_v26'])) {
if (! in_array(Arr::get($parsed, 'meta.version') ?? '', Egg::VERSIONS)) {
throw new InvalidFileUploadException('The JSON file provided is not in a format that can be recognized.');
}

Expand Down Expand Up @@ -56,13 +56,13 @@ public function fillFromParsed(Egg $model, array $parsed): Egg
}

/**
* Converts a PTDL_V1 egg into the expected PTDL_V2 egg format. This just handles
* Converts a PTDL_v1 egg into the expected modern egg format. This just handles
* the "docker_images" field potentially not being present, and not being in the
* expected "key => value" format.
*/
protected function convertToV2(array $parsed): array
{
if (Arr::get($parsed, 'meta.version') === Egg::EXPORT_VERSION) {
if (Arr::get($parsed, 'meta.version') !== 'PTDL_v1') {
return $parsed;
}

Expand Down
135 changes: 135 additions & 0 deletions tests/Unit/Services/Eggs/EggParserServiceTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
<?php

namespace Tests\Unit\Services\Eggs;

use App\Exceptions\Service\InvalidFileUploadException;
use App\Models\Egg;
use App\Services\Eggs\EggParserService;
use Illuminate\Http\UploadedFile;
use Tests\TestCase;

class EggParserServiceTest extends TestCase
{
private EggParserService $service;

protected function setUp(): void
{
parent::setUp();
$this->service = new EggParserService();
}

public function test_it_keeps_the_docker_images_of_a_ptdl_v2_egg()
{
$parsed = $this->parse([
'meta' => ['version' => 'PTDL_v2', 'update_url' => null],
'docker_images' => [
'Python 3.12' => 'ghcr.io/parkervcp/yolks:python_3.12',
'Python 3.11' => 'ghcr.io/parkervcp/yolks:python_3.11',
],
]);

$this->assertEquals([
'Python 3.12' => 'ghcr.io/parkervcp/yolks:python_3.12',
'Python 3.11' => 'ghcr.io/parkervcp/yolks:python_3.11',
], $parsed['docker_images']);
}

public function test_it_keeps_the_docker_images_of_a_current_export()
{
$parsed = $this->parse([
'meta' => ['version' => Egg::EXPORT_VERSION, 'update_url' => null],
'docker_images' => ['Python 3.12' => 'ghcr.io/parkervcp/yolks:python_3.12'],
]);

$this->assertEquals(['Python 3.12' => 'ghcr.io/parkervcp/yolks:python_3.12'], $parsed['docker_images']);
}

public function test_it_converts_the_single_image_of_a_ptdl_v1_egg()
{
$parsed = $this->parse([
'meta' => ['version' => 'PTDL_v1', 'update_url' => null],
'image' => 'quay.io/pterodactyl/core:python',
]);

$this->assertEquals([
'quay.io/pterodactyl/core:python' => 'quay.io/pterodactyl/core:python',
], $parsed['docker_images']);
$this->assertArrayNotHasKey('image', $parsed);
}

public function test_it_converts_the_image_list_of_a_ptdl_v1_egg()
{
$parsed = $this->parse([
'meta' => ['version' => 'PTDL_v1', 'update_url' => null],
'images' => ['quay.io/pterodactyl/core:python', 'quay.io/pterodactyl/core:python-3.8'],
]);

$this->assertEquals([
'quay.io/pterodactyl/core:python' => 'quay.io/pterodactyl/core:python',
'quay.io/pterodactyl/core:python-3.8' => 'quay.io/pterodactyl/core:python-3.8',
], $parsed['docker_images']);
$this->assertArrayNotHasKey('images', $parsed);
}

public function test_it_falls_back_to_nil_for_a_ptdl_v1_egg_without_an_image()
{
$parsed = $this->parse(['meta' => ['version' => 'PTDL_v1', 'update_url' => null]]);

$this->assertEquals(['nil' => 'nil'], $parsed['docker_images']);
}

public function test_it_rejects_an_unknown_egg_version()
{
$this->expectException(InvalidFileUploadException::class);

$this->parse(['meta' => ['version' => 'PTDL_v9', 'update_url' => null]]);
}

public function test_it_can_always_import_the_version_it_exports()
{
$this->assertContains(Egg::EXPORT_VERSION, Egg::VERSIONS);
}

public function test_it_can_still_import_every_previous_egg_version()
{
foreach (['PTDL_v1', 'PTDL_v2', 'RCYL_v26'] as $version) {
$this->assertContains($version, Egg::VERSIONS);
}
}

/**
* Parses an egg, filling in the fields that every version shares.
*
* @throws InvalidFileUploadException|\JsonException
*/
private function parse(array $egg): array
{
$path = tempnam(sys_get_temp_dir(), 'egg').'.json';
file_put_contents($path, json_encode(array_merge([
'exported_at' => '2024-04-02T14:11:47+02:00',
'name' => 'python generic',
'author' => 'parker@parkervcp.com',
'description' => 'A Generic Python Egg',
'features' => null,
'file_denylist' => [],
'startup' => '/usr/local/bin/python /home/container/{{PY_FILE}}',
'config' => ['files' => '{}', 'startup' => '{}', 'logs' => '{}', 'stop' => '^C'],
'scripts' => ['installation' => ['script' => 'exit 0', 'container' => 'python:3.8-slim-bookworm', 'entrypoint' => 'bash']],
'variables' => [[
'name' => 'App py file',
'description' => 'The file that starts the App.',
'env_variable' => 'PY_FILE',
'default_value' => 'app.py',
'user_viewable' => true,
'user_editable' => true,
'rules' => 'required|string',
]],
], $egg), JSON_THROW_ON_ERROR));

try {
return $this->service->handle(new UploadedFile($path, 'egg.json', 'application/json', null, true));
} finally {
@unlink($path);
}
}
}
Loading