Skip to content
Merged
7 changes: 1 addition & 6 deletions .github/workflows/ci-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,7 @@ jobs:
- run: composer install

- name: Start SolrCloud
run: docker-compose up -d

- name: Sleep a few seconds to wait for Solr to be ready
uses: jakejarvis/wait-action@master
with:
time: "10s"
run: docker compose up -d --wait

- name: PHPUnit
run: ./vendor/bin/phpunit --testdox --colors=always
Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## 2.1.0 (2025-07-07)

This release ensures that authentication configuration of the Solr client is properly utilized also for raw Solr HTTP requests.
We do this by now using Solr test setup that requires authentication and thus force all our integration tests to be properly authenticated to work.

- fix: Authentication credentials configured for an endpoint are not used for raw HTTP requests to Solr
- feat: Better error message in case of failed raw HTTP requests to Solr
- test: Move to [bitnami/solr](https://hub.docker.com/r/bitnami/solr) docker image for better authentication support
- ci: Change `docker-compose` to `docker compose` to fix failing job

## 2.0.0 (2024-01-04)

- feat: Support Solr Cloud collection configuration during creation (#3)
Expand Down
167 changes: 84 additions & 83 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,108 +1,109 @@
# Start a simple cluster with three ZooKeeper nodes and three Solr nodes.
# Source: https://github.com/docker-solr/docker-solr-examples/blob/master/docker-compose/docker-compose.yml
# Using the [bitnami/solr](https://hub.docker.com/r/bitnami/solr) image for easier authentication setup.

x-zookeeper: &zookeeper
image: docker.io/bitnami/zookeeper:3.9
ports:
- '2181'
- '2888'
- '3888'
environment: &zookeeper-environment
ZOO_SERVER_ID: 1 # NOTE: If you want to have an zookeeper ensemble, each must define their own unique ID
ALLOW_ANONYMOUS_LOGIN: yes
ZOO_4LW_COMMANDS_WHITELIST: srvr,mntr,conf,ruok
BITNAMI_DEBUG: true

healthcheck:
test: nc -z localhost 2181 || exit -1
interval: 30s
timeout: 5s
retries: 3
start_period: 30s

x-solr: &solr
image: docker.io/bitnami/solr:9.6.0
ports:
- '8983'
depends_on:
- zoo1
- zoo2
- zoo3
environment: &solr-environment
BITNAMI_DEBUG: true

SOLR_LOG_LEVEL: WARN
SOLR_HEAP: "${SOLR_HEAP_SIZE:-512m}"

SOLR_ZK_HOSTS: zoo1:2181,zoo2:2181,zoo3:2181

SOLR_ENABLE_CLOUD_MODE: yes # This will enable cloud mode and requires zookeeper (SOLR_ZK_HOSTS)
# SOLR_CLOUD_BOOTSTRAP is set per service to avoid race conditions

SOLR_ENABLE_AUTHENTICATION: yes

# NOTE: We need to set the SOLR_AUTH* environment variables so that solr scripts can
# still talk to the Solr instances, once authentication is enabled.
# The SOLR_AUTHENTICATION_OPTS should be specified in the Solr services that use this template.
# We don't really need this right now, but set this to avoid potential issues in the future.
# e.g.
# - Collection creation should work : docker compose exec solr-records solr create -c foo
# - Configuring invalid credentials fails collection creation : docker compose exec --env SOLR_AUTHENTICATION_OPTS='-Dbasicauth=foo:bar' solr-records solr create -c foo
SOLR_AUTH_TYPE: "basic"
SOLR_ADMIN_USERNAME: "admin"
SOLR_ADMIN_PASSWORD: "Bitnami"
SOLR_AUTHENTICATION_OPTS: "-Dbasicauth=admin:Bitnami"

healthcheck:
# NOTE: Is available publicly, even when auth is enabled
# See `health` permission in default security.json (https://github.com/apache/solr/blob/main/solr/core/src/resources/security.json)
# See Solr docs for health endpoint: https://solr.apache.org/guide/solr/latest/configuration-guide/implicit-requesthandlers.html
test: ["CMD", "curl", "-f", "localhost:8983/api/node/health"]
start_period: 30s
interval: 30s
retries: 3

version: '3.7'
services:
solr1:
image: solr:9.4
<<: *solr
container_name: solr1
ports:
- "8983:8983"
environment:
- ZK_HOST=zoo1:2181,zoo2:2181,zoo3:2181
networks:
- solr
depends_on:
- zoo1
- zoo2
- zoo3
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8983/solr/admin/collections?action=CLUSTERSTATUS"]
interval: 1s
retries: 30
<<: *solr-environment
SOLR_CLOUD_BOOTSTRAP: yes # Only solr1 should bootstrap


solr2:
image: solr:9.4
<<: *solr
container_name: solr2
environment:
- ZK_HOST=zoo1:2181,zoo2:2181,zoo3:2181
networks:
- solr
depends_on:
- zoo1
- zoo2
- zoo3
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8983/solr/admin/collections?action=CLUSTERSTATUS"]
interval: 1s
retries: 30
<<: *solr-environment
SOLR_CLOUD_BOOTSTRAP: no # Disable bootstrap for other nodes

solr3:
image: solr:9.4
<<: *solr
container_name: solr3
environment:
- ZK_HOST=zoo1:2181,zoo2:2181,zoo3:2181
networks:
- solr
depends_on:
- zoo1
- zoo2
- zoo3
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8983/solr/admin/collections?action=CLUSTERSTATUS"]
interval: 1s
retries: 30
<<: *solr-environment
SOLR_CLOUD_BOOTSTRAP: no # Disable bootstrap for other nodes

zoo1:
image: zookeeper:3.9
container_name: zoo1
restart: always
hostname: zoo1
<<: *zookeeper
environment:
ZOO_MY_ID: 1
ZOO_SERVERS: server.1=zoo1:2888:3888;2181 server.2=zoo2:2888:3888;2181 server.3=zoo3:2888:3888;2181
ZOO_4LW_COMMANDS_WHITELIST: mntr, conf, ruok
ZOO_CFG_EXTRA: "metricsProvider.className=org.apache.zookeeper.metrics.prometheus.PrometheusMetricsProvider metricsProvider.httpPort=7000 metricsProvider.exportJvmInfo=true"
networks:
- solr
healthcheck:
test: nc -z localhost 2181 || exit -1
interval: 1s
retries: 30
<<: *zookeeper-environment
ZOO_SERVERS: zoo1:2888:3888,zoo2:2888:3888,zoo3:2888:3888
ZOO_SERVER_ID: 1

zoo2:
image: zookeeper:3.9
container_name: zoo2
restart: always
hostname: zoo2
<<: *zookeeper
environment:
ZOO_MY_ID: 2
ZOO_SERVERS: server.1=zoo1:2888:3888;2181 server.2=zoo2:2888:3888;2181 server.3=zoo3:2888:3888;2181
ZOO_4LW_COMMANDS_WHITELIST: mntr, conf, ruok
ZOO_CFG_EXTRA: "metricsProvider.className=org.apache.zookeeper.metrics.prometheus.PrometheusMetricsProvider metricsProvider.httpPort=7000 metricsProvider.exportJvmInfo=true"
networks:
- solr
healthcheck:
test: nc -z localhost 2181 || exit -1
interval: 1s
retries: 30
<<: *zookeeper-environment
ZOO_SERVERS: zoo1:2888:3888,zoo2:2888:3888,zoo3:2888:3888
ZOO_SERVER_ID: 2

zoo3:
image: zookeeper:3.9
container_name: zoo3
restart: always
hostname: zoo3
<<: *zookeeper
environment:
ZOO_MY_ID: 3
ZOO_SERVERS: server.1=zoo1:2888:3888;2181 server.2=zoo2:2888:3888;2181 server.3=zoo3:2888:3888;2181
ZOO_4LW_COMMANDS_WHITELIST: mntr, conf, ruok
ZOO_CFG_EXTRA: "metricsProvider.className=org.apache.zookeeper.metrics.prometheus.PrometheusMetricsProvider metricsProvider.httpPort=7000 metricsProvider.exportJvmInfo=true"
networks:
- solr
healthcheck:
test: nc -z localhost 2181 || exit -1
interval: 1s
retries: 30

networks:
solr:
<<: *zookeeper-environment
ZOO_SERVERS: zoo1:2888:3888,zoo2:2888:3888,zoo3:2888:3888
ZOO_SERVER_ID: 3
18 changes: 16 additions & 2 deletions src/CollectionManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -218,9 +218,23 @@ protected function rawApiRequest(string $path): array
$base_uri = "{$scheme}://{$host}:{$port}/solr";

$url = "{$base_uri}/{$path}";
$res = file_get_contents($url);

assert(false !== $res, "Request failed: $url");
$auth = $endpoint->getAuthentication();
$contextOptions = [];
if (!empty($auth['username']) && !empty($auth['password'])) {
$authHeader = 'Authorization: Basic ' . base64_encode("{$auth['username']}:{$auth['password']}");
$contextOptions['http'] = [
'header' => $authHeader
];
}

$context = stream_context_create($contextOptions);
$res = file_get_contents($url, false, $context);
if (false === $res) {
$error = error_get_last();
$errorMessage = isset($error['message']) ? $error['message'] : 'Unknown error';
throw new \RuntimeException("Request failed: $url. Error: $errorMessage");
}

$json = json_decode($res, true);

Expand Down
5 changes: 5 additions & 0 deletions tests/Integration/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ protected function getManager(): CollectionManager

$client = new Client($adapter, $eventDispatcher, $config);

$client->getEndpoint()->setAuthentication(
SOLR_USERNAME,
SOLR_PASSWORD,
);

return new CollectionManager($client);
}
}
2 changes: 2 additions & 0 deletions tests/config.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@
define('SOLR_PORT', getenv('TEST_SOLR_PORT') ? getenv('TEST_SOLR_PORT') : '8983');
define('SOLR_PATH', getenv('TEST_SOLR_PATH') ? getenv('TEST_SOLR_PATH') : '/');
define('SOLR_TIMEOUT', getenv('TEST_SOLR_TIMEOUT') ? getenv('TEST_SOLR_TIMEOUT') : '10');
define('SOLR_USERNAME', getenv('TEST_SOLR_USERNAME') ? getenv('TEST_SOLR_USERNAME') : 'admin');
define('SOLR_PASSWORD', getenv('TEST_SOLR_PASSWORD') ? getenv('TEST_SOLR_PASSWORD') : 'Bitnami');
Loading