mirror of
https://github.com/e107inc/e107.git
synced 2025-01-18 05:09:05 +01:00
c31b645013
Broken due to Codeception moving modules out of codeception/codeception
375 lines
12 KiB
PHP
375 lines
12 KiB
PHP
<?php
|
||
|
||
include_once(__DIR__ . "/../cpaneluapi/cpaneluapi.class.php");
|
||
include_once(__DIR__ . "/Deployer.php");
|
||
|
||
class cPanelDeployer extends Deployer
|
||
{
|
||
const TEST_PREFIX = 'test_';
|
||
const TARGET_RELPATH = 'public_html/';
|
||
const DEFAULT_COMPONENTS = ['db', 'fs'];
|
||
protected $credentials;
|
||
protected $cPanel;
|
||
protected $run_id;
|
||
protected $db_id;
|
||
protected $homedir;
|
||
protected $docroot;
|
||
protected $domain;
|
||
private $skip_mysql_remote_hosts = false;
|
||
|
||
function __construct($params = [])
|
||
{
|
||
parent::__construct($params);
|
||
$this->credentials = $params['hosting'];
|
||
}
|
||
|
||
public function start()
|
||
{
|
||
self::println();
|
||
self::println("=== cPanel Deployer – Bring Up ===");
|
||
$creds = $this->credentials;
|
||
if (!$creds['hostname'] ||
|
||
!$creds['username'] ||
|
||
!$creds['password'])
|
||
{
|
||
throw new Exception("Cannot deploy cPanel environment because credentials are missing.");
|
||
}
|
||
|
||
$this->prepare();
|
||
|
||
foreach ($this->components as $component)
|
||
{
|
||
$method = "prepare_{$component}";
|
||
if (!method_exists($this, $method))
|
||
{
|
||
throw new Exception("Unsupported component \"{$component}\" requested.");
|
||
}
|
||
}
|
||
foreach ($this->components as $component)
|
||
{
|
||
$method = "prepare_{$component}";
|
||
$this->$method();
|
||
}
|
||
}
|
||
|
||
private function prepare()
|
||
{
|
||
$username = &$this->credentials['username'];
|
||
$password = &$this->credentials['password'];
|
||
$hostname = &$this->credentials['hostname'];
|
||
|
||
$this->run_id = $run_id = uniqid(self::TEST_PREFIX);
|
||
|
||
self::println("Test run ID: ".$this->run_id);
|
||
|
||
$this->cPanel = $cPanel = new cpanelAPI($username, $password, $hostname);
|
||
|
||
self::println("Connecting to cPanel at \"{$hostname}\" with username \"{$username}\"…");
|
||
|
||
$domains_data = $cPanel->uapi->DomainInfo->domains_data();
|
||
if (!$domains_data)
|
||
{
|
||
throw new Exception("Cannot connect to cPanel at \"{$hostname}\" with username \"{$username}\" and password \"{$password}\"");
|
||
}
|
||
$userdata = $domains_data->{'data'};
|
||
$this->homedir = $userdata->{'main_domain'}->{'homedir'};
|
||
$this->docroot = $userdata->{'main_domain'}->{'documentroot'};
|
||
$this->domain = $userdata->{'main_domain'}->{'domain'};
|
||
|
||
self::println("Obtained home directory from cPanel: " . $this->homedir);
|
||
self::println("Obtained document root from cPanel: " . $this->docroot);
|
||
self::println("Obtained domain name from cPanel: " . $this->domain);
|
||
|
||
$acceptance_tests = self::get_active_acceptance_tests($cPanel, $this->homedir);
|
||
|
||
self::println("Adding this test (".$this->run_id.") to registered tests list…");
|
||
$run_time = microtime(true);
|
||
array_push($acceptance_tests,
|
||
['id' => $run_id,
|
||
'time' => $run_time
|
||
]);
|
||
|
||
self::write_acceptance_tests($cPanel, $this->homedir, $acceptance_tests);
|
||
|
||
$valid_acceptance_test_ids = self::get_acceptance_test_ids($acceptance_tests);
|
||
self::println("Current unexpired tests: [".implode(", ", $valid_acceptance_test_ids)."]");
|
||
|
||
self::prune_inactive_acceptance_test_resources($cPanel, $valid_acceptance_test_ids);
|
||
}
|
||
|
||
private static function get_active_acceptance_tests($cPanel, $homedir)
|
||
{
|
||
self::println("Retrieving existing registered tests from cPanel account…");
|
||
$acceptance_tests = [];
|
||
$acceptance_tests_apiresponse = $cPanel->uapi->Fileman->get_file_content(['dir' => $homedir, 'file' => 'acceptance_tests.status.txt']);
|
||
if (!is_null($acceptance_tests_apiresponse->{'data'}))
|
||
{
|
||
$acceptance_tests_raw = $acceptance_tests_apiresponse->{'data'}->{'content'};
|
||
$acceptance_tests = (array) json_decode($acceptance_tests_raw, true);
|
||
self::prune_acceptance_tests($acceptance_tests);
|
||
}
|
||
return $acceptance_tests;
|
||
}
|
||
|
||
private static function prune_acceptance_tests(array &$list, $id_to_remove = null)
|
||
{
|
||
foreach ($list as $key => $item)
|
||
{
|
||
$time = $item['time'];
|
||
if ($item['id'] === $id_to_remove || $time <= strtotime("now - 10 seconds"))
|
||
{
|
||
unset($list[$key]);
|
||
}
|
||
}
|
||
$list = array_values($list);
|
||
return $list;
|
||
}
|
||
|
||
private static function write_acceptance_tests($cPanel, $homedir, $acceptance_tests)
|
||
{
|
||
$acceptance_tests_json = json_encode($acceptance_tests, JSON_PRETTY_PRINT);
|
||
|
||
self::println("Saving registered tests list to cPanel account…");
|
||
$cPanel->uapi->Fileman->save_file_content(['dir' => $homedir, 'file' => 'acceptance_tests.status.txt', 'content' => $acceptance_tests_json]);
|
||
}
|
||
|
||
private static function get_acceptance_test_ids(array $list)
|
||
{
|
||
$ids = [];
|
||
foreach ($list as $item)
|
||
{
|
||
$ids[] = $item['id'];
|
||
}
|
||
return $ids;
|
||
}
|
||
|
||
private static function prune_inactive_acceptance_test_resources($cPanel, $valid_acceptance_test_ids)
|
||
{
|
||
self::println("Pruning expired tests…");
|
||
$listdbs = $cPanel->api2->MysqlFE->listdbs()->{'cpanelresult'}->{'data'};
|
||
self::prune_mysql_databases($listdbs, $valid_acceptance_test_ids, $cPanel);
|
||
|
||
$listdbusers = $cPanel->api2->MysqlFE->listusers()->{'cpanelresult'}->{'data'};
|
||
self::prune_mysql_users($listdbusers, $valid_acceptance_test_ids, $cPanel);
|
||
|
||
$target_files_apiresponse = $cPanel->uapi->Fileman->list_files(['dir' => self::TARGET_RELPATH]);
|
||
$target_files = $target_files_apiresponse->{'data'};
|
||
foreach ($target_files as $target_file)
|
||
{
|
||
$questionable_filename = $target_file->{'file'};
|
||
if (substr($questionable_filename, 0, strlen(self::TEST_PREFIX)) === self::TEST_PREFIX &&
|
||
!in_array($questionable_filename, $valid_acceptance_test_ids))
|
||
{
|
||
self::println("Deleting expired test folder \"".self::TARGET_RELPATH.$questionable_filename."\"…");
|
||
$cPanel->api2->Fileman->fileop(['op' => 'unlink', 'sourcefiles' => self::TARGET_RELPATH.$questionable_filename]);
|
||
}
|
||
}
|
||
}
|
||
|
||
private static function prune_mysql_databases($dbs, $ids, $cPanel)
|
||
{
|
||
$prefix = $cPanel->user."_".self::TEST_PREFIX;
|
||
foreach ($dbs as $db)
|
||
{
|
||
$db = (array) $db;
|
||
if (substr($db['db'], 0, strlen($prefix)) !== $prefix)
|
||
continue;
|
||
$questionable_db = substr($db['db'], strlen($prefix));
|
||
if (!in_array($questionable_db, $ids))
|
||
{
|
||
self::println("Deleting expired MySQL database \"".$db['db']."\"…");
|
||
$cPanel->uapi->Mysql->delete_database(['name' => $db['db']]);
|
||
}
|
||
}
|
||
}
|
||
|
||
private static function prune_mysql_users($users, $ids, $cPanel)
|
||
{
|
||
$prefix = $cPanel->user."_".self::TEST_PREFIX;
|
||
foreach ($users as $user)
|
||
{
|
||
$user = (array) $user;
|
||
if (substr($user['user'], 0, strlen($prefix)) !== $prefix)
|
||
continue;
|
||
$questionable_user = substr($user['user'], strlen($prefix));
|
||
if (!in_array($questionable_user, $ids))
|
||
{
|
||
self::println("Deleting expired MySQL user \"".$user['user']."\"…");
|
||
$cPanel->uapi->Mysql->delete_user(['name' => $user['user']]);
|
||
}
|
||
}
|
||
}
|
||
|
||
public function stop()
|
||
{
|
||
self::println("=== cPanel Deployer – Tear Down ===");
|
||
$cPanel = $this->cPanel;
|
||
$acceptance_tests = self::get_active_acceptance_tests($cPanel, $this->homedir);
|
||
self::println("Removing this test (".$this->run_id.") from registered tests list…");
|
||
self::prune_acceptance_tests($acceptance_tests, $this->run_id);
|
||
self::write_acceptance_tests($cPanel, $this->homedir, $acceptance_tests);
|
||
|
||
$valid_acceptance_test_ids = self::get_acceptance_test_ids($acceptance_tests);
|
||
self::println("Current unexpired tests: [".implode(", ", $valid_acceptance_test_ids)."]");
|
||
self::prune_inactive_acceptance_test_resources($cPanel, $valid_acceptance_test_ids);
|
||
|
||
if (!$this->skip_mysql_remote_hosts)
|
||
{
|
||
self::clean_mysql_remote_hosts($cPanel);
|
||
}
|
||
}
|
||
|
||
private static function clean_mysql_remote_hosts($cPanel)
|
||
{
|
||
$remote_hosts = $cPanel->api2->MysqlFE->gethosts()->{'cpanelresult'}->{'data'};
|
||
if (in_array('%', $remote_hosts, true))
|
||
{
|
||
self::println("Removing cPanel MySQL remote host '%'…");
|
||
$cPanel->uapi->Mysql->delete_host(['host' => '%']);
|
||
}
|
||
}
|
||
|
||
public function reconfigure_db($module)
|
||
{
|
||
$db = $module->getDbModule();
|
||
$Db_config = $db->_getConfig();
|
||
$Db_config['dsn'] = $this->getDsn();
|
||
$Db_config['user'] = $this->getDbUsername();
|
||
$Db_config['password'] = $this->getDbPassword();
|
||
$db->_reconfigure($Db_config);
|
||
// Next line is used to make connection available to any code after this point
|
||
//$this->getModule('\Helper\DelayedDb')->_delayedInitialize();
|
||
}
|
||
|
||
private function getDsn()
|
||
{
|
||
$hostname = $this->credentials['hostname'];
|
||
$db_id = $this->getDbName();
|
||
return "mysql:host={$hostname};dbname={$db_id}";
|
||
}
|
||
|
||
private function getDbName()
|
||
{
|
||
return $this->db_id;
|
||
}
|
||
|
||
private function getDbUsername()
|
||
{
|
||
return $this->db_id;
|
||
}
|
||
|
||
private function getDbPassword()
|
||
{
|
||
return $this->run_id;
|
||
}
|
||
|
||
public function reconfigure_fs($module)
|
||
{
|
||
$url = $this->getUrl();
|
||
$browser = $module->getBrowserModule();
|
||
$browser->_reconfigure(array('url' => $url));
|
||
}
|
||
|
||
private function getUrl()
|
||
{
|
||
return "http://".$this->domain."/".$this->run_id."/";
|
||
}
|
||
|
||
public function unlinkAppFile($relative_path)
|
||
{
|
||
self::println("Deleting file \"$relative_path\" from deployed test location…");
|
||
$this->cPanel->api2->Fileman->fileop(['op' => 'unlink',
|
||
'sourcefiles' => self::TARGET_RELPATH.$this->run_id."/".$relative_path]);
|
||
}
|
||
|
||
private function prepare_db()
|
||
{
|
||
$cPanel = $this->cPanel;
|
||
$username = &$this->credentials['username'];
|
||
$run_id = &$this->run_id;
|
||
$this->db_id = $db_id = "{$username}_{$run_id}";
|
||
|
||
self::println("Ensuring that MySQL users allow any remote access hosts (%)…");
|
||
$remote_hosts = $cPanel->api2->MysqlFE->gethosts()->{'cpanelresult'}->{'data'};
|
||
if (!in_array('%', $remote_hosts, true))
|
||
{
|
||
$cPanel->uapi->Mysql->add_host(['host' => '%']);
|
||
register_shutdown_function(function() use ($cPanel)
|
||
{
|
||
self::clean_mysql_remote_hosts($cPanel);
|
||
});
|
||
}
|
||
else
|
||
{
|
||
$this->skip_mysql_remote_hosts = true;
|
||
}
|
||
|
||
self::println("Creating new MySQL database \"{$db_id}\"…");
|
||
$cPanel->uapi->Mysql->create_database(['name' => $db_id]);
|
||
|
||
self::println("Creating new MySQL user \"{$db_id}\" with password \"{$run_id}\"…");
|
||
$cPanel->uapi->Mysql->create_user(['name' => $db_id, 'password' => $run_id]);
|
||
self::println("Granting ALL PRIVILEGES to MySQL user \"{$db_id}\"…");
|
||
$cPanel->uapi->Mysql->set_privileges_on_database(['user' => $db_id,
|
||
'database' => $db_id,
|
||
'privileges' => 'ALL PRIVILEGES'
|
||
]);
|
||
}
|
||
|
||
private function prepare_fs()
|
||
{
|
||
$cPanel = $this->cPanel;
|
||
$app_archive = self::archive_app(APP_PATH, $this->run_id);
|
||
$app_archive_path = stream_get_meta_data($app_archive)['uri'];
|
||
$app_archive_name = basename($app_archive_path);
|
||
self::println("Sending archive to cPanel server…");
|
||
$cPanel->uapi->post->Fileman
|
||
->upload_files(['dir' => self::TARGET_RELPATH,
|
||
'file-1' => new CURLFile($app_archive_path)
|
||
]);
|
||
self::println("Extracting archive on cPanel server…");
|
||
$cPanel->api2->Fileman
|
||
->fileop(['op' => 'extract',
|
||
'sourcefiles' => self::TARGET_RELPATH.$app_archive_name,
|
||
'destfiles' => '.'
|
||
]);
|
||
self::println("Deleting archive from cPanel server…");
|
||
$cPanel->api2->Fileman
|
||
->fileop(['op' => 'unlink',
|
||
'sourcefiles' => self::TARGET_RELPATH.$app_archive_name
|
||
]);
|
||
}
|
||
|
||
private static function archive_app($path, $prefix = '')
|
||
{
|
||
$tmp_file = tmpfile();
|
||
$tmp_file_path = stream_get_meta_data($tmp_file)['uri'];
|
||
self::println("Touched temporary archive file; path: ".$tmp_file_path);
|
||
$archive = new ZipArchive();
|
||
$archive->open($tmp_file_path, ZipArchive::OVERWRITE);
|
||
$i = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path));
|
||
self::println("Adding app to temporary archive…");
|
||
$path = realpath($path);
|
||
/**
|
||
* @var $file_info SplFileInfo
|
||
*/
|
||
foreach ($i as $file_info)
|
||
{
|
||
$realpath = $file_info->getRealPath();
|
||
if (substr($realpath, 0, strlen($path)) === $path)
|
||
$relpath = substr($realpath, strlen($path));
|
||
if (substr($relpath, -3) === "/.." ||
|
||
substr($relpath, -2) === "/." ||
|
||
!file_exists($realpath) ||
|
||
!is_file($realpath) ||
|
||
empty($relpath)) continue;
|
||
$relpath = $prefix . $relpath;
|
||
$archive->addFile($realpath, $relpath);
|
||
$archive->setExternalAttributesName($relpath, ZipArchive::OPSYS_UNIX, fileperms($realpath) << 16);
|
||
}
|
||
$archive->close();
|
||
|
||
return $tmp_file;
|
||
}
|
||
}
|