1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-22 14:52:41 +01:00

Detect and raise setup warnings from within Phabricator

Summary:
This is basicaly a light version of D4286. The major problem with D4286 is that it's a huge leap and completely replaces the setup process in one step.

Instead, I want to do this:

  - Add the post-setup warnings (yellow bar with "6 unresolved warnings...").
  - Copy all setup checks into post-setup warnings (so every check has an old-style check and a new-style check).
  - Run that for a little bit and make sure it's stable.
  - Implement fatal post-setup checks (the red screen, vs the yellow bar).
  - Run that for a little bit.
  - Nuke setup mode and delete all the old checks.

This should give us a bunch of very gradual steps toward the brave new world of simpler setup.

Test Plan:
 - Faked APC setup failures, saw warnings raise.
 - Verified that this runs after restart (get + set).
 - Verified that this costs us only one cache hit after first-run (get only).

Reviewers: btrahan, codeblock, vrana, chad

Reviewed By: codeblock

CC: aran

Maniphest Tasks: T2228

Differential Revision: https://secure.phabricator.com/D4295
This commit is contained in:
epriestley 2012-12-30 06:37:49 -08:00
parent ba489f9d85
commit 96839d35f4
10 changed files with 768 additions and 1 deletions

View file

@ -686,6 +686,8 @@ phutil_register_library_map(array(
'PhabricatorConfigEntry' => 'applications/config/storage/PhabricatorConfigEntry.php',
'PhabricatorConfigEntryDAO' => 'applications/config/storage/PhabricatorConfigEntryDAO.php',
'PhabricatorConfigFileSource' => 'infrastructure/env/PhabricatorConfigFileSource.php',
'PhabricatorConfigIssueListController' => 'applications/config/controller/PhabricatorConfigIssueListController.php',
'PhabricatorConfigIssueViewController' => 'applications/config/controller/PhabricatorConfigIssueViewController.php',
'PhabricatorConfigListController' => 'applications/config/controller/PhabricatorConfigListController.php',
'PhabricatorConfigLocalSource' => 'infrastructure/env/PhabricatorConfigLocalSource.php',
'PhabricatorConfigManagementSetWorkflow' => 'infrastructure/env/management/PhabricatorConfigManagementSetWorkflow.php',
@ -1135,6 +1137,10 @@ phutil_register_library_map(array(
'PhabricatorSettingsPanelSSHKeys' => 'applications/settings/panel/PhabricatorSettingsPanelSSHKeys.php',
'PhabricatorSettingsPanelSearchPreferences' => 'applications/settings/panel/PhabricatorSettingsPanelSearchPreferences.php',
'PhabricatorSetup' => 'infrastructure/PhabricatorSetup.php',
'PhabricatorSetupCheck' => 'applications/config/check/PhabricatorSetupCheck.php',
'PhabricatorSetupCheckAPC' => 'applications/config/check/PhabricatorSetupCheckAPC.php',
'PhabricatorSetupIssue' => 'applications/config/issue/PhabricatorSetupIssue.php',
'PhabricatorSetupIssueView' => 'applications/config/view/PhabricatorSetupIssueView.php',
'PhabricatorSlowvoteChoice' => 'applications/slowvote/storage/PhabricatorSlowvoteChoice.php',
'PhabricatorSlowvoteComment' => 'applications/slowvote/storage/PhabricatorSlowvoteComment.php',
'PhabricatorSlowvoteController' => 'applications/slowvote/controller/PhabricatorSlowvoteController.php',
@ -2002,6 +2008,8 @@ phutil_register_library_map(array(
'PhabricatorConfigEntry' => 'PhabricatorConfigEntryDAO',
'PhabricatorConfigEntryDAO' => 'PhabricatorLiskDAO',
'PhabricatorConfigFileSource' => 'PhabricatorConfigProxySource',
'PhabricatorConfigIssueListController' => 'PhabricatorConfigController',
'PhabricatorConfigIssueViewController' => 'PhabricatorConfigController',
'PhabricatorConfigListController' => 'PhabricatorConfigController',
'PhabricatorConfigLocalSource' => 'PhabricatorConfigProxySource',
'PhabricatorConfigManagementSetWorkflow' => 'PhabricatorConfigManagementWorkflow',
@ -2409,6 +2417,8 @@ phutil_register_library_map(array(
'PhabricatorSettingsPanelProfile' => 'PhabricatorSettingsPanel',
'PhabricatorSettingsPanelSSHKeys' => 'PhabricatorSettingsPanel',
'PhabricatorSettingsPanelSearchPreferences' => 'PhabricatorSettingsPanel',
'PhabricatorSetupCheckAPC' => 'PhabricatorSetupCheck',
'PhabricatorSetupIssueView' => 'AphrontView',
'PhabricatorSlowvoteChoice' => 'PhabricatorSlowvoteDAO',
'PhabricatorSlowvoteComment' => 'PhabricatorSlowvoteDAO',
'PhabricatorSlowvoteController' => 'PhabricatorController',

View file

@ -25,8 +25,12 @@ final class PhabricatorApplicationConfig extends PhabricatorApplication {
public function getRoutes() {
return array(
'/config/' => array(
'' => 'PhabricatorConfigListController',
'' => 'PhabricatorConfigListController',
'edit/(?P<key>[\w\.\-]+)/' => 'PhabricatorConfigEditController',
'issue/' => array(
'' => 'PhabricatorConfigIssueListController',
'(?P<key>[^/]+)/' => 'PhabricatorConfigIssueViewController',
),
),
);
}

View file

@ -0,0 +1,79 @@
<?php
abstract class PhabricatorSetupCheck {
private $issues;
abstract protected function executeChecks();
final protected function newIssue($key) {
$issue = id(new PhabricatorSetupIssue())
->setIssueKey($key);
$this->issues[$key] = $issue;
return $issue;
}
final public function getIssues() {
return $this->issues;
}
final public function runSetupChecks() {
$this->issues = array();
$this->executeChecks();
}
final public static function getOpenSetupIssueCount() {
$cache = PhabricatorCaches::getSetupCache();
return $cache->getKey('phabricator.setup.issues');
}
final public static function setOpenSetupIssueCount($count) {
$cache = PhabricatorCaches::getSetupCache();
$cache->setKey('phabricator.setup.issues', $count);
}
final public static function willProcessRequest() {
$issue_count = self::getOpenSetupIssueCount();
if ($issue_count !== null) {
// We've already run setup checks, didn't hit any fatals, and then set
// an issue count. This means we're good and don't need to do any extra
// work.
return null;
}
$issues = self::runAllChecks();
self::setOpenSetupIssueCount(count($issues));
return null;
}
final public static function runAllChecks() {
$symbols = id(new PhutilSymbolLoader())
->setAncestorClass('PhabricatorSetupCheck')
->setConcreteOnly(true)
->selectAndLoadSymbols();
$checks = array();
foreach ($symbols as $symbol) {
$checks[] = newv($symbol['name'], array());
}
$issues = array();
foreach ($checks as $check) {
$check->runSetupChecks();
foreach ($check->getIssues() as $key => $issue) {
if (isset($issues[$key])) {
throw new Exception(
"Two setup checks raised an issue with key '{$key}'!");
}
$issues[$key] = $issue;
}
}
return $issues;
}
}

View file

@ -0,0 +1,37 @@
<?php
final class PhabricatorSetupCheckAPC extends PhabricatorSetupCheck {
protected function executeChecks() {
if (!extension_loaded('apc')) {
$message = pht(
"Installing the PHP extension 'APC' (Alternative PHP Cache) will ".
"dramatically improve performance.");
$this
->newIssue('extension.apc')
->setShortName(pht('APC'))
->setName(pht("PHP Extension 'APC' Not Installed"))
->setMessage($message)
->addPHPExtension('apc');
return;
}
if (!ini_get('apc.enabled')) {
$summary = pht("Enabling APC will dramatically improve performance.");
$message = pht(
"The PHP extension 'APC' is installed, but not enabled in your PHP ".
"configuration. Enabling it will dramatically improve Phabricator ".
"performance. Edit the 'apc.enabled' setting to enable the extension.");
$this
->newIssue('extension.apc.enabled')
->setShortName(pht('APC Disabled'))
->setName(pht("PHP Extension 'APC' Not Enabled"))
->setSummary($summary)
->setMessage($message)
->addPHPConfig('apc.enabled');
}
}
}

View file

@ -0,0 +1,65 @@
<?php
final class PhabricatorConfigIssueListController
extends PhabricatorConfigController {
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$nav = $this->buildSideNavView();
$issues = PhabricatorSetupCheck::runAllChecks();
PhabricatorSetupCheck::setOpenSetupIssueCount(count($issues));
$list = $this->buildIssueList($issues);
$list->setNoDataString(pht("There are no open setup issues."));
$header = id(new PhabricatorHeaderView())
->setHeader(pht('Open Phabricator Setup Issues'));
$nav->appendChild(
array(
$header,
$list,
));
$title = pht('Setup Issues');
$crumbs = $this
->buildApplicationCrumbs($nav)
->addCrumb(
id(new PhabricatorCrumbView())
->setName($title)
->setHref($this->getApplicationURI('issue/')));
$nav->setCrumbs($crumbs);
return $this->buildApplicationPage(
$nav,
array(
'title' => $title,
'device' => true,
)
);
}
private function buildIssueList(array $issues) {
assert_instances_of($issues, 'PhabricatorSetupIssue');
$list = new PhabricatorObjectItemListView();
foreach ($issues as $issue) {
$href = $this->getApplicationURI('/issue/'.$issue->getIssueKey().'/');
$item = id(new PhabricatorObjectItemView())
->setHeader($issue->getName())
->setHref($href)
->setBarColor('yellow')
->addIcon('warning', pht('Setup Warning'))
->addAttribute($issue->getSummary());
$list->addItem($item);
}
return $list;
}
}

View file

@ -0,0 +1,81 @@
<?php
final class PhabricatorConfigIssueViewController
extends PhabricatorConfigController {
private $issueKey;
public function willProcessRequest(array $data) {
$this->issueKey = $data['key'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$nav = $this->buildSideNavView();
$issues = PhabricatorSetupCheck::runAllChecks();
PhabricatorSetupCheck::setOpenSetupIssueCount(count($issues));
if (empty($issues[$this->issueKey])) {
$content = id(new AphrontErrorView())
->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
->setTitle(pht('Issue Resolved'))
->appendChild(pht('This setup issue has been resolved. '))
->appendChild(
phutil_render_tag(
'a',
array(
'href' => $this->getApplicationURI('issue/'),
),
pht('Return to Open Issue List')));
$title = pht('Resolved Issue');
} else {
$issue = $issues[$this->issueKey];
$content = $this->renderIssue($issue);
$title = $issue->getShortName();
}
$nav->appendChild($content);
$crumbs = $this
->buildApplicationCrumbs($nav)
->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Setup Issues'))
->setHref($this->getApplicationURI('issue/')))
->addCrumb(
id(new PhabricatorCrumbView())
->setName($title)
->setHref($request->getRequestURI()));
$nav->setCrumbs($crumbs);
return $this->buildApplicationPage(
$nav,
array(
'title' => $title,
'device' => true,
)
);
}
private function renderIssue(PhabricatorSetupIssue $issue) {
require_celerity_resource('setup-issue-css');
$view = new PhabricatorSetupIssueView();
$view->setIssue($issue);
$view;
$container = phutil_render_tag(
'div',
array(
'class' => 'setup-issue-background',
),
$view->render());
return $container;
}
}

View file

@ -0,0 +1,113 @@
<?php
final class PhabricatorSetupIssue {
private $issueKey;
private $name;
private $message;
private $isFatal;
private $summary;
private $shortName;
private $phpExtensions = array();
private $phabricatorConfig = array();
private $phpConfig = array();
private $commands = array();
public function addCommand($command) {
$this->commands[] = $command;
return $this;
}
public function getCommands() {
return $this->commands;
}
public function setShortName($short_name) {
$this->shortName = $short_name;
return $this;
}
public function getShortName() {
if ($this->shortName === null) {
return $this->getName();
}
return $this->shortName;
}
public function setName($name) {
$this->name = $name;
return $this;
}
public function getName() {
return $this->name;
}
public function setSummary($summary) {
$this->summary = $summary;
return $this;
}
public function getSummary() {
if ($this->summary === null) {
return $this->getMessage();
}
return $this->summary;
}
public function setIssueKey($issue_key) {
$this->issueKey = $issue_key;
return $this;
}
public function getIssueKey() {
return $this->issueKey;
}
public function setIsFatal($is_fatal) {
$this->isFatal = $is_fatal;
return $this;
}
public function getIsFatal() {
return $this->isFatal;
}
public function addPHPConfig($php_config) {
$this->phpConfig[] = $php_config;
return $this;
}
public function getPHPConfig() {
return $this->phpConfig;
}
public function addPhabricatorConfig($phabricator_config) {
$this->phabricatorConfig[] = $phabricator_config;
return $this;
}
public function getPhabricatorConfig() {
return $this->phabricatorConfig;
}
public function addPHPExtension($php_extension) {
$this->phpExtensions[] = $php_extension;
return $this;
}
public function getPHPExtensions() {
return $this->phpExtensions;
}
public function setMessage($message) {
$this->message = $message;
return $this;
}
public function getMessage() {
return $this->message;
}
}

View file

@ -0,0 +1,290 @@
<?php
final class PhabricatorSetupIssueView extends AphrontView {
private $issue;
public function setIssue(PhabricatorSetupIssue $issue) {
$this->issue = $issue;
return $this;
}
public function getIssue() {
return $this->issue;
}
public function render() {
$issue = $this->getIssue();
$description = phutil_render_tag(
'div',
array(
'class' => 'setup-issue-instructions',
),
nl2br(phutil_escape_html($issue->getMessage())));
$configs = $issue->getPhabricatorConfig();
if ($configs) {
$description .= $this->renderPhabricatorConfig($configs);
}
$configs = $issue->getPHPConfig();
if ($configs) {
$description .= $this->renderPHPConfig($configs);
}
$commands = $issue->getCommands();
if ($commands) {
$run_these = pht("Run these %d command(s):", count($commands));
$description .= phutil_render_tag(
'div',
array(
'class' => 'setup-issue-config',
),
phutil_render_tag('p', array(), $run_these).
phutil_render_tag('pre', array(), implode("\n", $commands)));
}
$extensions = $issue->getPHPExtensions();
if ($extensions) {
$install_these = pht(
"Install these %d PHP extension(s):", count($extensions));
$install_info = pht(
"You can usually install a PHP extension using <tt>apt-get</tt> or ".
"<tt>yum</tt>. Common package names are ".
"<tt>php-<em>extname</em></tt> or <tt>php5-<em>extname</em></tt>. ".
"Try commands like these:");
// TODO: We should do a better job of detecting how to install extensions
// on the current system.
$install_commands = array(
"$ sudo apt-get install php5-<em>extname</em> # Debian / Ubuntu",
"$ sudo yum install php5-<em>extname</em> # Red Hat / Derivatives",
);
$install_commands = implode("\n", $install_commands);
$fallback_info = pht(
"If those commands don't work, try Google. The process of installing ".
"PHP extensions is not specific to Phabricator, and any instructions ".
"you can find for installing them on your system should work. On Mac ".
"OS X, you might want to try Homebrew.");
$restart_info = pht(
"After installing new PHP extensions, <strong>restart your webserver ".
"for the changes to take effect</strong>.");
$description .= phutil_render_tag(
'div',
array(
'class' => 'setup-issue-config',
),
phutil_render_tag('p', array(), $install_these).
phutil_render_tag('pre', array(), implode("\n", $extensions)).
phutil_render_tag('p', array(), $install_info).
phutil_render_tag('pre', array(), $install_commands).
phutil_render_tag('p', array(), $fallback_info).
phutil_render_tag('p', array(), $restart_info));
}
$next = phutil_render_tag(
'div',
array(
'class' => 'setup-issue-next',
),
pht('To continue, resolve this problem and reload the page.'));
$name = phutil_render_tag(
'div',
array(
'class' => 'setup-issue-name',
),
phutil_escape_html($issue->getName()));
return phutil_render_tag(
'div',
array(
'class' => 'setup-issue',
),
$name.$description.$next);
}
private function renderPhabricatorConfig(array $configs) {
$table_info = phutil_render_tag(
'p',
array(),
pht(
"The current Phabricator configuration has these %d value(s):",
count($configs)));
$table = array();
foreach ($configs as $key) {
$table[] = '<tr>';
$table[] = '<th>'.phutil_escape_html($key).'</th>';
$value = PhabricatorEnv::getEnvConfig($key);
if ($value === null) {
$value = '<em>null</em>';
} else if ($value === false) {
$value = '<em>false</em>';
} else if ($value === true) {
$value = '<em>true</em>';
} else {
$value = phutil_escape_html($value);
}
$table[] = '<td>'.$value.'</td>';
$table[] = '</tr>';
}
$table = phutil_render_tag(
'table',
array(
),
implode("\n", $table));
$update_info = phutil_render_tag(
'p',
array(),
pht(
"To update these %d value(s), run these command(s) from the command ".
"line:",
count($configs)));
$update = array();
foreach ($configs as $key) {
$cmd = '<tt>phabricator/ $</tt> ./bin/config set '.
phutil_escape_html($key).' '.
'<em>value</em>';
$update[] = $cmd;
}
$update = phutil_render_tag('pre', array(), implode("\n", $update));
return phutil_render_tag(
'div',
array(
'class' => 'setup-issue-config',
),
self::renderSingleView(
array(
$table_info,
$table,
$update_info,
$update,
)));
}
private function renderPHPConfig(array $configs) {
$table_info = phutil_render_tag(
'p',
array(),
pht(
"The current PHP configuration has these %d value(s):",
count($configs)));
$table = array();
foreach ($configs as $key) {
$table[] = '<tr>';
$table[] = '<th>'.phutil_escape_html($key).'</th>';
$value = ini_get($key);
if ($value === null) {
$value = '<em>null</em>';
} else if ($value === false) {
$value = '<em>false</em>';
} else if ($value === true) {
$value = '<em>true</em>';
} else if ($value === '') {
$value = '<em>(empty string)</em>';
} else {
$value = phutil_escape_html($value);
}
$table[] = '<td>'.$value.'</td>';
$table[] = '</tr>';
}
$table = phutil_render_tag(
'table',
array(
),
implode("\n", $table));
ob_start();
phpinfo();
$phpinfo = ob_get_clean();
$rex = '@Loaded Configuration File\s*</td><td class="v">(.*?)</td>@i';
$matches = null;
$ini_loc = null;
if (preg_match($rex, $phpinfo, $matches)) {
$ini_loc = trim($matches[1]);
}
$rex = '@Additional \.ini files parsed\s*</td><td class="v">(.*?)</td>@i';
$more_loc = array();
if (preg_match($rex, $phpinfo, $matches)) {
$more_loc = trim($matches[1]);
if ($more_loc == '(none)') {
$more_loc = array();
} else {
$more_loc = preg_split('/\s*,\s*/', $more_loc);
}
}
if (!$ini_loc) {
$info = phutil_render_tag(
'p',
array(),
pht(
"To update these %d value(s), edit your PHP configuration file.",
count($configs)));
} else {
$info = phutil_render_tag(
'p',
array(),
pht(
"To update these %d value(s), edit your PHP configuration file, ".
"located here:",
count($configs)));
$info .= phutil_render_tag(
'pre',
array(),
phutil_escape_html($ini_loc));
}
if ($more_loc) {
$info .= phutil_render_tag(
'p',
array(),
pht(
"PHP also loaded these configuration file(s):",
count($more_loc)));
$info .= phutil_render_tag(
'pre',
array(),
phutil_escape_html(implode("\n", $more_loc)));
}
$info .= phutil_render_tag(
'p',
array(),
pht(
"After editing the PHP configuration, <strong>restart your webserver ".
"for the changes to take effect</strong>."));
return phutil_render_tag(
'div',
array(
'class' => 'setup-issue-config',
),
$table_info.$table.$info);
}
}

View file

@ -38,6 +38,8 @@ try {
return;
}
PhabricatorSetupCheck::willProcessRequest();
phabricator_detect_bad_base_uri();
$host = $_SERVER['HTTP_HOST'];

View file

@ -0,0 +1,86 @@
/**
* @provides setup-issue-css
*/
.setup-issue-background {
background-color: #edecef;
padding: 1em 0;
}
.setup-issue {
border: 1px solid #35393d;
margin: 15px auto;
max-width: 760px;
background: #ffffff;
box-shadow: 1px 2px 2px rgba(0, 0, 0, 0.25);
}
.setup-issue p {
margin: 1em 0;
}
.setup-issue table {
width: 90%;
margin: auto;
border-collapse: collapse;
border: 1px solid #dfdfdf;
}
.setup-issue table th {
text-align: right;
width: 30%;
background: #efefef;
border: 1px solid #dfdfdf;
padding: 8px;
}
.setup-issue table td {
border: 1px solid #dfdfdf;
padding: 8px;
}
.setup-issue pre {
width: 84%;
margin: auto;
border: 1px solid #dfdfdf;
padding: 10px 3%;
background: #efefef;
}
.setup-issue tt {
color: #666666;
}
.setup-issue em {
font-weight: bold;
}
.setup-issue-instructions {
font-size: 15px;
padding: 20px;
line-height: 1.4em;
background: #efefef;
border-bottom: 1px solid #bfbfbf;
}
.setup-issue-name {
padding: 15px;
background: #35393d;
color: #ffffff;
font-size: 15px;
font-weight: bold;
}
.setup-issue-next {
padding: 15px;
background: #35393d;
text-align: center;
font-size: 16px;
color: #ffffff;
}
.setup-issue-config {
margin: 15px 0;
padding: 0 20px;
}