config.drush.inc

  1. 8.0.x commands/core/config.drush.inc
  2. 7.x commands/core/config.drush.inc
  3. master commands/core/config.drush.inc

Provides Configuration Management commands.

Functions

Namesort descending Description
config_config_edit_complete Command argument complete callback.
config_config_export_complete Command argument complete callback.
config_config_get_complete Command argument complete callback.
config_config_import_complete Command argument complete callback.
config_config_pull_complete Command argument complete callback.
config_config_set_complete Command argument complete callback.
config_config_view_complete Command argument complete callback.
config_drush_command Implementation of hook_drush_command().
config_drush_help Implementation of hook_drush_help().
config_drush_help_alter Implements hook_drush_help_alter().
drush_config_delete Config delete command callback.
drush_config_edit Edit command callback.
drush_config_export Command callback: Export config to specified directory (usually sync).
drush_config_export_validate
drush_config_get Config get command callback.
drush_config_get_object Show and return a config object
drush_config_get_storage_filters Presently, the only configuration storage filter that is supported is the 'CoreExtensionFilter'. If other use cases arise that are not supported by Drupal's configuration override system, then we could add a hook here via…
drush_config_get_value Show and return a value from config system.
drush_config_import Command callback. Import from specified config directory (defaults to sync).
drush_config_import_validate
drush_config_list Config list command callback
drush_config_pull Config pull command callback
drush_config_pull_validate Config pull validate callback
drush_config_set Config set command callback.
_drush_config_directories_complete Helper function for command argument complete callback.
_drush_config_export
_drush_config_import
_drush_config_names_complete Helper function for command argument complete callback.
_drush_format_config_changes_table Print a table of config changes.
_drush_print_config_changes_table Print a table of config changes.

File

