Skip to content

Commit

Permalink
MAGECLOUD-4076: Service EOL validation (magento#625)
Browse files Browse the repository at this point in the history
  • Loading branch information
sikharam-adb authored and shiftedreality committed Nov 7, 2019
1 parent 96c2b42 commit b1a882e
Show file tree
Hide file tree
Showing 16 changed files with 853 additions and 6 deletions.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@
"illuminate/config": "^5.6",
"magento/magento-cloud-components": "^1.0.1",
"monolog/monolog": "^1.16",
"nesbot/carbon": "^2.25",
"psr/container": "^1.0",
"psr/log": "^1.0",
"symfony/console": "^2.3||^4.0",
"symfony/config": "^3.4||^4.3",
"symfony/console": "^2.3||^4.0",
"symfony/dependency-injection": "^3.4||^4.3",
"symfony/process": "^2.1||^4.1",
"symfony/serializer": "^3.3",
Expand Down
40 changes: 40 additions & 0 deletions config/eol.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Service EOLs (YYYY-MM-DD).
php:
- version: '7.0'
eol: 2019-01-10
- version: '7.1'
eol: 2019-12-01
- version: '7.2'
eol: 2020-11-30
- version: '7.3'
eol: 2021-12-06
mariadb:
- version: '10.0'
eol: 2019-03-31
- version: '10.1'
eol: 2020-10-17
- version: '10.2'
eol: 2022-05-23
elasticsearch:
- version: '1.7'
eol: 2017-01-16
- version: '2.4'
eol: 2018-02-28
- version: '5.2'
eol: 2018-07-31
- version: '6.5'
eol: 2020-05-14
rabbitmq:
- version: '3.5'
eol: 2016-10-31
- version: '3.7'
eol: 2020-03-31
redis:
- version: '3.2'
eol:
- version: '4.0'
eol:
- version: '5.0'
eol:
- version: '5.2'
eol:
12 changes: 7 additions & 5 deletions config/services.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd">

<services>
<defaults autowire="true" autoconfigure="true" public="true"/>
<defaults autowire="true" autoconfigure="true" public="true">
<bind key="int $errorLevel">300</bind>
</defaults>
<!-- ... -->
<prototype namespace="Magento\MagentoCloud\" resource="../src/*" exclude="../src/{Test}"/>

Expand All @@ -19,7 +21,6 @@
<service id="Magento\MagentoCloud\DB\Data\Connection" autowire="false" />
<service id="Magento\MagentoCloud\DB\Data\RelationshipConnection" autowire="false" />
<service id="Magento\MagentoCloud\DB\PDOException" autowire="false" />
<service id="Magento\MagentoCloud\Docker\ConfigurationMismatchException" autowire="false" />
<service id="Magento\MagentoCloud\Filesystem\Flag\ConfigurationMismatchException" autowire="false" />
<service id="Magento\MagentoCloud\Scenario\Exception\ProcessorException" autowire="false" />
<service id="Magento\MagentoCloud\Scenario\Exception\ValidationException" autowire="false" />
Expand All @@ -29,11 +30,8 @@
<service id="Magento\MagentoCloud\Step\Deploy\DeployStaticContent" autowire="false" />
<service id="Magento\MagentoCloud\Step\Deploy\InstallUpdate" autowire="false" />
<service id="Magento\MagentoCloud\Step\Deploy\InstallUpdate\ConfigUpdate" autowire="false" />
<service id="Magento\MagentoCloud\Step\Deploy\InstallUpdate\Install" autowire="false" />
<service id="Magento\MagentoCloud\Step\Deploy\InstallUpdate\Update" autowire="false" />
<service id="Magento\MagentoCloud\Step\Deploy\PreDeploy" autowire="false" />
<service id="Magento\MagentoCloud\Step\SkipStep" autowire="false" />
<service id="Magento\MagentoCloud\Step\StepComposite" autowire="false" />
<service id="Magento\MagentoCloud\Step\StepException" autowire="false" />
<service id="Magento\MagentoCloud\Step\ValidateConfiguration" autowire="false" />
<service id="Magento\MagentoCloud\Step\Build\BackupData" autowire="false" />
Expand All @@ -47,6 +45,10 @@
<argument key="deploy_is_failed">var/.deploy_is_failed</argument>
</argument>
</service>
<service id="ServiceEol.Warnings" class="Magento\MagentoCloud\Config\Validator\Deploy\ServiceEol" />
<service id="ServiceEol.Notices" class="Magento\MagentoCloud\Config\Validator\Deploy\ServiceEol">
<bind key="int $errorLevel">250</bind>
</service>
<service id="Composer\Package\Version\VersionParser"/>
<service id="Composer\Semver\VersionParser"/>
<service id="Composer\Semver\Semver"/>
Expand Down
4 changes: 4 additions & 0 deletions scenario/deploy.xml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@
<item name="cloud-variables" xsi:type="object">Magento\MagentoCloud\Config\Validator\Deploy\MagentoCloudVariables</item>
<item name="json-format-variable " xsi:type="object">Magento\MagentoCloud\Config\Validator\Deploy\JsonFormatVariable</item>
<item name="service-version" xsi:type="object">Magento\MagentoCloud\Config\Validator\Deploy\ServiceVersion</item>
<item name="service-eol-warning" xsi:type="object">ServiceEol.Warnings</item>
</item>
<item name="250" xsi:type="array">
<item name="service-eol-notice" xsi:type="object">ServiceEol.Notices</item>
</item>
</argument>
</arguments>
Expand Down
77 changes: 77 additions & 0 deletions src/Config/Validator/Deploy/ServiceEol.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);

namespace Magento\MagentoCloud\Config\Validator\Deploy;

use Magento\MagentoCloud\Config\Validator;
use Magento\MagentoCloud\Filesystem\FileSystemException;
use Magento\MagentoCloud\Service\EolValidator as EOLValidator;
use Magento\MagentoCloud\Config\ValidatorInterface;
use Magento\MagentoCloud\Service\ServiceMismatchException;

/**
* Class to check if services approaching their EOLs.
*/
class ServiceEol implements ValidatorInterface
{
/**
* @var integer
*/
private $errorLevel;

/**
* @var Validator\ResultFactory
*/
private $resultFactory;

/**
* @var EOLValidator
*/
private $eolValidator;

/**
* @param Validator\ResultFactory $resultFactory
* @param EOLValidator $eolValidator
* @param int $errorLevel
*/
public function __construct(
Validator\ResultFactory $resultFactory,
EOLValidator $eolValidator,
int $errorLevel
) {
$this->resultFactory = $resultFactory;
$this->eolValidator = $eolValidator;
$this->errorLevel = $errorLevel;
}

/**
* Get the defined services and versions and check for their EOLs by error level.
*
* @return Validator\ResultInterface
* @throws FileSystemException
*/
public function validate(): Validator\ResultInterface
{
try {
$errors = $this->eolValidator->validateServiceEol();

if (isset($errors[$this->errorLevel])) {
$message = $this->errorLevel == ValidatorInterface::LEVEL_WARNING ?
'Some services have passed EOL.' :
'Some services are approaching EOL.';
return $this->resultFactory->error(
$message,
implode(PHP_EOL, $errors[$this->errorLevel])
);
}
} catch (ServiceMismatchException $e) {
return $this->resultFactory->error('Can\'t validate version of some services: ' . $e->getMessage());
}

return $this->resultFactory->success();
}
}
1 change: 1 addition & 0 deletions src/Config/ValidatorInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
*/
interface ValidatorInterface
{
const LEVEL_NOTICE = Logger::NOTICE;
const LEVEL_WARNING = Logger::WARNING;
const LEVEL_CRITICAL = Logger::CRITICAL;

Expand Down
10 changes: 10 additions & 0 deletions src/Filesystem/FileList.php
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,14 @@ public function getServicesConfig(): string
{
return $this->directoryList->getMagentoRoot() . '/.magento/services.yaml';
}

/**
* Return the path to the service EOL configuration file.
*
* @return string
*/
public function getServiceEolsConfig() : string
{
return $this->directoryList->getRoot() . '/config/eol.yaml';
}
}
176 changes: 176 additions & 0 deletions src/Service/EolValidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);

