Skip to content

Commit 516ee3a

Browse files
hotfix31soyuka
andauthored
feat(elasticsearch): OpenSearch support (#7811)
Co-authored-by: Antoine Bluchet <soyuka@users.noreply.github.com>
1 parent 19809c6 commit 516ee3a

File tree

15 files changed

+234
-12
lines changed

15 files changed

+234
-12
lines changed

.github/workflows/ci.yml

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ jobs:
306306
continue-on-error: true
307307

308308
phpunit-components:
309-
name: PHPUnit ${{ matrix.component }} (PHP ${{ matrix.php.version }} ${{ matrix.php.coverage && 'coverage' || '' }}${{ matrix.php.lowest && 'lowest' || '' }}${{ matrix.php.minimal-changes && 'minimal-changes' || '' }})
309+
name: PHPUnit ${{ matrix.component }} (PHP ${{ matrix.php.version }} ${{ matrix.php.coverage && 'coverage' || '' }}${{ matrix.php.lowest && 'lowest' || '' }}${{ matrix.php.minimal-changes && 'minimal-changes' || '' }}${{ matrix.opensearch && 'opensearch' || '' }})
310310
runs-on: ubuntu-latest
311311
timeout-minutes: 20
312312
strategy:
@@ -338,6 +338,11 @@ jobs:
338338
- api-platform/state
339339
- api-platform/symfony
340340
- api-platform/validator
341+
include:
342+
- php:
343+
version: '8.5'
344+
component: api-platform/elasticsearch
345+
opensearch: true
341346
fail-fast: false
342347
steps:
343348
- name: Checkout
@@ -368,6 +373,11 @@ jobs:
368373
run: |
369374
cd $(composer ${{matrix.component}} --cwd)
370375
composer update${{ matrix.php.lowest && ' --prefer-lowest --prefer-source' || '' }}${{ matrix.php.minimal-changes && ' --minimal-changes' || '' }}
376+
- name: Install OpenSearch PHP client
377+
if: ${{ matrix.opensearch }}
378+
run: |
379+
cd $(composer ${{matrix.component}} --cwd)
380+
composer require --dev opensearch-project/opensearch-php "^2.5" -W
371381
- name: Run ${{ matrix.component }} tests
372382
run: |
373383
mkdir -p /tmp/build/logs/phpunit
@@ -888,6 +898,64 @@ jobs:
888898
- name: Run Behat tests
889899
run: vendor/bin/behat --out=std --format=progress --profile=elasticsearch --no-interaction
890900

901+
opensearch:
902+
name: Behat (PHP ${{ matrix.php }}) (OpenSearch ${{ matrix.opensearch-version }})
903+
runs-on: ubuntu-22.04
904+
timeout-minutes: 20
905+
strategy:
906+
matrix:
907+
include:
908+
- php: '8.5'
909+
opensearch-version: '2.19.4'
910+
extensions: 'intl, bcmath, curl, openssl, mbstring'
911+
fail-fast: false
912+
env:
913+
APP_ENV: opensearch
914+
services:
915+
opensearch:
916+
image: opensearchproject/opensearch:${{ matrix.opensearch-version }}
917+
ports:
918+
- 9200:9200
919+
env:
920+
discovery.type: single-node
921+
DISABLE_SECURITY_PLUGIN: 'true'
922+
OPENSEARCH_INITIAL_ADMIN_PASSWORD: 'Admin_1234!'
923+
options: >-
924+
--health-cmd "curl -f http://localhost:9200/_cluster/health || exit 1"
925+
--health-interval 10s
926+
--health-timeout 5s
927+
--health-retries 10
928+
steps:
929+
- name: Checkout
930+
uses: actions/checkout@v6
931+
- name: Setup PHP
932+
uses: shivammathur/setup-php@v2
933+
with:
934+
php-version: ${{ matrix.php }}
935+
tools: pecl, composer
936+
extensions: ${{ matrix.extensions }}
937+
coverage: none
938+
ini-values: memory_limit=-1
939+
- name: Get composer cache directory
940+
id: composercache
941+
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
942+
- name: Cache dependencies
943+
uses: actions/cache@v5
944+
with:
945+
path: ${{ steps.composercache.outputs.dir }}
946+
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
947+
restore-keys: ${{ runner.os }}-composer-
948+
- name: Update project dependencies
949+
run: |
950+
composer global require soyuka/pmu
951+
composer global config allow-plugins.soyuka/pmu true --no-interaction
952+
composer global link .
953+
composer require --dev opensearch-project/opensearch-php "^2.5" -W
954+
- name: Clear test app cache
955+
run: tests/Fixtures/app/console cache:clear --ansi
956+
- name: Run Behat tests
957+
run: vendor/bin/behat --out=std --format=progress --profile=opensearch --no-interaction
958+
891959
phpunit-no-deprecations:
892960
name: PHPUnit (PHP ${{ matrix.php }}) (no deprecations)
893961
runs-on: ubuntu-latest

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
### Features
6+
7+
* [f74d7ba1a](https://github.com/api-platform/core/commit/f74d7ba1a) feat(elasticsearch): add OpenSearch support (#7519)
8+
39
## v4.3.0-alpha.2
410

511
### Bug fixes

behat.yml.dist

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,21 @@ elasticsearch:
107107
filters:
108108
tags: '@elasticsearch&&~@mercure&&~@query_parameter_validator'
109109

110+
opensearch:
111+
suites:
112+
default: false
113+
opensearch:
114+
paths:
115+
- '%paths.base%/features/elasticsearch'
116+
contexts:
117+
- 'ApiPlatform\Tests\Behat\CommandContext'
118+
- 'ApiPlatform\Tests\Behat\ElasticsearchContext'
119+
- 'ApiPlatform\Tests\Behat\JsonContext'
120+
- 'Behat\MinkExtension\Context\MinkContext'
121+
- 'behatch:context:rest'
122+
filters:
123+
tags: '@elasticsearch&&~@mercure&&~@query_parameter_validator'
124+
110125
default-coverage:
111126
suites:
112127
default: &default-coverage-suite

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@
202202
"suggest": {
203203
"doctrine/mongodb-odm-bundle": "To support MongoDB. Only versions 4.0 and later are supported.",
204204
"elasticsearch/elasticsearch": "To support Elasticsearch.",
205+
"opensearch-project/opensearch-php": "To support OpenSearch (^2.5).",
205206
"phpstan/phpdoc-parser": "To support extracting metadata from PHPDoc.",
206207
"psr/cache-implementation": "To use metadata caching.",
207208
"ramsey/uuid": "To support Ramsey's UUID identifiers.",

src/Elasticsearch/State/CollectionProvider.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
use Elastic\Elasticsearch\Response\Elasticsearch;
2727
use Elasticsearch\Client as V7Client;
2828
use Elasticsearch\Common\Exceptions\Missing404Exception as V7Missing404Exception;
29+
use OpenSearch\Client as OpenSearchClient;
30+
use OpenSearch\Common\Exceptions\Missing404Exception as OpenSearchMissing404Exception;
2931
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
3032

3133
/**
@@ -40,7 +42,7 @@ final class CollectionProvider implements ProviderInterface
4042
* @param RequestBodySearchCollectionExtensionInterface[] $collectionExtensions
4143
*/
4244
public function __construct(
43-
private readonly V7Client|Client $client, // @phpstan-ignore-line
45+
private readonly V7Client|Client|OpenSearchClient $client, // @phpstan-ignore-line
4446
private readonly ?DenormalizerInterface $denormalizer = null,
4547
private readonly ?Pagination $pagination = null,
4648
private readonly iterable $collectionExtensions = [],
@@ -76,7 +78,7 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
7678

7779
try {
7880
$documents = $this->client->search($params); // @phpstan-ignore-line
79-
} catch (V7Missing404Exception $e) { // @phpstan-ignore-line
81+
} catch (V7Missing404Exception|OpenSearchMissing404Exception $e) { // @phpstan-ignore-line
8082
throw new Error(status: $e->getCode(), detail: $e->getMessage(), title: $e->getMessage(), originalTrace: $e->getTrace()); // @phpstan-ignore-line
8183
} catch (ClientResponseException $e) {
8284
$response = $e->getResponse();

src/Elasticsearch/State/ItemProvider.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
use Elastic\Elasticsearch\Response\Elasticsearch;
2525
use Elasticsearch\Client as V7Client;
2626
use Elasticsearch\Common\Exceptions\Missing404Exception as V7Missing404Exception;
27+
use OpenSearch\Client as OpenSearchClient;
28+
use OpenSearch\Common\Exceptions\Missing404Exception as OpenSearchMissing404Exception;
2729
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
2830
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
2931

@@ -36,7 +38,7 @@
3638
final class ItemProvider implements ProviderInterface
3739
{
3840
public function __construct(
39-
private readonly V7Client|Client $client, // @phpstan-ignore-line
41+
private readonly V7Client|Client|OpenSearchClient $client, // @phpstan-ignore-line
4042
private readonly ?DenormalizerInterface $denormalizer = null,
4143
private readonly ?InflectorInterface $inflector = new Inflector(),
4244
) {
@@ -60,7 +62,7 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
6062

6163
try {
6264
$document = $this->client->get($params); // @phpstan-ignore-line
63-
} catch (V7Missing404Exception) { // @phpstan-ignore-line
65+
} catch (V7Missing404Exception|OpenSearchMissing404Exception) { // @phpstan-ignore-line
6466
return null;
6567
} catch (ClientResponseException $e) {
6668
$response = $e->getResponse();

src/Elasticsearch/composer.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@
3636
"symfony/type-info": "^7.3 || ^8.0",
3737
"symfony/uid": "^6.4 || ^7.0 || ^8.0"
3838
},
39+
"suggest": {
40+
"opensearch-project/opensearch-php": "Required to use OpenSearch instead of Elasticsearch (^2.5)"
41+
},
3942
"require-dev": {
4043
"phpspec/prophecy-phpunit": "^2.2",
4144
"phpunit/phpunit": "^12.2"

src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -991,16 +991,21 @@ private function registerElasticsearchConfiguration(ContainerBuilder $container,
991991
throw new \LogicException('Elasticsearch support cannot be enabled as the Elasticsearch component is not installed. Try running "composer require api-platform/elasticsearch".');
992992
}
993993

994-
$clientClass = !class_exists(\Elasticsearch\Client::class)
995-
// ES v7
996-
? \Elastic\Elasticsearch\Client::class
994+
if ('opensearch' === $config['elasticsearch']['client']) {
995+
$clientClass = \OpenSearch\Client::class; // @phpstan-ignore class.notFound
996+
} elseif (!class_exists(\Elasticsearch\Client::class)) {
997997
// ES v8 and up
998-
: \Elasticsearch\Client::class;
998+
$clientClass = \Elastic\Elasticsearch\Client::class;
999+
} else {
1000+
// ES v7
1001+
$clientClass = \Elasticsearch\Client::class;
1002+
}
9991003

10001004
$clientDefinition = new Definition($clientClass);
10011005
$container->setDefinition('api_platform.elasticsearch.client', $clientDefinition);
10021006
$container->registerForAutoconfiguration(RequestBodySearchCollectionExtensionInterface::class)
10031007
->addTag('api_platform.elasticsearch.request_body_search_extension.collection');
1008+
$container->setParameter('api_platform.elasticsearch.client', $config['elasticsearch']['client']);
10041009
$container->setParameter('api_platform.elasticsearch.hosts', $config['elasticsearch']['hosts']);
10051010
$container->setParameter('api_platform.elasticsearch.ssl_ca_bundle', $config['elasticsearch']['ssl_ca_bundle']);
10061011
$container->setParameter('api_platform.elasticsearch.ssl_verification', $config['elasticsearch']['ssl_verification']);

src/Symfony/Bundle/DependencyInjection/Compiler/ElasticsearchClientPass.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ public function process(ContainerBuilder $container): void
3939
$clientConfiguration['hosts'] = $hosts;
4040
}
4141

42-
if (class_exists(\Elasticsearch\ClientBuilder::class)) {
42+
if ('opensearch' === $container->getParameter('api_platform.elasticsearch.client')) {
43+
$builderName = \OpenSearch\ClientBuilder::class; // @phpstan-ignore class.notFound
44+
} elseif (class_exists(\Elasticsearch\ClientBuilder::class)) {
4345
// ES v7
4446
$builderName = \Elasticsearch\ClientBuilder::class;
4547
} else {

src/Symfony/Bundle/DependencyInjection/Configuration.php

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -505,8 +505,10 @@ private function addElasticsearchSection(ArrayNodeDefinition $rootNode): void
505505
!class_exists(\Elasticsearch\Client::class)
506506
// ES v8 and up
507507
&& !class_exists(\Elastic\Elasticsearch\Client::class)
508+
// OpenSearch
509+
&& !class_exists(\OpenSearch\Client::class)
508510
) {
509-
throw new InvalidConfigurationException('The elasticsearch/elasticsearch package is required for Elasticsearch support.');
511+
throw new InvalidConfigurationException('The elasticsearch/elasticsearch or opensearch-project/opensearch-php package is required for Elasticsearch support.');
510512
}
511513

512514
return $v;
@@ -526,6 +528,21 @@ private function addElasticsearchSection(ArrayNodeDefinition $rootNode): void
526528
->defaultTrue()
527529
->info('Enable or disable SSL verification for Elasticsearch connections.')
528530
->end()
531+
->enumNode('client')
532+
->values(['elasticsearch', 'opensearch'])
533+
->defaultValue('elasticsearch')
534+
->info('The search engine client to use: "elasticsearch" or "opensearch".')
535+
->validate()
536+
->ifString()
537+
->then(static function (string $v): string {
538+
if ('opensearch' === $v && !class_exists(\OpenSearch\Client::class)) {
539+
throw new InvalidConfigurationException('Setting api_platform.elasticsearch.client to "opensearch" requires the opensearch-project/opensearch-php package. Try running "composer require opensearch-project/opensearch-php".');
540+
}
541+
542+
return $v;
543+
})
544+
->end()
545+
->end()
529546
->end()
530547
->end()
531548
->end();

0 commit comments

Comments
 (0)