Skip to content

Commit

Permalink
Allow to publish artifact package via API or composer push plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
vtsykun committed Jan 22, 2025
1 parent d281310 commit 951f0cd
Show file tree
Hide file tree
Showing 19 changed files with 338 additions and 62 deletions.
15 changes: 12 additions & 3 deletions src/Composer/Repository/ArtifactRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@ class ArtifactRepository extends ArrayRepository implements PacketonRepositoryIn
protected $lookup;

/** @var array */
protected $archives;
protected array $archives = [];

protected array $archivesOverwriteInfo = [];

protected array $archivesOverwriteInfoByRef = [];

public function __construct(
protected array $repoConfig,
Expand All @@ -51,6 +55,7 @@ public function __construct(
$this->lookup = $repoConfig['url'] ?? null;
$this->lookup = $this->lookup === '_unset' ? null : $this->lookup;
$this->archives = $this->repoConfig['archives'] ?? [];
$this->archivesOverwriteInfo = $this->repoConfig['archive_overwrite_mapping'] ?? [];

$this->process ??= new ProcessExecutor($this->io);
}
Expand Down Expand Up @@ -136,7 +141,7 @@ public function allArtifacts(): iterable
private function doInitialize(): void
{
foreach ($this->allArtifacts() as $ref => $file) {
$package = $this->getComposerInformation($file, $ref);
$package = $this->getComposerInformation($file, $ref, $this->archivesOverwriteInfoByRef[$ref] ?? null);
if (!$package) {
$this->io->writeError("File <comment>{$file->getBasename()}</comment> doesn't seem to hold a package", true, IOInterface::VERBOSE);
continue;
Expand Down Expand Up @@ -164,6 +169,8 @@ private function scanArchives(array $archives): iterable
continue;
}

$this->archivesOverwriteInfoByRef[$zip->getReference()] = $this->archivesOverwriteInfo[$zip->getId()] ?? null;

yield $zip->getReference() => new \SplFileInfo($path);
}
}
Expand All @@ -190,7 +197,7 @@ private function scanDirectory(string $path): iterable
/**
* {@inheritdoc}
*/
public function getComposerInformation(\SplFileInfo $file, $ref = null): ?BasePackage
public function getComposerInformation(\SplFileInfo $file, mixed $ref = null, ?array $overwrite = null): ?BasePackage
{
$json = null;
$fileExtension = pathinfo($file->getPathname(), PATHINFO_EXTENSION);
Expand All @@ -217,6 +224,8 @@ public function getComposerInformation(\SplFileInfo $file, $ref = null): ?BasePa
}

$package = JsonFile::parseJson($json, $file->getPathname().'#composer.json');
$package = array_merge($package, $overwrite ?? []);

$package['dist'] = [
'type' => $fileType,
'url' => strtr($file->getPathname(), '\\', '/'),
Expand Down
8 changes: 2 additions & 6 deletions src/Controller/PackageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -598,12 +598,8 @@ public function updatePackageAction(Request $req, $name): Response
$doctrine = $this->registry;
$this->checkSubrepositoryAccess($name);

try {
/** @var Package $package */
$package = $doctrine
->getRepository(Package::class)
->getPackageByName($name);
} catch (NoResultException) {
$package = $doctrine->getRepository(Package::class)->getPackageByName($name);
if (null === $package) {
return new JsonResponse(['status' => 'error', 'message' => 'Package not found'], 404);
}

Expand Down
10 changes: 8 additions & 2 deletions src/Controller/PushPackagesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Packeton\Controller;

use Packeton\Exception\ValidationException;
use Packeton\Form\Handler\PushPackageHandler;
use Packeton\Form\Type\Push\NexusPushType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
Expand All @@ -17,13 +18,18 @@
#[Route(defaults: ['_format' => 'json'])]
class PushPackagesController extends AbstractController
{
#[Route('/packages/upload/{name}/{version}', name: 'package_push_nexus', requirements: ['name' => '%package_name_regex%'], methods: ['PUT', 'POST'])]
#[IsGranted('ROLE_MAINTAINER')]
#[Route('/packages/upload/{name}/{version}', name: 'package_push_nexus', requirements: ['name' => '%package_name_regex%'], methods: ['PUT', 'POST'])]
#[Route('/api/packages/upload/{name}/{version}', name: 'package_push_api', requirements: ['name' => '%package_name_regex%'], methods: ['PUT'])]
public function pushNexusAction(PushPackageHandler $handler, Request $request, string $name, string $version): Response
{
$form = $this->createApiForm(NexusPushType::class, options: ['method' => $request->getMethod()]);

$handler($form, $request, $name, $version);
try {
$handler($form, $request, $name, $version, $this->getUser());
} catch (ValidationException $e) {
return new JsonResponse(['title' => $e->getMessage(), 'errors' => $e->getFormErrors()], 400);
}

return new JsonResponse([], 201);
}
Expand Down
12 changes: 9 additions & 3 deletions src/Controller/ZipballController.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Packeton\Entity\Package;
use Packeton\Entity\Zipball;
use Packeton\Event\ZipballEvent;
use Packeton\Exception\ZipballException;
use Packeton\Model\UploadZipballStorage;
use Packeton\Package\RepTypes;
use Packeton\Service\DistManager;
Expand Down Expand Up @@ -40,7 +41,7 @@ public function __construct(
) {
}

#[Route('/archive/upload', name: 'archive_upload', methods: ["POST"])]
#[Route('/archive/upload', name: 'archive_upload', methods: ["POST"], format: 'json')]
#[IsGranted('ROLE_MAINTAINER')]
public function upload(Request $request): Response
{
Expand All @@ -52,8 +53,13 @@ public function upload(Request $request): Response
return new JsonResponse(['error' => 'File is empty'], 400);
}

$result = $this->storage->save($file);
return new JsonResponse($result, $result['code'] ?? 201);
try {
$result = $this->storage->save($file);
} catch (ZipballException $e) {
return new JsonResponse(['error' => $e->getMessage()], max($e->getCode(), 300));
}

return new JsonResponse(['id' => $result->getId(), 'filename' => $result->getFilename(), 'size' => $result->getFileSize()], 201);
}

#[Route('/archive/remove/{id}', name: 'archive_remove', methods: ["DELETE"])]
Expand Down
1 change: 1 addition & 0 deletions src/Entity/Package.php
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,7 @@ public function getRepoConfig(): array
'repoType' => $this->repoType ?: 'vcs',
'subDirectory' => $this->getSubDirectory(),
'archives' => $this->getArchives(),
'archive_overwrite_mapping' => $this->getArchiveOverwrite(),
'oauth2' => $this->integration,
'externalRef' => $this->externalRef,
'customVersions' => $this->getCustomVersions(),
Expand Down
44 changes: 43 additions & 1 deletion src/Entity/PackageSerializedTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Composer\Package\Version\VersionParser;
use Composer\Semver\Constraint\Constraint;
use Packeton\Util\PacketonUtils;

trait PackageSerializedTrait
{
Expand Down Expand Up @@ -76,7 +77,7 @@ public function getArchives(): ?array
return $this->serializedFields['archives'] ?? null;
}

public function getAllArchives(): ?array
public function getAllArchives(): array
{
$archives = $this->serializedFields['archives'] ?? [];
foreach ($this->getCustomVersions() as $version) {
Expand All @@ -102,6 +103,38 @@ public function setArchives(?array $archives): void
$this->setSerialized('archives', $archives);
}

public function setArchiveOverwrite(int $archiveId, array $versionData): void
{
$mapping = $this->getSerialized('archive_version_mapping', 'array') ?? [];
$archives = $this->getArchives();
$version = $versionData['version'] ?? null;

$unset = [];
foreach ($mapping as $id => $data) {
if (($data['version'] ?? '_na') === $version) {
$unset[] = $id;
}
}

$archives[] = $archiveId;
$archives = array_diff($archives, $unset);
$this->setArchives(array_values(array_unique($archives)));

$mapping[$archiveId] = $versionData;
foreach ($mapping as $id => $data) {
if (!in_array($id, $archives)) {
unset($mapping[$id]);
}
}

$this->setSerialized('archive_version_mapping', $mapping);
}

public function getArchiveOverwrite(): array
{
return $this->getSerialized('archive_version_mapping', 'array') ?? [];
}

public function setUpdateFlags(int $flags): void
{
$this->setSerialized('update_flags', $flags);
Expand Down Expand Up @@ -199,6 +232,15 @@ public function setCustomVersions($versions): void
$this->setSerialized('custom_versions', $versions);
}

public function addCustomVersions(array $versionData): void
{
$versions = $this->getCustomVersions();
$versions = PacketonUtils::buildChoices($versions, 'version');
$versions[$versionData['version']] = $versionData;

$this->setCustomVersions(array_values($versions));
}

public function getCustomComposerJson(): array
{
return $this->getSerialized('custom_composer_json', 'array', []);
Expand Down
44 changes: 44 additions & 0 deletions src/Exception/ValidationException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

namespace Packeton\Exception;

use Symfony\Component\Form\FormInterface;

class ValidationException extends \RuntimeException implements DebugHttpExceptionInterface
{
private array $errors = [];

public static function create(string $message, FormInterface|array $errors = [], ?\Throwable $previous = null): static
{
$exception = new static($message, 400, $previous);
$exception->errors = $errors instanceof FormInterface ? $exception->getErrors($errors) : $errors;

return $exception;
}

private function getErrors(FormInterface $form): array
{
$errors = $base = [];

foreach ($form->getErrors() as $error) {
$base[] = $error->getMessage();
}
foreach ($form as $child) {
foreach ($child->getErrors(true) as $error) {
$errors[$child->getName()] = $error->getMessage();
}
}
if (count($base) > 0) {
$errors['root'] = implode("\n", $base);
}

return $errors;
}

public function getFormErrors(): array
{
return $this->errors;
}
}
9 changes: 9 additions & 0 deletions src/Exception/ZipballException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace Packeton\Exception;

class ZipballException extends \RuntimeException implements DebugHttpExceptionInterface
{
}
24 changes: 18 additions & 6 deletions src/Form/Handler/PushPackageHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
use Doctrine\Persistence\ManagerRegistry;
use Packeton\Entity\Package;
use Packeton\Entity\User;
use Packeton\Exception\ValidationException;
use Packeton\Exception\ZipballException;
use Packeton\Form\Model\PushRequestDtoInterface;
use Packeton\Model\PackageManager;
use Packeton\Package\RepTypes;
Expand All @@ -27,20 +29,25 @@ public function __construct(

public function __invoke(FormInterface $form, Request $request, string $name, ?string $version = null, ?UserInterface $user = null): void
{
// todo fix PUT support
$request->request->set('version', $version ?? 'dev-master');

$form->handleRequest($request);
if (!$form->isSubmitted() || !$form->isValid()) {
throw new \RuntimeException('todo');
throw ValidationException::create("Validation errors", $form);
}

/** @var PushRequestDtoInterface $artifact */
$artifact = $form->getData();
/** @var PushRequestDtoInterface $dtoRequest */
$dtoRequest = $form->getData();
$package = $this->getRepo()->getPackageByName($name);
if (null === $package) {
$package = $this->createArtifactPackage($name, $user);
}


try {
$this->handler->addVersion($dtoRequest, $package);
} catch (ZipballException $e) {
throw ValidationException::create($e->getMessage(), previous: $e);
}
}

private function getRepo(): PackageRepository
Expand All @@ -53,7 +60,12 @@ private function createArtifactPackage(string $name, ?UserInterface $user = null
$em = $this->registry->getManager();
$package = new Package();
$package->setName($name);
$package->setRepoType(RepTypes::ARTIFACT);
$package->setRepoType(RepTypes::CUSTOM);
$package->setRepositoryPath(null);

$user = null !== $user ?
$this->registry->getRepository(User::class)->findOneBy(['username' => $user->getUserIdentifier()])
: $user;

if ($user instanceof User) {
$package->addMaintainer($user);
Expand Down
13 changes: 13 additions & 0 deletions src/Form/Model/NexusPushRequestDto.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,17 @@ public function getPackageVersion(): string
{
return $this->version;
}

public function getSource(): ?array
{
if ($this->srcRef !== null && $this->srcUrl !== null) {
return [
'type' => $this->srcType ?? 'git',
'url' => $this->srcUrl,
'reference' => $this->srcRef,
];
}

return null;
}
}
2 changes: 2 additions & 0 deletions src/Form/Model/PushRequestDtoInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ public function getArtifact(): File;
public function getPackageName(): string;

public function getPackageVersion(): string;

public function getSource(): ?array;
}
Loading

0 comments on commit 951f0cd

Please sign in to comment.