diff --git a/composer.json b/composer.json index b96ed01..cab87a1 100644 --- a/composer.json +++ b/composer.json @@ -19,6 +19,9 @@ "inc/namespace.php", "inc/analysis/namespace.php", "inc/packages/namespace.php" + ], + "classmap": [ + "inc/" ] }, "extra": { @@ -26,7 +29,10 @@ "install-overrides": [ "10up/elasticpress", "humanmade/debug-bar-elasticpress" - ] + ], + "local-server": { + "compose-extension": "Altis\\Enhanced_Search\\Local_Server_Extension" + } } } } diff --git a/docs/configuration/README.md b/docs/configuration/README.md index 401e1b7..a7dc217 100644 --- a/docs/configuration/README.md +++ b/docs/configuration/README.md @@ -56,3 +56,8 @@ content. Depending on the configuration specified for `facets`, if the `match-type` property is set to `any`, it will force the results to match any selected taxonomy term. If set to `all`, it will match to results with all of the selected terms. + + +## Local Server + +See [the Local Server documentation](docs://local-server/elasticsearch.md) for information on configuring Local Server for ES settings. diff --git a/inc/class-local-server-extension.php b/inc/class-local-server-extension.php new file mode 100644 index 0000000..2652a32 --- /dev/null +++ b/inc/class-local-server-extension.php @@ -0,0 +1,246 @@ +generator = $generator; + } + + /** + * Filter the docker-compose.yml config. + * + * @param array $config Full docker-compose.yml configuration. + * @return array Altered docker-compose.yml configuration. + */ + public function filter_compose( array $config ) : array { + // Skip entirely if the module is disabled. + $full_config = Altis\get_config()['modules']; + if ( ! ( $full_config['search']['enabled'] ?? true ) ) { + return $config; + } + + $local_config = $full_config['search']['local'] ?? []; + if ( ! ( $local_config['enabled'] ?? true ) ) { + return $config; + } + + // Handle the main ES service. + $config['volumes']['es-data'] = null; + $config['services'] = array_merge( $config['services'], $this->get_service_elasticsearch() ); + + foreach ( [ 'php', 'cavalcade' ] as $php_svc ) { + if ( empty( $config['services'][ $php_svc ] ) ) { + continue; + } + + $config['services'][ $php_svc ]['external_links'][] = "proxy:elasticsearch-{$this->generator->hostname}"; + $config['services'][ $php_svc ]['environment']['ELASTICSEARCH_HOST'] = 'elasticsearch'; + $config['services'][ $php_svc ]['environment']['ELASTICSEARCH_PORT'] = 9200; + $config['services'][ $php_svc ]['depends_on']['elasticsearch'] = [ + 'condition' => 'service_healthy', + ]; + } + + // Enable Kibana. (Defaults to true, but supports new + old style.) + $has_kibana = $local_config['kibana'] ?? $full_config['local-server']['kibana'] ?? true; + if ( $has_kibana ) { + if ( ! empty( $full_config['local-server']['kibana'] ) ) { + trigger_error( + 'extra.altis.modules.local-server.kibana is deprecated, use extra.altis.modules.search.kibana instead.', + E_USER_DEPRECATED + ); + } + + $config['services'] = array_merge( $config['services'], $this->get_service_kibana() ); + } + + return $config; + } + + /** + * Get the Elasticsearch service. + * + * @return array + */ + protected function get_service_elasticsearch() : array { + $mem_limit = getenv( 'ES_MEM_LIMIT' ) ?: '1g'; + + $version_map = [ + '7.10' => 'humanmade/altis-local-server-elasticsearch:4.1.0', + '7' => 'humanmade/altis-local-server-elasticsearch:4.1.0', + '6.8' => 'humanmade/altis-local-server-elasticsearch:3.1.0', + '6' => 'humanmade/altis-local-server-elasticsearch:3.1.0', + '6.3' => 'humanmade/altis-local-server-elasticsearch:3.0.0', + ]; + + $this->check_elasticsearch_version( array_keys( $version_map ) ); + + $image = $version_map[ $this->get_elasticsearch_version() ]; + + return [ + 'elasticsearch' => [ + 'image' => $image, + 'restart' => 'unless-stopped', + 'container_name' => "{$this->generator->project_name}-es", + 'ulimits' => [ + 'memlock' => [ + 'soft' => -1, + 'hard' => -1, + ], + ], + 'mem_limit' => $mem_limit, + 'volumes' => [ + 'es-data:/usr/share/elasticsearch/data', + "{$this->generator->root_dir}/content/uploads/es-packages:/usr/share/elasticsearch/config/packages", + ], + 'ports' => [ + '9200', + ], + 'networks' => [ + 'proxy', + 'default', + ], + 'healthcheck' => [ + 'test' => [ + 'CMD-SHELL', + 'curl --silent --fail localhost:9200/_cluster/health || exit 1', + ], + 'interval' => '5s', + 'timeout' => '5s', + 'retries' => 25, + ], + 'labels' => [ + 'traefik.port=9200', + 'traefik.protocol=http', + 'traefik.docker.network=proxy', + "traefik.frontend.rule=HostRegexp:elasticsearch-{$this->generator->hostname}", + "traefik.domain=elasticsearch-{$this->generator->hostname}", + ], + 'environment' => [ + 'http.max_content_length=10mb', + // Force ES into single-node mode (otherwise defaults to zen discovery as + // network.host is set in the default config). + 'discovery.type=single-node', + // Use max container memory limit as the max JVM heap allocation value. + "ES_JAVA_OPTS=-Xms512m -Xmx{$mem_limit}", + ], + ], + ]; + } + + /** + * Get the Kibana service. + * + * @return array + */ + protected function get_service_kibana() : array { + $version_map = [ + '7.10' => 'humanmade/altis-local-server-kibana:1.1.1', + '7' => 'humanmade/altis-local-server-kibana:1.1.1', + '6.8' => 'blacktop/kibana:6.8', + '6' => 'blacktop/kibana:6.8', + '6.3' => 'blacktop/kibana:6.3', + ]; + + $this->check_elasticsearch_version( array_keys( $version_map ) ); + + $image = $version_map[ $this->get_elasticsearch_version() ]; + + $yml_file = 'kibana.yml'; + if ( version_compare( $this->get_elasticsearch_version(), '7', '>=' ) ) { + $yml_file = 'kibana-7.yml'; + } + + return [ + 'kibana' => [ + 'image' => $image, + 'container_name' => "{$this->generator->project_name}-kibana", + 'networks' => [ + 'proxy', + 'default', + ], + 'ports' => [ + '5601', + ], + 'labels' => [ + 'traefik.port=5601', + 'traefik.protocol=http', + 'traefik.docker.network=proxy', + "traefik.frontend.rule=Host:{$this->generator->hostname};PathPrefix:/kibana", + ], + 'depends_on' => [ + 'elasticsearch' => [ + 'condition' => 'service_healthy', + ], + ], + 'volumes' => [ + "{$this->generator->config_dir}/{$yml_file}:/usr/share/kibana/config/kibana.yml", + ], + ], + ]; + } + + /** + * Get the configured Elasticsearch version. + * + * @return int + */ + protected function get_elasticsearch_version() : string { + $full_config = Altis\get_config(); + + // Try new config first. + if ( ! empty( $full_config['modules']['search']['local']['version'] ) ) { + return (string) $full_config['modules']['search']['local']['version']; + } + + // Try legacy, and warn if it's still used. + if ( ! empty( $full_config['modules']['local-server']['elasticsearch'] ) ) { + trigger_error( + 'extra.altis.modules.local-server.elasticsearch is deprecated, use extra.altis.modules.search.version instead.', + E_USER_DEPRECATED + ); + return (string) $full_config['modules']['local-server']['elasticsearch']; + } + + return '7'; + } + + /** + * Check the configured Elasticsearch version in config. + * + * @param array $versions List of available version numbers. + * @return void + */ + protected function check_elasticsearch_version( array $versions ) { + $versions = array_map( 'strval', $versions ); + rsort( $versions ); + if ( in_array( $this->get_elasticsearch_version(), $versions, true ) ) { + return; + } + + echo sprintf( + "The configured elasticsearch version \"%s\" is not supported.\nTry one of the following:\n - %s\n", + // phpcs:ignore HM.Security.EscapeOutput.OutputNotEscaped + $this->get_elasticsearch_version(), + // phpcs:ignore HM.Security.EscapeOutput.OutputNotEscaped + implode( "\n - ", $versions ) + ); + exit( 1 ); + } +}