namespace Magento\MagentoCloud\Service;

use Carbon\Carbon;
use Composer\Semver\Semver;
use Magento\MagentoCloud\Config\ValidatorInterface;
use Magento\MagentoCloud\Filesystem\Driver\File;
use Magento\MagentoCloud\Filesystem\FileList;
use Magento\MagentoCloud\Filesystem\FileSystemException;
use Symfony\Component\Yaml\Yaml;

/**
* Service EOL validator.
*
* Class EolValidator
*/
class EolValidator
{
/**
* Set the notification period.
*/
private const NOTIFICATION_PERIOD = 3;

/**
* @var FileList
*/
private $fileList;

/**
* @var File
*/
private $file;

/**
* @var ServiceFactory
*/
private $serviceFactory;

/**
* @var array
*/
private $eolConfigs;

/**
* @var array
*/
private $services = [
ServiceInterface::NAME_PHP,
ServiceInterface::NAME_ELASTICSEARCH,
ServiceInterface::NAME_RABBITMQ,
ServiceInterface::NAME_REDIS,
ServiceInterface::NAME_DB
];

/**
* @param FileList $fileList
* @param ServiceFactory $serviceFactory
*/
public function __construct(
FileList $fileList,
File $file,
ServiceFactory $serviceFactory
) {
$this->fileList = $fileList;
$this->file = $file;
$this->serviceFactory = $serviceFactory;
}

/**
* Validate the EOL of a given service and version by error level.
*
* @return array
* @throws FileSystemException
* @throws ServiceMismatchException
*/
public function validateServiceEol(): array
{
$errors = [];

foreach ($this->services as $serviceName) {
$service = $this->serviceFactory->create($serviceName);
$serviceVersion = $service->getVersion();

if ($validationResult = $this->validateService(
$this->getConvertedServiceName($serviceName),
$serviceVersion
)) {
$errorLevel = current(array_keys($validationResult));
$errors[$errorLevel][] = $validationResult[$errorLevel];
}
}
return $errors;
}

/**
* Validates a given service and version.
*
* @param string $serviceName
* @param string $serviceVersion
* @return array
* @throws FileSystemException
*/
public function validateService(string $serviceName, string $serviceVersion) : array
{
$serviceConfigs = $this->getServiceConfigs($serviceName);

$versionConfigs = array_filter($serviceConfigs, function ($v) use ($serviceVersion) {
return Semver::satisfies($serviceVersion, sprintf('%s.x', $v['version']));
});

if (!isset($versionConfigs[current(array_keys($versionConfigs))]['eol'])) {
return [];
}

$eolDate = Carbon::createFromTimestamp($versionConfigs[current(array_keys($versionConfigs))]['eol']);

if (!$eolDate->isFuture()) {
return [ValidatorInterface::LEVEL_WARNING => sprintf(
'%s %s has passed EOL (%s).',
$serviceName,
$serviceVersion,
date_format($eolDate, 'Y-m-d')
)];
} elseif ($eolDate->isFuture()
&& $eolDate->diffInMonths(Carbon::now()) <= self::NOTIFICATION_PERIOD
) {
return [ValidatorInterface::LEVEL_NOTICE => sprintf(
'%s %s is approaching EOL (%s).',
$serviceName,
$serviceVersion,
date_format($eolDate, 'Y-m-d')
)];
}

return [];
}

/**
* Gets the EOL configurations for the current service from eol.yaml.
*
* @param string $serviceName
* @return array
* @throws FileSystemException
*/
private function getServiceConfigs(string $serviceName) : array
{
if ($this->eolConfigs === null) {
$this->eolConfigs = [];
$configsPath = $this->fileList->getServiceEolsConfig();
if ($this->file->isExists($configsPath)) {
$this->eolConfigs = Yaml::parse($this->file->fileGetContents($configsPath));
}
}

return $this->eolConfigs[$serviceName] ?? [];
}

/**
* Perform service name conversions.
* Explicitly resetting 'mysql' to 'mariadb' for MariaDB validation; getting the version from
* relationship returns mysql:<version>.
*
* @param string $serviceName
* @return string
*/
private function getConvertedServiceName(string $serviceName) : string
{
return $serviceName == 'mysql' ? 'mariadb' : $serviceName;
}
}
Loading

0 comments on commit b1a882e

Please sign in to comment.