diff --git a/composer.json b/composer.json index 228a275..ad1aa36 100644 --- a/composer.json +++ b/composer.json @@ -11,5 +11,14 @@ "issues": "https://github.com/previousnext/drush_cmi_tools/issues", "source": "https://github.com/previousnext/drush_cmi_tools" }, - "require": {} -} \ No newline at end of file + "require": { + "php": ">=5.6.0" + }, + "extra": { + "drush": { + "services": { + "drush.services.yml": "^9" + } + } + } +} diff --git a/drush.services.yml b/drush.services.yml new file mode 100644 index 0000000..be5edd5 --- /dev/null +++ b/drush.services.yml @@ -0,0 +1,5 @@ +services: + drush_cmi_tools.commands: + class: \Drupal\drush_cmi_tools\Commands\DrushCmiToolsCommands + tags: + - { name: drush.command } diff --git a/drush_cmi_tools.drush.inc b/drush_cmi_tools.drush.inc index baf5d7b..990ab26 100644 --- a/drush_cmi_tools.drush.inc +++ b/drush_cmi_tools.drush.inc @@ -9,7 +9,7 @@ use Drupal\Component\Serialization\Yaml; use Drupal\config\StorageReplaceDataWrapper; use Drupal\Core\Config\FileStorage; use Drupal\Core\Config\StorageComparer; -use Drush\Config\StorageWrapper; +use Drush\Drupal\Commands\config\ConfigCommands; use Drush\Log\LogLevel; /** @@ -103,7 +103,7 @@ function drush_drush_cmi_tools_config_export_plus($destination = NULL) { } } - $result = _drush_config_export($destination, $destination_dir, FALSE); + $result = \Drupal::service('config.export.commands')->doExport($destination, $destination_dir, FALSE); $file_service = \Drupal::service('file_system'); foreach ($patterns as $pattern) { foreach (file_scan_directory($destination_dir, $pattern) as $file_url => $file) { @@ -194,7 +194,7 @@ function drush_drush_cmi_tools_config_import_plus($destination = NULL) { foreach ($storage_comparer->getAllCollectionNames() as $collection) { $change_list[$collection] = $storage_comparer->getChangelist(NULL, $collection); } - _drush_print_config_changes_table($change_list); + ConfigCommands::configChangesTablePrint($change_list); } else { // Copy active storage to the temporary directory. @@ -212,6 +212,6 @@ function drush_drush_cmi_tools_config_import_plus($destination = NULL) { } if (drush_confirm(dt('Import the listed configuration changes?'))) { - return drush_op('_drush_config_import', $storage_comparer); + \Drupal::service('config.import.commands')->doImport($storage_comparer); } } diff --git a/drush_cmi_tools.info.yml b/drush_cmi_tools.info.yml new file mode 100644 index 0000000..5b82c43 --- /dev/null +++ b/drush_cmi_tools.info.yml @@ -0,0 +1,5 @@ +type: module +name: 'Drush CMI Tools' +description: 'Provides custom export/import commands.' +core: 8.x +package: 'Drush' diff --git a/src/Commands/DrushCmiToolsCommands.php b/src/Commands/DrushCmiToolsCommands.php new file mode 100644 index 0000000..e243794 --- /dev/null +++ b/src/Commands/DrushCmiToolsCommands.php @@ -0,0 +1,222 @@ + null, 'ignore-list' => null]) { + $this->logger()->debug(dt('Starting export')); + + // Do the actual config export operation + // Determine which target directory to use. + if (($target = $options['destination']) && $target !== TRUE) { + $destination_dir = $target; + + // Support writing to a destination from the $config_directories specified + // in settings.php + global $config_directories; + if (!empty($config_directories[$destination_dir])) { + $destination_dir = $config_directories[$destination_dir]; + } + + // It is important to be able to specify a destination directory that + // does not exist yet, for exporting on remote systems + drush_mkdir($destination_dir); + } else { + $this->logger()->error((dt('You must provide a --destination option'))); + return; + } + $patterns = []; + if ($ignore_list = $options['ignore-list']) { + if (!is_file($ignore_list)) { + $this->logger()->error(dt('The file specified in --ignore-list option does not exist.')); + return; + } + if ($string = file_get_contents($ignore_list)) { + $ignore_list_error = FALSE; + $parsed = FALSE; + try { + $parsed = Yaml::decode($string); + } + catch (InvalidDataTypeException $e) { + $ignore_list_error = TRUE; + } + if (!isset($parsed['ignore']) || !is_array($parsed['ignore'])) { + $ignore_list_error = TRUE; + } + if ($ignore_list_error) { + $this->logger()->error(dt('The file specified in --ignore-list option is in the wrong format. It must be valid YAML with a top-level ignore key.')); + return; + } + foreach ($parsed['ignore'] as $ignore) { + // Allow for accidental .yml extension. + if (substr($ignore, -4) === '.yml') { + $ignore = substr($ignore, 0, -4); + } + $patterns[] = '/^' . str_replace('\*', '(.*)', preg_quote($ignore)) . '\.yml/'; + } + } + } + + $drushExportOptions = [ + 'diff' => FALSE, + + ]; + $result = \Drupal::service('config.export.commands')->doExport($drushExportOptions, $destination_dir, FALSE); + $file_service = \Drupal::service('file_system'); + foreach ($patterns as $pattern) { + foreach (file_scan_directory($destination_dir, $pattern) as $file_url => $file) { + $file_service->unlink($file_url); + $this->logger()->notice("Removed $file_url according to ignore list."); + } + } + + return $result; + } + + /** + * Import config from a config directory resepecting live content and a delete list. + * + * @param array $options An associative array of options whose values come from cli, aliases, config, etc. + * @option preview + * Format for displaying proposed changes. Recognized values: list, diff. Defaults to list. + * @option source + * An arbitrary directory that holds the configuration files. + * @option delete-list + * Path to YAML file containing config to delete before importing. Useful when you need to remove items from active config store before importing. + * @option install + * Directory that holds the files to import once only. + * @usage drush config-import-plus --delete-list=./config-delete.yml --install=/some/install/folder --source=/some/export/folder + * Import configuration; do not enable or disable the devel module, regardless of whether or not it appears in the imported list of enabled modules. + * @validate-module-enabled config + * + * @command config:import-plus + * @aliases cimy,config-import-plus + */ + public function importPlus(array $options = ['preview' => null, 'source' => null, 'delete-list' => null, 'install' => null, 'diff' => null]) { + $this->logger()->debug(dt('Starting import')); + // Determine source directory. + if ($target = $options['source']) { + $source_dir = $target; + + // Support reading the source from the $config_directories specified in + // settings.php + global $config_directories; + if (!empty($config_directories[$source_dir])) { + $source_dir = $config_directories[$source_dir]; + } + } + else { + $this->logger()->error(dt('You must provide a --source option')); + return; + } + /** @var \Drupal\Core\Config\StorageInterface $active_storage */ + $active_storage = \Drupal::service('config.storage'); + $source_storage = new StorageReplaceDataWrapper($active_storage); + $file_storage = new FileStorage($source_dir); + foreach ($file_storage->listAll() as $name) { + $data = $file_storage->read($name); + $source_storage->replaceData($name, $data); + } + if ($delete_list = $options['delete-list']) { + if (!is_file($delete_list)) { + $this->logger()->error(dt('The file specified in --delete-list option does not exist.')); + return; + } + if ($string = file_get_contents($delete_list)) { + $delete_list_error = FALSE; + $parsed = FALSE; + try { + $parsed = Yaml::decode($string); + } + catch (InvalidDataTypeException $e) { + $delete_list_error = TRUE; + } + if (!isset($parsed['delete']) || !is_array($parsed['delete'])) { + $delete_list_error = TRUE; + } + if ($delete_list_error) { + $this->logger()->error(dt('The file specified in --delete-list option is in the wrong format. It must be valid YAML with a top-level delete key.')); + return; + } + foreach ($parsed['delete'] as $delete) { + // Allow for accidental .yml extension. + if (substr($delete, -4) === '.yml') { + $delete = substr($delete, 0, -4); + } + if ($source_storage->exists($delete)) { + $source_storage->delete($delete); + $this->logger()->notice("Deleted $delete as per delete list."); + } + else { + $this->logger()->notice("Ignored deleting $delete, does not exist."); + } + } + } + } + if ($install = $options['install']) { + $file_storage = new FileStorage($install); + foreach ($file_storage->listAll() as $name) { + if (!$source_storage->exists($name)) { + $data = $file_storage->read($name); + $source_storage->replaceData($name, $data); + $this->logger()->notice("Installed $name for first time."); + } + } + } + + /** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */ + $config_manager = \Drupal::service('config.manager'); + $storage_comparer = new StorageComparer($source_storage, $active_storage, $config_manager); + + + if (!$storage_comparer->createChangelist()->hasChanges()) { + $this->logger()->notice(dt('There are no changes to import.')); + return; + } + + // Copy active storage to the temporary directory. + if ($options['diff']) { + $temp_dir = drush_tempdir(); + $temp_storage = new FileStorage($temp_dir); + $source_dir_storage = new FileStorage($source_dir); + foreach ($source_dir_storage->listAll() as $name) { + if ($data = $active_storage->read($name)) { + $temp_storage->write($name, $data); + } + } + drush_shell_exec('diff -x %s -u %s %s', '*.git', $temp_dir, $source_dir); + $output = drush_shell_exec_output(); + $this->logger()->notice(implode("\n", $output)); + } + + if ($this->io()->confirm(dt('Import the listed configuration changes?'))) { + \Drupal::service('config.import.commands')->doImport($storage_comparer); + } + } + +}