diff --git a/bootstrap-definitions.php b/bootstrap-definitions.php index 5a5d72ffcc4441a119dc29d7c32d9d0a4bb77df4..40adf680d89666f0fcf242d7920a3b4c6403996e 100644 --- a/bootstrap-definitions.php +++ b/bootstrap-definitions.php @@ -1,9 +1,14 @@ <?php + +use GuzzleHttp\Handler\CurlHandler; +use GuzzleHttp\HandlerStack; +use GuzzleHttp\Psr7\Utils; use Http\Factory\Guzzle\RequestFactory; use Http\Factory\Guzzle\StreamFactory; use Http\Factory\Guzzle\UriFactory; use Psr\Container\ContainerInterface; use Psr\Http\Client\ClientInterface; +use Psr\Http\Message\ResponseInterface; use TracToGitlab\BytistConnector; return [ @@ -11,7 +16,26 @@ return [ return new BytistConnector(Config::get()->getValue('TRAC2GITLAB_BYTIST_TOKEN')); }, ClientInterface::class => function () { - return new GuzzleHttp\Client(); + $handlerStack = HandlerStack::create(); + $handlerStack->setHandler(new CurlHandler()); + + // Rewrite faulty response from gitlab + $handlerStack->push(GuzzleHttp\Middleware::mapResponse(function (ResponseInterface $response) { + if ( + $response->getStatusCode() === 200 + && ((string) $response->getBody()) === '200' + && $response->getHeaderLine('Content-Type') === 'application/json' + ) { + return $response + ->withoutHeader('Content-Type') + ->withBody(Utils::streamFor('')); + } + + return $response; + })); + return new GuzzleHttp\Client([ + 'handler' => $handlerStack, + ]); }, Gitlab\Client::class => function (ContainerInterface $container) { $builder = new Gitlab\HttpClient\Builder( diff --git a/controllers/admin.php b/controllers/admin.php index f63d9e3748ee4948dad6ec1a9e5cb013813d180d..d781830c25e83e91fbbcd9dfc373dcaf3894760d 100644 --- a/controllers/admin.php +++ b/controllers/admin.php @@ -18,6 +18,7 @@ final class AdminController extends \TracToGitlab\Controller $this->gitlab_url = Config::get()->TRAC2GITLAB_GITLAB_URL; $this->gitlab_token = Config::get()->TRAC2GITLAB_GITLAB_TOKEN; $this->gitlab_project_id = Config::get()->TRAC2GITLAB_GITLAB_PROJECT_ID; + $this->gitlab_container_registry_id = Config::get()->TRAC2GITLAB_GITLAB_CONTAINER_REGISTRY_ID; $this->bytist_token = Config::get()->TRAC2GITLAB_BYTIST_TOKEN; $this->webhook_secret = Config::get()->TRAC2GITLAB_GITLAB_WEBHOOK_SECRET; $this->systemhook_secret = Config::get()->TRAC2GITLAB_GITLAB_SYSTEMHOOK_SECRET; @@ -32,6 +33,7 @@ final class AdminController extends \TracToGitlab\Controller Config::get()->store('TRAC2GITLAB_GITLAB_URL', Request::get('gitlab-url')); Config::get()->store('TRAC2GITLAB_GITLAB_TOKEN', Request::get('gitlab-token')); Config::get()->store('TRAC2GITLAB_GITLAB_PROJECT_ID', Request::int('gitlab-project-id')); + Config::get()->store('TRAC2GITLAB_GITLAB_CONTAINER_REGISTRY_ID', Request::int('gitlab-container-registry-id')); Config::get()->store('TRAC2GITLAB_BYTIST_TOKEN', Request::get('bytist-token')); Config::get()->store('TRAC2GITLAB_GITLAB_WEBHOOK_SECRET', Request::get('webhook-secret')); Config::get()->store('TRAC2GITLAB_GITLAB_SYSTEMHOOK_SECRET', Request::get('systemhook-secret')); diff --git a/lib/EventHandlers/BranchDeleted.php b/lib/EventHandlers/BranchDeleted.php index e9f0f95c904410e72199179d12f12e3284627ef0..538c8ca2342cd68db45fa9a5829cbb52846d64fb 100644 --- a/lib/EventHandlers/BranchDeleted.php +++ b/lib/EventHandlers/BranchDeleted.php @@ -1,6 +1,8 @@ <?php namespace TracToGitlab\EventHandlers; +use Config; +use Gitlab; use TracToGitlab\BytistConnector; use TracToGitlab\Hooks; use TracToGitlab\Models\GitlabReviewApp; @@ -8,10 +10,12 @@ use TracToGitlab\Models\GitlabReviewApp; final class BranchDeleted extends Hooks\Push { private BytistConnector $connector; + private Gitlab\Client $client; public function __construct() { $this->connector = app(BytistConnector::class); + $this->client = app(Gitlab\Client::class); } public function __invoke(array $payload) @@ -21,6 +25,15 @@ final class BranchDeleted extends Hooks\Push echo $this->connector->decommission($branch); GitlabReviewApp::deleteBySQL('branch = ?', [$branch]); + + // Remove container from registry + $client = app(Gitlab\Client::class); + $registryRepositories = new \TracToGitlab\Gitlab\RegistryRepositories($client); + $registryRepositories->removeTag( + Config::get()->getValue('TRAC2GITLAB_GITLAB_PROJECT_ID'), + Config::get()->getValue('TRAC2GITLAB_GITLAB_CONTAINER_REGISTRY_ID'), + $branch + ); } } diff --git a/lib/EventHandlers/BuildImageJobSucceeded.php b/lib/EventHandlers/BuildImageJobSucceeded.php index 7aefe79e00b31489e1224ba79b6d358d43afdf4b..245978879b9f9c2400f7a226e5ebe9ab03c8fea3 100644 --- a/lib/EventHandlers/BuildImageJobSucceeded.php +++ b/lib/EventHandlers/BuildImageJobSucceeded.php @@ -10,11 +10,15 @@ use TracToGitlab\Traits\GetBuildImageJob; final class BuildImageJobSucceeded extends Hooks\Pipeline { + private BytistConnector $connector; + private Gitlab\Client $client; + use GetBuildImageJob; use BranchHasOpenMR; - public function __construct() { - $this->gitlab_client = app(Gitlab\Client::class); + public function __construct() + { + $this->client = app(Gitlab\Client::class); $this->connector = app(BytistConnector::class); } @@ -29,7 +33,7 @@ final class BuildImageJobSucceeded extends Hooks\Pipeline $branch = $payload['object_attributes']['ref']; - if (!$this->hasBranchOpenMergeRequests($this->gitlab_client, $branch)) { + if (!$this->hasBranchOpenMergeRequests($this->client, $branch)) { return; } diff --git a/lib/Gitlab/RegistryRepositories.php b/lib/Gitlab/RegistryRepositories.php new file mode 100644 index 0000000000000000000000000000000000000000..a7a10a96d13ea1926a6d1d252291ffc5d8a974a4 --- /dev/null +++ b/lib/Gitlab/RegistryRepositories.php @@ -0,0 +1,193 @@ +<?php + +declare(strict_types=1); + +/* + * This file is part of the Gitlab API library. + * + * (c) Jan-Hendrik Willms <tleilax@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace TracToGitlab\Gitlab; + +use Gitlab\Api\AbstractApi; +use Gitlab\HttpClient\Message\ResponseMediator; + +final class RegistryRepositories extends AbstractApi +{ + public const ACCESS_LEVEL_ENABLED = 'enabled'; + public const ACCESS_LEVEL_PRIVATE = 'private'; + public const ACCESS_LEVEL_DISABLED = 'disabled'; + + /** + * @param string|int $project_id + * @param array $parameters + * + * @var bool $tags include tags + * @var bool $tags_count include tags count + * + * @return mixed + */ + public function all($project_id, array $parameters = []) + { + $resolver = $this->createOptionsResolver(); + + $resolver->setDefined('tags') + ->setAllowedTypes('tags', ['bool']); + + $resolver->setDefined('tags_count') + ->setAllowedTypes('tags_count', ['bool']); + + return $this->get( + $this->getProjectPath($project_id, 'registry/repositories'), + $resolver->resolve($parameters) + ); + } + + /** + * @param string|int $project_id + * @param array $parameters + * + * @var string $container_registry_access_level access level to set (enabled, private or disabled) + * + * @return mixed + */ + public function changeContainerRegistryVisibility($project_id, array $parameters= []) + { + $resolver = $this->createOptionsResolver(); + + $resolver->setDefined('container_registry_access_level') + ->setAllowedValues('container_registry_access_level', [ + self::ACCESS_LEVEL_ENABLED, + self::ACCESS_LEVEL_PRIVATE, + self::ACCESS_LEVEL_DISABLED, + ]); + + return $this->put( + $this->getProjectPath($project_id, ''), + $resolver->resolve($parameters) + ); + } + + /** + * @param string|int $registry_repository_id + * @param array $parameters + * + * @var bool $tags include tags + * @var bool $tags_count include tags count + * @var bool $size include size + * + * @return mixed + */ + public function show($registry_repository_id, array $parameters = []) + { + $resolver = $this->createOptionsResolver(); + + $resolver->setDefined('tags') + ->setAllowedTypes('tags', ['bool']); + + $resolver->setDefined('tags_count') + ->setAllowedTypes('tags_count', ['bool']); + + $resolver->setDefined('size') + ->setAllowedTypes('size', ['bool']); + + return $this->get( + 'registry/repositories/'.self::encodePath($registry_repository_id), + $resolver->resolve($parameters) + ); + } + + /** + * @param string|int $project_id + * @param string|int $registry_repository_id + * + * @return mixed + */ + public function remove($project_id, $registry_repository_id) + { + return $this->delete( + $this->getProjectPath($project_id, 'registry/repositories/'.self::encodePath($registry_repository_id)) + ); + } + + /** + * @param string|int $project_id + * @param string|int $registry_repository_id + * + * @return mixed + */ + public function tags($project_id, $registry_repository_id) + { + return $this->get( + $this->getProjectPath($project_id, 'registry/repositories/'.self::encodePath($registry_repository_id).'/tags') + ); + } + + /** + * @param string|int $project_id + * @param string|int $registry_repository_id + * @param string $tag_name + * + * @return mixed + */ + public function tag($project_id, $registry_repository_id, $tag_name) + { + return $this->get( + $this->getProjectPath($project_id, 'registry/repositories/'.self::encodePath($registry_repository_id).'/tags/'.self::encodePath($tag_name)) + ); + } + + /** + * @param string|int $project_id + * @param string|int $registry_repository_id + * @param string $tag_name + * + * @return mixed + */ + public function removeTag($project_id, $registry_repository_id, $tag_name) + { + return $this->delete( + $this->getProjectPath($project_id, 'registry/repositories/'.self::encodePath($registry_repository_id).'/tags/'.self::encodePath($tag_name)) + ); + } + + /** + * @param string|int $project_id + * @param string|int $registry_repository_id + * @param array $parameters + * + * @var string $name_regex delete all tags matching this regex (deprecated in favor $name_regex_delete) + * @var string $name_regex_delete delete all tags matching this regex + * @var string $name_regex_keep keep all tags matching this regex + * @var int $keep_n keep n latest tags for each matching tag + * @var string $older_than delete tags older than the given time (written in human readable form 1h, 1d, 1month) + * + * @return mixed + */ + public function removeTags($project_id, $registry_repository_id, array $parameters = []) + { + $resolver = $this->createOptionsResolver(); + + $resolver->setDefined('name_regex_delete') + ->setRequired('name_regex_delete') + ->setAllowedTypes('name_regex_delete', 'string'); + + $resolver->setDefined('name_regex_keep') + ->setAllowedTypes('name_regex_keep', 'string'); + + $resolver->setDefined('keep_n') + ->setAllowedTypes('keep_n', 'int'); + + $resolver->setDefined('older_than') + ->setAllowedTypes('older_than', 'string'); + + return $this->delete( + $this->getProjectPath($project_id, 'registry/repositories/'.self::encodePath($registry_repository_id).'/tags'), + $resolver->resolve($parameters) + ); + } +} diff --git a/migrations/10_add_gitlab_container_registry_id.php b/migrations/10_add_gitlab_container_registry_id.php new file mode 100644 index 0000000000000000000000000000000000000000..9b49e3cfb9bb58b2d15701fe627676c1bc17a0e1 --- /dev/null +++ b/migrations/10_add_gitlab_container_registry_id.php @@ -0,0 +1,19 @@ +<?php +return new class extends Migration +{ + protected function up() + { + Config::get()->create('TRAC2GITLAB_GITLAB_CONTAINER_REGISTRY_ID', [ + 'value' => '', + 'type' => 'string', + 'range' => 'global', + 'section' => 'Trac2Gitlab', + 'description' => 'Id der Container Registry', + ]); + } + + protected function down() + { + Config::get()->delete('TRAC2GITLAB_GITLAB_CONTAINER_REGISTRY_ID'); + } +}; diff --git a/migrations/1_setup_plugin.php b/migrations/1_setup_plugin.php index 06cdb648c0107a703e340760af249d4bdcfe5f05..ed1c51d2b07adfa266af3c043924b3d5f6b12e2b 100644 --- a/migrations/1_setup_plugin.php +++ b/migrations/1_setup_plugin.php @@ -1,5 +1,5 @@ <?php -final class SetupPlugin extends Migration +return new class extends Migration { public function up() { @@ -65,4 +65,4 @@ final class SetupPlugin extends Migration } } -} +}; diff --git a/migrations/2_setup_transifex.php b/migrations/2_setup_transifex.php index 1939142b7a778f6a43bbf9143b3fa71c81f16498..cd57cc37076e59d3afe475f6e33c57599cb19307 100644 --- a/migrations/2_setup_transifex.php +++ b/migrations/2_setup_transifex.php @@ -1,5 +1,5 @@ <?php -final class SetupTransifex extends Migration +return new class extends Migration { public function up() { @@ -40,4 +40,4 @@ final class SetupTransifex extends Migration Config::get()->delete('TRAC2GITLAB_TRANSIFEX_ORGANISATION'); Config::get()->delete('TRAC2GITLAB_TRANSIFEX_PROJECT'); } -} +}; diff --git a/migrations/3_setup_cg_members_view.php b/migrations/3_setup_cg_members_view.php index 9731bdc802b84910293704d1fcf19f55eb560d1c..e624b68d3c96be26d0cdbbc2222655a631186a51 100644 --- a/migrations/3_setup_cg_members_view.php +++ b/migrations/3_setup_cg_members_view.php @@ -1,5 +1,5 @@ <?php -final class SetupCgMembersView extends Migration +return new class extends Migration { public function up() { @@ -17,4 +17,4 @@ final class SetupCgMembersView extends Migration Config::get()->delete('TRAC2GITLAB_CG_MEMBERS_COURSE_ID'); } -} +}; diff --git a/migrations/4_create_user_config.php b/migrations/4_create_user_config.php index d26c009c61b384cce8b8ea628707e1cb4106a4b8..72772ec9f989e02278f3ee4012202f90d3573053 100644 --- a/migrations/4_create_user_config.php +++ b/migrations/4_create_user_config.php @@ -1,5 +1,5 @@ <?php -final class CreateUserConfig extends Migration +return new class extends Migration { public function description() { @@ -20,4 +20,4 @@ final class CreateUserConfig extends Migration { UserConfig::get()->delete('TRAC2GITLAB_USER_FILTERS'); } -} +}; diff --git a/migrations/5_remove_trac.php b/migrations/5_remove_trac.php index dd5f5e5192427db9078caba61fa825e26adce516..adddc1085ba3116fc552ebf9df7e05bcfe8cfedd 100644 --- a/migrations/5_remove_trac.php +++ b/migrations/5_remove_trac.php @@ -1,5 +1,5 @@ <?php -final class RemoveTrac extends Migration +return new class extends Migration { protected function up() { @@ -16,4 +16,4 @@ final class RemoveTrac extends Migration 'description' => 'URL zur Trac-Instanz', ]); } -} +}; diff --git a/migrations/6_setup_bytist_connection.php b/migrations/6_setup_bytist_connection.php index ac7a7bb7933e4927c487f495d671b91d5afe90e4..85d4b41f4555b77b72a6e6bbfc9d2d8fa6212c6c 100644 --- a/migrations/6_setup_bytist_connection.php +++ b/migrations/6_setup_bytist_connection.php @@ -1,5 +1,5 @@ <?php -final class SetupBytistConnection extends Migration +return new class extends Migration { public function up() { @@ -16,4 +16,4 @@ final class SetupBytistConnection extends Migration { Config::get()->delete('TRAC2GITLAB_BYTIST_TOKEN'); } -} +}; diff --git a/migrations/7_add_webhook_secret_configuration.php b/migrations/7_add_webhook_secret_configuration.php index 7d1e41e1517cb848a27d0f09b615ba1c66585637..b6d675cc437193f2b48aae3b73a812b3d019593a 100644 --- a/migrations/7_add_webhook_secret_configuration.php +++ b/migrations/7_add_webhook_secret_configuration.php @@ -1,5 +1,5 @@ <?php -final class AddWebhookSecretConfiguration extends Migration +return new class extends Migration { protected function up() { @@ -16,5 +16,4 @@ final class AddWebhookSecretConfiguration extends Migration { Config::get()->delete('TRAC2GITLAB_GITLAB_WEBHOOK_SECRET'); } - -} +}; diff --git a/migrations/8_setup_database.php b/migrations/8_setup_database.php index 3de29f305176b5ac2e7d0a694f6d12cf80fcb5f0..b27521557be484e673ca7c28e2bbb9508cc06073 100644 --- a/migrations/8_setup_database.php +++ b/migrations/8_setup_database.php @@ -1,5 +1,5 @@ <?php -final class SetupDatabase extends Migration +return new class extends Migration { protected function up() { @@ -17,4 +17,4 @@ final class SetupDatabase extends Migration $query = "DROP TABLE IF EXISTS `gitlab_review_apps`"; DBManager::get()->exec($query); } -} +}; diff --git a/migrations/9_add_systemhook_secret_configuration.php b/migrations/9_add_systemhook_secret_configuration.php index aae8f099e554bb893c0fe9a82130f728ed13dab5..aa3323face3868eb95779150b78d297335e88d99 100644 --- a/migrations/9_add_systemhook_secret_configuration.php +++ b/migrations/9_add_systemhook_secret_configuration.php @@ -1,5 +1,6 @@ <?php -return new class extends Migration { +return new class extends Migration +{ protected function up() { Config::get()->create('TRAC2GITLAB_GITLAB_SYSTEMHOOK_SECRET', [ diff --git a/plugin.manifest b/plugin.manifest index 5edc2f9d5d3c1f4c9e6a505db00c8692527f4623..dd10aa52fb6549d47aa56d2b2d6ca5aa019a30a8 100644 --- a/plugin.manifest +++ b/plugin.manifest @@ -2,6 +2,6 @@ pluginname=Trac to gitlab converter pluginclassname=TracToGitlabPlugin pluginclassname=StudipReleasesPlugin origin=UOL -version=1.4.6 +version=1.4.7 studipMinVersion=5.3 localedomain=trac2gitlab diff --git a/views/admin/index.php b/views/admin/index.php index 739a8f72778b550e0e6998fd5ca1a87eae464451..82eba721246be9f735d2127b4ce21cc2bda2d12f 100644 --- a/views/admin/index.php +++ b/views/admin/index.php @@ -5,6 +5,7 @@ * @var string $gitlab_url * @var string $gitlab_token * @var string $gitlab_project_id + * @var string $gitlab_container_registry_id * @var string $bytist_token * @var string $webhook_secret * @var string $systemhook_secret @@ -29,6 +30,11 @@ <input type="number" name="gitlab-project-id" value="<?= htmlReady($gitlab_project_id) ?>"> </label> + <label> + <?= _('Id des Container Registry von Stud.IP') ?> + <input type="number" name="gitlab-container-registry-id" value="<?= htmlReady($gitlab_container_registry_id) ?>"> + </label> + <label> <?= _('Secret für den Webhook') ?> <input type="text" name="webhook-secret" value="<?= htmlReady($webhook_secret) ?>">