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