This commit is contained in:
Will Rossiter 2015-11-19 23:04:42 +00:00
commit 9360f96672
7 changed files with 206 additions and 43 deletions

View File

@ -1,10 +1,12 @@
<?php
/**
* @package environmentcheck
*/
class DevCheckController extends Controller {
/**
* @var array
*/
public static $allowed_actions = array(
private static $allowed_actions = array(
'index'
);
@ -22,7 +24,7 @@ class DevCheckController extends Controller {
*
* @throws SS_HTTPResponse_Exception
*/
function index($request) {
public function index($request) {
$suite = 'check';
if ($name = $request->param('Suite')) {

View File

@ -1,25 +1,29 @@
<?php
/**
* A public URL which requires no authenication which returns a simple Ok, Fail
* check.
*
* For a more detailed check, see {@link DevCheckController}
*
* @package environmentcheck
*/
class DevHealthController extends Controller {
/**
* @var array
*/
public static $allowed_actions = array(
private static $allowed_actions = array(
'index'
);
/**
* @return EnvironmentChecker
*
* @throws SS_HTTPResponse_Exception
*/
function index() {
// health check does not require permission to run
public function index() {
Config::inst()->update('EnvironmentChecker', 'template', 'DevHealthController');
$checker = new EnvironmentChecker('health', 'Site health');
$checker->init('');
$checker->setErrorCode(404);
$e = new EnvironmentChecker('health', 'Site health');
$e->init(''); //empty permission check, the "health" check does not require a permission check to run
$e->setErrorCode(500);
return $checker;
return $e;
}
}

View File

@ -45,6 +45,8 @@ class EnvironmentChecker extends RequestHandler {
* @param string $checkSuiteName
* @param string $title
*/
private static $template = 'EnvironmentChecker';
function __construct($checkSuiteName, $title) {
parent::__construct();
@ -56,8 +58,8 @@ class EnvironmentChecker extends RequestHandler {
* @param string $permission
*
* @throws SS_HTTPResponse_Exception
*/
function init($permission = 'ADMIN') {
*/
public function init($permission = 'ADMIN') {
// if the environment supports it, provide a basic auth challenge and see if it matches configured credentials
if(defined('ENVCHECK_BASICAUTH_USERNAME') && defined('ENVCHECK_BASICAUTH_PASSWORD')) {
if(isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) {
@ -101,7 +103,7 @@ class EnvironmentChecker extends RequestHandler {
$member = Member::currentUser();
}
if(!$member) {
if(!$member && $permission) {
$member = BasicAuth::requireLogin('Environment Checker', $permission, false);
}
@ -132,7 +134,7 @@ class EnvironmentChecker extends RequestHandler {
* @return SS_HTTPResponse
*/
function index() {
$response = new SS_HTTPResponse;
$response = new SS_HTTPResponse();
$result = EnvironmentCheckSuite::inst($this->checkSuiteName)->run();
if(!$result->ShouldPass()) {
@ -144,10 +146,15 @@ class EnvironmentChecker extends RequestHandler {
"Title" => $this->title,
"Name" => $this->checkSuiteName,
"ErrorCode" => $this->errorCode,
))->renderWith("EnvironmentChecker");
))->renderWith($this->config()->template);
if (self::$email_results && !$result->ShouldPass()) {
$email = new Email(self::$from_email_address, self::$to_email_address, $this->title, $resultText);
$email = new Email(
self::$from_email_address,
self::$to_email_address,
$this->title,
$resultText
);
$email->send();
}

View File

@ -0,0 +1,43 @@
<?php
/**
* @package environmentcheck
*/
class BinaryExistsCheck implements EnvironmentCheck {
protected $binary;
function __construct($binary = "Member") {
$this->binary = $binary;
}
function check() {
$command = (PHP_OS == 'WINNT') ? 'where' : 'which';
$binary = $this->binary;
$process = proc_open(
"$command $binary",
array(
0 => array("pipe", "r"),
1 => array("pipe", "w"),
2 => array("pipe", "w"),
),
$pipes
);
if ($process !== false) {
$stdout = stream_get_contents($pipes[1]);
$stderr = stream_get_contents($pipes[2]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);
if($stdout != '') {
return array(EnvironmentCheck::OK, '');
}
}
return array(EnvironmentCheck::ERROR, 'Could not execute '. $this->binary . ' in ');
}
}

View File

@ -3,8 +3,11 @@
/**
* Check that the connection to the database is working, by ensuring that the table exists and that
* the table contains some records.
*
* @package environmentcheck
*/
class DatabaseCheck implements EnvironmentCheck {
protected $checkTable;
/**

View File

@ -8,28 +8,40 @@
This module adds an API for running environment checks to your API.
* `dev/health` - A public URL that performs a quick check that this environment is functioning. This could be tied to a load balancer, for example.
* `dev/check` - An admin-only URL that performs a more comprehensive set of checks. This could be tied to a deployment system, for example.
* `dev/health` - A public URL that performs a quick check that this environment is functioning. This could be tied to
a load balancer, for example.
* `dev/check` - An admin-only URL that performs a more comprehensive set of checks. This could be tied to a deployment
system, for example.
* `dev/check/<suite>` - Check a specific suite (admin only)
## Aren't these just unit tests?
Almost, but not really. Environment checks differ from unit tests in two important ways:
* **They test environment specific settings.** Unit tests are designed to use dummy data and mock interfaces to external system. Environment checks check the real systems and data that the given environment is actually connected to.
* **They can't modify data.** Because these checks will run using production databases, they can't go modifying the data in there. This is the biggest reason why we haven't used the same base class as a unit test for writing environment checks - we wanted to make it impossible to accidentally plug a unit test into the environment checker!
* **They test environment specific settings.** Unit tests are designed to use dummy data and mock interfaces to
external system. Environment checks check the real systems and data that the given environment is actually connected
to.
* **They can't modify data.** Because these checks will run using production databases, they can't go modifying the
data in there. This is the biggest reason why we haven't used the same base class as a unit test for writing
environment checks - we wanted to make it impossible to accidentally plug a unit test into the environment checker!
## Installation
```sh
$ composer require silverstripe/environmentalcheck
$ composer require silverstripe/environmentcheck
```
You'll also need to run `/dev/build`.
## Registering Checks
There are two ways to register your checks, both can be used at the same time. The checks will be appended to the suite.
>>>>>>> Allow /dev/health to be public:README.md
### Activating Directly
Register checks in your own `_config.php` - see the `_config.php` in this module for some defaults.
Register checks in your own `_config.php` - see the `_config.php` in this
module for some defaults.
```php
EnvironmentCheckSuite::register('health', 'DatabaseCheck', "Can we connect to the database?");
@ -69,22 +81,25 @@ EnvironmentCheckSuite:
## Available checks
* `DatabaseCheck`: Check that the connection to the database is working, by ensuring that the table exists and that the table contain some records.
* `DatabaseCheck`: Check that the connection to the database is working, by ensuring that the table exists and that
the table contain some records.
* `URLCheck`: Check that a given URL is functioning, by default, the homepage.
* `HasFunctionCheck`: Check that the given function exists.
This can be used to check that PHP modules or features are installed.
* `HasClassCheck`: Check that the given class exists.
This can be used to check that PHP modules or features are installed.
* `FileWriteableCheck`: Check that the given file is writeable.
* `FileAgeCheck`: Checks for the maximum age of one or more files or folders.
Useful for files which should be frequently auto-generated,
like static caches, as well as for backup files and folders.
* `HasFunctionCheck`: Check that the given function exists. This can be used to check that PHP modules or features
are installed.
* `HasClassCheck`: Check that the given class exists. This can be used to check that PHP modules or features are
installed.
* `FileWriteableCheck`: Check that the given file is writable.
* `FileAgeCheck`: Checks for the maximum age of one or more files or folders. Useful for files which should be
frequently auto-generated, like static caches, as well as for backup files and folders.
* `ExternalURLCheck`: Checks that one or more URLs are reachable via HTTP.
* `SMTPConnectCheck`: Checks if the SMTP connection configured through PHP.ini works as expected.
* `SolrIndexCheck`: Checks if the Solr cores of given class are available.
* `BinaryExistsCheck`: Checks if a binary is available on the server
## Authentication
`dev/health` is public and requires no authentication.
By default, accessing the `dev/check` URL will not require authentication on CLI and dev environments, but if you're
trying to access it on a live or test environment, it will respond with a 403 HTTP status unless you're logged in as
an administrator on the site.
@ -106,18 +121,26 @@ To add more checks, you should put additional `EnvironmentCheckSuite::register`
EnvironmentCheckSuite::register('check', 'HasFunctionCheck("curl_init")', "Does PHP have CURL support?");
EnvironmentCheckSuite::register('check', 'HasFunctionCheck("imagecreatetruecolor")', "Does PHP have GD2 support?");
The first argument is the name of the check suite. There are two built-in check suites, "health", and "check", corresponding to the `dev/health` and `dev/check` URLs. If you wish, you can create your own check suites and execute them on other URLs. You can also add a check to more than one suite by passing the first argument as an array.
The first argument is the name of the check suite. There are two built-in check suites, "health", and "check",
corresponding to the `dev/health` and `dev/check` URLs. If you wish, you can create your own check suites and execute
them on other URLs. You can also add a check to more than one suite by passing the first argument as an array.
The module comes bundled with a few checks in `DefaultHealthChecks.php`. However, to test your own application, you probably want to write custom checks.
The module comes bundled with a few checks in `DefaultHealthChecks.php`. However, to test your own application, you
probably want to write custom checks.
* Implement the `EnvironmentCheck` interface
* Define the `check()` function, which returns a 2 element array:
* The first element is one of `EnvironmentCheck::OK`, `EnvironmentCheck::WARNING`, `EnvironmentCheck::ERROR`, depending on the status of the check
* The first element is one of `EnvironmentCheck::OK`, `EnvironmentCheck::WARNING`, `EnvironmentCheck::ERROR`,
depending on the status of the check.
* The second element is a string describing the response.
Here is a simple example of how you might create a check to test your own code. In this example, we are checking that an instance of the `MyGateway` class will return "foo" when `call()` is called on it. Testing interfaces with 3rd party systems is a common use case for custom environment checks.
Here is a simple example of how you might create a check to test your own code. In this example, we are checking that
an instance of the `MyGateway` class will return "foo" when `call()` is called on it. Testing interfaces with 3rd
party systems is a common use case for custom environment checks.
:::php
<?php
class MyGatewayCheck implements EnvironmentCheck {
protected $checkTable;
@ -144,9 +167,14 @@ Once you have created your custom check class, don't forget to register it in a
### Using other environment check suites
If you want to use the same UI as dev/health and dev/check, you can create an `EnvironmentChecker` object. This class is a `RequestHandler` and so can be returned from an action handler. The first argument to the `EnvironmentChecker` constructor is the suite name. For example:
If you want to use the same UI as dev/health and dev/check, you can create an `EnvironmentChecker` object. This class
is a `RequestHandler` and so can be returned from an action handler. The first argument to the `EnvironmentChecker`
constructor is the suite name. For example:
<?php
class DevHealth extends Controller {
function index() {
$e = new EnvironmentChecker('health', 'Site health');
return $e;

View File

@ -0,0 +1,76 @@
<!doctype html>
<html>
<head>
<title><% if ShouldPass %>Site is available<% else %>Site is unavailable<% end_if %></title>
<style>
* {
font-family: Helvetica, Arial;
font-size: 12px;
}
.subtext {
margin-top: -10px;
font-size: 10px;
}
h1 {
font-size: 30px;
margin-bottom: 3px;
margin-left: 8px;
}
h2 {
font-size: 16px;
margin: 2px 0 10px 8px;
}
p {
margin-left: 10px;
}
table {
border-collapse: collapse;
}
table th {
color: white;
background-color: #777;
/*border: 1px #aaa solid;*/
padding: 10px;
text-align: left;
}
table td {
background-color: #eee;
/*border: 1px #ddd solid;*/
padding: 5px 10px;
}
table tr:nth-child(odd) td {
background-color: #ddd;
}
.OK {
color: green;
}
.WARNING {
color: orange;
font-weight: bold;
}
.ERROR {
color: red;
font-weight: bold;
}
</style>
</head>
<body>
<% if ShouldPass %>
<p>Site is available</p>
<p class="subtext">(you may check for the presence of the text 'Site is available' rather than an HTTP $ErrorCode error on this page, if you prefer.)</p>
<% else %>
<% if Name == check %>
<p><b>A subsystem of the site is unavailable, but the site remains operational</b></p>
<% else %>
<p><b>Site is not available</b></p>
<% end_if %>
<% end_if %>
</body>
</html>