Compare commits
162 Commits
Author | SHA1 | Date |
---|---|---|
Michal Kleiner | 9881c03c22 | |
Guy Sartorelli | e8517ebfb0 | |
Michal Kleiner | 28ca036345 | |
Guy Sartorelli | 16caad30b2 | |
Maxime Rainville | 6f6c30bdcc | |
Guy Sartorelli | f918209e35 | |
Maxime Rainville | 714de64252 | |
Steve Boyd | d9d245a0da | |
Guy Sartorelli | 9d5f8fce7d | |
Guy Sartorelli | f12698f47f | |
Guy Sartorelli | 6f0d1c84f2 | |
Guy Sartorelli | 15a8bca241 | |
Steve Boyd | 94ff5b621c | |
Will Rossiter | 0e4867f736 | |
Steve Boyd | 7e59b7e88b | |
Steve Boyd | e4b8934148 | |
Guy Sartorelli | 737ac0d8ed | |
Steve Boyd | 3378dc7a99 | |
Steve Boyd | 58caa67c96 | |
Steve Boyd | 7be4c0dbf3 | |
Guy Sartorelli | 7ba4acc4b2 | |
Steve Boyd | a76c3b89ea | |
Guy Sartorelli | 7f6c03b0cb | |
Will Rossiter | 3e3907d14c | |
Guy Sartorelli | d3d20e5539 | |
Steve Boyd | b7554a5fc9 | |
Daniel Hensby | cb13f4ec12 | |
Steve Boyd | 3902a1d688 | |
Steve Boyd | aa1311fa01 | |
Steve Boyd | 47b184df5d | |
Maxime Rainville | 94fa4ee1f0 | |
Maxime Rainville | 3f2fbdaea0 | |
Steve Boyd | 05dd7bc0a7 | |
Steve Boyd | f6e001bf55 | |
Maxime Rainville | e5b77ff325 | |
Steve Boyd | 029078d157 | |
Steve Boyd | f14fe7c40d | |
Maxime Rainville | 585b6564b2 | |
Maxime Rainville | dd037c8f50 | |
Steve Boyd | d29a062562 | |
Steve Boyd | 2c4d32af6b | |
Robbie Averill | 7c0b7b0ca7 | |
Steve Boyd | b765ec87c1 | |
Steve Boyd | 56dab108c9 | |
Steve Boyd | b01567767c | |
Maxime Rainville | e19e476cf4 | |
Maxime Rainville | dfd9c15d56 | |
Maxime Rainville | 23104b77aa | |
Steve Boyd | 1aa18e7ced | |
Maxime Rainville | 08409bb9ca | |
Damian Mooyman | b2f6e06795 | |
Garion Herman | 0685dfad75 | |
Garion Herman | 5f2591a5c1 | |
Garion Herman | 2bbebc321e | |
Sam Minnée | 9829476dae | |
Garion Herman | 49d7d5f2a3 | |
Robbie Averill | f347d1d5c0 | |
Serge Latyntcev | c490c4e5a5 | |
Aaron Carlino | d8452c681a | |
Serge Latyntcev | e757c9477c | |
Damian Mooyman | 6fe0b1889e | |
Damian Mooyman | 40050e9ab4 | |
Robbie Averill | efd996d618 | |
Robbie Averill | 922d810a3a | |
Damian Mooyman | a54400681f | |
Dylan Wagstaff | c5783a2450 | |
Frank Mullenger | da9eddc0e1 | |
Frank Mullenger | 8f7f8570f2 | |
Frank Mullenger | 578c73e6ce | |
Robbie Averill | 128ddb56ed | |
Frank Mullenger | ace6c51aae | |
Frank Mullenger | 540710ab0f | |
Frank Mullenger | 2674beb94a | |
Frank Mullenger | 90f0556ca4 | |
Frank Mullenger | 27994e51fa | |
Guy Marriott | 986bc370b9 | |
Frank Mullenger | c1bfd5640d | |
Frank Mullenger | df59195634 | |
Guy Marriott | f74c371e16 | |
Guy Marriott | 41763c6042 | |
Guy Marriott | f8d35639a0 | |
Frank Mullenger | e323674adf | |
Frank Mullenger | da6a8a8ee4 | |
Frank Mullenger | 9940c5e8ab | |
Frank Mullenger | 1771a6ef11 | |
Robbie Averill | 05ac221c2f | |
Dylan Wagstaff | ed61c6bb3a | |
Robbie Averill | 014c23804f | |
Robbie Averill | f6652c2b37 | |
Robbie Averill | 161f0aefaf | |
Robbie Averill | a788b37de5 | |
Robbie Averill | 449a265301 | |
Dylan Wagstaff | 7ad2334f49 | |
Robbie Averill | 250c8d2e00 | |
Robbie Averill | e5651c429c | |
Robbie Averill | 564936ee01 | |
Dylan Wagstaff | b89aefaa20 | |
Daniel Hensby | 152c11638b | |
Robbie Averill | e6e5ac96df | |
Dylan Wagstaff | 1ce832f859 | |
Robbie Averill | b1f5ef184b | |
Robbie Averill | bf5076f2df | |
Dylan Wagstaff | 227b64cbcc | |
Robbie Averill | 0e84848c3d | |
Daniel Hensby | ab610bb55e | |
Dylan Wagstaff | fb0bddec2c | |
Daniel Hensby | 1fe68e7d4a | |
Robbie Averill | a68532cef0 | |
Robbie Averill | 2131840da4 | |
Andrew Aitken-Fincham | 9dcfc4f337 | |
Daniel Hensby | 7204c4792d | |
Andrew Aitken-Fincham | 5b4adb286e | |
Daniel Hensby | 04b7cb2ff1 | |
Andrew Aitken-Fincham | 1e727bf8c6 | |
Sam Minnee | 5897559cd2 | |
Robbie Averill | b9af2d0734 | |
Robbie Averill | 745a9f93ef | |
Robbie Averill | 254ed4801f | |
Daniel Hensby | 1f6ba594a6 | |
Daniel Hensby | b78c2af4aa | |
Will Rossiter | ad54d788d4 | |
Daniel Hensby | 582c4173e1 | |
Ed | c1ac12193e | |
Damian Mooyman | a8c0963c69 | |
Stig Lindqvist | f48efcf963 | |
Stig Lindqvist | c24c52e77f | |
Stig Lindqvist | a19fcb42e6 | |
Damian Mooyman | 3135c1eec7 | |
Damian Mooyman | b084f5433c | |
Scott Hutchinson | 26817742d2 | |
Damian Mooyman | 580a2deafe | |
Damian Mooyman | c814d653f9 | |
helpfulrobot | f2d0a39ff7 | |
Damian Mooyman | 18d79e1b12 | |
Daniel Hensby | e090e4adb7 | |
helpfulrobot | b62632d6ab | |
helpfulrobot | 5f1f1fcd53 | |
Damian Mooyman | d6a65e1614 | |
Damian Mooyman | c9c6076156 | |
Damian Mooyman | 9223736d30 | |
Sam Minnée | 1ef045d1b7 | |
Ingo Schommer | ea047df63d | |
Ingo Schommer | 81c7f2ba27 | |
Daniel Hensby | b9bfacf223 | |
Daniel Hensby | e45a9019a1 | |
Daniel Hensby | 79e75aede9 | |
helpfulrobot | 3a3dd59964 | |
helpfulrobot | 6c92cf3a8d | |
helpfulrobot | a24b6f23ba | |
Scott Hutchinson | ac27101e1c | |
helpfulrobot | e2fdefab59 | |
helpfulrobot | 8ccafb5cd4 | |
Damian Mooyman | be424c145d | |
helpfulrobot | 0f67667ce5 | |
Damian Mooyman | 9b3d3be032 | |
Christopher Pitt | 53aafd97ba | |
Damian Mooyman | 7e28448664 | |
Ingo Schommer | 631b7ba502 | |
Stig Lindqvist | 98126dc448 | |
Sean Harvey | 2c0c77df13 | |
Stig Lindqvist | 20f0a2bf88 | |
Sean Harvey | e11e4349f9 |
|
@ -0,0 +1,17 @@
|
|||
# For more information about the properties used in this file,
|
||||
# please see the EditorConfig documentation:
|
||||
# http://editorconfig.org
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[{*.yml,package.json}]
|
||||
indent_size = 2
|
||||
|
||||
# The indent size used in the package.json file cannot be changed:
|
||||
# https://github.com/npm/npm/pull/3180#issuecomment-16336516
|
|
@ -0,0 +1,7 @@
|
|||
/tests export-ignore
|
||||
/.gitattributes export-ignore
|
||||
/.gitignore export-ignore
|
||||
/.travis.yml export-ignore
|
||||
/.scrutinizer.yml export-ignore
|
||||
/codecov.yml export-ignore
|
||||
/phpunit.xml.dist export-ignore
|
|
@ -0,0 +1,11 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
name: CI
|
||||
uses: silverstripe/gha-ci/.github/workflows/ci.yml@v1
|
|
@ -0,0 +1,16 @@
|
|||
name: Dispatch CI
|
||||
|
||||
on:
|
||||
# At 1:20 PM UTC, only on Wednesday and Thursday
|
||||
schedule:
|
||||
- cron: '20 13 * * 3,4'
|
||||
|
||||
jobs:
|
||||
dispatch-ci:
|
||||
name: Dispatch CI
|
||||
# Only run cron on the silverstripe account
|
||||
if: (github.event_name == 'schedule' && github.repository_owner == 'silverstripe') || (github.event_name != 'schedule')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Dispatch CI
|
||||
uses: silverstripe/gha-dispatch-ci@v1
|
|
@ -0,0 +1,17 @@
|
|||
name: Keepalive
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
# The 4th of every month at 10:50am UTC
|
||||
schedule:
|
||||
- cron: '50 10 4 * *'
|
||||
|
||||
jobs:
|
||||
keepalive:
|
||||
name: Keepalive
|
||||
# Only run cron on the silverstripe account
|
||||
if: (github.event_name == 'schedule' && github.repository_owner == 'silverstripe') || (github.event_name != 'schedule')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Keepalive
|
||||
uses: silverstripe/gha-keepalive@v1
|
|
@ -0,0 +1,29 @@
|
|||
mappings:
|
||||
EnvironmentCheck: SilverStripe\EnvironmentCheck\EnvironmentCheck
|
||||
EnvironmentChecker: SilverStripe\EnvironmentCheck\EnvironmentChecker
|
||||
EnvironmentCheckSuite: SilverStripe\EnvironmentCheck\EnvironmentCheckSuite
|
||||
EnvironmentCheckSuiteResult: SilverStripe\EnvironmentCheck\EnvironmentCheckSuiteResult
|
||||
DatabaseCheck: SilverStripe\EnvironmentCheck\Checks\DatabaseCheck
|
||||
ExternalURLCheck: SilverStripe\EnvironmentCheck\Checks\ExternalURLCheck
|
||||
FileAccessibilityAndValidationCheck: SilverStripe\EnvironmentCheck\Checks\FileAccessibilityAndValidationCheck
|
||||
FileAgeCheck: SilverStripe\EnvironmentCheck\Checks\FileAgeCheck
|
||||
FileWriteableCheck: SilverStripe\EnvironmentCheck\Checks\FileWriteableCheck
|
||||
HasClassCheck: SilverStripe\EnvironmentCheck\Checks\HasClassCheck
|
||||
HasFunctionCheck: SilverStripe\EnvironmentCheck\Checks\HasFunctionCheck
|
||||
SMTPConnectCheck: SilverStripe\EnvironmentCheck\Checks\SMTPConnectCheck
|
||||
SolrIndexCheck: SilverStripe\EnvironmentCheck\Checks\SolrIndexCheck
|
||||
URLCheck: SilverStripe\EnvironmentCheck\Checks\URLCheck
|
||||
DevCheckController: SilverStripe\EnvironmentCheck\Controllers\DevCheckController
|
||||
DevHealthController: SilverStripe\EnvironmentCheck\Controllers\DevHealthController
|
||||
EnvironmentCheckerTest: SilverStripe\EnvironmentCheck\Tests\EnvironmentCheckerTest
|
||||
EnvironmentCheckerTest_CheckNoErrors: SilverStripe\EnvironmentCheck\Tests\EnvironmentCheckerTest_CheckNoErrors
|
||||
EnvironmentCheckerTest_CheckWarnings: SilverStripe\EnvironmentCheck\Tests\EnvironmentCheckerTest_CheckWarnings
|
||||
EnvironmentCheckerTest_CheckErrors: SilverStripe\EnvironmentCheck\Tests\EnvironmentCheckerTest_CheckErrors
|
||||
DatabaseCheckTest: SilverStripe\EnvironmentCheck\Tests\Checks\DatabaseCheckTest
|
||||
ExternalURLCheckTest: SilverStripe\EnvironmentCheck\Tests\Checks\ExternalURLCheckTest
|
||||
FileWritableCheckTest: SilverStripe\EnvironmentCheck\Tests\Checks\FileWritableCheckTest
|
||||
HasClassCheckTest: SilverStripe\EnvironmentCheck\Tests\Checks\HasClassCheckTest
|
||||
HasFunctionCheckTest: SilverStripe\EnvironmentCheck\Tests\Checks\HasFunctionCheckTest
|
||||
URLCheckTest: SilverStripe\EnvironmentCheck\Tests\Checks\URLCheckTest
|
||||
DevCheckControllerTest: SilverStripe\EnvironmentCheck\Tests\Controllers\DevCheckControllerTest
|
||||
DevHealthControllerTest: SilverStripe\EnvironmentCheck\Tests\Controllers\DevHealthControllerTest
|
190
README.md
190
README.md
|
@ -1,13 +1,21 @@
|
|||
# SilverStripe Environment Checker Module
|
||||
# Silverstripe Environment Checker Module
|
||||
|
||||
Initially developed by Sam Minnée, thanks to Will Rossiter.
|
||||
[![CI](https://github.com/silverstripe/silverstripe-environmentcheck/actions/workflows/ci.yml/badge.svg)](https://github.com/silverstripe/silverstripe-environmentcheck/actions/workflows/ci.yml)
|
||||
[![Silverstripe supported module](https://img.shields.io/badge/silverstripe-supported-0071C4.svg)](https://www.silverstripe.org/software/addons/silverstripe-commercially-supported-module-list/)
|
||||
|
||||
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.
|
||||
* `health/check` - 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)
|
||||
|
||||
## Requirements
|
||||
|
||||
* Silverstripe 4.x
|
||||
* PHP 7.3
|
||||
|
||||
For Silverstripe 3.x support, please use a `1.x` tagged release.
|
||||
|
||||
## Aren't these just unit tests?
|
||||
|
||||
Almost, but not really. Environment checks differ from unit tests in two important ways:
|
||||
|
@ -17,23 +25,29 @@ Almost, but not really. Environment checks differ from unit tests in two importa
|
|||
|
||||
## Installation
|
||||
|
||||
There are two ways to register your checks, both can be used at the same time. The checks will be appended to the suite.
|
||||
|
||||
### Direct method
|
||||
|
||||
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?");
|
||||
EnvironmentCheckSuite::register('check', 'URLCheck("")', "Is the homepage accessible?");
|
||||
```sh
|
||||
$ composer require silverstripe/environmentcheck
|
||||
```
|
||||
|
||||
### Config system method
|
||||
You'll also need to run `/dev/build`.
|
||||
|
||||
### Activating Directly
|
||||
|
||||
Register checks in your own `_config.php` - see the `_config.php` in this module for some defaults. Don't forget to
|
||||
either use a fully-qualified (namespaced) class name for `EnvironmentCheckSuite`, or `use` (import) the namespaced class
|
||||
first.
|
||||
|
||||
```php
|
||||
EnvironmentCheckSuite::register('health', 'DatabaseCheck', 'Can we connect to the database?');
|
||||
EnvironmentCheckSuite::register('check', 'URLCheck("")', 'Is the homepage accessible?');
|
||||
```
|
||||
|
||||
### Activating Via Config
|
||||
|
||||
Register your checks on the `EnvironmentCheckSuite`. The same named check may be used multiple times.
|
||||
|
||||
```yaml
|
||||
EnvironmentCheckSuite:
|
||||
SilverStripe\EnvironmentCheck\EnvironmentCheckSuite:
|
||||
registered_checks:
|
||||
db:
|
||||
definition: 'DatabaseCheck("Page")'
|
||||
|
@ -53,7 +67,7 @@ You can also disable checks configured this way. This is handy if you want to ov
|
|||
by some other module. Just set the "state" property of the check to "disabled" like this:
|
||||
|
||||
```yaml
|
||||
EnvironmentCheckSuite:
|
||||
SilverStripe\EnvironmentCheck\EnvironmentCheckSuite:
|
||||
registered_checks:
|
||||
db:
|
||||
state: disabled
|
||||
|
@ -68,12 +82,41 @@ EnvironmentCheckSuite:
|
|||
* `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.
|
||||
* `FileAccessibilityAndValidationCheck`: Check that a given file is accessible and optionally matches a given format.
|
||||
* `FileAgeCheck`: Checks for the maximum age of one or more files or folders.
|
||||
Useful for files which should be frequently auto-generated,
|
||||
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.
|
||||
* `SessionCheck`: Checks that a given URL does not generate a session.
|
||||
* `CacheHeadersCheck`: Check cache headers in response for directives that must either be included or excluded as well
|
||||
checking for existence of ETag.
|
||||
* `EnvTypeCheck`: Checks environment type, dev and test should not be used on production environments.
|
||||
|
||||
## Monitoring Checks
|
||||
|
||||
Checks will return an appropriate HTTP status code, so are easy to hook into common uptime montoring
|
||||
solutions like pingdom.com.
|
||||
|
||||
You can also have the environment checker email results with the following configuration:
|
||||
|
||||
```yaml
|
||||
SilverStripe\EnvironmentCheck\EnvironmentChecker:
|
||||
email_results: true
|
||||
to_email_address: support@test.com
|
||||
from_email_address: admin@test.com
|
||||
```
|
||||
|
||||
Errors can be logged via the standard Silverstripe PSR-3 compatible logging. Each check will cause an individual log
|
||||
entry. You can choose to enable logging separately for warnings and errors, identified through the
|
||||
result of `EnvironmentCheck->check()`.
|
||||
|
||||
```yaml
|
||||
SilverStripe\EnvironmentCheck\EnvironmentChecker:
|
||||
log_results_warning: true
|
||||
log_results_error: true
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
|
@ -82,73 +125,90 @@ trying to access it on a live or test environment, it will respond with a 403 HT
|
|||
an administrator on the site.
|
||||
|
||||
You may wish to have an automated service check `dev/check` periodically, but not want to open it up for public access.
|
||||
You can enable basic authentication by defining the following in your environment:
|
||||
You can enable basic authentication by defining the following in your environment (`.env` file):
|
||||
|
||||
define('ENVCHECK_BASICAUTH_USERNAME', 'test');
|
||||
define('ENVCHECK_BASICAUTH_PASSWORD', 'password');
|
||||
```
|
||||
ENVCHECK_BASICAUTH_USERNAME="test"
|
||||
ENVCHECK_BASICAUTH_PASSWORD="password"
|
||||
```
|
||||
|
||||
Now if you access `dev/check` in a browser it will pop up a basic auth popup, and if the submitted username and password
|
||||
match the ones defined the username and password defined in the environment, access will be granted to the page.
|
||||
|
||||
## Adding more checks
|
||||
|
||||
To add more checks, you should put additional `EnvironmentCheckSuite::register` calls into your `_config.php`. See the `_config.php` file of this module for examples.
|
||||
To add more checks, you should put additional `EnvironmentCheckSuite::register` calls into your `_config.php`.
|
||||
See the `_config.php` file of this module for examples.
|
||||
|
||||
:::php
|
||||
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.
|
||||
```php
|
||||
EnvironmentCheckSuite::register('check', 'HasFunctionCheck("curl_init")', "Does PHP have CURL support?");
|
||||
EnvironmentCheckSuite::register('check', 'HasFunctionCheck("imagecreatetruecolor")', "Does PHP have GD2 support?");
|
||||
```
|
||||
|
||||
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 first argument is the name of the check suite. There are two built-in check suites, "health", and "check", corresponding to the `health/check` 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.
|
||||
|
||||
* Implement the `EnvironmentCheck` interface
|
||||
To test your own application, you probably want to write custom checks:
|
||||
|
||||
* Implement the `SilverStripe\EnvironmentCheck\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 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.
|
||||
|
||||
:::php
|
||||
class MyGatewayCheck implements EnvironmentCheck {
|
||||
protected $checkTable;
|
||||
```php
|
||||
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
|
||||
|
||||
class MyGatewayCheck implements EnvironmentCheck
|
||||
{
|
||||
protected $checkTable;
|
||||
|
||||
function check()
|
||||
{
|
||||
$g = new \MyGateway;
|
||||
|
||||
$response = $g->call();
|
||||
$expectedResponse = 'foo';
|
||||
|
||||
if($response == null) {
|
||||
return array(EnvironmentCheck::ERROR, "MyGateway didn't return a response");
|
||||
} else if($response != $expectedResponse) {
|
||||
return array(EnvironmentCheck::WARNING, "MyGateway returned unexpected response $response");
|
||||
}
|
||||
return array(EnvironmentCheck::OK, '');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
function check() {
|
||||
$g = new MyGateway;
|
||||
|
||||
$response = $g->call();
|
||||
$expectedResponse = "foo";
|
||||
|
||||
if($response == null) {
|
||||
return array(EnvironmentCheck::ERROR, "MyGateway didn't return a response");
|
||||
} else if($response != $expectedResponse) {
|
||||
return array(EnvironmentCheck::WARNING, "MyGateway returned unexpected response $response");
|
||||
} else {
|
||||
return array(EnvironmentCheck::OK, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Once you have created your custom check class, don't forget to register it in a check suite
|
||||
|
||||
:::php
|
||||
EnvironmentCheckSuite::register('check', 'MyGatewayCheck', "Can I connect to the gateway?");
|
||||
|
||||
```php
|
||||
EnvironmentCheckSuite::register('check', 'MyGatewayCheck', 'Can I connect to the gateway?');
|
||||
```
|
||||
|
||||
### 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 `health/check` 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:
|
||||
|
||||
class DevHealth extends Controller {
|
||||
function index() {
|
||||
$e = new EnvironmentChecker('health', 'Site health');
|
||||
return $e;
|
||||
}
|
||||
}
|
||||
|
||||
If you wish to embed an environment check suite in another, you can use the following call.
|
||||
```php
|
||||
use SilverStripe\Control\Controller;
|
||||
|
||||
class DevHealth extends Controller
|
||||
{
|
||||
function index()
|
||||
{
|
||||
$e = new EnvironmentChecker('health', 'Site health');
|
||||
return $e;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If you wish to embed an environment check suite in another, you can use the following call:
|
||||
|
||||
```php
|
||||
$result = EnvironmentCheckSuite::inst('health')->run();
|
||||
```
|
||||
|
||||
$result = EnvironmentCheckSuite::inst("health")->run();
|
||||
|
||||
`$result` will contain a `EnvironmentCheckSuiteResult` object
|
||||
|
||||
* `$result->ShouldPass()`: Return a boolean of whether or not the tests passed.
|
||||
|
@ -156,3 +216,13 @@ If you wish to embed an environment check suite in another, you can use the foll
|
|||
* `$result->Details()`: A `DataObjectSet` of details about the result of each check in the suite.
|
||||
|
||||
See `EnvironmentChecker.ss` to see how these can be used to build a UI.
|
||||
|
||||
## Versioning
|
||||
|
||||
This library follows [Semver](http://semver.org). According to Semver, you will be able to upgrade to any minor or patch version of this library without any breaking changes to the public API. Semver also requires that we clearly define the public API for this library.
|
||||
|
||||
All methods, with `public` visibility, are part of the public API. All other methods are not part of the public API. Where possible, we'll try to keep `protected` methods backwards-compatible in minor/patch versions, but if you're overriding methods then please test your work before upgrading.
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
Please [create an issue](http://github.com/silverstripe/silverstripe-environmentcheck/issues) for any bugs you've found, or features you're missing.
|
||||
|
|
89
_config.php
89
_config.php
|
@ -1,21 +1,70 @@
|
|||
<?php
|
||||
|
||||
// // These power dev/health, which can be used by load balancers and other such systems
|
||||
// EnvironmentCheckSuite::register('health', 'DatabaseCheck');
|
||||
|
||||
// // These power dev/check, which is used for diagnostics and for deployment
|
||||
// EnvironmentCheckSuite::register('check', 'DatabaseCheck("Member")', "Is the database accessible?");
|
||||
// EnvironmentCheckSuite::register('check', 'URLCheck("")', "Is the homepage accessible?");
|
||||
|
||||
// EnvironmentCheckSuite::register('check', 'HasFunctionCheck("curl_init")', "Does PHP have CURL support?");
|
||||
// EnvironmentCheckSuite::register('check', 'HasFunctionCheck("imagecreatetruecolor")', "Does PHP have GD2 support?");
|
||||
// EnvironmentCheckSuite::register('check', 'HasFunctionCheck("xml_set_object")', "Does PHP have XML support?");
|
||||
// EnvironmentCheckSuite::register('check', 'HasFunctionCheck("token_get_all")', "Does PHP have tokenizer support?");
|
||||
// EnvironmentCheckSuite::register('check', 'HasFunctionCheck("iconv")', "Does PHP have iconv support?");
|
||||
// EnvironmentCheckSuite::register('check', 'HasFunctionCheck("hash")', "Does PHP have hash support?");
|
||||
// EnvironmentCheckSuite::register('check', 'HasFunctionCheck("session_start")', "Does PHP have session support?");
|
||||
// EnvironmentCheckSuite::register('check', 'HasClassCheck("DOMDocument")', "Does PHP have DOMDocument support?");
|
||||
|
||||
// EnvironmentCheckSuite::register('check', 'FileWriteableCheck("assets")', "Is assets/ writeable?");
|
||||
// EnvironmentCheckSuite::register('check', 'FileWriteableCheck("' . TEMP_FOLDER . '")', "Is the temp folder writeable?");
|
||||
|
||||
//
|
||||
//use SilverStripe\EnvironmentCheck\EnvironmentCheckSuite;
|
||||
//
|
||||
//// These power dev/health, which can be used by load balancers and other such systems
|
||||
//EnvironmentCheckSuite::register('health', 'DatabaseCheck');
|
||||
//
|
||||
//// These power dev/check, which is used for diagnostics and for deployment
|
||||
//EnvironmentCheckSuite::register('check', 'DatabaseCheck("Member")', "Is the database accessible?");
|
||||
//EnvironmentCheckSuite::register('check', 'URLCheck("")', "Is the homepage accessible?");
|
||||
//
|
||||
//EnvironmentCheckSuite::register(
|
||||
// 'check',
|
||||
// 'HasFunctionCheck("curl_init")',
|
||||
// "Does PHP have CURL support?"
|
||||
//);
|
||||
//
|
||||
//EnvironmentCheckSuite::register(
|
||||
// 'check',
|
||||
// 'HasFunctionCheck("imagecreatetruecolor")',
|
||||
// "Does PHP have GD2 support?"
|
||||
//);
|
||||
//
|
||||
//EnvironmentCheckSuite::register(
|
||||
// 'check',
|
||||
// 'HasFunctionCheck("xml_set_object")',
|
||||
// "Does PHP have XML support?"
|
||||
//);
|
||||
//
|
||||
//EnvironmentCheckSuite::register(
|
||||
// 'check',
|
||||
// 'HasFunctionCheck("token_get_all")',
|
||||
// "Does PHP have tokenizer support?"
|
||||
//);
|
||||
//
|
||||
//EnvironmentCheckSuite::register(
|
||||
// 'check',
|
||||
// 'HasFunctionCheck("iconv")',
|
||||
// "Does PHP have iconv support?"
|
||||
//);
|
||||
//
|
||||
//EnvironmentCheckSuite::register(
|
||||
// 'check',
|
||||
// 'HasFunctionCheck("hash")',
|
||||
// "Does PHP have hash support?"
|
||||
//);
|
||||
//
|
||||
//EnvironmentCheckSuite::register(
|
||||
// 'check',
|
||||
// 'HasFunctionCheck("session_start")',
|
||||
// "Does PHP have session support?"
|
||||
//);
|
||||
//
|
||||
//EnvironmentCheckSuite::register(
|
||||
// 'check',
|
||||
// 'HasClassCheck("DOMDocument")',
|
||||
// "Does PHP have DOMDocument support?"
|
||||
//);
|
||||
//
|
||||
//EnvironmentCheckSuite::register(
|
||||
// 'check',
|
||||
// 'FileWriteableCheck("assets")',
|
||||
// "Is assets/ writeable?"
|
||||
//);
|
||||
//
|
||||
//EnvironmentCheckSuite::register(
|
||||
// 'check',
|
||||
// 'FileWriteableCheck("' . TEMP_FOLDER . '")',
|
||||
// "Is the temp folder writeable?"
|
||||
//);
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
---
|
||||
Name: environmentcheckinjector
|
||||
---
|
||||
SilverStripe\Core\Injector\Injector:
|
||||
CacheHeadersCheck:
|
||||
class: SilverStripe\EnvironmentCheck\Checks\CacheHeadersCheck
|
||||
DatabaseCheck:
|
||||
class: SilverStripe\EnvironmentCheck\Checks\DatabaseCheck
|
||||
EnvTypeCheck:
|
||||
class: SilverStripe\EnvironmentCheck\Checks\EnvTypeCheck
|
||||
ExternalURLCheck:
|
||||
class: SilverStripe\EnvironmentCheck\Checks\ExternalURLCheck
|
||||
FileAccessibilityAndValidationCheck:
|
||||
class: SilverStripe\EnvironmentCheck\Checks\FileAccessibilityAndValidationCheck
|
||||
FileAgeCheck:
|
||||
class: SilverStripe\EnvironmentCheck\Checks\FileAgeCheck
|
||||
FileWriteableCheck:
|
||||
class: SilverStripe\EnvironmentCheck\Checks\FileWriteableCheck
|
||||
HasClassCheck:
|
||||
class: SilverStripe\EnvironmentCheck\Checks\HasClassCheck
|
||||
HasFunctionCheck:
|
||||
class: SilverStripe\EnvironmentCheck\Checks\HasFunctionCheck
|
||||
SMTPConnectCheck:
|
||||
class: SilverStripe\EnvironmentCheck\Checks\SMTPConnectCheck
|
||||
SolrIndexCheck:
|
||||
class: SilverStripe\EnvironmentCheck\Checks\SolrIndexCheck
|
||||
URLCheck:
|
||||
class: SilverStripe\EnvironmentCheck\Checks\URLCheck
|
||||
EnvCheckClient:
|
||||
factory: 'SilverStripe\EnvironmentCheck\Services\ClientFactory'
|
||||
constructor:
|
||||
timeout: 10.0
|
||||
|
||||
SilverStripe\EnvironmentCheck\Checks\SessionCheck:
|
||||
dependencies:
|
||||
client: '%$EnvCheckClient'
|
||||
SilverStripe\EnvironmentCheck\Checks\CacheHeadersCheck:
|
||||
dependencies:
|
||||
client: '%$EnvCheckClient'
|
|
@ -1,9 +1,25 @@
|
|||
---
|
||||
Name: environmentcheckroutes
|
||||
After: 'framework/*','cms/*'
|
||||
---
|
||||
Director:
|
||||
SilverStripe\Control\Director:
|
||||
rules:
|
||||
'dev/health': 'DevHealthController'
|
||||
'dev/check/$Suite': 'DevCheckController'
|
||||
'health/check': 'Silverstripe\EnvironmentCheck\Controllers\DevHealthController'
|
||||
'dev/check/$Suite': 'Silverstripe\EnvironmentCheck\Controllers\DevCheckController'
|
||||
|
||||
SilverStripe\Dev\DevelopmentAdmin:
|
||||
registered_controllers:
|
||||
check:
|
||||
controller: Silverstripe\EnvironmentCheck\Controllers\DevCheckController
|
||||
links:
|
||||
check: 'Run registered environment checks and display their status'
|
||||
|
||||
---
|
||||
Name: environmentcheckroutes-dev_urls-confirmation-exceptions
|
||||
After:
|
||||
- 'dev_urls-confirmation-middleware'
|
||||
---
|
||||
SilverStripe\Core\Injector\Injector:
|
||||
DevUrlsConfirmationMiddleware:
|
||||
properties:
|
||||
Bypasses:
|
||||
- '%$SilverStripe\Control\Middleware\ConfirmationMiddleware\UrlPathStartsWith("dev/check")'
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [2.1.0]
|
||||
|
||||
* Add new checks:
|
||||
* Environment type
|
||||
* Session
|
||||
* Cache Headers
|
||||
* Fix basic auth
|
||||
* Add unit tests
|
||||
* PSR-2 Conformance
|
||||
|
||||
## [2.0.0]
|
||||
|
||||
* Minimum versions required:
|
||||
* SilverStripe 4.x
|
||||
* PHP 5.6+
|
||||
* PHP 7 supported
|
||||
|
||||
## [1.2.0]
|
||||
|
||||
* Fix broken badges
|
||||
* Converted to PSR-2
|
||||
* Use Config API
|
||||
* Optionally log check failures
|
||||
* Update documentation and configuration to supported module standard
|
||||
|
||||
## [1.1.2]
|
||||
|
||||
* Bring up to supported module standard.
|
||||
|
||||
## [1.1.1]
|
||||
|
||||
* Changelog added.
|
|
@ -0,0 +1 @@
|
|||
When having discussions about this module in issues or pull request please adhere to the [SilverStripe Community Code of Conduct](https://docs.silverstripe.org/en/contributing/code_of_conduct).
|
|
@ -1,15 +0,0 @@
|
|||
<?php
|
||||
|
||||
class DevCheckController extends Controller {
|
||||
|
||||
public static $allowed_actions = array(
|
||||
'index'
|
||||
);
|
||||
|
||||
function index($request) {
|
||||
$suiteName = $request->param('Suite') ? $request->param('Suite') : 'check';
|
||||
$e = new EnvironmentChecker($suiteName, 'Environment status');
|
||||
$e->init('ADMIN'); //check for admin permissions before running this check
|
||||
return $e;
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
<?php
|
||||
|
||||
class DevHealthController extends Controller {
|
||||
|
||||
public static $allowed_actions = array(
|
||||
'index'
|
||||
);
|
||||
|
||||
function index() {
|
||||
$e = new EnvironmentChecker('health', 'Site health');
|
||||
$e->init(''); //empty permission check, the "health" check does not require a permission check to run
|
||||
$e->setErrorCode(404);
|
||||
return $e;
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Interface for environment checks
|
||||
*
|
||||
* An environment check is a test that can be performed on a live environment. They differ from unit
|
||||
* tests in that they are designed to check the state of the evnironment / server, rather than the code.
|
||||
*
|
||||
* Environment checks should *never* alter production data.
|
||||
*
|
||||
* Some things you might make environment checks for:
|
||||
* - Can I access the database?
|
||||
* - Are the right PHP modules installed?
|
||||
* - Are the file permissions correct?
|
||||
*/
|
||||
interface EnvironmentCheck {
|
||||
|
||||
const ERROR = 3;
|
||||
const WARNING = 2;
|
||||
const OK = 1;
|
||||
|
||||
/**
|
||||
* Perform this check
|
||||
*
|
||||
* @return 2 element array( $status, $message )
|
||||
* $status is EnvironmentCheck::ERROR, EnvironmentCheck::WARNING, or EnvironmentCheck::OK
|
||||
*/
|
||||
function check();
|
||||
|
||||
}
|
|
@ -1,207 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Represents a suite of environment checks.
|
||||
* Specific checks can be registered against a named instance of EnvironmentCheckSuite.
|
||||
*
|
||||
* Usage #1 - _config.php
|
||||
* EnvironmentCheckSuite::register('health', 'MyHealthCheck("my param")', 'Title of my health check');
|
||||
*
|
||||
* Usage #2 - config.yml
|
||||
* EnvironmentCheckSuite:
|
||||
* registered_checks:
|
||||
* mycheck:
|
||||
* definition: 'MyHealthCheck("my param")'
|
||||
* title: 'Title of my health check'
|
||||
* registered_suites:
|
||||
* health:
|
||||
* - mycheck
|
||||
*
|
||||
* $result = EnvironmentCheckSuite::inst('health')->run();
|
||||
*/
|
||||
class EnvironmentCheckSuite extends Object {
|
||||
|
||||
/**
|
||||
* Name of this suite.
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
protected $checks = array();
|
||||
|
||||
/**
|
||||
* Associative array of named checks registered via the config system. Each check should specify:
|
||||
* - definition (e.g. 'MyHealthCheck("my param")')
|
||||
* - title (e.g. 'Is my feature working?')
|
||||
* - state (setting this to 'disabled' will cause suites to skip this check entirely.
|
||||
*/
|
||||
private static $registered_checks;
|
||||
|
||||
/**
|
||||
* Associative array of named suites registered via the config system. Each suite should enumerate
|
||||
* named checks that have been configured in 'registered_checks'.
|
||||
*/
|
||||
private static $registered_suites;
|
||||
|
||||
/**
|
||||
* Load checks for this suite from the configuration system. This is an alternative to the
|
||||
* EnvironmentCheckSuite::register - both can be used, checks will be appended to the suite.
|
||||
*
|
||||
* @param string $suiteName The name of this suite.
|
||||
*/
|
||||
public function __construct($suiteName) {
|
||||
if (empty($this->config()->registered_suites[$suiteName])) {
|
||||
// Not registered via config system, but it still may be configured later via self::register.
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->config()->registered_suites[$suiteName] as $checkName) {
|
||||
if (empty($this->config()->registered_checks[$checkName])) {
|
||||
throw new InvalidArgumentException(
|
||||
"Bad EnvironmentCheck: '$checkName' - the named check has not been registered."
|
||||
);
|
||||
}
|
||||
|
||||
$check = $this->config()->registered_checks[$checkName];
|
||||
|
||||
// Existing named checks can be disabled by setting their 'state' to 'disabled'.
|
||||
// This is handy for disabling checks mandated by modules.
|
||||
if (!empty($check['state']) && $check['state']==='disabled') continue;
|
||||
|
||||
// Add the check to this suite.
|
||||
$this->push($check['definition'], $check['title']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run this test suite
|
||||
* @return The result code of the worst result.
|
||||
*/
|
||||
public function run() {
|
||||
$worstResult = 0;
|
||||
|
||||
$result = new EnvironmentCheckSuiteResult();
|
||||
foreach($this->checkInstances() as $check) {
|
||||
list($checkClass, $checkTitle) = $check;
|
||||
try {
|
||||
list($status, $message) = $checkClass->check();
|
||||
// If the check fails, register that as an error
|
||||
} catch(Exception $e) {
|
||||
$status = EnvironmentCheck::ERROR;
|
||||
$message = $e->getMessage();
|
||||
}
|
||||
$result->addResult($status, $message, $checkTitle);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get instances of all the environment checks
|
||||
*/
|
||||
protected function checkInstances() {
|
||||
$output = array();
|
||||
foreach($this->checks as $check) {
|
||||
list($checkClass, $checkTitle) = $check;
|
||||
if(is_string($checkClass)) {
|
||||
$checkInst = Object::create_from_string($checkClass);
|
||||
if($checkInst instanceof EnvironmentCheck) {
|
||||
$output[] = array($checkInst, $checkTitle);
|
||||
} else {
|
||||
throw new InvalidArgumentException("Bad EnvironmentCheck: '$checkClass' - the named class doesn't implement EnvironmentCheck");
|
||||
}
|
||||
} else if($checkClass instanceof EnvironmentCheck) {
|
||||
$output[] = array($checkClass, $checkTitle);
|
||||
} else {
|
||||
throw new InvalidArgumentException("Bad EnvironmentCheck: " . var_export($check, true));
|
||||
}
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a check to this suite.
|
||||
*/
|
||||
public function push($check, $title = null) {
|
||||
if(!$title) {
|
||||
$title = is_string($check) ? $check : get_class($check);
|
||||
}
|
||||
$this->checks[] = array($check, $title);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
protected static $instances = array();
|
||||
|
||||
/**
|
||||
* Return a named instance of EnvironmentCheckSuite.
|
||||
*/
|
||||
static function inst($name) {
|
||||
if(!isset(self::$instances[$name])) self::$instances[$name] = new EnvironmentCheckSuite($name);
|
||||
return self::$instances[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a check against the named check suite.
|
||||
*
|
||||
* @param String|Array
|
||||
*/
|
||||
static function register($names, $check, $title = null) {
|
||||
if(!is_array($names)) $names = array($names);
|
||||
foreach($names as $name) self::inst($name)->push($check, $title);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A single set of results from running an EnvironmentCheckSuite
|
||||
*/
|
||||
class EnvironmentCheckSuiteResult extends ViewableData {
|
||||
protected $details, $worst = 0;
|
||||
|
||||
function __construct() {
|
||||
parent::__construct();
|
||||
$this->details = new ArrayList();
|
||||
}
|
||||
|
||||
function addResult($status, $message, $checkIdentifier) {
|
||||
$this->details->push(new ArrayData(array(
|
||||
'Check' => $checkIdentifier,
|
||||
'Status' => $this->statusText($status),
|
||||
'Message' => $message,
|
||||
)));
|
||||
|
||||
$this->worst = max($this->worst, $status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if there are no ERRORs, only WARNINGs or OK
|
||||
*/
|
||||
function ShouldPass() {
|
||||
return $this->worst <= EnvironmentCheck::WARNING;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns overall (i.e. worst) status as a string.
|
||||
*/
|
||||
function Status() {
|
||||
return $this->statusText($this->worst);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns detailed status information about each check
|
||||
*/
|
||||
function Details() {
|
||||
return $this->details;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a text version of a status code
|
||||
*/
|
||||
protected function statusText($status) {
|
||||
switch($status) {
|
||||
case EnvironmentCheck::ERROR: return "ERROR";
|
||||
case EnvironmentCheck::WARNING: return "WARNING";
|
||||
case EnvironmentCheck::OK: return "OK";
|
||||
case 0: return "NO CHECKS";
|
||||
default: throw new InvalidArgumentException("Bad environment check status '$status'");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,145 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Provides an interface for checking the given EnvironmentCheckSuite.
|
||||
*/
|
||||
class EnvironmentChecker extends RequestHandler {
|
||||
|
||||
static $url_handlers = array(
|
||||
'' => 'index',
|
||||
);
|
||||
|
||||
protected $checkSuiteName;
|
||||
|
||||
protected $title;
|
||||
|
||||
protected $errorCode = 500;
|
||||
|
||||
public static $to_email_address = null;
|
||||
|
||||
public static $from_email_address = null;
|
||||
|
||||
public static $email_results = false;
|
||||
|
||||
function __construct($checkSuiteName, $title) {
|
||||
parent::__construct();
|
||||
|
||||
$this->checkSuiteName = $checkSuiteName;
|
||||
$this->title = $title;
|
||||
}
|
||||
|
||||
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'])) {
|
||||
// authenticate the input user/pass with the configured credentials
|
||||
if(
|
||||
!(
|
||||
$_SERVER['PHP_AUTH_USER'] == ENVCHECK_BASICAUTH_USERNAME
|
||||
&& $_SERVER['PHP_AUTH_PW'] == ENVCHECK_BASICAUTH_PASSWORD
|
||||
)
|
||||
) {
|
||||
$response = new SS_HTTPResponse(null, 401);
|
||||
$response->addHeader('WWW-Authenticate', "Basic realm=\"Environment check\"");
|
||||
// Exception is caught by RequestHandler->handleRequest() and will halt further execution
|
||||
$e = new SS_HTTPResponse_Exception(null, 401);
|
||||
$e->setResponse($response);
|
||||
throw $e;
|
||||
}
|
||||
} else {
|
||||
$response = new SS_HTTPResponse(null, 401);
|
||||
$response->addHeader('WWW-Authenticate', "Basic realm=\"Environment check\"");
|
||||
// Exception is caught by RequestHandler->handleRequest() and will halt further execution
|
||||
$e = new SS_HTTPResponse_Exception(null, 401);
|
||||
$e->setResponse($response);
|
||||
throw $e;
|
||||
}
|
||||
} else {
|
||||
if(!$this->canAccess(null, $permission)) return $this->httpError(403);
|
||||
}
|
||||
}
|
||||
|
||||
function canAccess($member = null, $permission = "ADMIN") {
|
||||
if(!$member) $member = Member::currentUser();
|
||||
|
||||
// We allow access to this controller regardless of live-status or ADMIN permission only
|
||||
// if on CLI. Access to this controller is always allowed in "dev-mode", or of the user is ADMIN.
|
||||
if(
|
||||
Director::isDev()
|
||||
|| Director::is_cli()
|
||||
|| empty($permission)
|
||||
|| Permission::checkMember($member, $permission)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Extended access checks.
|
||||
// "Veto" style, return NULL to abstain vote.
|
||||
$canExtended = null;
|
||||
$results = $this->extend('canAccess', $member);
|
||||
if($results && is_array($results)) {
|
||||
if(!min($results)) return false;
|
||||
else return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function index() {
|
||||
$response = new SS_HTTPResponse;
|
||||
$result = EnvironmentCheckSuite::inst($this->checkSuiteName)->run();
|
||||
|
||||
if(!$result->ShouldPass()) {
|
||||
$response->setStatusCode($this->errorCode);
|
||||
}
|
||||
|
||||
$resultText = $result->customise(array(
|
||||
"URL" => Director::absoluteBaseURL(),
|
||||
"Title" => $this->title,
|
||||
"Name" => $this->checkSuiteName,
|
||||
"ErrorCode" => $this->errorCode,
|
||||
))->renderWith("EnvironmentChecker");
|
||||
|
||||
if (self::$email_results && !$result->ShouldPass()) {
|
||||
$email = new Email(self::$from_email_address, self::$to_email_address, $this->title, $resultText);
|
||||
$email->send();
|
||||
}
|
||||
|
||||
$response->setBody($resultText);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the HTTP status code that should be returned when there's an error.
|
||||
* Defaults to 500
|
||||
*/
|
||||
function setErrorCode($errorCode) {
|
||||
$this->errorCode = $errorCode;
|
||||
}
|
||||
|
||||
public static function set_from_email_address($from) {
|
||||
self::$from_email_address = $from;
|
||||
}
|
||||
|
||||
public static function get_from_email_address() {
|
||||
return self::$from_email_address;
|
||||
}
|
||||
|
||||
public static function set_to_email_address($to) {
|
||||
self::$to_email_address = $to;
|
||||
}
|
||||
|
||||
public static function get_to_email_address() {
|
||||
return self::$to_email_address;
|
||||
}
|
||||
|
||||
public static function set_email_results($results) {
|
||||
self::$email_results = $results;
|
||||
}
|
||||
|
||||
public static function get_email_results() {
|
||||
return self::$email_results;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* This file contains a number of default environment checks that you can use.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Check that the connection to the database is working, by ensuring that the table exists and that
|
||||
* the table contain some records.
|
||||
* By default, Member will be checked.
|
||||
*
|
||||
* @param $checkTable The table that will be checked.
|
||||
*/
|
||||
class DatabaseCheck implements EnvironmentCheck {
|
||||
protected $checkTable;
|
||||
|
||||
function __construct($checkTable = "Member") {
|
||||
$this->checkTable = $checkTable;
|
||||
}
|
||||
|
||||
function check() {
|
||||
|
||||
if(!DB::getConn()->hasTable($this->checkTable)) {
|
||||
return array(EnvironmentCheck::ERROR, "$this->checkTable not present in the database");
|
||||
}
|
||||
|
||||
$count = DB::query("SELECT COUNT(*) FROM \"$this->checkTable\"")->value();
|
||||
|
||||
if($count > 0) {
|
||||
return array(EnvironmentCheck::OK, "");
|
||||
} else {
|
||||
return array(EnvironmentCheck::WARNING, "$this->checkTable queried ok but has no records");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,111 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Checks that one or more URLs are reachable via HTTP.
|
||||
* Note that the HTTP connectivity can just be verified from the server to the remote URL,
|
||||
* it can still fail if the URL in question is requested by the client, e.g. through an iframe.
|
||||
*
|
||||
* Requires curl to present, so ensure to check it before with the following:
|
||||
* <code>EnvironmentCheckSuite::register('check', 'HasFunctionCheck("curl_init")', "Does PHP have CURL support?");</code>
|
||||
*/
|
||||
class ExternalURLCheck implements EnvironmentCheck {
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $urls = array();
|
||||
|
||||
/**
|
||||
* @var Int Timeout in seconds.
|
||||
*/
|
||||
protected $timeout;
|
||||
|
||||
/**
|
||||
* @param String Space separated list of absolute URLs
|
||||
* (can't be an array as we're using Object::create() with strings for the constructor signature)
|
||||
*/
|
||||
function __construct($urls, $timeout = 15) {
|
||||
if($urls) $this->urls = explode(' ', $urls);
|
||||
$this->timeout = $timeout;
|
||||
}
|
||||
|
||||
function check() {
|
||||
$urls = $this->getURLs();
|
||||
|
||||
$chs = array();
|
||||
foreach($urls as $url) {
|
||||
$ch = curl_init();
|
||||
$chs[] = $ch;
|
||||
curl_setopt_array($ch, $this->getCurlOpts($url));
|
||||
}
|
||||
// Parallel execution for faster performance
|
||||
$mh = curl_multi_init();
|
||||
foreach($chs as $ch) curl_multi_add_handle($mh,$ch);
|
||||
|
||||
$active = null;
|
||||
// Execute the handles
|
||||
do {
|
||||
$mrc = curl_multi_exec($mh, $active);
|
||||
curl_multi_select($mh);
|
||||
} while ($active > 0);
|
||||
|
||||
while ($active && $mrc == CURLM_OK) {
|
||||
if (curl_multi_select($mh) != -1) {
|
||||
do {
|
||||
$mrc = curl_multi_exec($mh, $active);
|
||||
} while ($mrc == CURLM_CALL_MULTI_PERFORM);
|
||||
}
|
||||
}
|
||||
|
||||
$hasError = false;
|
||||
$msgs = array();
|
||||
foreach($chs as $ch) {
|
||||
$url = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
|
||||
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
if(curl_errno($ch) || $code >= 400) {
|
||||
$hasError = true;
|
||||
$msgs[] = sprintf(
|
||||
'Error retrieving "%s": %s (Code: %s)',
|
||||
$url,
|
||||
curl_error($ch),
|
||||
$code
|
||||
);
|
||||
} else {
|
||||
$msgs[] = sprintf(
|
||||
'Success retrieving "%s" (Code: %s)',
|
||||
$url,
|
||||
$code
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Close the handles
|
||||
foreach($chs as $ch) curl_multi_remove_handle($mh, $ch);
|
||||
curl_multi_close($mh);
|
||||
|
||||
if($hasError) {
|
||||
return array(EnvironmentCheck::ERROR, implode(', ', $msgs));
|
||||
} else {
|
||||
return array(EnvironmentCheck::OK, implode(', ', $msgs));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Array
|
||||
*/
|
||||
protected function getCurlOpts($url) {
|
||||
return array(
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_HEADER => 0,
|
||||
CURLOPT_RETURNTRANSFER => 1,
|
||||
CURLOPT_FAILONERROR => 1,
|
||||
CURLOPT_TIMEOUT => $this->timeout,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Array
|
||||
*/
|
||||
protected function getURLs() {
|
||||
return $this->urls;
|
||||
}
|
||||
}
|
|
@ -1,145 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Checks for the accessiblility and tiletype validation of one or more files or folders.
|
||||
*
|
||||
* Examples:
|
||||
* // Checks /assets/caluclator_files has .json files and all files are valid json files.
|
||||
* EnvironmentCheckSuite::register('check', 'FileAccessibilityAndValidationCheck("' . BASE_PATH . '/assets/caluclator_files/*.json",
|
||||
* "jsonValidate", '.FileAccessibilityAndValidationCheck::CHECK_ALL.')', 'Check a json file exist and are all valid json files'
|
||||
* );
|
||||
*
|
||||
* // Checks /assets/caluclator_files/calculator.json exists and is valid json file.
|
||||
* EnvironmentCheckSuite::register('check', 'FileAccessibilityAndValidationCheck("' . BASE_PATH . '/assets/caluclator_files/calculator.json",
|
||||
* "jsonValidate", '.FileAccessibilityAndValidationCheck::CHECK_SINGLE.')', 'Check a calculator.json exists and is valid json file'
|
||||
* );
|
||||
*
|
||||
* // Check only existence
|
||||
* EnvironmentCheckSuite::register('check', 'FileAccessibilityAndValidationCheck("' . BASE_PATH . '/assets/caluclator_files/calculator.json")',
|
||||
* 'Check a calculator.json exists only'
|
||||
* );
|
||||
*/
|
||||
class FileAccessibilityAndValidationCheck implements EnvironmentCheck {
|
||||
|
||||
const CHECK_SINGLE = 1;
|
||||
|
||||
const CHECK_ALL = 2;
|
||||
|
||||
/**
|
||||
* @var String Absolute path to a file or folder, compatible with glob().
|
||||
*/
|
||||
protected $path;
|
||||
|
||||
/**
|
||||
* @var Int Constant, check for a single file to match age criteria, or all of them.
|
||||
*/
|
||||
protected $fileTypeValidateFunc;
|
||||
|
||||
/**
|
||||
* @var Int Constant, check for a single file to match age criteria, or all of them.
|
||||
*/
|
||||
protected $checkType;
|
||||
|
||||
function __construct($path, $fileTypeValidateFunc = 'noVidation', $checkType = null) {
|
||||
$this->path = $path;
|
||||
$this->fileTypeValidateFunc = ($fileTypeValidateFunc)? $fileTypeValidateFunc:'noVidation';
|
||||
$this->checkType = ($checkType) ? $checkType : self::CHECK_SINGLE;
|
||||
}
|
||||
|
||||
function check() {
|
||||
$origStage = Versioned::get_reading_mode();
|
||||
Versioned::set_reading_mode('Live');
|
||||
|
||||
$files = $this->getFiles();
|
||||
if($files){
|
||||
$fileTypeValidateFunc = $this->fileTypeValidateFunc;
|
||||
if(method_exists ($this, $fileTypeValidateFunc)){
|
||||
$invalidFiles = array();
|
||||
$validFiles = array();
|
||||
|
||||
foreach($files as $file){
|
||||
if($this->$fileTypeValidateFunc($file)){
|
||||
$validFiles[] = $file;
|
||||
}else{
|
||||
$invalidFiles[] = $file;
|
||||
}
|
||||
}
|
||||
|
||||
// If at least one file was valid, count as passed
|
||||
if($this->checkType == self::CHECK_SINGLE && count($invalidFiles) < count($files)) {
|
||||
$validFileList = "\n";
|
||||
foreach($validFiles as $vf){
|
||||
$validFileList .= $vf."\n";
|
||||
}
|
||||
if($fileTypeValidateFunc == 'noVidation') {
|
||||
$checkReturn = array(
|
||||
EnvironmentCheck::OK,
|
||||
sprintf('At least these file(s) accessible: %s', $validFileList)
|
||||
);
|
||||
}else{
|
||||
$checkReturn = array(
|
||||
EnvironmentCheck::OK,
|
||||
sprintf('At least these file(s) passed file type validate function "%s": %s', $fileTypeValidateFunc, $validFileList)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (count($invalidFiles) == 0) $checkReturn = array(EnvironmentCheck::OK, 'All files valideted');
|
||||
else {
|
||||
$invalidFileList = "\n";
|
||||
foreach($invalidFiles as $vf){
|
||||
$invalidFileList .= $vf."\n";
|
||||
}
|
||||
|
||||
if($fileTypeValidateFunc == 'noVidation'){
|
||||
$checkReturn = array(
|
||||
EnvironmentCheck::ERROR,
|
||||
sprintf('File(s) not accessible: %s', $invalidFileList)
|
||||
);
|
||||
}else{
|
||||
$checkReturn = array(
|
||||
EnvironmentCheck::ERROR,
|
||||
sprintf('File(s) not passing the file type validate function "%s": %s', $fileTypeValidateFunc, $invalidFileList)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}else{
|
||||
$checkReturn = array(
|
||||
EnvironmentCheck::ERROR,
|
||||
sprintf("Invalid file type validation method name passed: %s ", $fileTypeValidateFunc)
|
||||
);
|
||||
}
|
||||
|
||||
}else{
|
||||
$checkReturn = array(
|
||||
EnvironmentCheck::ERROR,
|
||||
sprintf("No files accessible at path %s", $this->path)
|
||||
);
|
||||
}
|
||||
|
||||
Versioned::set_reading_mode($origStage);
|
||||
|
||||
return $checkReturn;
|
||||
}
|
||||
|
||||
private function jsonValidate($file){
|
||||
$json = json_decode(file_get_contents($file));
|
||||
if(!$json) {
|
||||
return false;
|
||||
}else{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
protected function noVidation($file) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Array Of absolute file paths
|
||||
*/
|
||||
protected function getFiles() {
|
||||
return glob($this->path);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,107 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* 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.
|
||||
* Does NOT check for existence of a file (will silently fail).
|
||||
*
|
||||
* Examples:
|
||||
* // Checks that Requirements::combine_files() has regenerated files in the last 24h
|
||||
* EnvironmentCheckSuite::register(
|
||||
* 'check',
|
||||
* 'FileAgeCheck("' . ASSETS_PATH . '/_combined_files/*.js' . '", "-1 day", '>', " . FileAgeCheck::CHECK_ALL) . "'
|
||||
* );
|
||||
*
|
||||
* // Checks that at least one backup folder has been created in the last 24h
|
||||
* EnvironmentCheckSuite::register(
|
||||
* 'check',
|
||||
* 'FileAgeCheck("' . BASE_PATH . '/../backups/*' . '", "-1 day", '>', " . FileAgeCheck::CHECK_SINGLE) . "'
|
||||
* );
|
||||
*/
|
||||
class FileAgeCheck implements EnvironmentCheck {
|
||||
|
||||
const CHECK_SINGLE = 1;
|
||||
|
||||
const CHECK_ALL = 2;
|
||||
|
||||
/**
|
||||
* @var String Absolute path to a file or folder, compatible with glob().
|
||||
*/
|
||||
protected $path;
|
||||
|
||||
/**
|
||||
* @var String strtotime() compatible relative date specification.
|
||||
*/
|
||||
protected $relativeAge;
|
||||
|
||||
/**
|
||||
* @var String The function to use for checking file age,
|
||||
* so filemtime(), filectime() or fileatime().
|
||||
*/
|
||||
protected $checkFn;
|
||||
|
||||
/**
|
||||
* @var Int Constant, check for a single file to match age criteria, or all of them.
|
||||
*/
|
||||
protected $checkType;
|
||||
|
||||
/**
|
||||
* @var String Either '>' or '<'.
|
||||
*/
|
||||
protected $compareOperand;
|
||||
|
||||
function __construct($path, $relativeAge, $compareOperand = '>', $checkType = null, $checkFn = 'filemtime') {
|
||||
$this->path = $path;
|
||||
$this->relativeAge = $relativeAge;
|
||||
$this->checkFn = $checkFn;
|
||||
$this->checkType = ($checkType) ? $checkType : self::CHECK_SINGLE;
|
||||
$this->compareOperand = $compareOperand;
|
||||
}
|
||||
|
||||
function check() {
|
||||
$cutoffTime = strtotime($this->relativeAge, SS_Datetime::now()->Format('U'));
|
||||
$files = $this->getFiles();
|
||||
$invalidFiles = array();
|
||||
$validFiles = array();
|
||||
$checkFn = $this->checkFn;
|
||||
$allValid = true;
|
||||
if($files) foreach($files as $file) {
|
||||
$fileTime = $checkFn($file);
|
||||
$valid = ($this->compareOperand == '>') ? ($fileTime >= $cutoffTime) : ($fileTime <= $cutoffTime);
|
||||
if($valid) {
|
||||
$validFiles[] = $file;
|
||||
} else {
|
||||
$invalidFiles[] = $file;
|
||||
if($this->checkType == self::CHECK_ALL) {
|
||||
return array(
|
||||
EnvironmentCheck::ERROR,
|
||||
sprintf(
|
||||
'File "%s" doesn\'t match age check (compare %s: %s, actual: %s)',
|
||||
$file, $this->compareOperand, date('c', $cutoffTime), date('c', $fileTime)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If at least one file was valid, count as passed
|
||||
if($this->checkType == self::CHECK_SINGLE && count($invalidFiles) < count($files)) {
|
||||
return array(EnvironmentCheck::OK, '');
|
||||
} else {
|
||||
if (count($invalidFiles) == 0) return array(EnvironmentCheck::OK, '');
|
||||
else return array(
|
||||
EnvironmentCheck::ERROR,
|
||||
sprintf('No files matched criteria (%s %s)', $this->compareOperand, date('c', $cutoffTime))
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Array Of absolute file paths
|
||||
*/
|
||||
protected function getFiles() {
|
||||
return glob($this->path);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Check that the given file is writeable.
|
||||
* This can be used to check that the environment doesn't have permission set-up errors.
|
||||
* @param $path The full path. If a relative path, it will relative to the BASE_PATH
|
||||
*/
|
||||
class FileWriteableCheck implements EnvironmentCheck {
|
||||
protected $path;
|
||||
|
||||
function __construct($path) {
|
||||
$this->path = $path;
|
||||
}
|
||||
|
||||
function check() {
|
||||
if($this->path[0] == '/') $filename = $this->path;
|
||||
else $filename = BASE_PATH . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $this->path);
|
||||
|
||||
if(file_exists($filename)) $isWriteable = is_writeable($filename);
|
||||
else $isWriteable = is_writeable(dirname($filename));
|
||||
|
||||
if(!$isWriteable) {
|
||||
if(function_exists('posix_getgroups')) {
|
||||
$userID = posix_geteuid();
|
||||
$user = posix_getpwuid($userID);
|
||||
|
||||
$currentOwnerID = fileowner(file_exists($filename) ? $filename : dirname($filename) );
|
||||
$currentOwner = posix_getpwuid($currentOwnerID);
|
||||
|
||||
$message = "User '$user[name]' needs to be able to write to this file:\n$filename\n\nThe file is currently owned by '$currentOwner[name]'. ";
|
||||
|
||||
if($user['name'] == $currentOwner['name']) {
|
||||
$message .= "We recommend that you make the file writeable.";
|
||||
} else {
|
||||
|
||||
$groups = posix_getgroups();
|
||||
$groupList = array();
|
||||
foreach($groups as $group) {
|
||||
$groupInfo = posix_getgrgid($group);
|
||||
if(in_array($currentOwner['name'], $groupInfo['members'])) $groupList[] = $groupInfo['name'];
|
||||
}
|
||||
if($groupList) {
|
||||
$message .= " We recommend that you make the file group-writeable and change the group to one of these groups:\n - ". implode("\n - ", $groupList)
|
||||
. "\n\nFor example:\nchmod g+w $filename\nchgrp " . $groupList[0] . " $filename";
|
||||
} else {
|
||||
$message .= " There is no user-group that contains both the web-server user and the owner of this file. Change the ownership of the file, create a new group, or temporarily make the file writeable by everyone during the install process.";
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
$message = "The webserver user needs to be able to write to this file:\n$filename";
|
||||
}
|
||||
|
||||
return array(EnvironmentCheck::ERROR, $message);
|
||||
}
|
||||
|
||||
return array(EnvironmentCheck::OK,'');
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Check that the given class exists.
|
||||
* This can be used to check that PHP modules or features are installed.
|
||||
* @param $className The name of the class to look for.
|
||||
*/
|
||||
class HasClassCheck implements EnvironmentCheck {
|
||||
protected $className;
|
||||
|
||||
function __construct($className) {
|
||||
$this->className = $className;
|
||||
}
|
||||
|
||||
function check() {
|
||||
if(class_exists($this->className)) return array(EnvironmentCheck::OK, 'Class ' . $this->className.' exists');
|
||||
else return array(EnvironmentCheck::ERROR, 'Class ' . $this->className.' doesn\'t exist');
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Check that the given function exists.
|
||||
* This can be used to check that PHP modules or features are installed.
|
||||
* @param $functionName The name of the function to look for.
|
||||
*/
|
||||
class HasFunctionCheck implements EnvironmentCheck {
|
||||
protected $functionName;
|
||||
|
||||
function __construct($functionName) {
|
||||
$this->functionName = $functionName;
|
||||
}
|
||||
|
||||
function check() {
|
||||
if(function_exists($this->functionName)) return array(EnvironmentCheck::OK, $this->functionName.'() exists');
|
||||
else return array(EnvironmentCheck::ERROR, $this->functionName.'() doesn\'t exist');
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Checks if the SMTP connection configured through PHP.ini works as expected.
|
||||
* Only checks socket connection with HELO command, not actually sending the email.
|
||||
*/
|
||||
class SMTPConnectCheck implements EnvironmentCheck {
|
||||
|
||||
/**
|
||||
* @var String
|
||||
*/
|
||||
protected $host;
|
||||
|
||||
/**
|
||||
* @var Int
|
||||
*/
|
||||
protected $port;
|
||||
|
||||
/**
|
||||
* @var Int In seconds
|
||||
*/
|
||||
protected $timeout;
|
||||
|
||||
/**
|
||||
* @param String
|
||||
* @param Int
|
||||
*/
|
||||
function __construct($host = null, $port = null, $timeout = 15) {
|
||||
$this->host = ($host) ? $host : ini_get('SMTP');
|
||||
if(!$this->host) $this->host = 'localhost';
|
||||
|
||||
$this->port = ($port) ? $port : ini_get('smtp_port');
|
||||
if(!$this->port) $this->port = 25;
|
||||
|
||||
$this->timeout = $timeout;
|
||||
}
|
||||
|
||||
function check() {
|
||||
$f = @fsockopen($this->host, $this->port, $errno, $errstr, $this->timeout);
|
||||
if(!$f) {
|
||||
return array(
|
||||
EnvironmentCheck::ERROR,
|
||||
sprintf("Couldn't connect to SMTP on %s:%s (Error: %s %s)", $this->host, $this->port, $errno, $errstr)
|
||||
);
|
||||
}
|
||||
|
||||
fwrite($f, "HELO its_me\r\n");
|
||||
$response = fread($f, 26);
|
||||
if(substr($response, 0, 3) != '220') {
|
||||
return array(
|
||||
EnvironmentCheck::ERROR,
|
||||
sprintf("Invalid mail server response: %s", $response)
|
||||
);
|
||||
}
|
||||
|
||||
return array(EnvironmentCheck::OK, '');
|
||||
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Check the availability of all Solr indexes of given class.
|
||||
* If there are no indexes of given class found, the returned status will still be "OK".
|
||||
*
|
||||
* @param $indexClass Limit the index checks to the specified class and all its subclasses.
|
||||
*/
|
||||
class SolrIndexCheck implements EnvironmentCheck {
|
||||
|
||||
protected $indexClass;
|
||||
|
||||
function __construct($indexClass = null) {
|
||||
$this->indexClass = $indexClass;
|
||||
}
|
||||
|
||||
function check() {
|
||||
$brokenCores = array();
|
||||
|
||||
if (!class_exists('Solr')) {
|
||||
return array(
|
||||
EnvironmentCheck::ERROR,
|
||||
'Class `Solr` not found. Is the fulltextsearch module installed?'
|
||||
);
|
||||
}
|
||||
|
||||
$service = Solr::service();
|
||||
foreach (Solr::get_indexes($this->indexClass) as $index) {
|
||||
$core = $index->getIndexName();
|
||||
if (!$service->coreIsActive($core)) {
|
||||
$brokenCores[] = $core;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($brokenCores)) {
|
||||
return array(
|
||||
EnvironmentCheck::ERROR,
|
||||
'The following indexes are unavailable: ' . implode($brokenCores, ', ')
|
||||
);
|
||||
}
|
||||
|
||||
return array(EnvironmentCheck::OK, 'Expected indexes are available.');
|
||||
}
|
||||
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Check that a given URL is functioning, by default, the homepage.
|
||||
*
|
||||
* Note that Director::test() will be used rather than a CURL check.
|
||||
*/
|
||||
class URLCheck implements EnvironmentCheck {
|
||||
protected $url;
|
||||
protected $testString;
|
||||
|
||||
/*
|
||||
* @param $url The URL to check, relative to the site. "" is the homepage.
|
||||
* @param $testString A piece of text to optionally search for in the homepage HTML. If omitted, no such check is made.
|
||||
*/
|
||||
function __construct($url = "", $testString = "") {
|
||||
$this->url = $url;
|
||||
$this->testString = $testString;
|
||||
}
|
||||
|
||||
function check() {
|
||||
$response = Director::test($this->url);
|
||||
|
||||
if($response->getStatusCode() != 200) {
|
||||
return array(
|
||||
EnvironmentCheck::ERROR,
|
||||
sprintf('Error retrieving "%s" (Code: %d)', $this->url, $response->getStatusCode())
|
||||
);
|
||||
|
||||
} else if($this->testString && (strpos($response->getBody(), $this->testString) === false)) {
|
||||
return array(
|
||||
EnvironmentCheck::WARNING,
|
||||
sprintf('Success retrieving "%s", but string "%s" not found', $this->url, $this->testString)
|
||||
);
|
||||
|
||||
} else {
|
||||
return array(
|
||||
EnvironmentCheck::OK,
|
||||
sprintf('Success retrieving "%s"', $this->url)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
comment: false
|
|
@ -1,17 +1,41 @@
|
|||
{
|
||||
"name": "silverstripe/environmentcheck",
|
||||
"description": "Provides an API for building environment tests",
|
||||
"type": "silverstripe-module",
|
||||
"keywords": ["silverstripe", "testing", "environment", "environmentcheck"],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Sam Minnee",
|
||||
"email": "sam@silverstripe.com"
|
||||
}
|
||||
],
|
||||
|
||||
"require":
|
||||
{
|
||||
"silverstripe/framework": "3.*"
|
||||
}
|
||||
}
|
||||
"name": "silverstripe/environmentcheck",
|
||||
"description": "Provides an API for building environment tests",
|
||||
"license": "BSD-3-Clause",
|
||||
"type": "silverstripe-vendormodule",
|
||||
"keywords": [
|
||||
"silverstripe",
|
||||
"testing",
|
||||
"environment",
|
||||
"check"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Will Rossiter",
|
||||
"email": "will@fullscreen.io"
|
||||
},
|
||||
{
|
||||
"name": "Sam Minnee",
|
||||
"email": "sam@silverstripe.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^7.4 || ^8.0",
|
||||
"silverstripe/framework": "^4.10",
|
||||
"silverstripe/versioned": "^1.0",
|
||||
"guzzlehttp/guzzle": "^6.3.3 || ^7"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.5",
|
||||
"squizlabs/php_codesniffer": "^3.0"
|
||||
},
|
||||
"extra": [],
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"SilverStripe\\EnvironmentCheck\\": "src/",
|
||||
"SilverStripe\\EnvironmentCheck\\Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
# Contributing
|
||||
|
||||
Contributions are welcome! Create an issue, explaining a bug or proposal. Submit pull requests if you feel brave. Speak to me on [Twitter](https://twitter.com/assertchris).
|
|
@ -0,0 +1,12 @@
|
|||
Copyright (c) 2017, SilverStripe Limited
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ruleset name="SilverStripe">
|
||||
<description>CodeSniffer ruleset for SilverStripe coding conventions.</description>
|
||||
|
||||
<file>src</file>
|
||||
<file>tests</file>
|
||||
|
||||
<rule ref="PSR2" >
|
||||
<!-- Current exclusions -->
|
||||
<exclude name="PSR1.Methods.CamelCapsMethodName" />
|
||||
</rule>
|
||||
</ruleset>
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit bootstrap="vendor/silverstripe/framework/tests/bootstrap.php" colors="true">
|
||||
|
||||
<testsuites>
|
||||
<testsuite name="Default">
|
||||
<directory>tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<filter>
|
||||
<whitelist addUncoveredFilesFromWhitelist="true">
|
||||
<directory suffix=".php">src/.</directory>
|
||||
<exclude>
|
||||
<directory suffix=".php">tests/</directory>
|
||||
</exclude>
|
||||
</whitelist>
|
||||
</filter>
|
||||
|
||||
</phpunit>
|
|
@ -0,0 +1,207 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\EnvironmentCheck\Checks;
|
||||
|
||||
use SilverStripe\Control\Director;
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\ORM\ValidationResult;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use SilverStripe\Core\Config\Configurable;
|
||||
use SilverStripe\EnvironmentCheck\Traits\Fetcher;
|
||||
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
|
||||
|
||||
/**
|
||||
* Check cache headers for any response, can specify directives that must be included and
|
||||
* also must be excluded from Cache-Control headers in response. Also checks for
|
||||
* existence of ETag.
|
||||
*
|
||||
* @example SilverStripe\EnvironmentCheck\Checks\CacheHeadersCheck("/",["must-revalidate", "max-age=120"],["no-store"])
|
||||
* @package environmentcheck
|
||||
*/
|
||||
class CacheHeadersCheck implements EnvironmentCheck
|
||||
{
|
||||
use Fetcher;
|
||||
|
||||
/**
|
||||
* Settings that must be included in the Cache-Control header
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $mustInclude = [];
|
||||
|
||||
/**
|
||||
* Settings that must be excluded in the Cache-Control header
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $mustExclude = [];
|
||||
|
||||
/**
|
||||
* Result to keep track of status and messages for all checks, reuses
|
||||
* ValidationResult for convenience.
|
||||
*
|
||||
* @var ValidationResult
|
||||
*/
|
||||
protected $result;
|
||||
|
||||
/**
|
||||
* Set up with URL, arrays of header settings to check.
|
||||
*
|
||||
* @param string $url
|
||||
* @param array $mustInclude Settings that must be included in Cache-Control
|
||||
* @param array $mustExclude Settings that must be excluded in Cache-Control
|
||||
*/
|
||||
public function __construct($url = '', $mustInclude = [], $mustExclude = [])
|
||||
{
|
||||
$this->setURL($url);
|
||||
$this->mustInclude = $mustInclude;
|
||||
$this->mustExclude = $mustExclude;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that correct caching headers are present.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function check()
|
||||
{
|
||||
// Using a validation result to capture messages
|
||||
$this->result = new ValidationResult();
|
||||
|
||||
$response = $this->client->get($this->getURL());
|
||||
$fullURL = $this->getURL();
|
||||
if ($response === null) {
|
||||
return [
|
||||
EnvironmentCheck::ERROR,
|
||||
"Cache headers check request failed for $fullURL",
|
||||
];
|
||||
}
|
||||
|
||||
//Check that Etag exists
|
||||
$this->checkEtag($response);
|
||||
|
||||
// Check Cache-Control settings
|
||||
$this->checkCacheControl($response);
|
||||
|
||||
if ($this->result->isValid()) {
|
||||
return [
|
||||
EnvironmentCheck::OK,
|
||||
$this->getMessage(),
|
||||
];
|
||||
} else {
|
||||
// @todo Ability to return a warning
|
||||
return [
|
||||
EnvironmentCheck::ERROR,
|
||||
$this->getMessage(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collate messages from ValidationResult so that it is clear which parts
|
||||
* of the check passed and which failed.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getMessage()
|
||||
{
|
||||
$ret = '';
|
||||
// Filter good messages
|
||||
$goodTypes = [ValidationResult::TYPE_GOOD, ValidationResult::TYPE_INFO];
|
||||
$good = array_filter(
|
||||
$this->result->getMessages() ?? [],
|
||||
function ($val, $key) use ($goodTypes) {
|
||||
if (in_array($val['messageType'], $goodTypes ?? [])) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
ARRAY_FILTER_USE_BOTH
|
||||
);
|
||||
if (!empty($good)) {
|
||||
$ret .= "GOOD: " . implode('; ', array_column($good ?? [], 'message')) . " ";
|
||||
}
|
||||
|
||||
// Filter bad messages
|
||||
$badTypes = [ValidationResult::TYPE_ERROR, ValidationResult::TYPE_WARNING];
|
||||
$bad = array_filter(
|
||||
$this->result->getMessages() ?? [],
|
||||
function ($val, $key) use ($badTypes) {
|
||||
if (in_array($val['messageType'], $badTypes ?? [])) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
ARRAY_FILTER_USE_BOTH
|
||||
);
|
||||
if (!empty($bad)) {
|
||||
$ret .= "BAD: " . implode('; ', array_column($bad ?? [], 'message'));
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that ETag header exists
|
||||
*
|
||||
* @param ResponseInterface $response
|
||||
* @return void
|
||||
*/
|
||||
private function checkEtag(ResponseInterface $response)
|
||||
{
|
||||
$eTag = $response->getHeaderLine('ETag');
|
||||
$fullURL = Controller::join_links(Director::absoluteBaseURL(), $this->url);
|
||||
|
||||
if ($eTag) {
|
||||
$this->result->addMessage(
|
||||
"$fullURL includes an Etag header in response",
|
||||
ValidationResult::TYPE_GOOD
|
||||
);
|
||||
return;
|
||||
}
|
||||
$this->result->addError(
|
||||
"$fullURL is missing an Etag header",
|
||||
ValidationResult::TYPE_WARNING
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the correct header settings are either included or excluded.
|
||||
*
|
||||
* @param ResponseInterface $response
|
||||
* @return void
|
||||
*/
|
||||
private function checkCacheControl(ResponseInterface $response)
|
||||
{
|
||||
$cacheControl = $response->getHeaderLine('Cache-Control');
|
||||
$vals = array_map('trim', explode(',', $cacheControl ?? ''));
|
||||
$fullURL = Controller::join_links(Director::absoluteBaseURL(), $this->url);
|
||||
|
||||
// All entries from must contain should be present
|
||||
if ($this->mustInclude == array_intersect($this->mustInclude ?? [], $vals)) {
|
||||
$matched = implode(",", $this->mustInclude);
|
||||
$this->result->addMessage(
|
||||
"$fullURL includes all settings: {$matched}",
|
||||
ValidationResult::TYPE_GOOD
|
||||
);
|
||||
} else {
|
||||
$missing = implode(",", array_diff($this->mustInclude ?? [], $vals));
|
||||
$this->result->addError(
|
||||
"$fullURL is excluding some settings: {$missing}"
|
||||
);
|
||||
}
|
||||
|
||||
// All entries from must exclude should not be present
|
||||
if (empty(array_intersect($this->mustExclude ?? [], $vals))) {
|
||||
$missing = implode(",", $this->mustExclude);
|
||||
$this->result->addMessage(
|
||||
"$fullURL excludes all settings: {$missing}",
|
||||
ValidationResult::TYPE_GOOD
|
||||
);
|
||||
} else {
|
||||
$matched = implode(",", array_intersect($this->mustExclude ?? [], $vals));
|
||||
$this->result->addError(
|
||||
"$fullURL is including some settings: {$matched}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\EnvironmentCheck\Checks;
|
||||
|
||||
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
|
||||
use SilverStripe\ORM\DB;
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $checkTable;
|
||||
|
||||
/**
|
||||
* By default, Member will be checked.
|
||||
*
|
||||
* @param string $checkTable
|
||||
*/
|
||||
public function __construct($checkTable = 'Member')
|
||||
{
|
||||
$this->checkTable = $checkTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function check()
|
||||
{
|
||||
if (!DB::get_schema()->hasTable($this->checkTable)) {
|
||||
return [EnvironmentCheck::ERROR, "$this->checkTable not present in the database"];
|
||||
}
|
||||
|
||||
$count = DB::query("SELECT COUNT(*) FROM \"$this->checkTable\"")->value();
|
||||
|
||||
if ($count > 0) {
|
||||
return [EnvironmentCheck::OK, ''];
|
||||
}
|
||||
|
||||
return [EnvironmentCheck::WARNING, "$this->checkTable queried ok but has no records"];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\EnvironmentCheck\Checks;
|
||||
|
||||
use SilverStripe\Control\Director;
|
||||
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
|
||||
|
||||
/**
|
||||
* Check whether the environment setting is safe. Useful for live sites where a
|
||||
* non "Live" setting might disclose sensitive information.
|
||||
*
|
||||
* @package environmentcheck
|
||||
*/
|
||||
class EnvTypeCheck implements EnvironmentCheck
|
||||
{
|
||||
/**
|
||||
* Check the environment setting.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function check()
|
||||
{
|
||||
$envSetting = Director::get_environment_type();
|
||||
switch ($envSetting) {
|
||||
case 'live':
|
||||
return [
|
||||
EnvironmentCheck::OK,
|
||||
"Env setting is 'live'",
|
||||
];
|
||||
// Fallthrough
|
||||
default:
|
||||
case 'dev':
|
||||
case 'test':
|
||||
return [
|
||||
EnvironmentCheck::ERROR,
|
||||
"Env setting is '{$envSetting}' and may disclose information",
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\EnvironmentCheck\Checks;
|
||||
|
||||
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
|
||||
|
||||
/**
|
||||
* Checks that one or more URLs are reachable via HTTP.
|
||||
* Note that the HTTP connectivity can just be verified from the server to the remote URL,
|
||||
* it can still fail if the URL in question is requested by the client, e.g. through an iframe.
|
||||
*
|
||||
* Requires curl to present, so ensure to check it before with the following:
|
||||
* <code>
|
||||
* EnvironmentCheckSuite::register(
|
||||
* 'check',
|
||||
* 'HasFunctionCheck("curl_init")',
|
||||
* "Does PHP have CURL support?"
|
||||
* );
|
||||
* </code>
|
||||
*/
|
||||
class ExternalURLCheck implements EnvironmentCheck
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $urls = [];
|
||||
|
||||
/**
|
||||
* @var Int Timeout in seconds.
|
||||
*/
|
||||
protected $timeout;
|
||||
|
||||
/**
|
||||
* @param string $urls Space-separated list of absolute URLs.
|
||||
* @param int $timeout
|
||||
*/
|
||||
public function __construct($urls, $timeout = 15)
|
||||
{
|
||||
if ($urls) {
|
||||
$this->urls = explode(' ', $urls ?? '');
|
||||
}
|
||||
$this->timeout = $timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function check()
|
||||
{
|
||||
$urls = $this->getURLs();
|
||||
|
||||
$chs = [];
|
||||
foreach ($urls as $url) {
|
||||
$ch = curl_init();
|
||||
$chs[] = $ch;
|
||||
curl_setopt_array($ch, $this->getCurlOpts($url) ?? []);
|
||||
}
|
||||
// Parallel execution for faster performance
|
||||
$mh = curl_multi_init();
|
||||
foreach ($chs as $ch) {
|
||||
curl_multi_add_handle($mh, $ch);
|
||||
}
|
||||
|
||||
$active = null;
|
||||
// Execute the handles
|
||||
do {
|
||||
$mrc = curl_multi_exec($mh, $active);
|
||||
curl_multi_select($mh);
|
||||
} while ($active > 0);
|
||||
|
||||
while ($active && $mrc == CURLM_OK) {
|
||||
if (curl_multi_select($mh) != -1) {
|
||||
do {
|
||||
$mrc = curl_multi_exec($mh, $active);
|
||||
} while ($mrc == CURLM_CALL_MULTI_PERFORM);
|
||||
}
|
||||
}
|
||||
|
||||
$hasError = false;
|
||||
$msgs = [];
|
||||
foreach ($chs as $ch) {
|
||||
$url = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
|
||||
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
if (curl_errno($ch) || $code >= 400) {
|
||||
$hasError = true;
|
||||
$msgs[] = sprintf(
|
||||
'Error retrieving "%s": %s (Code: %s)',
|
||||
$url,
|
||||
curl_error($ch),
|
||||
$code
|
||||
);
|
||||
} else {
|
||||
$msgs[] = sprintf(
|
||||
'Success retrieving "%s" (Code: %s)',
|
||||
$url,
|
||||
$code
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Close the handles
|
||||
foreach ($chs as $ch) {
|
||||
curl_multi_remove_handle($mh, $ch);
|
||||
}
|
||||
curl_multi_close($mh);
|
||||
|
||||
if ($hasError) {
|
||||
return [EnvironmentCheck::ERROR, implode(', ', $msgs)];
|
||||
}
|
||||
|
||||
return [EnvironmentCheck::OK, implode(', ', $msgs)];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function getCurlOpts($url)
|
||||
{
|
||||
return [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_HEADER => 0,
|
||||
CURLOPT_RETURNTRANSFER => 1,
|
||||
CURLOPT_FAILONERROR => 1,
|
||||
CURLOPT_TIMEOUT => $this->timeout,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function getURLs()
|
||||
{
|
||||
return $this->urls;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,209 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\EnvironmentCheck\Checks;
|
||||
|
||||
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
|
||||
use SilverStripe\Versioned\Versioned;
|
||||
|
||||
/**
|
||||
* Checks for the accessibility and file type validation of one or more files or folders.
|
||||
*
|
||||
* Examples:
|
||||
* // Checks /assets/calculator_files has .json files and all files are valid json files.
|
||||
* EnvironmentCheckSuite::register(
|
||||
* 'check',
|
||||
* 'FileAccessibilityAndValidationCheck(
|
||||
* "' . BASE_PATH . '/assets/calculator_files/*.json",
|
||||
* "jsonValidate",
|
||||
* '.FileAccessibilityAndValidationCheck::CHECK_ALL.'
|
||||
* )',
|
||||
* 'Check a json file exist and are all valid json files'
|
||||
* );
|
||||
*
|
||||
* // Checks /assets/calculator_files/calculator.json exists and is valid json file.
|
||||
* EnvironmentCheckSuite::register(
|
||||
* 'check',
|
||||
* 'FileAccessibilityAndValidationCheck(
|
||||
* "' . BASE_PATH . '/assets/calculator_files/calculator.json",
|
||||
* "jsonValidate",
|
||||
* '.FileAccessibilityAndValidationCheck::CHECK_SINGLE.'
|
||||
* )',
|
||||
* 'Check a calculator.json exists and is valid json file'
|
||||
* );
|
||||
*
|
||||
* // Check only existence
|
||||
* EnvironmentCheckSuite::register(
|
||||
* 'check',
|
||||
* 'FileAccessibilityAndValidationCheck("' . BASE_PATH . '/assets/calculator_files/calculator.json")',
|
||||
* 'Check a calculator.json exists only'
|
||||
* );
|
||||
*/
|
||||
class FileAccessibilityAndValidationCheck implements EnvironmentCheck
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
const CHECK_SINGLE = 1;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
const CHECK_ALL = 2;
|
||||
|
||||
/**
|
||||
* Absolute path to a file or folder, compatible with glob().
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $path;
|
||||
|
||||
/**
|
||||
* Constant, check for a single file to match age criteria, or all of them.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $fileTypeValidateFunc;
|
||||
|
||||
/**
|
||||
* Constant, check for a single file to match age criteria, or all of them.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $checkType;
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param string $fileTypeValidateFunc
|
||||
* @param null|int $checkType
|
||||
*/
|
||||
public function __construct($path, $fileTypeValidateFunc = 'noVidation', $checkType = null)
|
||||
{
|
||||
$this->path = $path;
|
||||
$this->fileTypeValidateFunc = ($fileTypeValidateFunc)? $fileTypeValidateFunc : 'noVidation';
|
||||
$this->checkType = ($checkType) ? $checkType : self::CHECK_SINGLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function check()
|
||||
{
|
||||
$origStage = Versioned::get_reading_mode();
|
||||
Versioned::set_reading_mode(Versioned::LIVE);
|
||||
|
||||
$files = $this->getFiles();
|
||||
if ($files) {
|
||||
$fileTypeValidateFunc = $this->fileTypeValidateFunc;
|
||||
if (method_exists($this, $fileTypeValidateFunc ?? '')) {
|
||||
$invalidFiles = [];
|
||||
$validFiles = [];
|
||||
|
||||
foreach ($files as $file) {
|
||||
if ($this->$fileTypeValidateFunc($file)) {
|
||||
$validFiles[] = $file;
|
||||
} else {
|
||||
$invalidFiles[] = $file;
|
||||
}
|
||||
}
|
||||
|
||||
// If at least one file was valid, count as passed
|
||||
if ($this->checkType == self::CHECK_SINGLE && count($invalidFiles ?? []) < count($files ?? [])) {
|
||||
$validFileList = PHP_EOL;
|
||||
foreach ($validFiles as $vf) {
|
||||
$validFileList .= $vf . PHP_EOL;
|
||||
}
|
||||
if ($fileTypeValidateFunc == 'noVidation') {
|
||||
$checkReturn = [
|
||||
EnvironmentCheck::OK,
|
||||
sprintf('At least these file(s) accessible: %s', $validFileList)
|
||||
];
|
||||
} else {
|
||||
$checkReturn = [
|
||||
EnvironmentCheck::OK,
|
||||
sprintf(
|
||||
'At least these file(s) passed file type validate function "%s": %s',
|
||||
$fileTypeValidateFunc,
|
||||
$validFileList
|
||||
)
|
||||
];
|
||||
}
|
||||
} else {
|
||||
if (count($invalidFiles ?? []) == 0) {
|
||||
$checkReturn = [EnvironmentCheck::OK, 'All files valideted'];
|
||||
} else {
|
||||
$invalidFileList = PHP_EOL;
|
||||
foreach ($invalidFiles as $vf) {
|
||||
$invalidFileList .= $vf . PHP_EOL;
|
||||
}
|
||||
|
||||
if ($fileTypeValidateFunc == 'noVidation') {
|
||||
$checkReturn = [
|
||||
EnvironmentCheck::ERROR,
|
||||
sprintf('File(s) not accessible: %s', $invalidFileList)
|
||||
];
|
||||
} else {
|
||||
$checkReturn = [
|
||||
EnvironmentCheck::ERROR,
|
||||
sprintf(
|
||||
'File(s) not passing the file type validate function "%s": %s',
|
||||
$fileTypeValidateFunc,
|
||||
$invalidFileList
|
||||
)
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$checkReturn = array(
|
||||
EnvironmentCheck::ERROR,
|
||||
sprintf("Invalid file type validation method name passed: %s ", $fileTypeValidateFunc)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$checkReturn = array(
|
||||
EnvironmentCheck::ERROR,
|
||||
sprintf("No files accessible at path %s", $this->path)
|
||||
);
|
||||
}
|
||||
|
||||
Versioned::set_reading_mode($origStage);
|
||||
|
||||
return $checkReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $file
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function jsonValidate($file)
|
||||
{
|
||||
$json = json_decode(file_get_contents($file ?? '') ?? '');
|
||||
if (!$json) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $file
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function noVidation($file)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of absolute file paths.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getFiles()
|
||||
{
|
||||
return glob($this->path ?? '');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\EnvironmentCheck\Checks;
|
||||
|
||||
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
|
||||
use SilverStripe\ORM\FieldType\DBDatetime;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Does NOT check for existence of a file (will silently fail).
|
||||
*
|
||||
* Examples:
|
||||
* // Checks that Requirements::combine_files() has regenerated files in the last 24h
|
||||
* EnvironmentCheckSuite::register(
|
||||
* 'check',
|
||||
* 'FileAgeCheck("' . ASSETS_PATH . '/_combined_files/*.js' . '", "-1 day", '>', " . FileAgeCheck::CHECK_ALL) . "'
|
||||
* );
|
||||
*
|
||||
* // Checks that at least one backup folder has been created in the last 24h
|
||||
* EnvironmentCheckSuite::register(
|
||||
* 'check',
|
||||
* 'FileAgeCheck("' . BASE_PATH . '/../backups/*' . '", "-1 day", '>', " . FileAgeCheck::CHECK_SINGLE) . "'
|
||||
* );
|
||||
*
|
||||
* @package environmentcheck
|
||||
*/
|
||||
class FileAgeCheck implements EnvironmentCheck
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
const CHECK_SINGLE = 1;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
const CHECK_ALL = 2;
|
||||
|
||||
/**
|
||||
* Absolute path to a file or folder, compatible with glob().
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $path;
|
||||
|
||||
/**
|
||||
* Relative date specification, compatible with strtotime().
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $relativeAge;
|
||||
|
||||
/**
|
||||
* The function to use for checking file age: so filemtime(), filectime(), or fileatime().
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $checkFn;
|
||||
|
||||
/**
|
||||
* Constant, check for a single file to match age criteria, or all of them.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $checkType;
|
||||
|
||||
/**
|
||||
* Type of comparison (either > or <).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $compareOperand;
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param string $relativeAge
|
||||
* @param string $compareOperand
|
||||
* @param null|int $checkType
|
||||
* @param string $checkFn
|
||||
*/
|
||||
public function __construct($path, $relativeAge, $compareOperand = '>', $checkType = null, $checkFn = 'filemtime')
|
||||
{
|
||||
$this->path = $path;
|
||||
$this->relativeAge = $relativeAge;
|
||||
$this->checkFn = $checkFn;
|
||||
$this->checkType = ($checkType) ? $checkType : self::CHECK_SINGLE;
|
||||
$this->compareOperand = $compareOperand;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function check()
|
||||
{
|
||||
$cutoffTime = strtotime($this->relativeAge ?? '', DBDatetime::now()->Format('U'));
|
||||
$files = $this->getFiles();
|
||||
$invalidFiles = [];
|
||||
$validFiles = [];
|
||||
$checkFn = $this->checkFn;
|
||||
$allValid = true;
|
||||
if ($files) {
|
||||
foreach ($files as $file) {
|
||||
$fileTime = $checkFn($file);
|
||||
$valid = ($this->compareOperand == '>') ? ($fileTime >= $cutoffTime) : ($fileTime <= $cutoffTime);
|
||||
if ($valid) {
|
||||
$validFiles[] = $file;
|
||||
} else {
|
||||
$invalidFiles[] = $file;
|
||||
if ($this->checkType == self::CHECK_ALL) {
|
||||
return [
|
||||
EnvironmentCheck::ERROR,
|
||||
sprintf(
|
||||
'File "%s" doesn\'t match age check (compare %s: %s, actual: %s)',
|
||||
$file,
|
||||
$this->compareOperand,
|
||||
date('c', $cutoffTime),
|
||||
date('c', $fileTime)
|
||||
)
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If at least one file was valid, count as passed
|
||||
if ($this->checkType == self::CHECK_SINGLE && count($invalidFiles ?? []) < count($files ?? [])) {
|
||||
return [EnvironmentCheck::OK, ''];
|
||||
}
|
||||
if (count($invalidFiles ?? []) == 0) {
|
||||
return [EnvironmentCheck::OK, ''];
|
||||
}
|
||||
return [
|
||||
EnvironmentCheck::ERROR,
|
||||
sprintf('No files matched criteria (%s %s)', $this->compareOperand, date('c', $cutoffTime))
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of absolute file paths.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getFiles()
|
||||
{
|
||||
return glob($this->path ?? '');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\EnvironmentCheck\Checks;
|
||||
|
||||
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
|
||||
|
||||
/**
|
||||
* Check that the given file is writable.
|
||||
*
|
||||
* @package environmentcheck
|
||||
*/
|
||||
class FileWriteableCheck implements EnvironmentCheck
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $path;
|
||||
|
||||
/**
|
||||
* @param string $path The full path. If a relative path, it will relative to the BASE_PATH.
|
||||
*/
|
||||
public function __construct($path)
|
||||
{
|
||||
$this->path = $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function check()
|
||||
{
|
||||
if ($this->path[0] == '/') {
|
||||
$filename = $this->path;
|
||||
} else {
|
||||
$filename = BASE_PATH . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $this->path ?? '');
|
||||
}
|
||||
|
||||
if (file_exists($filename ?? '')) {
|
||||
$isWriteable = is_writeable($filename ?? '');
|
||||
} else {
|
||||
$isWriteable = is_writeable(dirname($filename ?? ''));
|
||||
}
|
||||
|
||||
if (!$isWriteable) {
|
||||
if (function_exists('posix_getgroups')) {
|
||||
$userID = posix_geteuid();
|
||||
$user = posix_getpwuid($userID ?? 0);
|
||||
|
||||
$currentOwnerID = fileowner(file_exists($filename ?? '') ? $filename : dirname($filename ?? ''));
|
||||
$currentOwner = posix_getpwuid($currentOwnerID ?? 0);
|
||||
|
||||
$message = "User '$user[name]' needs to be able to write to this file:\n$filename\n\nThe file is "
|
||||
. "currently owned by '$currentOwner[name]'. ";
|
||||
|
||||
if ($user['name'] == $currentOwner['name']) {
|
||||
$message .= 'We recommend that you make the file writeable.';
|
||||
} else {
|
||||
$groups = posix_getgroups();
|
||||
$groupList = [];
|
||||
foreach ($groups as $group) {
|
||||
$groupInfo = posix_getgrgid($group ?? 0);
|
||||
if (in_array($currentOwner['name'], $groupInfo['members'] ?? [])) {
|
||||
$groupList[] = $groupInfo['name'];
|
||||
}
|
||||
}
|
||||
if ($groupList) {
|
||||
$message .= " We recommend that you make the file group-writeable and change the group to "
|
||||
. "one of these groups:\n - " . implode("\n - ", $groupList)
|
||||
. "\n\nFor example:\nchmod g+w $filename\nchgrp " . $groupList[0] . " $filename";
|
||||
} else {
|
||||
$message .= " There is no user-group that contains both the web-server user and the owner "
|
||||
. "of this file. Change the ownership of the file, create a new group, or temporarily "
|
||||
. "make the file writeable by everyone during the install process.";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$message = "The webserver user needs to be able to write to this file:\n$filename";
|
||||
}
|
||||
|
||||
return [EnvironmentCheck::ERROR, $message];
|
||||
}
|
||||
|
||||
return [EnvironmentCheck::OK, ''];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\EnvironmentCheck\Checks;
|
||||
|
||||
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
|
||||
|
||||
/**
|
||||
* Check that the given class exists.
|
||||
*
|
||||
* @package environmentcheck
|
||||
*/
|
||||
class HasClassCheck implements EnvironmentCheck
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $className;
|
||||
|
||||
/**
|
||||
* @param string $className The name of the class to look for.
|
||||
*/
|
||||
public function __construct($className)
|
||||
{
|
||||
$this->className = $className;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function check()
|
||||
{
|
||||
if (class_exists($this->className ?? '')) {
|
||||
return [EnvironmentCheck::OK, 'Class ' . $this->className.' exists'];
|
||||
}
|
||||
return [EnvironmentCheck::ERROR, 'Class ' . $this->className.' doesn\'t exist'];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\EnvironmentCheck\Checks;
|
||||
|
||||
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
|
||||
|
||||
/**
|
||||
* Check that the given function exists.
|
||||
*
|
||||
* @package environmentcheck
|
||||
*/
|
||||
class HasFunctionCheck implements EnvironmentCheck
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $functionName;
|
||||
|
||||
/**
|
||||
* @param string $functionName The name of the function to look for.
|
||||
*/
|
||||
public function __construct($functionName)
|
||||
{
|
||||
$this->functionName = $functionName;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function check()
|
||||
{
|
||||
if (function_exists($this->functionName ?? '')) {
|
||||
return [EnvironmentCheck::OK, $this->functionName . '() exists'];
|
||||
}
|
||||
return [EnvironmentCheck::ERROR, $this->functionName . '() doesn\'t exist'];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\EnvironmentCheck\Checks;
|
||||
|
||||
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
|
||||
|
||||
/**
|
||||
* Checks if the SMTP connection configured through PHP.ini works as expected.
|
||||
*
|
||||
* Only checks socket connection with HELO command, not actually sending the email.
|
||||
*
|
||||
* @package environmentcheck
|
||||
*/
|
||||
class SMTPConnectCheck implements EnvironmentCheck
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $host;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $port;
|
||||
|
||||
/**
|
||||
* Timeout (in seconds).
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $timeout;
|
||||
|
||||
/**
|
||||
* @param null|string $host
|
||||
* @param null|int $port
|
||||
* @param int $timeout
|
||||
*/
|
||||
public function __construct($host = null, $port = null, $timeout = 15)
|
||||
{
|
||||
$this->host = ($host) ? $host : ini_get('SMTP');
|
||||
if (!$this->host) {
|
||||
$this->host = 'localhost';
|
||||
}
|
||||
|
||||
$this->port = ($port) ? $port : ini_get('smtp_port');
|
||||
if (!$this->port) {
|
||||
$this->port = 25;
|
||||
}
|
||||
|
||||
$this->timeout = $timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function check()
|
||||
{
|
||||
$f = @fsockopen($this->host ?? '', $this->port ?? 0, $errno, $errstr, $this->timeout);
|
||||
if (!$f) {
|
||||
return [
|
||||
EnvironmentCheck::ERROR,
|
||||
sprintf("Couldn't connect to SMTP on %s:%s (Error: %s %s)", $this->host, $this->port, $errno, $errstr)
|
||||
];
|
||||
}
|
||||
|
||||
fwrite($f, "HELO its_me\r\n");
|
||||
$response = fread($f, 26);
|
||||
if (substr($response ?? '', 0, 3) != '220') {
|
||||
return [
|
||||
EnvironmentCheck::ERROR,
|
||||
sprintf('Invalid mail server response: %s', $response)
|
||||
];
|
||||
}
|
||||
|
||||
return [EnvironmentCheck::OK, ''];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\EnvironmentCheck\Checks;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use SilverStripe\EnvironmentCheck\Traits\Fetcher;
|
||||
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
|
||||
|
||||
/**
|
||||
* Check that a given URL does not generate a session.
|
||||
*
|
||||
* @author Adrian Humphreys
|
||||
* @package environmentcheck
|
||||
*/
|
||||
class SessionCheck implements EnvironmentCheck
|
||||
{
|
||||
use Fetcher;
|
||||
|
||||
/**
|
||||
* Set up check with URL
|
||||
*
|
||||
* @param string $url The route, excluding the domain
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function __construct($url = '')
|
||||
{
|
||||
$this->setURL($url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the response for URL does not create a session
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function check()
|
||||
{
|
||||
$response = $this->client->get($this->getURL());
|
||||
$cookie = $this->getCookie($response);
|
||||
$fullURL = $this->getURL();
|
||||
|
||||
if ($cookie) {
|
||||
return [
|
||||
EnvironmentCheck::ERROR,
|
||||
"Sessions are being set for {$fullURL} : Set-Cookie => " . $cookie,
|
||||
];
|
||||
}
|
||||
return [
|
||||
EnvironmentCheck::OK,
|
||||
"Sessions are not being created for {$fullURL} 👍",
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get PHPSESSID or SECSESSID cookie set from the response if it exists.
|
||||
*
|
||||
* @param ResponseInterface $response
|
||||
* @return string|null Cookie contents or null if it doesn't exist
|
||||
*/
|
||||
public function getCookie(ResponseInterface $response)
|
||||
{
|
||||
$result = null;
|
||||
$cookies = $response->getHeader('Set-Cookie');
|
||||
|
||||
foreach ($cookies as $cookie) {
|
||||
if (strpos($cookie ?? '', 'SESSID') !== false) {
|
||||
$result = $cookie;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\EnvironmentCheck\Checks;
|
||||
|
||||
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
|
||||
use SilverStripe\FullTextSearch\Solr\Solr;
|
||||
use SilverStripe\FullTextSearch\Solr\SolrIndex;
|
||||
|
||||
/**
|
||||
* Check the availability of all Solr indexes
|
||||
*
|
||||
* If there are no indexes of given class found, the returned status will still be "OK".
|
||||
*
|
||||
* @package environmentcheck
|
||||
*/
|
||||
class SolrIndexCheck implements EnvironmentCheck
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function check()
|
||||
{
|
||||
$brokenCores = [];
|
||||
|
||||
if (!class_exists(Solr::class)) {
|
||||
return [
|
||||
EnvironmentCheck::ERROR,
|
||||
'Class `' . Solr::class . '` not found. Is the fulltextsearch module installed?'
|
||||
];
|
||||
}
|
||||
|
||||
$service = Solr::service();
|
||||
foreach (Solr::get_indexes() as $index) {
|
||||
/** @var SolrIndex $core */
|
||||
$core = $index->getIndexName();
|
||||
if (!$service->coreIsActive($core)) {
|
||||
$brokenCores[] = $core;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($brokenCores)) {
|
||||
return [
|
||||
EnvironmentCheck::ERROR,
|
||||
'The following indexes are unavailable: ' . implode(', ', $brokenCores)
|
||||
];
|
||||
}
|
||||
|
||||
return [EnvironmentCheck::OK, 'Expected indexes are available.'];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\EnvironmentCheck\Checks;
|
||||
|
||||
use SilverStripe\Control\Director;
|
||||
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
|
||||
|
||||
/**
|
||||
* Check that a given URL is functioning, by default, the homepage.
|
||||
*
|
||||
* Note that Director::test() will be used rather than a CURL check.
|
||||
*
|
||||
* @package environmentcheck
|
||||
*/
|
||||
class URLCheck implements EnvironmentCheck
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $url;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $testString;
|
||||
|
||||
/**
|
||||
* @param string $url The URL to check, relative to the site (homepage is '').
|
||||
* @param string $testString An optional piece of text to search for on the homepage.
|
||||
*/
|
||||
public function __construct($url = '', $testString = '')
|
||||
{
|
||||
$this->url = $url;
|
||||
$this->testString = $testString;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @return array
|
||||
* @throws HTTPResponse_Exception
|
||||
*/
|
||||
public function check()
|
||||
{
|
||||
$response = Director::test($this->url);
|
||||
|
||||
if ($response->getStatusCode() != 200) {
|
||||
return [
|
||||
EnvironmentCheck::ERROR,
|
||||
sprintf('Error retrieving "%s" (Code: %d)', $this->url, $response->getStatusCode())
|
||||
];
|
||||
} elseif ($this->testString && (strpos($response->getBody() ?? '', $this->testString ?? '') === false)) {
|
||||
return [
|
||||
EnvironmentCheck::WARNING,
|
||||
sprintf('Success retrieving "%s", but string "%s" not found', $this->url, $this->testString)
|
||||
];
|
||||
}
|
||||
return [
|
||||
EnvironmentCheck::OK,
|
||||
sprintf('Success retrieving "%s"', $this->url)
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\EnvironmentCheck\Controllers;
|
||||
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Control\HTTPResponse_Exception;
|
||||
use SilverStripe\EnvironmentCheck\EnvironmentChecker;
|
||||
|
||||
/**
|
||||
* Class DevCheckController
|
||||
*
|
||||
* @package environmentcheck
|
||||
*/
|
||||
class DevCheckController extends Controller
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private static $allowed_actions = [
|
||||
'index'
|
||||
];
|
||||
|
||||
/**
|
||||
* Permission code to check for access to this controller.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private static $permission = 'ADMIN';
|
||||
|
||||
/**
|
||||
* @param HTTPRequest $request
|
||||
*
|
||||
* @return EnvironmentChecker
|
||||
*
|
||||
* @throws HTTPResponse_Exception
|
||||
*/
|
||||
public function index($request)
|
||||
{
|
||||
$suite = 'check';
|
||||
|
||||
if ($name = $request->param('Suite')) {
|
||||
$suite = $name;
|
||||
}
|
||||
|
||||
/** @var EnvironmentChecker */
|
||||
$checker = EnvironmentChecker::create($suite, 'Environment status')
|
||||
->setRequest($request)
|
||||
->setIncludeDetails(true);
|
||||
|
||||
$checker->init($this->config()->permission);
|
||||
|
||||
return $checker;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\EnvironmentCheck\Controllers;
|
||||
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Control\HTTPResponse_Exception;
|
||||
use SilverStripe\EnvironmentCheck\EnvironmentChecker;
|
||||
|
||||
/**
|
||||
* Class DevHealthController
|
||||
*
|
||||
* @package environmentcheck
|
||||
*/
|
||||
class DevHealthController extends Controller
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private static $allowed_actions = [
|
||||
'index'
|
||||
];
|
||||
|
||||
/**
|
||||
* @return EnvironmentChecker
|
||||
*
|
||||
* @throws HTTPResponse_Exception
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
// health check does not require permission to run
|
||||
$checker = new EnvironmentChecker('health', 'Site health');
|
||||
$checker->setErrorCode(500);
|
||||
|
||||
return $checker;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\EnvironmentCheck;
|
||||
|
||||
/**
|
||||
* Interface for environment checks
|
||||
*
|
||||
* An environment check is a test that can be performed on a live environment. They differ from
|
||||
* unit tests in that they are designed to check the state of the environment/server, rather than
|
||||
* the code.
|
||||
*
|
||||
* Environment checks should *never* alter production data.
|
||||
*
|
||||
* Some things you might make environment checks for:
|
||||
* - Can I access the database?
|
||||
* - Are the right PHP modules installed?
|
||||
* - Are the file permissions correct?
|
||||
*
|
||||
* @package environmentcheck
|
||||
*/
|
||||
interface EnvironmentCheck
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
const ERROR = 3;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
const WARNING = 2;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
const OK = 1;
|
||||
|
||||
/**
|
||||
* @return array Result with 'status' and 'message' keys.
|
||||
*
|
||||
* Status is EnvironmentCheck::ERROR, EnvironmentCheck::WARNING, or EnvironmentCheck::OK.
|
||||
*/
|
||||
public function check();
|
||||
}
|
|
@ -0,0 +1,215 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\EnvironmentCheck;
|
||||
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
use SilverStripe\Core\Config\Configurable;
|
||||
use SilverStripe\Core\Extensible;
|
||||
use SilverStripe\Core\Injector\Injectable;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
|
||||
/**
|
||||
* Represents a suite of environment checks.
|
||||
* Specific checks can be registered against a named instance of EnvironmentCheckSuite.
|
||||
*
|
||||
* Usage #1 - _config.php
|
||||
* EnvironmentCheckSuite::register('health', 'MyHealthCheck("my param")', 'Title of my health check');
|
||||
*
|
||||
* Usage #2 - config.yml
|
||||
* EnvironmentCheckSuite:
|
||||
* registered_checks:
|
||||
* mycheck:
|
||||
* definition: 'MyHealthCheck("my param")'
|
||||
* title: 'Title of my health check'
|
||||
* registered_suites:
|
||||
* health:
|
||||
* - mycheck
|
||||
*
|
||||
* $result = EnvironmentCheckSuite::inst('health')->run();
|
||||
*
|
||||
* @package environmentcheck
|
||||
*/
|
||||
class EnvironmentCheckSuite
|
||||
{
|
||||
use Configurable;
|
||||
use Injectable;
|
||||
use Extensible;
|
||||
/**
|
||||
* Name of this suite.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $checks = [];
|
||||
|
||||
/**
|
||||
* Associative array of named checks registered via the config system. Each check should specify:
|
||||
* - definition (e.g. 'MyHealthCheck("my param")')
|
||||
* - title (e.g. 'Is my feature working?')
|
||||
* - state (setting this to 'disabled' will cause suites to skip this check entirely.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $registered_checks = [];
|
||||
|
||||
/**
|
||||
* Associative array of named suites registered via the config system. Each suite should enumerate
|
||||
* named checks that have been configured in 'registered_checks'.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $registered_suites = [];
|
||||
|
||||
/**
|
||||
* Load checks for this suite from the configuration system. This is an alternative to the
|
||||
* EnvironmentCheckSuite::register - both can be used, checks will be appended to the suite.
|
||||
*
|
||||
* @param string $suiteName The name of this suite.
|
||||
*/
|
||||
public function __construct($suiteName)
|
||||
{
|
||||
if (empty($this->config()->registered_suites[$suiteName])) {
|
||||
// Not registered via config system, but it still may be configured later via self::register.
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->config()->registered_suites[$suiteName] as $checkName) {
|
||||
if (empty($this->config()->registered_checks[$checkName])) {
|
||||
throw new InvalidArgumentException(
|
||||
"Bad EnvironmentCheck: '$checkName' - the named check has not been registered."
|
||||
);
|
||||
}
|
||||
|
||||
$check = $this->config()->registered_checks[$checkName];
|
||||
|
||||
// Existing named checks can be disabled by setting their 'state' to 'disabled'.
|
||||
// This is handy for disabling checks mandated by modules.
|
||||
if (!empty($check['state']) && $check['state'] === 'disabled') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add the check to this suite.
|
||||
$this->push($check['definition'], $check['title']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run this test suite and return the result code of the worst result.
|
||||
*
|
||||
* @return EnvironmentCheckSuiteResult
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
$result = new EnvironmentCheckSuiteResult();
|
||||
|
||||
foreach ($this->checkInstances() as $check) {
|
||||
list($checkClass, $checkTitle) = $check;
|
||||
try {
|
||||
list($status, $message) = $checkClass->check();
|
||||
// If the check fails, register that as an error
|
||||
} catch (Exception $e) {
|
||||
$status = EnvironmentCheck::ERROR;
|
||||
$message = $e->getMessage();
|
||||
}
|
||||
$result->addResult($status, $message, $checkTitle);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get instances of all the environment checks.
|
||||
*
|
||||
* @return EnvironmentChecker[]
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
protected function checkInstances()
|
||||
{
|
||||
$output = [];
|
||||
foreach ($this->checks as $check) {
|
||||
list($checkClass, $checkTitle) = $check;
|
||||
if (is_string($checkClass)) {
|
||||
$checkInst = Injector::inst()->create($checkClass);
|
||||
if ($checkInst instanceof EnvironmentCheck) {
|
||||
$output[] = [$checkInst, $checkTitle];
|
||||
} else {
|
||||
throw new InvalidArgumentException(
|
||||
"Bad EnvironmentCheck: '$checkClass' - the named class doesn't implement EnvironmentCheck"
|
||||
);
|
||||
}
|
||||
} elseif ($checkClass instanceof EnvironmentCheck) {
|
||||
$output[] = [$checkClass, $checkTitle];
|
||||
} else {
|
||||
throw new InvalidArgumentException("Bad EnvironmentCheck: " . var_export($check, true));
|
||||
}
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a check to this suite.
|
||||
*
|
||||
* @param mixed $check
|
||||
* @param string $title
|
||||
*/
|
||||
public function push($check, $title = null)
|
||||
{
|
||||
if (!$title) {
|
||||
$title = is_string($check) ? $check : get_class($check);
|
||||
}
|
||||
$this->checks[] = [$check, $title];
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected static $instances = [];
|
||||
|
||||
/**
|
||||
* Return a named instance of EnvironmentCheckSuite.
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return EnvironmentCheckSuite
|
||||
*/
|
||||
public static function inst($name)
|
||||
{
|
||||
if (!isset(self::$instances[$name])) {
|
||||
self::$instances[$name] = new EnvironmentCheckSuite($name);
|
||||
}
|
||||
return self::$instances[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a check against the named check suite.
|
||||
*
|
||||
* @param string|array $names
|
||||
* @param EnvironmentCheck $check
|
||||
* @param string|array
|
||||
*/
|
||||
public static function register($names, $check, $title = null)
|
||||
{
|
||||
if (!is_array($names)) {
|
||||
$names = [$names];
|
||||
}
|
||||
|
||||
foreach ($names as $name) {
|
||||
self::inst($name)->push($check, $title);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters all checks.
|
||||
*/
|
||||
public static function reset()
|
||||
{
|
||||
self::$instances = [];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\EnvironmentCheck;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use SilverStripe\ORM\ArrayList;
|
||||
use SilverStripe\View\ArrayData;
|
||||
use SilverStripe\View\ViewableData;
|
||||
|
||||
/**
|
||||
* A single set of results from running an EnvironmentCheckSuite
|
||||
*
|
||||
* @package environmentcheck
|
||||
*/
|
||||
class EnvironmentCheckSuiteResult extends ViewableData
|
||||
{
|
||||
/**
|
||||
* @var ArrayList
|
||||
*/
|
||||
protected $details;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $worst = 0;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->details = new ArrayList();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $status
|
||||
* @param string $message
|
||||
* @param string $checkIdentifier
|
||||
*/
|
||||
public function addResult($status, $message, $checkIdentifier)
|
||||
{
|
||||
$this->details->push(new ArrayData([
|
||||
'Check' => $checkIdentifier,
|
||||
'Status' => $this->statusText($status),
|
||||
'StatusCode' => $status,
|
||||
'Message' => $message,
|
||||
]));
|
||||
|
||||
$this->worst = max($this->worst, $status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if there are no errors.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function ShouldPass()
|
||||
{
|
||||
return $this->worst <= EnvironmentCheck::WARNING;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns overall (i.e. worst) status as a string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function Status()
|
||||
{
|
||||
return $this->statusText($this->worst);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns detailed status information about each check.
|
||||
*
|
||||
* @return ArrayList
|
||||
*/
|
||||
public function Details()
|
||||
{
|
||||
return $this->details;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the final result status and details to JSON.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toJSON()
|
||||
{
|
||||
$result = [
|
||||
'Status' => $this->Status(),
|
||||
'ShouldPass' => $this->ShouldPass(),
|
||||
'Checks' => []
|
||||
];
|
||||
foreach ($this->details as $detail) {
|
||||
$result['Checks'][] = $detail->toMap();
|
||||
}
|
||||
return json_encode($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a text version of a status code.
|
||||
*
|
||||
* @param int $status
|
||||
* @return string
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
protected function statusText($status)
|
||||
{
|
||||
switch ($status) {
|
||||
case EnvironmentCheck::ERROR:
|
||||
return 'ERROR';
|
||||
break;
|
||||
case EnvironmentCheck::WARNING:
|
||||
return 'WARNING';
|
||||
break;
|
||||
case EnvironmentCheck::OK:
|
||||
return 'OK';
|
||||
break;
|
||||
case 0:
|
||||
return 'NO CHECKS';
|
||||
break;
|
||||
default:
|
||||
throw new InvalidArgumentException("Bad environment check status '$status'");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,337 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\EnvironmentCheck;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\LogLevel;
|
||||
use SilverStripe\Control\Director;
|
||||
use SilverStripe\Control\Email\Email;
|
||||
use SilverStripe\Control\HTTPResponse;
|
||||
use SilverStripe\Control\HTTPResponse_Exception;
|
||||
use SilverStripe\Control\RequestHandler;
|
||||
use SilverStripe\Core\Environment;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Dev\Deprecation;
|
||||
use SilverStripe\Security\Member;
|
||||
use SilverStripe\Security\Permission;
|
||||
use SilverStripe\Security\Security;
|
||||
|
||||
/**
|
||||
* Provides an interface for checking the given EnvironmentCheckSuite.
|
||||
*
|
||||
* @package environmentcheck
|
||||
*/
|
||||
class EnvironmentChecker extends RequestHandler
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private static $url_handlers = [
|
||||
'' => 'index',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $checkSuiteName;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $title;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $includeDetails = false;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $errorCode = 500;
|
||||
|
||||
/**
|
||||
* @var null|string
|
||||
*/
|
||||
private static $to_email_address = null;
|
||||
|
||||
/**
|
||||
* @var null|string
|
||||
*/
|
||||
private static $from_email_address = null;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private static $email_results = false;
|
||||
|
||||
/**
|
||||
* @var bool Log results via {@link \Psr\Log\LoggerInterface}
|
||||
*/
|
||||
private static $log_results_warning = false;
|
||||
|
||||
/**
|
||||
* @var string Maps to {@link \Psr\Log\LogLevel} levels. Defaults to LogLevel::WARNING
|
||||
*/
|
||||
private static $log_results_warning_level = LogLevel::WARNING;
|
||||
|
||||
/**
|
||||
* @var bool Log results via a {@link \Psr\Log\LoggerInterface}
|
||||
*/
|
||||
private static $log_results_error = false;
|
||||
|
||||
/**
|
||||
* @var int Maps to {@link \Psr\Log\LogLevel} levels. Defaults to LogLevel::ALERT
|
||||
*/
|
||||
private static $log_results_error_level = LogLevel::ALERT;
|
||||
|
||||
/**
|
||||
* @param string $checkSuiteName
|
||||
* @param string $title
|
||||
*/
|
||||
public function __construct($checkSuiteName, $title)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->checkSuiteName = $checkSuiteName;
|
||||
$this->title = $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $permission
|
||||
*
|
||||
* @throws HTTPResponse_Exception
|
||||
*/
|
||||
public function init($permission = 'ADMIN')
|
||||
{
|
||||
// if the environment supports it, provide a basic auth challenge and see if it matches configured credentials
|
||||
if (Environment::getEnv('ENVCHECK_BASICAUTH_USERNAME')
|
||||
&& Environment::getEnv('ENVCHECK_BASICAUTH_PASSWORD')
|
||||
) {
|
||||
// Check that details are both provided, and match
|
||||
if (empty($_SERVER['PHP_AUTH_USER']) || empty($_SERVER['PHP_AUTH_PW'])
|
||||
|| $_SERVER['PHP_AUTH_USER'] != Environment::getEnv('ENVCHECK_BASICAUTH_USERNAME')
|
||||
|| $_SERVER['PHP_AUTH_PW'] != Environment::getEnv('ENVCHECK_BASICAUTH_PASSWORD')
|
||||
) {
|
||||
// Fail check with basic auth challenge
|
||||
$response = new HTTPResponse(null, 401);
|
||||
$response->addHeader('WWW-Authenticate', "Basic realm=\"Environment check\"");
|
||||
throw new HTTPResponse_Exception($response);
|
||||
}
|
||||
} elseif (!$this->canAccess(null, $permission)) {
|
||||
// Fail check with silverstripe login challenge
|
||||
$result = Security::permissionFailure(
|
||||
$this,
|
||||
"You must have the {$permission} permission to access this check"
|
||||
);
|
||||
throw new HTTPResponse_Exception($result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the current member can access the environment checker
|
||||
*
|
||||
* @param null|int|Member $member
|
||||
* @param string $permission
|
||||
* @return bool
|
||||
*/
|
||||
public function canAccess($member = null, $permission = 'ADMIN')
|
||||
{
|
||||
if (!$member) {
|
||||
$member = Security::getCurrentUser();
|
||||
}
|
||||
|
||||
// We allow access to this controller regardless of live-status or ADMIN permission only
|
||||
// if on CLI. Access to this controller is always allowed in "dev-mode", or of the user is ADMIN.
|
||||
if (Director::isDev()
|
||||
|| Director::is_cli()
|
||||
|| empty($permission)
|
||||
|| Permission::checkMember($member, $permission)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Extended access checks.
|
||||
// "Veto" style, return NULL to abstain vote.
|
||||
$canExtended = null;
|
||||
$results = $this->extend('canAccess', $member);
|
||||
if ($results && is_array($results)) {
|
||||
if (!min($results)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return HTTPResponse
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$response = new HTTPResponse;
|
||||
$result = EnvironmentCheckSuite::inst($this->checkSuiteName)->run();
|
||||
|
||||
if (!$result->ShouldPass()) {
|
||||
$response->setStatusCode($this->errorCode);
|
||||
}
|
||||
|
||||
$data = [
|
||||
'URL' => Director::absoluteBaseURL(),
|
||||
'Title' => $this->title,
|
||||
'Name' => $this->checkSuiteName,
|
||||
'ErrorCode' => $this->errorCode
|
||||
];
|
||||
|
||||
$emailContent = $result->customise(array_merge($data, [
|
||||
'IncludeDetails' => true
|
||||
]))->renderWith(__CLASS__);
|
||||
|
||||
if (!$this->includeDetails) {
|
||||
$webContent = $result->customise(array_merge($data, [
|
||||
'IncludeDetails' => false
|
||||
]))->renderWith(__CLASS__);
|
||||
} else {
|
||||
$webContent = $emailContent;
|
||||
}
|
||||
|
||||
if ($this->config()->get('email_results') && !$result->ShouldPass()) {
|
||||
$email = new Email(
|
||||
$this->config()->get('from_email_address'),
|
||||
$this->config()->get('to_email_address'),
|
||||
$this->title,
|
||||
$emailContent
|
||||
);
|
||||
$email->send();
|
||||
}
|
||||
|
||||
// Optionally log errors and warnings individually
|
||||
foreach ($result->Details() as $detail) {
|
||||
if ($this->config()->get('log_results_warning') && $detail->StatusCode == EnvironmentCheck::WARNING) {
|
||||
$this->log(
|
||||
sprintf('EnvironmentChecker warning at "%s" check. Message: %s', $detail->Check, $detail->Message),
|
||||
$this->config()->get('log_results_warning_level')
|
||||
);
|
||||
} elseif ($this->config()->get('log_results_error') && $detail->StatusCode == EnvironmentCheck::ERROR) {
|
||||
$this->log(
|
||||
sprintf('EnvironmentChecker error at "%s" check. Message: %s', $detail->Check, $detail->Message),
|
||||
$this->config()->get('log_results_error_level')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// output the result as JSON if requested
|
||||
if ($this->getRequest()->getExtension() == 'json'
|
||||
|| strpos($this->getRequest()->getHeader('Accept') ?? '', 'application/json') !== false
|
||||
) {
|
||||
$response->setBody($result->toJSON());
|
||||
$response->addHeader('Content-Type', 'application/json');
|
||||
return $response;
|
||||
}
|
||||
|
||||
$response->setBody($webContent);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a log entry to the configured PSR-3 LoggerInterface
|
||||
*
|
||||
* @param string $message
|
||||
* @param int $level
|
||||
*/
|
||||
public function log($message, $level)
|
||||
{
|
||||
Injector::inst()->get(LoggerInterface::class)->log($level, $message);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the HTTP status code that should be returned when there's an error.
|
||||
*
|
||||
* @param int $errorCode
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setErrorCode($errorCode)
|
||||
{
|
||||
$this->errorCode = $errorCode;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether to include the full breakdown of services
|
||||
*
|
||||
* @param bool $includeDetails
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setIncludeDetails($includeDetails)
|
||||
{
|
||||
$this->includeDetails = $includeDetails;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 2.0.0 Use config API instead
|
||||
* @param string $from
|
||||
*/
|
||||
public static function set_from_email_address($from)
|
||||
{
|
||||
Deprecation::notice('2.0.0', 'Use config API instead');
|
||||
static::config()->set('from_email_address', $from);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 2.0.0 Use config API instead
|
||||
* @return null|string
|
||||
*/
|
||||
public static function get_from_email_address()
|
||||
{
|
||||
Deprecation::notice('2.0.0', 'Use config API instead');
|
||||
return static::config()->get('from_email_address');
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 2.0.0 Use config API instead
|
||||
* @param string $to
|
||||
*/
|
||||
public static function set_to_email_address($to)
|
||||
{
|
||||
Deprecation::notice('2.0.0', 'Use config API instead');
|
||||
static::config()->set('to_email_address', $to);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 2.0.0 Use config API instead
|
||||
* @return null|string
|
||||
*/
|
||||
public static function get_to_email_address()
|
||||
{
|
||||
Deprecation::notice('2.0.0', 'Use config API instead');
|
||||
return static::config()->get('to_email_address');
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 2.0.0 Use config API instead
|
||||
* @param bool $results
|
||||
*/
|
||||
public static function set_email_results($results)
|
||||
{
|
||||
Deprecation::notice('2.0.0', 'Use config API instead');
|
||||
static::config()->set('email_results', $results);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 2.0.0 Use config API instead
|
||||
* @return bool
|
||||
*/
|
||||
public static function get_email_results()
|
||||
{
|
||||
Deprecation::notice('2.0.0', 'Use config API instead');
|
||||
return static::config()->get('email_results');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\EnvironmentCheck\Services;
|
||||
|
||||
use GuzzleHttp\Client as GuzzleClient;
|
||||
use SilverStripe\Core\Injector\Factory;
|
||||
use SilverStripe\Core\Config\Configurable;
|
||||
|
||||
/**
|
||||
* Factory class for creating HTTP client which are injected into some env check classes. Inject via YAML,
|
||||
* arguments for Guzzle client can be supplied using "constructor" property or set as default_config.
|
||||
*
|
||||
* @see SilverStripe\EnvironmentCheck\Traits\Fetcher
|
||||
*/
|
||||
class ClientFactory implements Factory
|
||||
{
|
||||
use Configurable;
|
||||
|
||||
/**
|
||||
* Default config for Guzzle client.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $default_config = [];
|
||||
|
||||
/**
|
||||
* Wrapper to create a Guzzle client.
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function create($service, array $params = [])
|
||||
{
|
||||
return new GuzzleClient($this->getConfig($params));
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge config provided from yaml with default config
|
||||
*
|
||||
* @param array $overrides
|
||||
* @return array
|
||||
*/
|
||||
public function getConfig(array $overrides)
|
||||
{
|
||||
return array_merge(
|
||||
$this->config()->get('default_config'),
|
||||
$overrides
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\EnvironmentCheck\Traits;
|
||||
|
||||
use SilverStripe\Control\Director;
|
||||
|
||||
/**
|
||||
* Simple helper for env checks which require HTTP clients.
|
||||
*
|
||||
* @package environmentcheck
|
||||
*/
|
||||
trait Fetcher
|
||||
{
|
||||
/**
|
||||
* Client for making requests, set vi Injector.
|
||||
*
|
||||
* @see SilverStripe\EnvironmentCheck\Services
|
||||
*
|
||||
* @var GuzzleHttp\Client
|
||||
*/
|
||||
public $client = null;
|
||||
|
||||
/**
|
||||
* Absolute URL for requests.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $url;
|
||||
|
||||
/**
|
||||
* Set URL for requests.
|
||||
*
|
||||
* @param string $url Relative URL
|
||||
* @return self
|
||||
*/
|
||||
public function setURL($url)
|
||||
{
|
||||
$this->url = Director::absoluteURL($url);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for URL
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getURL()
|
||||
{
|
||||
return $this->url;
|
||||
}
|
||||
}
|
|
@ -10,7 +10,7 @@
|
|||
margin-top: -10px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
|
||||
h1 {
|
||||
font-size: 30px;
|
||||
margin-bottom: 3px;
|
||||
|
@ -21,11 +21,11 @@
|
|||
font-size: 16px;
|
||||
margin: 2px 0 10px 8px;
|
||||
}
|
||||
|
||||
|
||||
p {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
@ -44,7 +44,7 @@
|
|||
table tr:nth-child(odd) td {
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
|
||||
.OK {
|
||||
color: green;
|
||||
}
|
||||
|
@ -59,23 +59,25 @@
|
|||
</style>
|
||||
</head>
|
||||
|
||||
|
||||
|
||||
<body>
|
||||
<h1 class="$Status">$Title: $Status</h1>
|
||||
<h2 class="website">Site: $URL</h2>
|
||||
|
||||
|
||||
<% if $IncludeDetails %>
|
||||
<table>
|
||||
<tr><th>Check</th> <th>Status</th> <th>Message</th></tr>
|
||||
<% loop Details %>
|
||||
<% loop $Details %>
|
||||
<tr><td>$Check</td> <td class="$Status">$Status</td> <td>$Message.XML</td></tr>
|
||||
<% end_loop %>
|
||||
</table>
|
||||
<% end_if %>
|
||||
|
||||
<% 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>
|
||||
<% 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.<% if not $IncludeDetails %> Full details are available for logged in users at <a href="{$AbsoluteBaseURL}dev/check/">dev/check</a><% end_if %>)</p>
|
||||
<% else %>
|
||||
<% if Name == check %>
|
||||
<% 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>
|
|
@ -0,0 +1,101 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\EnvironmentCheck\Tests\Checks;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\HandlerStack;
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use GuzzleHttp\Handler\MockHandler;
|
||||
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
|
||||
use SilverStripe\EnvironmentCheck\Checks\CacheHeadersCheck;
|
||||
|
||||
/**
|
||||
* Test session checks.
|
||||
*/
|
||||
class CacheHeadersCheckTest extends SapphireTest
|
||||
{
|
||||
/**
|
||||
* Test that directives that must be included, are.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testMustInclude()
|
||||
{
|
||||
// Create a mock and queue responses
|
||||
$mock = new MockHandler([
|
||||
new Response(200, ['Cache-Control' => 'must-revalidate', 'ETag' => '123']),
|
||||
new Response(200, ['Cache-Control' =>'no-cache', 'ETag' => '123']),
|
||||
new Response(200, ['ETag' => '123']),
|
||||
new Response(200, ['Cache-Control' => 'must-revalidate, private', 'ETag' => '123']),
|
||||
]);
|
||||
|
||||
$handler = HandlerStack::create($mock);
|
||||
$client = new Client(['handler' => $handler]);
|
||||
|
||||
$cacheHeadersCheck = new CacheHeadersCheck('/', ['must-revalidate']);
|
||||
$cacheHeadersCheck->client = $client;
|
||||
|
||||
// Run checks for each response above
|
||||
$this->assertContains(EnvironmentCheck::OK, $cacheHeadersCheck->check());
|
||||
$this->assertContains(EnvironmentCheck::ERROR, $cacheHeadersCheck->check());
|
||||
$this->assertContains(EnvironmentCheck::ERROR, $cacheHeadersCheck->check());
|
||||
$this->assertContains(EnvironmentCheck::OK, $cacheHeadersCheck->check());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that directives that must be excluded, are.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testMustExclude()
|
||||
{
|
||||
// Create a mock and queue responses
|
||||
$mock = new MockHandler([
|
||||
new Response(200, ['Cache-Control' => 'must-revalidate', 'ETag' => '123']),
|
||||
new Response(200, ['Cache-Control' =>'no-cache', 'ETag' => '123']),
|
||||
new Response(200, ['ETag' => '123']),
|
||||
new Response(200, ['Cache-Control' =>'private, no-store', 'ETag' => '123']),
|
||||
]);
|
||||
|
||||
$handler = HandlerStack::create($mock);
|
||||
$client = new Client(['handler' => $handler]);
|
||||
|
||||
$cacheHeadersCheck = new CacheHeadersCheck('/', [], ["no-store", "no-cache", "private"]);
|
||||
$cacheHeadersCheck->client = $client;
|
||||
|
||||
// Run checks for each response above
|
||||
$this->assertContains(EnvironmentCheck::OK, $cacheHeadersCheck->check());
|
||||
$this->assertContains(EnvironmentCheck::ERROR, $cacheHeadersCheck->check());
|
||||
$this->assertContains(EnvironmentCheck::OK, $cacheHeadersCheck->check());
|
||||
$this->assertContains(EnvironmentCheck::ERROR, $cacheHeadersCheck->check());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that Etag header must exist in response.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testEtag()
|
||||
{
|
||||
// Create a mock and queue responses
|
||||
$mock = new MockHandler([
|
||||
new Response(200, ['Cache-Control' => 'must-revalidate', 'ETag' => '123']),
|
||||
new Response(200, ['Cache-Control' =>'no-cache']),
|
||||
new Response(200, ['ETag' => '123']),
|
||||
new Response(200, []),
|
||||
]);
|
||||
|
||||
$handler = HandlerStack::create($mock);
|
||||
$client = new Client(['handler' => $handler]);
|
||||
|
||||
$cacheHeadersCheck = new CacheHeadersCheck('/');
|
||||
$cacheHeadersCheck->client = $client;
|
||||
|
||||
// Run checks for each response above
|
||||
$this->assertContains(EnvironmentCheck::OK, $cacheHeadersCheck->check());
|
||||
$this->assertContains(EnvironmentCheck::ERROR, $cacheHeadersCheck->check());
|
||||
$this->assertContains(EnvironmentCheck::OK, $cacheHeadersCheck->check());
|
||||
$this->assertContains(EnvironmentCheck::ERROR, $cacheHeadersCheck->check());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\EnvironmentCheck\Tests\Checks;
|
||||
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\EnvironmentCheck\Checks\DatabaseCheck;
|
||||
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
|
||||
use SilverStripe\Security\Member;
|
||||
|
||||
/**
|
||||
* Class DatabaseCheckTest
|
||||
*
|
||||
* @package environmentcheck
|
||||
*/
|
||||
class DatabaseCheckTest extends SapphireTest
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @var bool
|
||||
*/
|
||||
protected $usesDatabase = true;
|
||||
|
||||
public function testCheckReportsValidConnection()
|
||||
{
|
||||
$member = new Member;
|
||||
$member->FirstName = 'Bob';
|
||||
$member->write();
|
||||
|
||||
$check = new DatabaseCheck();
|
||||
|
||||
$expected = [
|
||||
EnvironmentCheck::OK,
|
||||
''
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, $check->check());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\EnvironmentCheck\Tests\Checks;
|
||||
|
||||
use SilverStripe\Core\Kernel;
|
||||
use SilverStripe\Control\Director;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
|
||||
use SilverStripe\EnvironmentCheck\Checks\EnvTypeCheck;
|
||||
|
||||
/**
|
||||
* Test the env setting check.
|
||||
*/
|
||||
class EnvTypeCheckTest extends SapphireTest
|
||||
{
|
||||
/**
|
||||
* Check is OK when in live mode
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testEnvSettingLive()
|
||||
{
|
||||
/** @var Kernel $kernel */
|
||||
$kernel = Injector::inst()->get(Kernel::class);
|
||||
$kernel->setEnvironment('live');
|
||||
|
||||
$this->assertTrue(Director::isLive());
|
||||
|
||||
$checker = Injector::inst()->get(EnvTypeCheck::class);
|
||||
$result = $checker->check();
|
||||
|
||||
$this->assertSame($result[0], EnvironmentCheck::OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check is ERROR when in test mode
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testEnvSettingTest()
|
||||
{
|
||||
/** @var Kernel $kernel */
|
||||
$kernel = Injector::inst()->get(Kernel::class);
|
||||
$kernel->setEnvironment('test');
|
||||
|
||||
$this->assertTrue(Director::isTest());
|
||||
|
||||
$checker = Injector::inst()->get(EnvTypeCheck::class);
|
||||
$result = $checker->check();
|
||||
|
||||
$this->assertSame($result[0], EnvironmentCheck::ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check is ERROR when in dev mode
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testEnvSettingDev()
|
||||
{
|
||||
/** @var Kernel $kernel */
|
||||
$kernel = Injector::inst()->get(Kernel::class);
|
||||
$kernel->setEnvironment('dev');
|
||||
|
||||
$this->assertTrue(Director::isDev());
|
||||
|
||||
$checker = Injector::inst()->get(EnvTypeCheck::class);
|
||||
$result = $checker->check();
|
||||
|
||||
$this->assertSame($result[0], EnvironmentCheck::ERROR);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\EnvironmentCheck\Tests\Checks;
|
||||
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\EnvironmentCheck\Checks\ExternalURLCheck;
|
||||
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
|
||||
|
||||
/**
|
||||
* Class ExternalURLCheckTest
|
||||
*
|
||||
* @package environmentcheck
|
||||
*/
|
||||
class ExternalURLCheckTest extends SapphireTest
|
||||
{
|
||||
public function testCheckReportsMissingPages()
|
||||
{
|
||||
$this->markTestSkipped('ExternalURLCheck seems faulty on some systems');
|
||||
|
||||
$check = new ExternalURLCheck('http://missing-site/');
|
||||
|
||||
$expected = [
|
||||
EnvironmentCheck::ERROR,
|
||||
'Success retrieving "http://missing-site/" (Code: 404)'
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, $check->check());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\EnvironmentCheck\Tests\Checks;
|
||||
|
||||
use SilverStripe\EnvironmentCheck\Checks\FileWriteableCheck;
|
||||
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
|
||||
/**
|
||||
* Class FileWritableCheckTest
|
||||
*
|
||||
* @package environmentcheck
|
||||
*/
|
||||
class FileWritableCheckTest extends SapphireTest
|
||||
{
|
||||
public function testCheckReportsWritablePaths()
|
||||
{
|
||||
$check = new FileWriteableCheck(TEMP_FOLDER);
|
||||
|
||||
$expected = [
|
||||
EnvironmentCheck::OK,
|
||||
''
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, $check->check());
|
||||
}
|
||||
|
||||
public function testCheckReportsNonWritablePaths()
|
||||
{
|
||||
$check = new FileWriteableCheck('/var');
|
||||
|
||||
$result = $check->check();
|
||||
|
||||
$this->assertEquals(EnvironmentCheck::ERROR, $result[0]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\EnvironmentCheck\Tests\Checks;
|
||||
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\EnvironmentCheck\Checks\HasClassCheck;
|
||||
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
|
||||
|
||||
/**
|
||||
* Class HasClassCheckTest
|
||||
*
|
||||
* @package environmentcheck
|
||||
*/
|
||||
class HasClassCheckTest extends SapphireTest
|
||||
{
|
||||
public function testCheckReportsMissingClasses()
|
||||
{
|
||||
$check = new HasClassCheck('foo');
|
||||
|
||||
$expected = [
|
||||
EnvironmentCheck::ERROR,
|
||||
'Class foo doesn\'t exist'
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, $check->check());
|
||||
}
|
||||
|
||||
public function testCheckReportsFoundClasses()
|
||||
{
|
||||
$check = new HasClassCheck('stdClass');
|
||||
|
||||
$expected = [
|
||||
EnvironmentCheck::OK,
|
||||
'Class stdClass exists',
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, $check->check());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\EnvironmentCheck\Tests\Checks;
|
||||
|
||||
use SilverStripe\EnvironmentCheck\Checks\HasFunctionCheck;
|
||||
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
|
||||
/**
|
||||
* Class HasFunctionCheckTest
|
||||
*
|
||||
* @package environmentcheck
|
||||
*/
|
||||
class HasFunctionCheckTest extends SapphireTest
|
||||
{
|
||||
public function testCheckReportsMissingFunctions()
|
||||
{
|
||||
$check = new HasFunctionCheck('foo');
|
||||
|
||||
$expected = [
|
||||
EnvironmentCheck::ERROR,
|
||||
'foo() doesn\'t exist'
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, $check->check());
|
||||
}
|
||||
|
||||
public function testCheckReportsFoundFunctions()
|
||||
{
|
||||
$check = new HasFunctionCheck('class_exists');
|
||||
|
||||
$expected = [
|
||||
EnvironmentCheck::OK,
|
||||
'class_exists() exists'
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, $check->check());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\EnvironmentCheck\Tests\Checks;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\HandlerStack;
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use GuzzleHttp\Handler\MockHandler;
|
||||
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
|
||||
use SilverStripe\EnvironmentCheck\Checks\SessionCheck;
|
||||
|
||||
/**
|
||||
* Test session checks.
|
||||
*/
|
||||
class SessionCheckTest extends SapphireTest
|
||||
{
|
||||
/**
|
||||
* @var SilverStripe\EnvironmentCheck\Checks\SessionCheck
|
||||
*/
|
||||
public $sessionCheck = null;
|
||||
|
||||
/**
|
||||
* Create a session check for use by tests.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->sessionCheck = new SessionCheck('/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Env check reports error when session cookies are being set.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testSessionSet()
|
||||
{
|
||||
// Create a mock and queue two responses.
|
||||
$mock = new MockHandler([
|
||||
new Response(200, ['Set-Cookie' => 'PHPSESSID:foo']),
|
||||
new Response(200, ['Set-Cookie' => 'SECSESSID:bar'])
|
||||
]);
|
||||
|
||||
$handler = HandlerStack::create($mock);
|
||||
$client = new Client(['handler' => $handler]);
|
||||
$this->sessionCheck->client = $client;
|
||||
|
||||
// Check for PHPSESSID
|
||||
$this->assertContains(EnvironmentCheck::ERROR, $this->sessionCheck->check());
|
||||
|
||||
// Check for SECSESSID
|
||||
$this->assertContains(EnvironmentCheck::ERROR, $this->sessionCheck->check());
|
||||
}
|
||||
|
||||
/**
|
||||
* Env check responds OK when no session cookies are set in response.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testSessionNotSet()
|
||||
{
|
||||
// Create a mock and queue two responses.
|
||||
$mock = new MockHandler([
|
||||
new Response(200)
|
||||
]);
|
||||
|
||||
$handler = HandlerStack::create($mock);
|
||||
$client = new Client(['handler' => $handler]);
|
||||
$this->sessionCheck->client = $client;
|
||||
|
||||
$this->assertContains(EnvironmentCheck::OK, $this->sessionCheck->check());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\EnvironmentCheck\Tests\Checks;
|
||||
|
||||
use SilverStripe\EnvironmentCheck\Checks\URLCheck;
|
||||
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
|
||||
/**
|
||||
* Class URLCheckTest
|
||||
*
|
||||
* @package environmentcheck
|
||||
*/
|
||||
class URLCheckTest extends SapphireTest
|
||||
{
|
||||
public function testCheckReportsMissingPages()
|
||||
{
|
||||
$check = new URLCheck('foo', 'bar');
|
||||
|
||||
$expected = [
|
||||
EnvironmentCheck::ERROR,
|
||||
'Error retrieving "foo" (Code: 404)'
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, $check->check());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\EnvironmentCheck\Tests\Controllers;
|
||||
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\EnvironmentCheck\Controllers\DevCheckController;
|
||||
use SilverStripe\EnvironmentCheck\EnvironmentChecker;
|
||||
|
||||
/**
|
||||
* Class DevCheckControllerTest
|
||||
*
|
||||
* @package environmentcheck
|
||||
*/
|
||||
class DevCheckControllerTest extends SapphireTest
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @var array
|
||||
*/
|
||||
protected $usesDatabase = true;
|
||||
|
||||
public function testIndexCreatesChecker()
|
||||
{
|
||||
$controller = new DevCheckController();
|
||||
|
||||
$request = new HTTPRequest('GET', 'example.com');
|
||||
|
||||
$this->assertInstanceOf(EnvironmentChecker::class, $controller->index($request));
|
||||
}
|
||||
|
||||
public function testCheckIncludesDetails()
|
||||
{
|
||||
$controller = new DevCheckController();
|
||||
$request = new HTTPRequest('GET', 'example.com');
|
||||
|
||||
$response = $controller->index($request)->index();
|
||||
|
||||
$this->assertStringContainsString('<table>', $response->getBody());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\EnvironmentCheck\Tests\Controllers;
|
||||
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\EnvironmentCheck\Controllers\DevHealthController;
|
||||
use SilverStripe\EnvironmentCheck\EnvironmentChecker;
|
||||
|
||||
/**
|
||||
* Class DevHealthControllerTest
|
||||
*
|
||||
* @package environmentcheck
|
||||
*/
|
||||
class DevHealthControllerTest extends SapphireTest
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @var array
|
||||
*/
|
||||
protected $usesDatabase = true;
|
||||
|
||||
public function testIndexCreatesChecker()
|
||||
{
|
||||
$controller = new DevHealthController();
|
||||
|
||||
$request = new HTTPRequest('GET', 'example.com');
|
||||
|
||||
// we need to fake authenticated access as BasicAuth::requireLogin doesn't like empty
|
||||
// permission type strings, which is what health check uses.
|
||||
|
||||
putenv('ENVCHECK_BASICAUTH_USERNAME="foo"');
|
||||
putenv('ENVCHECK_BASICAUTH_PASSWORD="bar"');
|
||||
|
||||
$_SERVER['PHP_AUTH_USER'] = 'foo';
|
||||
$_SERVER['PHP_AUTH_PW'] = 'bar';
|
||||
|
||||
$this->assertInstanceOf(EnvironmentChecker::class, $controller->index($request));
|
||||
}
|
||||
|
||||
|
||||
public function testHealthDoesNotIncludeDetails()
|
||||
{
|
||||
$controller = new DevHealthController();
|
||||
$request = new HTTPRequest('GET', 'example.com');
|
||||
|
||||
$response = $controller->index($request)->index();
|
||||
|
||||
$this->assertFalse(strpos($response->getBody(), '<table>'));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\EnvironmentCheck\Tests;
|
||||
|
||||
use Monolog\Logger;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\LogLevel;
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\EnvironmentCheck\EnvironmentChecker;
|
||||
use SilverStripe\EnvironmentCheck\EnvironmentCheckSuite;
|
||||
|
||||
/**
|
||||
* Class EnvironmentCheckerTest
|
||||
*
|
||||
* @package environmentcheck
|
||||
*/
|
||||
class EnvironmentCheckerTest extends SapphireTest
|
||||
{
|
||||
protected $usesDatabase = true;
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
EnvironmentCheckSuite::reset();
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
public function testOnlyLogsWithErrors()
|
||||
{
|
||||
Config::modify()->set(EnvironmentChecker::class, 'log_results_warning', true);
|
||||
Config::modify()->set(EnvironmentChecker::class, 'log_results_error', true);
|
||||
|
||||
EnvironmentCheckSuite::register('test suite', new EnvironmentCheckerTest\CheckNoErrors());
|
||||
|
||||
$logger = $this->getMockBuilder(Logger::class)
|
||||
->disableOriginalConstructor()
|
||||
->setMethods(['log'])
|
||||
->getMock();
|
||||
|
||||
$logger->expects($this->never())->method('log');
|
||||
|
||||
Injector::inst()->registerService($logger, LoggerInterface::class);
|
||||
|
||||
(new EnvironmentChecker('test suite', 'test'))->index();
|
||||
}
|
||||
|
||||
public function testLogsWithWarnings()
|
||||
{
|
||||
Config::modify()->set(EnvironmentChecker::class, 'log_results_warning', true);
|
||||
Config::modify()->set(EnvironmentChecker::class, 'log_results_error', false);
|
||||
|
||||
EnvironmentCheckSuite::register('test suite', new EnvironmentCheckerTest\CheckWarnings());
|
||||
EnvironmentCheckSuite::register('test suite', new EnvironmentCheckerTest\CheckErrors());
|
||||
|
||||
$logger = $this->getMockBuilder(Logger::class)
|
||||
->disableOriginalConstructor()
|
||||
->setMethods(['log'])
|
||||
->getMock();
|
||||
|
||||
$logger->expects($this->once())
|
||||
->method('log')
|
||||
->withConsecutive(
|
||||
[$this->equalTo(LogLevel::WARNING)],
|
||||
[$this->anything()]
|
||||
);
|
||||
|
||||
Injector::inst()->registerService($logger, LoggerInterface::class);
|
||||
|
||||
(new EnvironmentChecker('test suite', 'test'))->index();
|
||||
}
|
||||
|
||||
public function testLogsWithErrors()
|
||||
{
|
||||
Config::modify()->set(EnvironmentChecker::class, 'log_results_error', false);
|
||||
Config::modify()->set(EnvironmentChecker::class, 'log_results_error', true);
|
||||
|
||||
EnvironmentCheckSuite::register('test suite', new EnvironmentCheckerTest\CheckWarnings());
|
||||
EnvironmentCheckSuite::register('test suite', new EnvironmentCheckerTest\CheckErrors());
|
||||
|
||||
$logger = $this->getMockBuilder(Logger::class)
|
||||
->disableOriginalConstructor()
|
||||
->setMethods(['log'])
|
||||
->getMock();
|
||||
|
||||
$logger->expects($this->once())
|
||||
->method('log')
|
||||
->withConsecutive(
|
||||
[$this->equalTo(LogLevel::ALERT), $this->anything()],
|
||||
[$this->equalTo(LogLevel::WARNING), $this->anything()]
|
||||
);
|
||||
|
||||
Injector::inst()->registerService($logger, LoggerInterface::class);
|
||||
|
||||
(new EnvironmentChecker('test suite', 'test'))->index();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\EnvironmentCheck\Tests\EnvironmentCheckerTest;
|
||||
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
|
||||
|
||||
class CheckErrors implements EnvironmentCheck, TestOnly
|
||||
{
|
||||
public function check()
|
||||
{
|
||||
return [EnvironmentCheck::ERROR, 'test error'];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\EnvironmentCheck\Tests\EnvironmentCheckerTest;
|
||||
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
|
||||
|
||||
class CheckNoErrors implements EnvironmentCheck, TestOnly
|
||||
{
|
||||
public function check()
|
||||
{
|
||||
return [EnvironmentCheck::OK, ''];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
namespace SilverStripe\EnvironmentCheck\Tests\EnvironmentCheckerTest;
|
||||
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\EnvironmentCheck\EnvironmentCheck;
|
||||
|
||||
class CheckWarnings implements EnvironmentCheck, TestOnly
|
||||
{
|
||||
public function check()
|
||||
{
|
||||
return [EnvironmentCheck::WARNING, 'test warning'];
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue