first commit
This commit is contained in:
348
inc/console/database/abstractconfigurecommand.class.php
Normal file
348
inc/console/database/abstractconfigurecommand.class.php
Normal file
@ -0,0 +1,348 @@
|
||||
<?php
|
||||
/**
|
||||
* ---------------------------------------------------------------------
|
||||
* GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2015-2020 Teclib' and contributors.
|
||||
*
|
||||
* http://glpi-project.org
|
||||
*
|
||||
* based on GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
|
||||
*
|
||||
* ---------------------------------------------------------------------
|
||||
*
|
||||
* LICENSE
|
||||
*
|
||||
* This file is part of GLPI.
|
||||
*
|
||||
* GLPI is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GLPI is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ---------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace Glpi\Console\Database;
|
||||
|
||||
if (!defined('GLPI_ROOT')) {
|
||||
die("Sorry. You can't access this file directly");
|
||||
}
|
||||
|
||||
use Config;
|
||||
use DBConnection;
|
||||
use Glpi\Console\AbstractCommand;
|
||||
use Glpi\Console\Command\ForceNoPluginsOptionCommandInterface;
|
||||
|
||||
use Symfony\Component\Console\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Console\Helper\Table;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||
use Symfony\Component\Console\Question\Question;
|
||||
|
||||
abstract class AbstractConfigureCommand extends AbstractCommand implements ForceNoPluginsOptionCommandInterface {
|
||||
|
||||
/**
|
||||
* Error code returned if DB configuration is aborted by user.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const ABORTED_BY_USER = -1;
|
||||
|
||||
/**
|
||||
* Error code returned if DB configuration succeed.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const SUCCESS = 0;
|
||||
|
||||
/**
|
||||
* Error code returned if DB connection initialization fails.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const ERROR_DB_CONNECTION_FAILED = 1;
|
||||
|
||||
/**
|
||||
* Error code returned if DB engine is unsupported.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const ERROR_DB_ENGINE_UNSUPPORTED = 2;
|
||||
|
||||
/**
|
||||
* Error code returned when trying to configure and having a DB config already set.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const ERROR_DB_CONFIG_ALREADY_SET = 3;
|
||||
|
||||
/**
|
||||
* Error code returned when failing to save database configuration file.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const ERROR_DB_CONFIG_FILE_NOT_SAVED = 4;
|
||||
|
||||
protected function configure() {
|
||||
|
||||
parent::configure();
|
||||
|
||||
$this->setName('glpi:database:install');
|
||||
$this->setAliases(['db:install']);
|
||||
$this->setDescription('Install database schema');
|
||||
|
||||
$this->addOption(
|
||||
'db-host',
|
||||
'H',
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
__('Database host'),
|
||||
'localhost'
|
||||
);
|
||||
|
||||
$this->addOption(
|
||||
'db-name',
|
||||
'd',
|
||||
InputOption::VALUE_REQUIRED,
|
||||
__('Database name')
|
||||
);
|
||||
|
||||
$this->addOption(
|
||||
'db-password',
|
||||
'p',
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
__('Database password (will be prompted for value if option passed without value)'),
|
||||
'' // Empty string by default (enable detection of null if passed without value)
|
||||
);
|
||||
|
||||
$this->addOption(
|
||||
'db-port',
|
||||
'P',
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
__('Database port')
|
||||
);
|
||||
|
||||
$this->addOption(
|
||||
'db-user',
|
||||
'u',
|
||||
InputOption::VALUE_REQUIRED,
|
||||
__('Database user')
|
||||
);
|
||||
|
||||
$this->addOption(
|
||||
'reconfigure',
|
||||
'r',
|
||||
InputOption::VALUE_NONE,
|
||||
__('Reconfigure database, override configuration file if it already exists')
|
||||
);
|
||||
}
|
||||
|
||||
protected function interact(InputInterface $input, OutputInterface $output) {
|
||||
|
||||
$questions = [
|
||||
'db-name' => new Question(__('Database name:'), ''), // Required
|
||||
'db-user' => new Question(__('Database user:'), ''), // Required
|
||||
'db-password' => new Question(__('Database password:'), ''), // Prompt if null (passed without value)
|
||||
];
|
||||
$questions['db-password']->setHidden(true); // Make password input hidden
|
||||
|
||||
foreach ($questions as $name => $question) {
|
||||
if (null === $input->getOption($name)) {
|
||||
/** @var \Symfony\Component\Console\Helper\QuestionHelper $question_helper */
|
||||
$question_helper = $this->getHelper('question');
|
||||
$value = $question_helper->ask($input, $output, $question);
|
||||
$input->setOption($name, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function initDbConnection() {
|
||||
|
||||
return; // Prevent DB connection
|
||||
}
|
||||
|
||||
/**
|
||||
* Save database configuration file.
|
||||
*
|
||||
* @param InputInterface $input
|
||||
* @param OutputInterface $output
|
||||
* @throws InvalidArgumentException
|
||||
* @return string
|
||||
*/
|
||||
protected function configureDatabase(InputInterface $input, OutputInterface $output) {
|
||||
|
||||
$db_pass = $input->getOption('db-password');
|
||||
$db_host = $input->getOption('db-host');
|
||||
$db_name = $input->getOption('db-name');
|
||||
$db_port = $input->getOption('db-port');
|
||||
$db_user = $input->getOption('db-user');
|
||||
$db_hostport = $db_host . (!empty($db_port) ? ':' . $db_port : '');
|
||||
|
||||
$reconfigure = $input->getOption('reconfigure');
|
||||
|
||||
if (file_exists(GLPI_CONFIG_DIR . '/config_db.php') && !$reconfigure) {
|
||||
// Prevent overriding of existing DB
|
||||
$output->writeln(
|
||||
'<error>' . __('Database configuration already exists. Use --reconfigure option to override existing configuration.') . '</error>'
|
||||
);
|
||||
return self::ERROR_DB_CONFIG_ALREADY_SET;
|
||||
}
|
||||
|
||||
$this->validateConfigInput($input);
|
||||
|
||||
$run = $this->askForDbConfigConfirmation(
|
||||
$input,
|
||||
$output,
|
||||
$db_hostport,
|
||||
$db_name,
|
||||
$db_user
|
||||
);
|
||||
if (!$run) {
|
||||
$output->writeln(
|
||||
'<comment>' . __('Configuration aborted.') . '</comment>',
|
||||
OutputInterface::VERBOSITY_VERBOSE
|
||||
);
|
||||
return self::ABORTED_BY_USER;
|
||||
}
|
||||
|
||||
$mysqli = new \mysqli();
|
||||
if (intval($db_port) > 0) {
|
||||
// Network port
|
||||
@$mysqli->connect($db_host, $db_user, $db_pass, null, $db_port);
|
||||
} else {
|
||||
// Unix Domain Socket
|
||||
@$mysqli->connect($db_host, $db_user, $db_pass, null, 0, $db_port);
|
||||
}
|
||||
|
||||
if (0 !== $mysqli->connect_errno) {
|
||||
$message = sprintf(
|
||||
__('Database connection failed with message "(%s) %s".'),
|
||||
$mysqli->connect_errno,
|
||||
$mysqli->connect_error
|
||||
);
|
||||
$output->writeln('<error>' . $message . '</error>', OutputInterface::VERBOSITY_QUIET);
|
||||
return self::ERROR_DB_CONNECTION_FAILED;
|
||||
}
|
||||
|
||||
ob_start();
|
||||
$db_version_data = $mysqli->query('SELECT version()')->fetch_array();
|
||||
$checkdb = Config::displayCheckDbEngine(false, $db_version_data[0]);
|
||||
$message = ob_get_clean();
|
||||
if ($checkdb > 0) {
|
||||
$output->writeln('<error>' . $message . '</error>', OutputInterface::VERBOSITY_QUIET);
|
||||
return self::ERROR_DB_ENGINE_UNSUPPORTED;
|
||||
}
|
||||
|
||||
$db_name = $mysqli->real_escape_string($db_name);
|
||||
|
||||
$output->writeln(
|
||||
'<comment>' . __('Saving configuration file...') . '</comment>',
|
||||
OutputInterface::VERBOSITY_VERBOSE
|
||||
);
|
||||
if (!DBConnection::createMainConfig($db_hostport, $db_user, $db_pass, $db_name)) {
|
||||
$message = sprintf(
|
||||
__('Cannot write configuration file "%s".'),
|
||||
GLPI_CONFIG_DIR . DIRECTORY_SEPARATOR . 'config_db.php'
|
||||
);
|
||||
$output->writeln(
|
||||
'<error>' . $message . '</error>',
|
||||
OutputInterface::VERBOSITY_QUIET
|
||||
);
|
||||
return self::ERROR_DB_CONFIG_FILE_NOT_SAVED;
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
public function getNoPluginsOptionValue() {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if DB is already configured.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function isDbAlreadyConfigured() {
|
||||
|
||||
return file_exists(GLPI_CONFIG_DIR . '/config_db.php');
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate configuration variables from input.
|
||||
*
|
||||
* @param InputInterface $input
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
protected function validateConfigInput(InputInterface $input) {
|
||||
|
||||
$db_name = $input->getOption('db-name');
|
||||
$db_user = $input->getOption('db-user');
|
||||
$db_pass = $input->getOption('db-password');
|
||||
|
||||
if (empty($db_name)) {
|
||||
throw new InvalidArgumentException(
|
||||
__('Database name defined by --db-name option cannot be empty.')
|
||||
);
|
||||
}
|
||||
|
||||
if (empty($db_user)) {
|
||||
throw new InvalidArgumentException(
|
||||
__('Database user defined by --db-user option cannot be empty.')
|
||||
);
|
||||
}
|
||||
|
||||
if (null === $db_pass) {
|
||||
// Will be null if option used without value and without interaction
|
||||
throw new InvalidArgumentException(
|
||||
__('--db-password option value cannot be null.')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask user to confirm DB configuration.
|
||||
*
|
||||
* @param InputInterface $input
|
||||
* @param OutputInterface $output
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function askForDbConfigConfirmation(
|
||||
InputInterface $input,
|
||||
OutputInterface $output,
|
||||
$db_hostport,
|
||||
$db_name,
|
||||
$db_user) {
|
||||
|
||||
$informations = new Table($output);
|
||||
$informations->addRow([__('Database host'), $db_hostport]);
|
||||
$informations->addRow([__('Database name'), $db_name]);
|
||||
$informations->addRow([__('Database user'), $db_user]);
|
||||
$informations->render();
|
||||
|
||||
if ($input->getOption('no-interaction')) {
|
||||
// Consider that config is validated if user require no interaction
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @var \Symfony\Component\Console\Helper\QuestionHelper $question_helper */
|
||||
$question_helper = $this->getHelper('question');
|
||||
return $question_helper->ask(
|
||||
$input,
|
||||
$output,
|
||||
new ConfirmationQuestion(__('Do you want to continue ?') . ' [Yes/no]', true)
|
||||
);
|
||||
}
|
||||
}
|
||||
106
inc/console/database/checkcommand.class.php
Normal file
106
inc/console/database/checkcommand.class.php
Normal file
@ -0,0 +1,106 @@
|
||||
<?php
|
||||
/**
|
||||
* ---------------------------------------------------------------------
|
||||
* GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2015-2020 Teclib' and contributors.
|
||||
*
|
||||
* http://glpi-project.org
|
||||
*
|
||||
* based on GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
|
||||
*
|
||||
* ---------------------------------------------------------------------
|
||||
*
|
||||
* LICENSE
|
||||
*
|
||||
* This file is part of GLPI.
|
||||
*
|
||||
* GLPI is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GLPI is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ---------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace Glpi\Console\Database;
|
||||
|
||||
if (!defined('GLPI_ROOT')) {
|
||||
die("Sorry. You can't access this file directly");
|
||||
}
|
||||
|
||||
use Glpi\Console\AbstractCommand;
|
||||
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use SebastianBergmann\Diff\Differ;
|
||||
|
||||
class CheckCommand extends AbstractCommand {
|
||||
|
||||
/**
|
||||
* Error code returned when failed to read empty SQL file.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const ERROR_UNABLE_TO_READ_EMPTYSQL = 1;
|
||||
|
||||
protected function configure() {
|
||||
parent::configure();
|
||||
|
||||
$this->setName('glpi:database:check');
|
||||
$this->setAliases(['db:check']);
|
||||
$this->setDescription(__('Check for schema differences between current database and installation file.'));
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
|
||||
$differ = new Differ();
|
||||
|
||||
if (false === ($empty_file = realpath(GLPI_ROOT . '/install/mysql/glpi-empty.sql'))
|
||||
|| false === ($empty_sql = file_get_contents($empty_file))) {
|
||||
$message = sprintf(__('Unable to read installation file "%s".'), $empty_file);
|
||||
$output->writeln(
|
||||
'<error>' . $message . '</error>',
|
||||
OutputInterface::VERBOSITY_QUIET
|
||||
);
|
||||
return self::ERROR_UNABLE_TO_READ_EMPTYSQL;
|
||||
}
|
||||
|
||||
$matches = [];
|
||||
preg_match_all('/CREATE TABLE `(.+)`[^;]+/', $empty_sql, $matches);
|
||||
$empty_tables_names = $matches[1];
|
||||
$empty_tables_schemas = $matches[0];
|
||||
|
||||
foreach ($empty_tables_schemas as $index => $table_schema) {
|
||||
$table_name = $empty_tables_names[$index];
|
||||
|
||||
$output->writeln(
|
||||
sprintf(__('Processing table "%s"...'), $table_name),
|
||||
OutputInterface::VERBOSITY_VERY_VERBOSE
|
||||
);
|
||||
|
||||
$base_table_struct = $this->db->getTableSchema($table_name, $table_schema);
|
||||
$existing_table_struct = $this->db->getTableSchema($table_name);
|
||||
|
||||
if ($existing_table_struct['schema'] != $base_table_struct['schema']) {
|
||||
$message = sprintf(__('Table schema differs for table "%s".'), $table_name);
|
||||
$output->writeln(
|
||||
'<info>' . $message . '</info>',
|
||||
OutputInterface::VERBOSITY_QUIET
|
||||
);
|
||||
$output->write(
|
||||
$differ->diff($base_table_struct['schema'], $existing_table_struct['schema'])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return 0; // Success
|
||||
}
|
||||
}
|
||||
63
inc/console/database/configurecommand.class.php
Normal file
63
inc/console/database/configurecommand.class.php
Normal file
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/**
|
||||
* ---------------------------------------------------------------------
|
||||
* GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2015-2020 Teclib' and contributors.
|
||||
*
|
||||
* http://glpi-project.org
|
||||
*
|
||||
* based on GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
|
||||
*
|
||||
* ---------------------------------------------------------------------
|
||||
*
|
||||
* LICENSE
|
||||
*
|
||||
* This file is part of GLPI.
|
||||
*
|
||||
* GLPI is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GLPI is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ---------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace Glpi\Console\Database;
|
||||
|
||||
if (!defined('GLPI_ROOT')) {
|
||||
die("Sorry. You can't access this file directly");
|
||||
}
|
||||
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class ConfigureCommand extends AbstractConfigureCommand {
|
||||
|
||||
protected function configure() {
|
||||
|
||||
parent::configure();
|
||||
|
||||
$this->setName('glpi:database:configure');
|
||||
$this->setAliases(['db:configure']);
|
||||
$this->setDescription('Define database configuration');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
|
||||
$result = $this->configureDatabase($input, $output);
|
||||
|
||||
if (self::ABORTED_BY_USER === $result) {
|
||||
return 0; // Considered as success
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
342
inc/console/database/installcommand.class.php
Normal file
342
inc/console/database/installcommand.class.php
Normal file
@ -0,0 +1,342 @@
|
||||
<?php
|
||||
/**
|
||||
* ---------------------------------------------------------------------
|
||||
* GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2015-2020 Teclib' and contributors.
|
||||
*
|
||||
* http://glpi-project.org
|
||||
*
|
||||
* based on GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
|
||||
*
|
||||
* ---------------------------------------------------------------------
|
||||
*
|
||||
* LICENSE
|
||||
*
|
||||
* This file is part of GLPI.
|
||||
*
|
||||
* GLPI is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GLPI is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ---------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace Glpi\Console\Database;
|
||||
|
||||
if (!defined('GLPI_ROOT')) {
|
||||
die("Sorry. You can't access this file directly");
|
||||
}
|
||||
|
||||
use DB;
|
||||
use GLPIKey;
|
||||
use Toolbox;
|
||||
|
||||
use Symfony\Component\Console\Exception\RuntimeException;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||
|
||||
class InstallCommand extends AbstractConfigureCommand {
|
||||
|
||||
/**
|
||||
* Error code returned when failing to create database.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const ERROR_DB_CREATION_FAILED = 5;
|
||||
|
||||
/**
|
||||
* Error code returned when trying to install and having a DB already containing glpi_* tables.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const ERROR_DB_ALREADY_CONTAINS_TABLES = 6;
|
||||
|
||||
/**
|
||||
* Error code returned when failing to create database schema.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const ERROR_SCHEMA_CREATION_FAILED = 7;
|
||||
|
||||
/**
|
||||
* Error code returned when failing to create encryption key file.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const ERROR_CANNOT_CREATE_ENCRYPTION_KEY_FILE = 8;
|
||||
|
||||
protected function configure() {
|
||||
|
||||
parent::configure();
|
||||
|
||||
$this->setName('glpi:database:install');
|
||||
$this->setAliases(['db:install']);
|
||||
$this->setDescription('Install database schema');
|
||||
|
||||
$this->addOption(
|
||||
'default-language',
|
||||
'L',
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
__('Default language of GLPI'),
|
||||
'en_GB'
|
||||
);
|
||||
|
||||
$this->addOption(
|
||||
'force',
|
||||
'f',
|
||||
InputOption::VALUE_NONE,
|
||||
__('Force execution of installation, overriding existing database')
|
||||
);
|
||||
}
|
||||
|
||||
protected function initialize(InputInterface $input, OutputInterface $output) {
|
||||
|
||||
parent::initialize($input, $output);
|
||||
|
||||
$this->outputWarningOnMissingOptionnalRequirements();
|
||||
}
|
||||
|
||||
protected function interact(InputInterface $input, OutputInterface $output) {
|
||||
|
||||
if ($this->isDbAlreadyConfigured()
|
||||
&& $this->isInputContainingConfigValues($input, $output)
|
||||
&& !$input->getOption('reconfigure')) {
|
||||
/** @var \Symfony\Component\Console\Helper\QuestionHelper $question_helper */
|
||||
$question_helper = $this->getHelper('question');
|
||||
$reconfigure = $question_helper->ask(
|
||||
$input,
|
||||
$output,
|
||||
new ConfirmationQuestion(
|
||||
__('Command input contains configuration options that may override existing configuration.')
|
||||
. PHP_EOL
|
||||
. __('Do you want to reconfigure database ?') . ' [Yes/no]',
|
||||
true
|
||||
)
|
||||
);
|
||||
$input->setOption('reconfigure', $reconfigure);
|
||||
}
|
||||
|
||||
if (!$this->isDbAlreadyConfigured() || $input->getOption('reconfigure')) {
|
||||
parent::interact($input, $output);
|
||||
}
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
|
||||
global $DB;
|
||||
|
||||
$default_language = $input->getOption('default-language');
|
||||
$force = $input->getOption('force');
|
||||
|
||||
if ($this->isDbAlreadyConfigured()
|
||||
&& $this->isInputContainingConfigValues($input, $output)
|
||||
&& !$input->getOption('reconfigure')) {
|
||||
// Prevent overriding of existing DB when input contains configuration values and
|
||||
// --reconfigure option is not used.
|
||||
$output->writeln(
|
||||
'<error>' . __('Database configuration already exists. Use --reconfigure option to override existing configuration.') . '</error>'
|
||||
);
|
||||
return self::ERROR_DB_CONFIG_ALREADY_SET;
|
||||
}
|
||||
|
||||
if (!$this->isDbAlreadyConfigured() || $input->getOption('reconfigure')) {
|
||||
$result = $this->configureDatabase($input, $output);
|
||||
|
||||
if (self::ABORTED_BY_USER === $result) {
|
||||
return 0; // Considered as success
|
||||
} else if (self::SUCCESS !== $result) {
|
||||
return $result; // Fail with error code
|
||||
}
|
||||
|
||||
$db_host = $input->getOption('db-host');
|
||||
$db_port = $input->getOption('db-port');
|
||||
$db_hostport = $db_host . (!empty($db_port) ? ':' . $db_port : '');
|
||||
$db_name = $input->getOption('db-name');
|
||||
$db_user = $input->getOption('db-user');
|
||||
$db_pass = $input->getOption('db-password');
|
||||
} else {
|
||||
// Ask to confirm installation based on existing configuration.
|
||||
|
||||
// $DB->dbhost can be array when using round robin feature
|
||||
$db_hostport = is_array($DB->dbhost) ? $DB->dbhost[0] : $DB->dbhost;
|
||||
|
||||
$hostport = explode(':', $db_hostport);
|
||||
$db_host = $hostport[0];
|
||||
if (count($hostport) < 2) {
|
||||
// Host only case
|
||||
$db_port = null;
|
||||
} else {
|
||||
// Host:port case or :Socket case
|
||||
$db_port = $hostport[1];
|
||||
}
|
||||
|
||||
$db_name = $DB->dbdefault;
|
||||
$db_user = $DB->dbuser;
|
||||
$db_pass = rawurldecode($DB->dbpassword); //rawurldecode as in DBmysql::connect()
|
||||
|
||||
$run = $this->askForDbConfigConfirmation(
|
||||
$input,
|
||||
$output,
|
||||
$db_hostport,
|
||||
$db_name,
|
||||
$db_user
|
||||
);
|
||||
if (!$run) {
|
||||
$output->writeln(
|
||||
'<comment>' . __('Installation aborted.') . '</comment>',
|
||||
OutputInterface::VERBOSITY_VERBOSE
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Create security key
|
||||
$glpikey = new GLPIKey();
|
||||
if (!$glpikey->keyExists() && !$glpikey->generate()) {
|
||||
$message = __('Security key cannot be generated!');
|
||||
$output->writeln('<error>' . $message . '</error>', OutputInterface::VERBOSITY_QUIET);
|
||||
return self::ERROR_CANNOT_CREATE_ENCRYPTION_KEY_FILE;
|
||||
}
|
||||
|
||||
$mysqli = new \mysqli();
|
||||
if (intval($db_port) > 0) {
|
||||
// Network port
|
||||
@$mysqli->connect($db_host, $db_user, $db_pass, null, $db_port);
|
||||
} else {
|
||||
// Unix Domain Socket
|
||||
@$mysqli->connect($db_host, $db_user, $db_pass, null, 0, $db_port);
|
||||
}
|
||||
|
||||
if (0 !== $mysqli->connect_errno) {
|
||||
$message = sprintf(
|
||||
__('Database connection failed with message "(%s) %s".'),
|
||||
$mysqli->connect_errno,
|
||||
$mysqli->connect_error
|
||||
);
|
||||
$output->writeln('<error>' . $message . '</error>', OutputInterface::VERBOSITY_QUIET);
|
||||
return self::ERROR_DB_CONNECTION_FAILED;
|
||||
}
|
||||
|
||||
// Create database or select existing one
|
||||
$output->writeln(
|
||||
'<comment>' . __('Creating the database...') . '</comment>',
|
||||
OutputInterface::VERBOSITY_VERBOSE
|
||||
);
|
||||
if (!$mysqli->query('CREATE DATABASE IF NOT EXISTS `' . $db_name .'`')
|
||||
|| !$mysqli->select_db($db_name)) {
|
||||
$message = sprintf(
|
||||
__('Database creation failed with message "(%s) %s".'),
|
||||
$mysqli->errno,
|
||||
$mysqli->error
|
||||
);
|
||||
$output->writeln('<error>' . $message . '</error>', OutputInterface::VERBOSITY_QUIET);
|
||||
return self::ERROR_DB_CREATION_FAILED;
|
||||
}
|
||||
|
||||
// Prevent overriding of existing DB
|
||||
$tables_result = $mysqli->query(
|
||||
"SELECT COUNT(table_name)
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = '{$db_name}'
|
||||
AND table_type = 'BASE TABLE'
|
||||
AND table_name LIKE 'glpi_%'"
|
||||
);
|
||||
if (!$tables_result) {
|
||||
throw new RuntimeException('Unable to check GLPI tables existence.');
|
||||
}
|
||||
if ($tables_result->fetch_array()[0] > 0 && !$force) {
|
||||
$output->writeln(
|
||||
'<error>' . __('Database already contains "glpi_*" tables. Use --force option to override existing database.') . '</error>'
|
||||
);
|
||||
return self::ERROR_DB_ALREADY_CONTAINS_TABLES;
|
||||
}
|
||||
|
||||
if ($DB instanceof DB) {
|
||||
// If global $DB is set at this point, it means that configuration file has been loaded
|
||||
// prior to reconfiguration.
|
||||
// As configuration is part of a class, it cannot be reloaded and class properties
|
||||
// have to be updated manually in order to make `Toolbox::createSchema()` work correctly.
|
||||
$DB->dbhost = $db_hostport;
|
||||
$DB->dbuser = $db_user;
|
||||
$DB->dbpassword = rawurlencode($db_pass);
|
||||
$DB->dbdefault = $db_name;
|
||||
$DB->clearSchemaCache();
|
||||
$DB->connect();
|
||||
|
||||
$db_instance = $DB;
|
||||
} else {
|
||||
include_once (GLPI_CONFIG_DIR . "/config_db.php");
|
||||
$db_instance = new DB();
|
||||
}
|
||||
|
||||
$output->writeln(
|
||||
'<comment>' . __('Loading default schema...') . '</comment>',
|
||||
OutputInterface::VERBOSITY_VERBOSE
|
||||
);
|
||||
// TODO Get rid of output buffering
|
||||
ob_start();
|
||||
Toolbox::createSchema($default_language, $db_instance);
|
||||
$message = ob_get_clean();
|
||||
if (!empty($message)) {
|
||||
$output->writeln('<error>' . $message . '</error>', OutputInterface::VERBOSITY_QUIET);
|
||||
return self::ERROR_SCHEMA_CREATION_FAILED;
|
||||
}
|
||||
|
||||
$output->writeln('<info>' . __('Installation done.') . '</info>');
|
||||
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if DB config should be set by current command run.
|
||||
*
|
||||
* @param InputInterface $input
|
||||
* @param OutputInterface $output
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
private function shouldSetDBConfig(InputInterface $input, OutputInterface $output) {
|
||||
|
||||
return $input->getOption('reconfigure') || !file_exists(GLPI_CONFIG_DIR . '/config_db.php');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if input contains DB config options.
|
||||
*
|
||||
* @param InputInterface $input
|
||||
* @param OutputInterface $output
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
private function isInputContainingConfigValues(InputInterface $input, OutputInterface $output) {
|
||||
|
||||
$config_options = [
|
||||
'db-host',
|
||||
'db-port',
|
||||
'db-name',
|
||||
'db-user',
|
||||
'db-password',
|
||||
];
|
||||
foreach ($config_options as $option) {
|
||||
$default_value = $this->getDefinition()->getOption($option)->getDefault();
|
||||
$input_value = $input->getOption($option);
|
||||
|
||||
if ($default_value !== $input_value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
203
inc/console/database/updatecommand.class.php
Normal file
203
inc/console/database/updatecommand.class.php
Normal file
@ -0,0 +1,203 @@
|
||||
<?php
|
||||
/**
|
||||
* ---------------------------------------------------------------------
|
||||
* GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2015-2020 Teclib' and contributors.
|
||||
*
|
||||
* http://glpi-project.org
|
||||
*
|
||||
* based on GLPI - Gestionnaire Libre de Parc Informatique
|
||||
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
|
||||
*
|
||||
* ---------------------------------------------------------------------
|
||||
*
|
||||
* LICENSE
|
||||
*
|
||||
* This file is part of GLPI.
|
||||
*
|
||||
* GLPI is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GLPI is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
|
||||
* ---------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
namespace Glpi\Console\Database;
|
||||
|
||||
if (!defined('GLPI_ROOT')) {
|
||||
die("Sorry. You can't access this file directly");
|
||||
}
|
||||
|
||||
use Glpi\Console\AbstractCommand;
|
||||
use Glpi\Console\Command\ForceNoPluginsOptionCommandInterface;
|
||||
use Migration;
|
||||
use Session;
|
||||
use Update;
|
||||
|
||||
use Symfony\Component\Console\Helper\Table;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||
|
||||
class UpdateCommand extends AbstractCommand implements ForceNoPluginsOptionCommandInterface {
|
||||
|
||||
/**
|
||||
* Error code returned when trying to update from an unstable version.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const ERROR_NO_UNSTABLE_UPDATE = 1;
|
||||
|
||||
/**
|
||||
* Error code returned when security key file is missing.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const ERROR_MISSING_SECURITY_KEY_FILE = 2;
|
||||
|
||||
protected function configure() {
|
||||
parent::configure();
|
||||
|
||||
$this->setName('glpi:database:update');
|
||||
$this->setAliases(['db:update']);
|
||||
$this->setDescription(__('Update database schema to new version'));
|
||||
|
||||
$this->addOption(
|
||||
'allow-unstable',
|
||||
'u',
|
||||
InputOption::VALUE_NONE,
|
||||
__('Allow update to an unstable version')
|
||||
);
|
||||
|
||||
$this->addOption(
|
||||
'force',
|
||||
'f',
|
||||
InputOption::VALUE_NONE,
|
||||
__('Force execution of update from v-1 version of GLPI even if schema did not changed')
|
||||
);
|
||||
}
|
||||
|
||||
protected function initialize(InputInterface $input, OutputInterface $output) {
|
||||
|
||||
parent::initialize($input, $output);
|
||||
|
||||
$this->outputWarningOnMissingOptionnalRequirements();
|
||||
|
||||
$this->db->disableTableCaching();
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
|
||||
$allow_unstable = $input->getOption('allow-unstable');
|
||||
$force = $input->getOption('force');
|
||||
$no_interaction = $input->getOption('no-interaction'); // Base symfony/console option
|
||||
|
||||
$update = new Update($this->db);
|
||||
|
||||
// Initialize entities
|
||||
$_SESSION['glpidefault_entity'] = 0;
|
||||
Session::initEntityProfiles(2);
|
||||
Session::changeProfile(4);
|
||||
|
||||
// Display current/future state informations
|
||||
$currents = $update->getCurrents();
|
||||
$current_version = $currents['version'];
|
||||
$current_db_version = $currents['dbversion'];
|
||||
|
||||
global $migration; // Migration scripts are using global migrations
|
||||
$migration = new Migration(GLPI_SCHEMA_VERSION);
|
||||
$migration->setOutputHandler($output);
|
||||
$update->setMigration($migration);
|
||||
|
||||
$informations = new Table($output);
|
||||
$informations->setHeaders(['', __('Current'), _n('Target', 'Targets', 1)]);
|
||||
$informations->addRow([__('Database host'), $this->db->dbhost, '']);
|
||||
$informations->addRow([__('Database name'), $this->db->dbdefault, '']);
|
||||
$informations->addRow([__('Database user'), $this->db->dbuser, '']);
|
||||
$informations->addRow([__('GLPI version'), $current_version, GLPI_VERSION]);
|
||||
$informations->addRow([__('GLPI database version'), $current_db_version, GLPI_SCHEMA_VERSION]);
|
||||
$informations->render();
|
||||
|
||||
if (defined('GLPI_PREVER')) {
|
||||
// Prevent unstable update unless explicitly asked
|
||||
if (!$allow_unstable && version_compare($current_db_version, GLPI_SCHEMA_VERSION, 'ne')) {
|
||||
$output->writeln(
|
||||
sprintf(
|
||||
'<error>' . __('%s is not a stable release. Please upgrade manually or add --allow-unstable option.') . '</error>',
|
||||
GLPI_SCHEMA_VERSION
|
||||
),
|
||||
OutputInterface::VERBOSITY_QUIET
|
||||
);
|
||||
return self::ERROR_NO_UNSTABLE_UPDATE;
|
||||
}
|
||||
}
|
||||
|
||||
if (version_compare($current_db_version, GLPI_SCHEMA_VERSION, 'eq') && !$force) {
|
||||
$output->writeln('<info>' . __('No migration needed.') . '</info>');
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($update->isExpectedSecurityKeyFileMissing()) {
|
||||
$output->writeln(
|
||||
sprintf(
|
||||
'<error>' . __('The key file "%s" used to encrypt/decrypt sensitive data is missing. You should retrieve it from your previous installation or encrypted data will be unreadable.') . '</error>',
|
||||
$update->getExpectedSecurityKeyFilePath()
|
||||
),
|
||||
OutputInterface::VERBOSITY_QUIET
|
||||
);
|
||||
|
||||
if ($no_interaction) {
|
||||
return self::ERROR_MISSING_SECURITY_KEY_FILE;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$no_interaction) {
|
||||
// Ask for confirmation (unless --no-interaction)
|
||||
/** @var \Symfony\Component\Console\Helper\QuestionHelper $question_helper */
|
||||
$question_helper = $this->getHelper('question');
|
||||
$run = $question_helper->ask(
|
||||
$input,
|
||||
$output,
|
||||
new ConfirmationQuestion(__('Do you want to continue ?') . ' [Yes/no]', true)
|
||||
);
|
||||
if (!$run) {
|
||||
$output->writeln(
|
||||
'<comment>' . __('Update aborted.') . '</comment>',
|
||||
OutputInterface::VERBOSITY_VERBOSE
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
$update->doUpdates($current_version);
|
||||
|
||||
if (version_compare($current_db_version, GLPI_SCHEMA_VERSION, 'ne')) {
|
||||
// Migration is considered as done as Update class has the responsibility
|
||||
// to run updates if schema has changed (even for "pre-versions".
|
||||
$output->writeln('<info>' . __('Migration done.') . '</info>');
|
||||
} else if ($force) {
|
||||
// Replay last update script even if there is no schema change.
|
||||
// It can be used in dev environment when update script has been updated/fixed.
|
||||
include_once(GLPI_ROOT . '/install/update_951_952.php');
|
||||
update951to952();
|
||||
|
||||
$output->writeln('<info>' . __('Last migration replayed.') . '</info>');
|
||||
}
|
||||
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
public function getNoPluginsOptionValue() {
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user