mirror of
https://github.com/Combodo/iTop.git
synced 2026-04-23 18:48:51 +02:00
N°8760 - Audit uninstall of extensions that declare classes - first prototype
N°8760 - Audit uninstall of extensions that declare classes - be able to trace DM classes created_in N°8760 - be able to test with additional extensions installed in test SDK N°8760 - provide a service dedicated to extension removal N°8760 - compute all rules by default add comment adapt audit to both extension and mtp
This commit is contained in:
194
setup/feature_removal/SetupAudit.php
Normal file
194
setup/feature_removal/SetupAudit.php
Normal file
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Setup\FeatureRemoval;
|
||||
|
||||
use Config;
|
||||
use CoreException;
|
||||
use DBObjectSearch;
|
||||
use DBObjectSet;
|
||||
use Exception;
|
||||
use MetaModel;
|
||||
use RunTimeEnvironment;
|
||||
use SetupUtils;
|
||||
|
||||
class SetupAudit
|
||||
{
|
||||
const DRY_REMOVAL_AUDIT_ENV = "extension-removal";
|
||||
|
||||
private array $aClassesBeforeRemoval;
|
||||
private array $aClassesAfterRemoval;
|
||||
private array $aExtensionToRemove;
|
||||
private array $aRemovedClasses;
|
||||
private array $aFinalClassesRemoved;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->aExtensionToRemove = [];
|
||||
$this->aClassesBeforeRemoval = [];
|
||||
$this->aClassesAfterRemoval = [];
|
||||
$this->aRemovedClasses = [];
|
||||
$this->aFinalClassesRemoved = [];
|
||||
}
|
||||
|
||||
public function SetSelectedExtensions(Config $oConfig, array $aSelectedExtensions)
|
||||
{
|
||||
$oExtensionsMap = new \iTopExtensionsMap();
|
||||
$oExtensionsMap->LoadChoicesFromDatabase($oConfig);
|
||||
|
||||
sort($aSelectedExtensions);
|
||||
$this->aExtensionToRemove = $oExtensionsMap->GetMissingExtensions($aSelectedExtensions);
|
||||
sort($this->aExtensionToRemove);
|
||||
\SetupLog::Info(__METHOD__, null, ['aExtensionToRemove' => $this->aExtensionToRemove]);
|
||||
}
|
||||
|
||||
public function ComputeClassesBeforeRemoval(string $sTargetEnv)
|
||||
{
|
||||
$this->aClassesBeforeRemoval = $this->GetModelFromEnvironment($sTargetEnv);
|
||||
}
|
||||
|
||||
public function SetClassesAfterRemovalFromCurrentEnv()
|
||||
{
|
||||
$this->aClassesAfterRemoval = MetaModel::GetClasses();
|
||||
}
|
||||
|
||||
public function SetClassesBeforeRemovalFromCurrentEnv()
|
||||
{
|
||||
$this->aClassesBeforeRemoval = MetaModel::GetClasses();
|
||||
}
|
||||
|
||||
public function ComputeDryExtensionRemoval(array $aExtensionToRemove): void
|
||||
{
|
||||
$this->aExtensionToRemove = $aExtensionToRemove;
|
||||
|
||||
if (count($this->aExtensionToRemove) == 0) {
|
||||
//avoid time consuming setup audit when no extension removed
|
||||
return;
|
||||
}
|
||||
|
||||
$sDryRemovalEnv = self::DRY_REMOVAL_AUDIT_ENV;
|
||||
self::Cleanup($sDryRemovalEnv);
|
||||
|
||||
$sSourceEnvt = MetaModel::GetEnvironment();
|
||||
|
||||
$oDryRemovalRuntimeEnvt = new RunTimeEnvironment($sDryRemovalEnv);
|
||||
$oDryRemovalConfig = clone(MetaModel::GetConfig());
|
||||
$oDryRemovalConfig->ChangeModulesPath($sSourceEnvt, $sDryRemovalEnv);
|
||||
|
||||
$oDryRemovalRuntimeEnvt->WriteConfigFileSafe($oDryRemovalConfig);
|
||||
SetupUtils::copydir(APPROOT."/data/$sSourceEnvt-modules", APPROOT."/data/$sDryRemovalEnv-modules");
|
||||
$this->RemoveExtensionsLocally($sDryRemovalEnv, $this->aExtensionToRemove);
|
||||
|
||||
$oDryRemovalRuntimeEnvt->CompileFrom($sSourceEnvt);
|
||||
|
||||
$this->aClassesAfterRemoval = $this->GetModelFromEnvironment($sDryRemovalEnv);
|
||||
|
||||
$oDryRemovalRuntimeEnvt->Rollback();
|
||||
self::Cleanup($sDryRemovalEnv);
|
||||
}
|
||||
|
||||
public static function Cleanup(string $sEnv)
|
||||
{
|
||||
SetupUtils::rrmdir(APPROOT."/data/$sEnv-modules");
|
||||
SetupUtils::rrmdir(APPROOT."/data/cache-$sEnv");
|
||||
SetupUtils::rrmdir(APPROOT."/env-$sEnv");
|
||||
SetupUtils::rrmdir(APPROOT."/conf/$sEnv");
|
||||
@unlink(APPROOT."/data/datamodel-$sEnv.xml");
|
||||
}
|
||||
|
||||
public function GetModelFromEnvironment(string $sEnv): array
|
||||
{
|
||||
$sPHPExec = trim(\MetaModel::GetConfig()->Get('php_path'));
|
||||
$sOutput = "";
|
||||
$iRes = 0;
|
||||
exec(sprintf("$sPHPExec %s/get_model_reflection.php --env='%s'", __DIR__, $sEnv), $sOutput, $iRes);
|
||||
if ($iRes != 0) {
|
||||
\IssueLog::Error("Cannot get classes", null, ['code' => $iRes, "output" => $sOutput]);
|
||||
throw new CoreException("Cannot get classes");
|
||||
}
|
||||
|
||||
$aClasses = json_decode($sOutput[0] ?? null, true);
|
||||
if (false === $aClasses) {
|
||||
\IssueLog::Error("Invalid JSON", null, ["output" => $sOutput]);
|
||||
throw new Exception("cannot get classes");
|
||||
}
|
||||
|
||||
if (!is_array($aClasses)) {
|
||||
\IssueLog::Error("not an array", null, ["classes" => $aClasses]);
|
||||
throw new Exception("cannot get classes");
|
||||
}
|
||||
|
||||
return $aClasses;
|
||||
}
|
||||
|
||||
private function RemoveExtensionsLocally(string $sTargetEnv, array $aExtensionCodes): void
|
||||
{
|
||||
$oExtensionsMap = new \iTopExtensionsMap($sTargetEnv);
|
||||
|
||||
foreach ($aExtensionCodes as $sCode) {
|
||||
/** @var \iTopExtension $oExtension */
|
||||
$oExtension = $oExtensionsMap->Get($sCode);
|
||||
if (!is_null($oExtension)) {
|
||||
$sDir = $oExtension->sSourceDir;
|
||||
\IssueLog::Info(__METHOD__.": remove extension locally", null, [$oExtension->sCode => $sDir]);
|
||||
SetupUtils::rrmdir($sDir);
|
||||
} else {
|
||||
\IssueLog::Warning(__METHOD__." cannot find extensions", null, ['env' => $sTargetEnv, 'code' => $sCode]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function GetRemovedClasses(): array
|
||||
{
|
||||
if (count($this->aRemovedClasses) == 0) {
|
||||
if (count($this->aClassesBeforeRemoval) == 0) {
|
||||
return $this->aRemovedClasses;
|
||||
}
|
||||
|
||||
if (count($this->aClassesAfterRemoval) == 0) {
|
||||
return $this->aRemovedClasses;
|
||||
}
|
||||
|
||||
$aExtensionsNames = array_diff($this->aClassesBeforeRemoval, $this->aClassesAfterRemoval);
|
||||
$this->aRemovedClasses = [];
|
||||
$aClasses = array_values($aExtensionsNames);
|
||||
sort($aClasses);
|
||||
|
||||
foreach ($aClasses as $i => $sClass) {
|
||||
$this->aRemovedClasses[] = $sClass;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->aRemovedClasses;
|
||||
}
|
||||
|
||||
public function AuditExtensionsCleanupRules(bool $bStopAtFirstIssue = false): array
|
||||
{
|
||||
$this->aFinalClassesRemoved = [];
|
||||
|
||||
foreach ($this->GetRemovedClasses() as $sClass) {
|
||||
if (MetaModel::IsAbstract($sClass)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!MetaModel::IsStandaloneClass($sClass)) {
|
||||
$iCount = $this->Count($sClass);
|
||||
$this->aFinalClassesRemoved[$sClass] = $iCount;
|
||||
if ($bStopAtFirstIssue && $iCount > 0) {
|
||||
//setup envt: should raise issue ASAP
|
||||
throw new \Exception($sClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->aFinalClassesRemoved;
|
||||
}
|
||||
|
||||
private function Count($sClass): int
|
||||
{
|
||||
$oSearch = DBObjectSearch::FromOQL("SELECT $sClass", []);
|
||||
$oSearch->AllowAllData();
|
||||
$oSet = new DBObjectSet($oSearch);
|
||||
|
||||
return $oSet->Count();
|
||||
}
|
||||
}
|
||||
40
setup/feature_removal/get_model_reflection.php
Normal file
40
setup/feature_removal/get_model_reflection.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
require_once(dirname(__DIR__, 2).'/approot.inc.php');
|
||||
require_once(APPROOT.'application/application.inc.php');
|
||||
|
||||
$sEnv = null;
|
||||
|
||||
if (isset($argv)) {
|
||||
foreach ($argv as $iArg => $sArg) {
|
||||
if (preg_match('/^--env=(.*)$/', $sArg, $aMatches)) {
|
||||
$sEnv = $aMatches[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (is_null($sEnv)) {
|
||||
echo "No environment provided (--env) to read datamodel.";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$sConfFile = utils::GetConfigFilePath($sEnv);
|
||||
|
||||
try {
|
||||
MetaModel::Startup($sConfFile, false /* $bModelOnly */, true /* $bAllowCache */, false /* $bTraceSourceFiles */, $sEnv);
|
||||
}
|
||||
catch (\Throwable $e) {
|
||||
\IssueLog::Error("Cannot read model from provided environment", null,
|
||||
[
|
||||
'env' => $sEnv,
|
||||
'error' => $e->getMessage(),
|
||||
'stack' => $e->getTraceAsString(),
|
||||
]
|
||||
);
|
||||
echo "Cannot read model from provided environment";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$aClasses = MetaModel::GetClasses();
|
||||
|
||||
echo json_encode($aClasses);
|
||||
Reference in New Issue
Block a user