commands/core/config.drush.inc
View source
  1. <?php
  2. /**
  3. * @file
  4. * Provides Configuration Management commands.
  5. */
  6. use Drupal\config\StorageReplaceDataWrapper;
  7. use Drush\Log\LogLevel;
  8. use Drupal\Core\Config\StorageComparer;
  9. use Drupal\Core\Config\ConfigImporter;
  10. use Drupal\Core\Config\ConfigException;
  11. use Drupal\Core\Config\FileStorage;
  12. use Drupal\Component\Utility\NestedArray;
  13. use Drush\Config\StorageWrapper;
  14. use Drush\Config\CoreExtensionFilter;
  15. use Symfony\Component\Yaml\Parser;
  16. /**
  17. * Implementation of hook_drush_help().
  18. */
  19. function config_drush_help($section) {
  20. switch ($section) {
  21. case 'meta:config:title':
  22. return dt('Config commands');
  23. case 'meta:config:summary':
  24. return dt('Interact with the configuration system.');
  25. }
  26. }
  27. /**
  28. * Implementation of hook_drush_command().
  29. */
  30. function config_drush_command() {
  31. $deps = array('drupal dependencies' => array('config'));
  32. $items['config-get'] = array(
  33. 'description' => 'Display a config value, or a whole configuration object.',
  34. 'arguments' => array(
  35. 'config-name' => 'The config object name, for example "system.site".',
  36. 'key' => 'The config key, for example "page.front". Optional.',
  37. ),
  38. 'required-arguments' => 1,
  39. 'options' => array(
  40. 'source' => array(
  41. 'description' => 'The config storage source to read. Additional labels may be defined in settings.php',
  42. 'example-value' => 'sync',
  43. 'value' => 'required',
  44. ),
  45. 'include-overridden' => array(
  46. 'description' => 'Include overridden values.',
  47. )
  48. ),
  49. 'examples' => array(
  50. 'drush config-get system.site' => 'Displays the system.site config.',
  51. 'drush config-get system.site page.front' => 'gets system.site:page.front value.',
  52. ),
  53. 'outputformat' => array(
  54. 'default' => 'yaml',
  55. 'pipe-format' => 'var_export',
  56. ),
  57. 'aliases' => array('cget'),
  58. 'core' => array('8+'),
  59. );
  60. $items['config-set'] = array(
  61. 'description' => 'Set config value directly. Does not perform a config import.',
  62. 'arguments' => array(
  63. 'config-name' => 'The config object name, for example "system.site".',
  64. 'key' => 'The config key, for example "page.front".',
  65. 'value' => 'The value to assign to the config key. Use \'-\' to read from STDIN.',
  66. ),
  67. 'options' => array(
  68. 'format' => array(
  69. 'description' => 'Format to parse the object. Use "string" for string (default), and "yaml" for YAML.',
  70. 'example-value' => 'yaml',
  71. 'value' => 'required',
  72. ),
  73. // A convenient way to pass a multiline value within a backend request.
  74. 'value' => array(
  75. 'description' => 'The value to assign to the config key (if any).',
  76. 'hidden' => TRUE,
  77. ),
  78. ),
  79. 'examples' => array(
  80. 'drush config-set system.site page.front node' => 'Sets system.site:page.front to "node".',
  81. ),
  82. 'aliases' => array('cset'),
  83. 'core' => array('8+'),
  84. );
  85. $items['config-export'] = array(
  86. 'description' => 'Export configuration to a directory.',
  87. 'core' => array('8+'),
  88. 'aliases' => array('cex'),
  89. 'arguments' => array(
  90. 'label' => "A config directory label (i.e. a key in \$config_directories array in settings.php). Defaults to 'sync'",
  91. ),
  92. 'options' => array(
  93. 'add' => 'Run `git add -p` after exporting. This lets you choose which config changes to sync for commit.',
  94. 'commit' => 'Run `git add -A` and `git commit` after exporting. This commits everything that was exported without prompting.',
  95. 'message' => 'Commit comment for the exported configuration. Optional; may only be used with --commit or --push.',
  96. 'push' => 'Run `git push` after committing. Implies --commit.',
  97. 'remote' => array(
  98. 'description' => 'The remote git branch to use to push changes. Defaults to "origin".',
  99. 'example-value' => 'origin',
  100. ),
  101. 'branch' => array(
  102. 'description' => 'Make commit on provided working branch. Ignored if used without --commit or --push.',
  103. 'example-value' => 'branchname',
  104. ),
  105. 'destination' => 'An arbitrary directory that should receive the exported files. An alternative to label argument.',
  106. 'skip-modules' => 'A list of modules to ignore during export (e.g. to avoid listing dev-only modules in exported configuration).',
  107. ),
  108. 'examples' => array(
  109. 'drush config-export --skip-modules=devel' => 'Export configuration; do not include the devel module in the exported configuration, regardless of whether or not it is enabled in the site.',
  110. 'drush config-export --destination' => 'Export configuration; Save files in a backup directory named config-export.',
  111. ),
  112. );
  113. $items['config-import'] = array(
  114. 'description' => 'Import config from a config directory.',
  115. 'arguments' => array(
  116. 'label' => "A config directory label (i.e. a key in \$config_directories array in settings.php). Defaults to 'sync'",
  117. ),
  118. 'options' => array(
  119. 'preview' => array(
  120. 'description' => 'Format for displaying proposed changes. Recognized values: list, diff. Defaults to list.',
  121. 'example-value' => 'list',
  122. ),
  123. 'source' => array(
  124. 'description' => 'An arbitrary directory that holds the configuration files. An alternative to label argument',
  125. ),
  126. 'partial' => array(
  127. 'description' => 'Allows for partial config imports from the source directory. Only updates and new configs will be processed with this flag (missing configs will not be deleted).',
  128. ),
  129. 'skip-modules' => 'A list of modules to ignore during import (e.g. to avoid disabling dev-only modules that are not enabled in the imported configuration).',
  130. ),
  131. 'core' => array('8+'),
  132. 'examples' => array(
  133. 'drush config-import --skip-modules=devel' => 'Import configuration; do not enable or disable the devel module, regardless of whether or not it appears in the imported list of enabled modules.',
  134. ),
  135. 'aliases' => array('cim'),
  136. );
  137. $items['config-list'] = array(
  138. 'description' => 'List config names by prefix.',
  139. 'core' => array('8+'),
  140. 'aliases' => array('cli'),
  141. 'arguments' => array(
  142. 'prefix' => 'The config prefix. For example, "system". No prefix will return all names in the system.',
  143. ),
  144. 'examples' => array(
  145. 'drush config-list system' => 'Return a list of all system config names.',
  146. 'drush config-list "image.style"' => 'Return a list of all image styles.',
  147. 'drush config-list --format="json"' => 'Return all config names as json.',
  148. ),
  149. 'outputformat' => array(
  150. 'default' => 'list',
  151. 'pipe-format' => 'var_export',
  152. 'output-data-type' => 'format-list',
  153. ),
  154. );
  155. $items['config-edit'] = $deps + array(
  156. 'description' => 'Open a config file in a text editor. Edits are imported into active configuration after closing editor.',
  157. 'core' => array('8+'),
  158. 'aliases' => array('cedit'),
  159. 'arguments' => array(
  160. 'config-name' => 'The config object name, for example "system.site".',
  161. ),
  162. 'global-options' => array('editor', 'bg'),
  163. 'allow-additional-options' => array('config-import'),
  164. 'examples' => array(
  165. 'drush config-edit image.style.large' => 'Edit the image style configurations.',
  166. 'drush config-edit' => 'Choose a config file to edit.',
  167. 'drush config-edit --choice=2' => 'Edit the second file in the choice list.',
  168. 'drush --bg config-edit image.style.large' => 'Return to shell prompt as soon as the editor window opens.',
  169. ),
  170. );
  171. $items['config-delete'] = array(
  172. 'description' => 'Delete a configuration object.',
  173. 'core' => array('8+'),
  174. 'aliases' => array('cdel'),
  175. 'arguments' => array(
  176. 'config-name' => 'The config object name, for example "system.site".',
  177. ),
  178. 'required arguments'
  179. );
  180. $items['config-pull'] = array(
  181. 'description' => 'Export and transfer config from one environment to another.',
  182. // 'core' => array('8+'), Operates on remote sites so not possible to declare this locally.
  183. 'drush dependencies' => array('config', 'core'), // core-rsync, core-execute.
  184. 'bootstrap' => DRUSH_BOOTSTRAP_NONE,
  185. 'aliases' => array('cpull'),
  186. 'arguments' => array(
  187. 'source' => 'A site-alias or the name of a subdirectory within /sites whose config you want to copy from.',
  188. 'target' => 'A site-alias or the name of a subdirectory within /sites whose config you want to replace.',
  189. ),
  190. 'required-arguments' => TRUE,
  191. 'allow-additional-options' => array(), // Most options from config-export and core-rsync unusable.
  192. 'examples' => array(
  193. 'drush config-pull @prod @stage' => "Export config from @prod and transfer to @stage.",
  194. 'drush config-pull @prod @self --label=vcs' => "Export config from @prod and transfer to the 'vcs' config directory of current site.",
  195. ),
  196. 'options' => array(
  197. 'safe' => 'Validate that there are no git uncommitted changes before proceeding',
  198. 'label' => "A config directory label (i.e. a key in \$config_directories array in settings.php). Defaults to 'sync'",
  199. 'runner' => 'Where to run the rsync command; defaults to the local site. Can also be "source" or "destination".',
  200. ),
  201. 'topics' => array('docs-aliases', 'docs-config-exporting'),
  202. );
  203. return $items;
  204. }
  205. /**
  206. * Implements hook_drush_help_alter().
  207. */
  208. function config_drush_help_alter(&$command) {
  209. // Hide additional-options which are for internal use only.
  210. if ($command['command'] == 'config-edit') {
  211. $command['options']['source']['hidden'] = TRUE;
  212. $command['options']['partial']['hidden'] = TRUE;
  213. }
  214. }
  215. /**
  216. * Config list command callback
  217. *
  218. * @param string $prefix
  219. * The config prefix to retrieve, or empty to return all.
  220. */
  221. function drush_config_list($prefix = '') {
  222. $names = \Drupal::configFactory()->listAll($prefix);
  223. if (empty($names)) {
  224. // Just in case there is no config.
  225. if (!$prefix) {
  226. return drush_set_error(dt('No config storage names found.'));
  227. }
  228. else {
  229. return drush_set_error(dt('No config storage names found matching @prefix', array('@prefix' => $prefix)));
  230. }
  231. }
  232. return $names;
  233. }
  234. /**
  235. * Config get command callback.
  236. *
  237. * @param $config_name
  238. * The config name.
  239. * @param $key
  240. * The config key.
  241. */
  242. function drush_config_get($config_name, $key = NULL) {
  243. if (!isset($key)) {
  244. return drush_config_get_object($config_name);
  245. }
  246. else {
  247. return drush_config_get_value($config_name, $key);
  248. }
  249. }
  250. /**
  251. * Config delete command callback.
  252. *
  253. * @param $config_name
  254. * The config name.
  255. */
  256. function drush_config_delete($config_name) {
  257. $config =\Drupal::service('config.factory')->getEditable($config_name);
  258. if ($config->isNew()) {
  259. return drush_set_error('DRUSH_CONFIG_ERROR', 'Configuration name not recognized. Use config-list to see all names.');
  260. }
  261. else {
  262. $config->delete();
  263. }
  264. }
  265. /**
  266. * Config set command callback.
  267. *
  268. * @param $config_name
  269. * The config name.
  270. * @param $key
  271. * The config key.
  272. * @param $data
  273. * The data to save to config.
  274. */
  275. function drush_config_set($config_name, $key = NULL, $data = NULL) {
  276. // This hidden option is a convenient way to pass a value without passing a key.
  277. $data = drush_get_option('value', $data);
  278. if (!isset($data)) {
  279. return drush_set_error('DRUSH_CONFIG_ERROR', dt('No config value specified.'));
  280. }
  281. $config = \Drupal::configFactory()->getEditable($config_name);
  282. // Check to see if config key already exists.
  283. if ($config->get($key) === NULL) {
  284. $new_key = TRUE;
  285. }
  286. else {
  287. $new_key = FALSE;
  288. }
  289. // Special flag indicating that the value has been passed via STDIN.
  290. if ($data === '-') {
  291. $data = stream_get_contents(STDIN);
  292. }
  293. // Now, we parse the value.
  294. switch (drush_get_option('format', 'string')) {
  295. case 'yaml':
  296. $parser = new Parser();
  297. $data = $parser->parse($data, TRUE);
  298. }
  299. if (is_array($data) && drush_confirm(dt('Do you want to update or set multiple keys on !name config.', array('!name' => $config_name)))) {
  300. foreach ($data as $key => $value) {
  301. $config->set($key, $value);
  302. }
  303. return $config->save();
  304. }
  305. else {
  306. $confirmed = FALSE;
  307. if ($config->isNew() && drush_confirm(dt('!name config does not exist. Do you want to create a new config object?', array('!name' => $config_name)))) {
  308. $confirmed = TRUE;
  309. }
  310. elseif ($new_key && drush_confirm(dt('!key key does not exist in !name config. Do you want to create a new config key?', array('!key' => $key, '!name' => $config_name)))) {
  311. $confirmed = TRUE;
  312. }
  313. elseif (drush_confirm(dt('Do you want to update !key key in !name config?', array('!key' => $key, '!name' => $config_name)))) {
  314. $confirmed = TRUE;
  315. }
  316. if ($confirmed && !drush_get_context('DRUSH_SIMULATE')) {
  317. return $config->set($key, $data)->save();
  318. }
  319. }
  320. }
  321. /*
  322. * If provided $destination is not TRUE and not empty, make sure it is writable.
  323. */
  324. function drush_config_export_validate() {
  325. $destination = drush_get_option('destination');
  326. if ($destination === TRUE) {
  327. // We create a dir in command callback. No need to validate.
  328. return;
  329. }
  330. if (!empty($destination)) {
  331. $additional = array();
  332. $values = drush_sitealias_evaluate_path($destination, $additional, TRUE);
  333. if (!isset($values['path'])) {
  334. return drush_set_error('config_export_target', 'The destination directory could not be evaluated.');
  335. }
  336. $destination = $values['path'];
  337. drush_set_option('destination', $destination);
  338. if (!file_exists($destination)) {
  339. $parent = dirname($destination);
  340. if (!is_dir($parent)) {
  341. return drush_set_error('config_export_target', 'The destination parent directory does not exist.');
  342. }
  343. if (!is_writable($parent)) {
  344. return drush_set_error('config_export_target', 'The destination parent directory is not writable.');
  345. }
  346. }
  347. else {
  348. if (!is_dir($destination)) {
  349. return drush_set_error('config_export_target', 'The destination is not a directory.');
  350. }
  351. if (!is_writable($destination)) {
  352. return drush_set_error('config_export_target', 'The destination directory is not writable.');
  353. }
  354. }
  355. }
  356. }
  357. /**
  358. * Command callback: Export config to specified directory (usually sync).
  359. */
  360. function drush_config_export($destination = NULL) {
  361. global $config_directories;
  362. // Determine which target directory to use.
  363. if ($target = drush_get_option('destination')) {
  364. if ($target === TRUE) {
  365. // User did not pass a specific value for --destination. Make one.
  366. /** @var drush_version_control_backup $backup */
  367. $backup = drush_include_engine('version_control', 'backup');
  368. $destination_dir = $backup->prepare_backup_dir('config-export');
  369. }
  370. else {
  371. $destination_dir = $target;
  372. // It is important to be able to specify a destination directory that
  373. // does not exist yet, for exporting on remote systems
  374. drush_mkdir($destination_dir);
  375. }
  376. }
  377. else {
  378. $choices = drush_map_assoc(array_keys($config_directories));
  379. unset($choices[CONFIG_ACTIVE_DIRECTORY]);
  380. if (!isset($destination) && count($choices) >= 2) {
  381. $destination = drush_choice($choices, 'Choose a destination.');
  382. if (empty($destination)) {
  383. return drush_user_abort();
  384. }
  385. }
  386. elseif (!isset($destination)) {
  387. $destination = CONFIG_SYNC_DIRECTORY;
  388. }
  389. $destination_dir = config_get_config_directory($destination);
  390. }
  391. // Prepare a new branch, if applicable
  392. $remote = drush_get_option('push', FALSE);
  393. $original_branch = FALSE;
  394. $branch = FALSE;
  395. if ($remote) {
  396. // Get the branch that we're on at the moment
  397. $result = drush_shell_cd_and_exec($destination_dir, 'git rev-parse --abbrev-ref HEAD');
  398. if (!$result) {
  399. return drush_set_error('DRUSH_CONFIG_EXPORT_NO_GIT', dt("The drush config-export command requires that the selected configuration directory !dir be under git revision control when using --commit or --push options.", array('!dir' => $destination_dir)));
  400. }
  401. $output = drush_shell_exec_output();
  402. $original_branch = $output[0];
  403. $branch = drush_get_option('branch', FALSE);
  404. if (!$branch) {
  405. $branch = $original_branch;
  406. }
  407. if ($branch != $original_branch) {
  408. // Switch to the working branch; create it if it does not exist.
  409. // We do NOT want to use -B here, as we do NOT want to reset the
  410. // branch if it already exists.
  411. $result = drush_shell_cd_and_exec($destination_dir, 'git checkout %s', $branch);
  412. if (!$result) {
  413. $result = drush_shell_cd_and_exec($destination_dir, 'git checkout -b %s', $branch);
  414. }
  415. }
  416. }
  417. // Do the actual config export operation
  418. $result = _drush_config_export($destination, $destination_dir, $branch);
  419. // Regardless of the result of the export, reset to our original branch.
  420. if ($branch != $original_branch) {
  421. drush_shell_cd_and_exec($destination_dir, 'git checkout %s', $original_branch);
  422. }
  423. return $result;
  424. }
  425. function _drush_config_export($destination, $destination_dir, $branch) {
  426. $commit = drush_get_option('commit');
  427. $comment = drush_get_option('message', 'Exported configuration.');
  428. $storage_filters = drush_config_get_storage_filters();
  429. if (count(glob($destination_dir . '/*')) > 0) {
  430. // Retrieve a list of differences between the active and target configuration (if any).
  431. $target_storage = new FileStorage($destination_dir);
  432. /** @var \Drupal\Core\Config\StorageInterface $active_storage */
  433. $active_storage = \Drupal::service('config.storage');
  434. $comparison_source = $active_storage;
  435. // If the output is being filtered, then write a temporary copy before doing
  436. // any comparison.
  437. if (!empty($storage_filters)) {
  438. $tmpdir = drush_tempdir();
  439. drush_copy_dir($destination_dir, $tmpdir, FILE_EXISTS_OVERWRITE);
  440. $comparison_source = new FileStorage($tmpdir);
  441. $comparison_source_filtered = new StorageWrapper($comparison_source, $storage_filters);
  442. foreach ($active_storage->listAll() as $name) {
  443. // Copy active storage to our temporary active store.
  444. if ($existing = $active_storage->read($name)) {
  445. $comparison_source_filtered->write($name, $existing);
  446. }
  447. }
  448. }
  449. $config_comparer = new StorageComparer($comparison_source, $target_storage, \Drupal::service('config.manager'));
  450. if (!$config_comparer->createChangelist()->hasChanges()) {
  451. return drush_log(dt('The active configuration is identical to the configuration in the export directory (!target).', array('!target' => $destination_dir)), LogLevel::OK);
  452. }
  453. drush_print("Differences of the active config to the export directory:\n");
  454. $change_list = array();
  455. foreach ($config_comparer->getAllCollectionNames() as $collection) {
  456. $change_list[$collection] = $config_comparer->getChangelist(NULL, $collection);
  457. }
  458. // Print a table with changes in color, then re-generate again without
  459. // color to place in the commit comment.
  460. _drush_print_config_changes_table($change_list);
  461. $tbl = _drush_format_config_changes_table($change_list);
  462. $output = $tbl->getTable();
  463. if (!stristr(PHP_OS, 'WIN')) {
  464. $output = str_replace("\r\n", PHP_EOL, $output);
  465. }
  466. $comment .= "\n\n$output";
  467. if (!$commit && !drush_confirm(dt('The .yml files in your export directory (!target) will be deleted and replaced with the active config.', array('!target' => $destination_dir)))) {
  468. return drush_user_abort();
  469. }
  470. // Only delete .yml files, and not .htaccess or .git.
  471. drush_scan_directory($destination_dir, '/\.yml$/', array('.', '..'), 'unlink');
  472. }
  473. // Write all .yml files.
  474. $source_storage = \Drupal::service('config.storage');
  475. $destination_storage = new FileStorage($destination_dir);
  476. // If there are any filters, then attach them to the destination storage
  477. if (!empty($storage_filters)) {
  478. $destination_storage = new StorageWrapper($destination_storage, $storage_filters);
  479. }
  480. foreach ($source_storage->listAll() as $name) {
  481. $destination_storage->write($name, $source_storage->read($name));
  482. }
  483. // Export configuration collections.
  484. foreach (\Drupal::service('config.storage')->getAllCollectionNames() as $collection) {
  485. $source_storage = $source_storage->createCollection($collection);
  486. $destination_storage = $destination_storage->createCollection($collection);
  487. foreach ($source_storage->listAll() as $name) {
  488. $destination_storage->write($name, $source_storage->read($name));
  489. }
  490. }
  491. drush_log(dt('Configuration successfully exported to !target.', array('!target' => $destination_dir)), LogLevel::SUCCESS);
  492. drush_backend_set_result($destination_dir);
  493. // Commit and push, or add exported configuration if requested.
  494. $remote = drush_get_option('push', FALSE);
  495. if ($commit || $remote) {
  496. // There must be changed files at the destination dir; if there are not, then
  497. // we will skip the commit-and-push step
  498. $result = drush_shell_cd_and_exec($destination_dir, 'git status --porcelain .');
  499. if (!$result) {
  500. return drush_set_error('DRUSH_CONFIG_EXPORT_FAILURE', dt("`git status` failed."));
  501. }
  502. $uncommitted_changes = drush_shell_exec_output();
  503. if (!empty($uncommitted_changes)) {
  504. $result = drush_shell_cd_and_exec($destination_dir, 'git add -A .');
  505. if (!$result) {
  506. return drush_set_error('DRUSH_CONFIG_EXPORT_FAILURE', dt("`git add -A` failed."));
  507. }
  508. $comment_file = drush_save_data_to_temp_file($comment);
  509. $result = drush_shell_cd_and_exec($destination_dir, 'git commit --file=%s', $comment_file);
  510. if (!$result) {
  511. return drush_set_error('DRUSH_CONFIG_EXPORT_FAILURE', dt("`git commit` failed. Output:\n\n!output", array('!output' => implode("\n", drush_shell_exec_output()))));
  512. }
  513. if ($remote) {
  514. // Remote might be FALSE, if --push was not specified, or
  515. // it might be TRUE if --push was not given a value.
  516. if (!is_string($remote)) {
  517. $remote = 'origin';
  518. }
  519. $result = drush_shell_cd_and_exec($destination_dir, 'git push --set-upstream %s %s', $remote, $branch);
  520. if (!$result) {
  521. return drush_set_error('DRUSH_CONFIG_EXPORT_FAILURE', dt("`git push` failed."));
  522. }
  523. }
  524. }
  525. }
  526. elseif (drush_get_option('add')) {
  527. drush_shell_exec_interactive('git add -p %s', $destination_dir);
  528. }
  529. $values = array(
  530. 'destination' => $destination_dir,
  531. );
  532. return $values;
  533. }
  534. function drush_config_import_validate() {
  535. drush_include_engine('drupal', 'environment');
  536. if (drush_get_option('partial') && !drush_module_exists('config')) {
  537. return drush_set_error('config_import_partial', 'Enable the config module in order to use the --partial option.');
  538. }
  539. if ($source = drush_get_option('source')) {
  540. if (!file_exists($source)) {
  541. return drush_set_error('config_import_target', 'The source directory does not exist.');
  542. }
  543. if (!is_dir($source)) {
  544. return drush_set_error('config_import_target', 'The source is not a directory.');
  545. }
  546. }
  547. }
  548. /**
  549. * Presently, the only configuration storage filter that is supported
  550. * is the 'CoreExtensionFilter'. If other use cases arise that are
  551. * not supported by Drupal's configuration override system, then we
  552. * could add a hook here via drush_command_invoke_all('drush_storage_filters');
  553. *
  554. * See: https://github.com/drush-ops/drush/pull/1522
  555. */
  556. function drush_config_get_storage_filters() {
  557. $result = array();
  558. $module_adjustments = drush_get_option('skip-modules');
  559. if (!empty($module_adjustments)) {
  560. if (is_string($module_adjustments)) {
  561. $module_adjustments = explode(',', $module_adjustments);
  562. }
  563. $result[] = new CoreExtensionFilter($module_adjustments);
  564. }
  565. return $result;
  566. }
  567. /**
  568. * Command callback. Import from specified config directory (defaults to sync).
  569. */
  570. function drush_config_import($source = NULL) {
  571. global $config_directories;
  572. // Determine source directory.
  573. if ($target = drush_get_option('source')) {
  574. $source_dir = $target;
  575. }
  576. else {
  577. $choices = drush_map_assoc(array_keys($config_directories));
  578. unset($choices[CONFIG_ACTIVE_DIRECTORY]);
  579. if (!isset($source) && count($choices) >= 2) {
  580. $source= drush_choice($choices, 'Choose a source.');
  581. if (empty($source)) {
  582. return drush_user_abort();
  583. }
  584. }
  585. elseif (!isset($source)) {
  586. $source = CONFIG_SYNC_DIRECTORY;
  587. }
  588. $source_dir = config_get_config_directory($source);
  589. }
  590. // Determine $source_storage in partial and non-partial cases.
  591. /** @var \Drupal\Core\Config\StorageInterface $active_storage */
  592. $active_storage = \Drupal::service('config.storage');
  593. if (drush_get_option('partial')) {
  594. $source_storage = new StorageReplaceDataWrapper($active_storage);
  595. $file_storage = new FileStorage($source_dir);
  596. foreach ($file_storage->listAll() as $name) {
  597. $data = $file_storage->read($name);
  598. $source_storage->replaceData($name, $data);
  599. }
  600. }
  601. else {
  602. $source_storage = new FileStorage($source_dir);
  603. }
  604. // If our configuration storage is being filtered, then attach all filters
  605. // to the source storage object. We will use the filtered values uniformly
  606. // for comparison, full imports, and partial imports.
  607. $storage_filters = drush_config_get_storage_filters();
  608. if (!empty($storage_filters)) {
  609. $source_storage = new StorageWrapper($source_storage, $storage_filters);
  610. }
  611. /** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */
  612. $config_manager = \Drupal::service('config.manager');
  613. $storage_comparer = new StorageComparer($source_storage, $active_storage, $config_manager);
  614. if (!$storage_comparer->createChangelist()->hasChanges()) {
  615. return drush_log(dt('There are no changes to import.'), LogLevel::OK);
  616. }
  617. if (drush_get_option('preview', 'list') == 'list') {
  618. $change_list = array();
  619. foreach ($storage_comparer->getAllCollectionNames() as $collection) {
  620. $change_list[$collection] = $storage_comparer->getChangelist(NULL, $collection);
  621. }
  622. _drush_print_config_changes_table($change_list);
  623. }
  624. else {
  625. // Copy active storage to the temporary directory.
  626. $temp_dir = drush_tempdir();
  627. $temp_storage = new FileStorage($temp_dir);
  628. $source_dir_storage = new FileStorage($source_dir);
  629. foreach ($source_dir_storage->listAll() as $name) {
  630. if ($data = $active_storage->read($name)) {
  631. $temp_storage->write($name, $data);
  632. }
  633. }
  634. drush_shell_exec('diff -x %s -u %s %s', '*.git', $temp_dir, $source_dir);
  635. $output = drush_shell_exec_output();
  636. drush_print(implode("\n", $output));
  637. }
  638. if (drush_confirm(dt('Import the listed configuration changes?'))) {
  639. return drush_op('_drush_config_import', $storage_comparer);
  640. }
  641. }
  642. // Copied from submitForm() at /core/modules/config/src/Form/ConfigSync.php
  643. function _drush_config_import(StorageComparer $storage_comparer) {
  644. $config_importer = new ConfigImporter(
  645. $storage_comparer,
  646. \Drupal::service('event_dispatcher'),
  647. \Drupal::service('config.manager'),
  648. \Drupal::lock(),
  649. \Drupal::service('config.typed'),
  650. \Drupal::moduleHandler(),
  651. \Drupal::service('module_installer'),
  652. \Drupal::service('theme_handler'),
  653. \Drupal::service('string_translation')
  654. );
  655. if ($config_importer->alreadyImporting()) {
  656. drush_log('Another request may be synchronizing configuration already.', LogLevel::WARNING);
  657. }
  658. else{
  659. try {
  660. $config_importer->import();
  661. drush_log('The configuration was imported successfully.', LogLevel::SUCCESS);
  662. }
  663. catch (ConfigException $e) {
  664. // Return a negative result for UI purposes. We do not differentiate
  665. // between an actual synchronization error and a failed lock, because
  666. // concurrent synchronizations are an edge-case happening only when
  667. // multiple developers or site builders attempt to do it without
  668. // coordinating.
  669. $message = 'The import failed due for the following reasons:' . "\n";
  670. $message .= implode("\n", $config_importer->getErrors());
  671. watchdog_exception('config_import', $e);
  672. return drush_set_error('config_import_fail', $message);
  673. }
  674. }
  675. }
  676. /**
  677. * Edit command callback.
  678. */
  679. function drush_config_edit($config_name = '') {
  680. // Identify and validate input.
  681. if ($config_name) {
  682. $config = \Drupal::configFactory()->get($config_name);
  683. if ($config->isNew()) {
  684. return drush_set_error(dt('Config !name does not exist', array('!name' => $config_name)));
  685. }
  686. }
  687. else {
  688. $config_names = \Drupal::configFactory()->listAll();
  689. $choice = drush_choice($config_names, 'Choose a configuration.');
  690. if (empty($choice)) {
  691. return drush_user_abort();
  692. }
  693. else {
  694. $config_name = $config_names[$choice];
  695. $config = \Drupal::configFactory()->get($config_name);
  696. }
  697. }
  698. $active_storage = $config->getStorage();
  699. $contents = $active_storage->read($config_name);
  700. // Write tmp YAML file for editing
  701. $temp_dir = drush_tempdir();
  702. $temp_storage = new FileStorage($temp_dir);
  703. $temp_storage->write($config_name, $contents);
  704. $exec = drush_get_editor();
  705. drush_shell_exec_interactive($exec, $temp_storage->getFilePath($config_name));
  706. // Perform import operation if user did not immediately exit editor.
  707. if (!drush_get_option('bg', FALSE)) {
  708. $options = drush_redispatch_get_options() + array('partial' => TRUE, 'source' => $temp_dir);
  709. $backend_options = array('interactive' => TRUE);
  710. return (bool) drush_invoke_process('@self', 'config-import', array(), $options, $backend_options);
  711. }
  712. }
  713. /**
  714. * Config pull validate callback
  715. *
  716. */
  717. function drush_config_pull_validate($source, $destination) {
  718. if (drush_get_option('safe')) {
  719. $return = drush_invoke_process($destination, 'core-execute', array('git diff --quiet'), array('escape' => 0));
  720. if ($return['error_status']) {
  721. return drush_set_error('DRUSH_GIT_DIRTY', 'There are uncommitted changes in your git working copy.');
  722. }
  723. }
  724. }
  725. /**
  726. * Config pull command callback
  727. *
  728. * @param string $label
  729. * The config label which receives the transferred files.
  730. */
  731. function drush_config_pull($source, $destination) {
  732. // @todo drush_redispatch_get_options() assumes you will execute same command. Not good.
  733. $global_options = drush_redispatch_get_options() + array(
  734. 'strict' => 0,
  735. );
  736. // @todo If either call is made interactive, we don't get an $return['object'] back.
  737. $backend_options = array('interactive' => FALSE);
  738. if (drush_get_context('DRUSH_SIMULATE')) {
  739. $backend_options['backend-simulate'] = TRUE;
  740. }
  741. $export_options = array(
  742. // Use the standard backup directory on Destination.
  743. 'destination' => TRUE,
  744. );
  745. drush_log(dt('Starting to export configuration on Target.'), LogLevel::OK);
  746. $return = drush_invoke_process($source, 'config-export', array(), $global_options + $export_options, $backend_options);
  747. if ($return['error_status']) {
  748. return drush_set_error('DRUSH_CONFIG_PULL_EXPORT_FAILED', dt('Config-export failed.'));
  749. }
  750. else {
  751. // Trailing slash assures that transfer files and not the containing dir.
  752. $export_path = $return['object'] . '/';
  753. }
  754. $rsync_options = array(
  755. 'remove-source-files' => TRUE,
  756. 'delete' => TRUE,
  757. 'exclude-paths' => '.htaccess',
  758. 'yes' => TRUE, // No need to prompt as destination is always the target config directory.
  759. );
  760. $label = drush_get_option('label', 'sync');
  761. $runner = drush_get_runner($source, $destination, drush_get_option('runner', FALSE));
  762. drush_log(dt('Starting to rsync configuration files from !source to !dest.', array('!source' => $source, '!dest' => $destination)), LogLevel::OK);
  763. // This comment applies similarly to sql-sync's use of core-rsync.
  764. // Since core-rsync is a strict-handling command and drush_invoke_process() puts options at end, we can't send along cli options to rsync.
  765. // Alternatively, add options like --ssh-options to a site alias (usually on the machine that initiates the sql-sync).
  766. $return = drush_invoke_process($runner, 'core-rsync', array("$source:$export_path", "$destination:%config-$label"), $rsync_options);
  767. if ($return['error_status']) {
  768. return drush_set_error('DRUSH_CONFIG_PULL_RSYNC_FAILED', dt('Config-pull rsync failed.'));
  769. }
  770. drush_backend_set_result($return['object']);
  771. }
  772. /**
  773. * Show and return a config object
  774. *
  775. * @param $config_name
  776. * The config object name.
  777. */
  778. function drush_config_get_object($config_name) {
  779. $source = drush_get_option('source', 'active');
  780. $include_overridden = drush_get_option('include-overridden', FALSE);
  781. if ($include_overridden) {
  782. // Displaying overrides only applies to active storage.
  783. $config = \Drupal::config($config_name);
  784. $data = $config->get();
  785. }
  786. elseif ($source == 'active') {
  787. $config = \Drupal::service('config.storage');
  788. $data = $config->read($config_name);
  789. }
  790. elseif ($source == 'sync') {
  791. $config = \Drupal::service('config.storage.sync');
  792. $data = $config->read($config_name);
  793. }
  794. else {
  795. return drush_set_error(dt('Unknown value !value for config source.', array('!value' => $source)));
  796. }
  797. if ($data === FALSE) {
  798. return drush_set_error(dt('Config !name does not exist in !source configuration.', array('!name' => $config_name, '!source' => $source)));
  799. }
  800. if (empty($data)) {
  801. drush_log(dt('Config !name exists but has no data.', array('!name' => $config_name)), LogLevel::INFO);
  802. return;
  803. }
  804. return $data;
  805. }
  806. /**
  807. * Show and return a value from config system.
  808. *
  809. * @param $config_name
  810. * The config name.
  811. * @param $key
  812. * The config key.
  813. */
  814. function drush_config_get_value($config_name, $key) {
  815. $data = drush_config_get_object($config_name);
  816. $parts = explode('.', $key);
  817. if (count($parts) == 1) {
  818. $value = isset($data[$key]) ? $data[$key] : NULL;
  819. }
  820. else {
  821. $value = NestedArray::getValue($data, $parts, $key_exists);
  822. $value = $key_exists ? $value : NULL;
  823. }
  824. $returns[$config_name . ':' . $key] = $value;
  825. if ($value === NULL) {
  826. return drush_set_error('DRUSH_CONFIG_ERROR', dt('No matching key found in !name config.', array('!name' => $config_name)));
  827. }
  828. else {
  829. return $returns;
  830. }
  831. }
  832. /**
  833. * Print a table of config changes.
  834. *
  835. * @param array $config_changes
  836. * An array of changes keyed by collection.
  837. */
  838. function _drush_format_config_changes_table(array $config_changes, $use_color = FALSE) {
  839. if (!$use_color) {
  840. $red = "%s";
  841. $yellow = "%s";
  842. $green = "%s";
  843. }
  844. else {
  845. $red = "\033[31;40m\033[1m%s\033[0m";
  846. $yellow = "\033[1;33;40m\033[1m%s\033[0m";
  847. $green = "\033[1;32;40m\033[1m%s\033[0m";
  848. }
  849. $rows = array();
  850. $rows[] = array('Collection', 'Config', 'Operation');
  851. foreach ($config_changes as $collection => $changes) {
  852. foreach ($changes as $change => $configs) {
  853. switch ($change) {
  854. case 'delete':
  855. $colour = $red;
  856. break;
  857. case 'update':
  858. $colour = $yellow;
  859. break;
  860. case 'create':
  861. $colour = $green;
  862. break;
  863. default:
  864. $colour = "%s";
  865. break;
  866. }
  867. foreach($configs as $config) {
  868. $rows[] = array(
  869. $collection,
  870. $config,
  871. sprintf($colour, $change)
  872. );
  873. }
  874. }
  875. }
  876. $tbl = _drush_format_table($rows);
  877. return $tbl;
  878. }
  879. /**
  880. * Print a table of config changes.
  881. *
  882. * @param array $config_changes
  883. * An array of changes keyed by collection.
  884. */
  885. function _drush_print_config_changes_table(array $config_changes) {
  886. $tbl = _drush_format_config_changes_table($config_changes, !drush_get_context('DRUSH_NOCOLOR'));
  887. $output = $tbl->getTable();
  888. if (!stristr(PHP_OS, 'WIN')) {
  889. $output = str_replace("\r\n", PHP_EOL, $output);
  890. }
  891. drush_print(rtrim($output));
  892. return $tbl;
  893. }
  894. /**
  895. * Command argument complete callback.
  896. */
  897. function config_config_get_complete() {
  898. return _drush_config_names_complete();
  899. }
  900. /**
  901. * Command argument complete callback.
  902. */
  903. function config_config_set_complete() {
  904. return _drush_config_names_complete();
  905. }
  906. /**
  907. * Command argument complete callback.
  908. */
  909. function config_config_view_complete() {
  910. return _drush_config_names_complete();
  911. }
  912. /**
  913. * Command argument complete callback.
  914. */
  915. function config_config_edit_complete() {
  916. return _drush_config_names_complete();
  917. }
  918. /**
  919. * Command argument complete callback.
  920. */
  921. function config_config_import_complete() {
  922. return _drush_config_directories_complete();
  923. }
  924. /**
  925. * Command argument complete callback.
  926. */
  927. function config_config_export_complete() {
  928. return _drush_config_directories_complete();
  929. }
  930. /**
  931. * Command argument complete callback.
  932. */
  933. function config_config_pull_complete() {
  934. return array('values' => array_keys(_drush_sitealias_all_list()));
  935. }
  936. /**
  937. * Helper function for command argument complete callback.
  938. *
  939. * @return
  940. * Array of available config directories.
  941. */
  942. function _drush_config_directories_complete() {
  943. drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION);
  944. global $config_directories;
  945. return array('values' => array_keys($config_directories));
  946. }
  947. /**
  948. * Helper function for command argument complete callback.
  949. *
  950. * @return
  951. * Array of available config names.
  952. */
  953. function _drush_config_names_complete() {
  954. drush_bootstrap_max();
  955. return array('values' => $storage = \Drupal::service('config.storage')->listAll());
  956. }