CI / php (push) Successful in 2m53s
- Add EnvServicePortResolver (DeploymentConfig) for MySQL, Redis frontends, session Redis, AMQP, http_cache_hosts, optional search - Extend tunnel registry with tunnel_type, service_key, service metadata; provisionServiceTunnel without store/NGINX map; teardown guards - Admin: Type (Website|Service), service dropdown, list Type column; JS toggle - TunnelManager/start-server: forward to resolved host:port - Tests: EnvServicePortResolver + service teardown; test stubs for Magento/PSR - docs: admin-ui Type/Service and security notes
285 lines
8.4 KiB
PHP
285 lines
8.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace MageSail\Magesail\Model;
|
|
|
|
use Magento\Framework\App\DeploymentConfig;
|
|
|
|
/**
|
|
* Discovers TCP endpoints from app/etc/env.php (via DeploymentConfig) for service-type Holesail tunnels.
|
|
*/
|
|
class EnvServicePortResolver
|
|
{
|
|
private const MYSQL_DEFAULT_KEY = 'mysql_default';
|
|
|
|
private const SESSION_REDIS_KEY = 'session_redis';
|
|
|
|
private const QUEUE_AMQP_KEY = 'queue_amqp';
|
|
|
|
public function __construct(
|
|
private readonly DeploymentConfig $deploymentConfig
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* @return list<array{service_key: string, label: string, host: string, port: int}>
|
|
*/
|
|
public function getDiscoveredServices(): array
|
|
{
|
|
$out = [];
|
|
foreach ($this->discoverMysql() as $row) {
|
|
$out[] = $row;
|
|
}
|
|
foreach ($this->discoverCacheFrontends() as $row) {
|
|
$out[] = $row;
|
|
}
|
|
foreach ($this->discoverSessionRedis() as $row) {
|
|
$out[] = $row;
|
|
}
|
|
foreach ($this->discoverAmqp() as $row) {
|
|
$out[] = $row;
|
|
}
|
|
foreach ($this->discoverHttpCacheHosts() as $row) {
|
|
$out[] = $row;
|
|
}
|
|
foreach ($this->discoverCatalogSearch() as $row) {
|
|
$out[] = $row;
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
|
|
/**
|
|
* @return array{host: string, port: int}|null
|
|
*/
|
|
public function resolveService(string $serviceKey): ?array
|
|
{
|
|
foreach ($this->getDiscoveredServices() as $row) {
|
|
if ($row['service_key'] === $serviceKey) {
|
|
return ['host' => $row['host'], 'port' => $row['port']];
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* @return list<array{service_key: string, label: string, host: string, port: int}>
|
|
*/
|
|
private function discoverMysql(): array
|
|
{
|
|
$conn = $this->deploymentConfig->get('db/connection/default');
|
|
if (!\is_array($conn)) {
|
|
return [];
|
|
}
|
|
$host = trim((string) ($conn['host'] ?? ''));
|
|
if ($host === '') {
|
|
return [];
|
|
}
|
|
$port = $this->parsePort($conn['port'] ?? null) ?? 3306;
|
|
|
|
return [[
|
|
'service_key' => self::MYSQL_DEFAULT_KEY,
|
|
'label' => sprintf('MySQL (db/connection/default) — %s:%d', $host, $port),
|
|
'host' => $host,
|
|
'port' => $port,
|
|
]];
|
|
}
|
|
|
|
/**
|
|
* @return list<array{service_key: string, label: string, host: string, port: int}>
|
|
*/
|
|
private function discoverCacheFrontends(): array
|
|
{
|
|
$cache = $this->deploymentConfig->get('cache');
|
|
if (!\is_array($cache)) {
|
|
return [];
|
|
}
|
|
$frontends = $cache['frontend'] ?? null;
|
|
if (!\is_array($frontends)) {
|
|
return [];
|
|
}
|
|
$out = [];
|
|
foreach ($frontends as $name => $frontend) {
|
|
if (!\is_array($frontend)) {
|
|
continue;
|
|
}
|
|
$backend = (string) ($frontend['backend'] ?? '');
|
|
if ($backend === '' || stripos($backend, 'Redis') === false) {
|
|
continue;
|
|
}
|
|
$opts = $frontend['backend_options'] ?? null;
|
|
if (!\is_array($opts)) {
|
|
continue;
|
|
}
|
|
$host = trim((string) ($opts['server'] ?? ''));
|
|
$port = $this->parsePort($opts['port'] ?? null);
|
|
if ($host === '' || $port === null) {
|
|
continue;
|
|
}
|
|
$safeName = preg_replace('/[^a-zA-Z0-9_\-]/', '_', (string) $name) ?: 'frontend';
|
|
$key = 'cache_frontend_' . $safeName;
|
|
$out[] = [
|
|
'service_key' => $key,
|
|
'label' => sprintf('Redis cache (%s) — %s:%d', (string) $name, $host, $port),
|
|
'host' => $host,
|
|
'port' => $port,
|
|
];
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
|
|
/**
|
|
* @return list<array{service_key: string, label: string, host: string, port: int}>
|
|
*/
|
|
private function discoverSessionRedis(): array
|
|
{
|
|
$session = $this->deploymentConfig->get('session');
|
|
if (!\is_array($session)) {
|
|
return [];
|
|
}
|
|
if (strtolower((string) ($session['save'] ?? '')) !== 'redis') {
|
|
return [];
|
|
}
|
|
$redis = $session['redis'] ?? null;
|
|
if (!\is_array($redis)) {
|
|
return [];
|
|
}
|
|
$host = trim((string) ($redis['host'] ?? ''));
|
|
$port = $this->parsePort($redis['port'] ?? null);
|
|
if ($host === '' || $port === null) {
|
|
return [];
|
|
}
|
|
|
|
return [[
|
|
'service_key' => self::SESSION_REDIS_KEY,
|
|
'label' => sprintf('Redis (sessions) — %s:%d', $host, $port),
|
|
'host' => $host,
|
|
'port' => $port,
|
|
]];
|
|
}
|
|
|
|
/**
|
|
* @return list<array{service_key: string, label: string, host: string, port: int}>
|
|
*/
|
|
private function discoverAmqp(): array
|
|
{
|
|
$queue = $this->deploymentConfig->get('queue');
|
|
if (!\is_array($queue)) {
|
|
return [];
|
|
}
|
|
$amqp = $queue['amqp'] ?? null;
|
|
if (!\is_array($amqp)) {
|
|
return [];
|
|
}
|
|
$host = trim((string) ($amqp['host'] ?? ''));
|
|
$port = $this->parsePort($amqp['port'] ?? null);
|
|
if ($host === '' || $port === null) {
|
|
return [];
|
|
}
|
|
|
|
return [[
|
|
'service_key' => self::QUEUE_AMQP_KEY,
|
|
'label' => sprintf('RabbitMQ (AMQP) — %s:%d', $host, $port),
|
|
'host' => $host,
|
|
'port' => $port,
|
|
]];
|
|
}
|
|
|
|
/**
|
|
* @return list<array{service_key: string, label: string, host: string, port: int}>
|
|
*/
|
|
private function discoverHttpCacheHosts(): array
|
|
{
|
|
$hosts = $this->deploymentConfig->get('http_cache_hosts');
|
|
if (!\is_array($hosts)) {
|
|
return [];
|
|
}
|
|
$out = [];
|
|
foreach ($hosts as $i => $entry) {
|
|
if (!\is_array($entry)) {
|
|
continue;
|
|
}
|
|
$host = trim((string) ($entry['host'] ?? ''));
|
|
$port = $this->parsePort($entry['port'] ?? null);
|
|
if ($host === '' || $port === null) {
|
|
continue;
|
|
}
|
|
$key = 'http_cache_' . $i;
|
|
$out[] = [
|
|
'service_key' => $key,
|
|
'label' => sprintf('HTTP cache host — %s:%d', $host, $port),
|
|
'host' => $host,
|
|
'port' => $port,
|
|
];
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
|
|
/**
|
|
* Elasticsearch / OpenSearch from catalog/search in env when present.
|
|
*
|
|
* @return list<array{service_key: string, label: string, host: string, port: int}>
|
|
*/
|
|
private function discoverCatalogSearch(): array
|
|
{
|
|
$catalog = $this->deploymentConfig->get('catalog');
|
|
if (!\is_array($catalog)) {
|
|
return [];
|
|
}
|
|
$search = $catalog['search'] ?? null;
|
|
if (!\is_array($search)) {
|
|
return [];
|
|
}
|
|
$candidates = [
|
|
[
|
|
'key' => 'catalog_search_elasticsearch7',
|
|
'host' => 'elasticsearch7_server_hostname',
|
|
'port' => 'elasticsearch7_server_port',
|
|
'label' => 'Elasticsearch 7',
|
|
],
|
|
[
|
|
'key' => 'catalog_search_elasticsearch8',
|
|
'host' => 'elasticsearch8_server_hostname',
|
|
'port' => 'elasticsearch8_server_port',
|
|
'label' => 'Elasticsearch 8',
|
|
],
|
|
[
|
|
'key' => 'catalog_search_opensearch',
|
|
'host' => 'opensearch_server_hostname',
|
|
'port' => 'opensearch_server_port',
|
|
'label' => 'OpenSearch',
|
|
],
|
|
];
|
|
$out = [];
|
|
foreach ($candidates as $c) {
|
|
$host = trim((string) ($search[$c['host']] ?? ''));
|
|
$port = $this->parsePort($search[$c['port']] ?? null);
|
|
if ($host === '' || $port === null) {
|
|
continue;
|
|
}
|
|
$out[] = [
|
|
'service_key' => $c['key'],
|
|
'label' => sprintf('%s — %s:%d', $c['label'], $host, $port),
|
|
'host' => $host,
|
|
'port' => $port,
|
|
];
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
|
|
private function parsePort(mixed $value): ?int
|
|
{
|
|
if ($value === null || $value === '') {
|
|
return null;
|
|
}
|
|
$n = (int) $value;
|
|
|
|
return ($n >= 1 && $n <= 65535) ? $n : null;
|
|
}
|
|
}
|