drush-ops / drush

Drush is a command-line shell and scripting interface for Drupal, a veritable Swiss Army knife designed to make life easier for those who spend their working hours hacking away at the command prompt.
https://www.drush.org
2.34k stars 1.08k forks source link

Config import single command #5146

Closed malcomio closed 1 year ago

malcomio commented 2 years ago

As per https://drupal.stackexchange.com/questions/221592/import-a-single-yml-configuration-file, there is not currently a way to import a single configuration file via drush, equivalent to the config:import:single command provided by Drupal Console.

It would be good to have either a flag for the config:import command, or a separate command.

As far as I'm aware, the other options are:

Both of those are time-consuming.

fede-green commented 2 years ago

This would be so nice to have!

JPustkuchen commented 2 years ago

Just ran into a situation where this would have been very helpful!

In my case something like: drush cim --single cookies.cookies_service.etracker cookies.cookies_service.google_analytics would have been very helpful.

Single should not mean here, that it must be just a single config, but can also be a list of config names, but ONLY these should be imported.

So perhaps there's a better standard name typically used for such things? Perhaps other linux tools give an idea? sftp file transfer by file name or others?

@weitzman would you prefer to use "cim" for this (I'd do) or a separate one like "cims"...

Edit: what about for example: drush cim --only cookies.cookies_service.etracker cookies.cookies_service.google_analytics

weitzman commented 2 years ago

copy the file(s) into a separate directory and use the --partial flag

Thats the recommended way.

Both of those are time-consuming.

Its one cp. I would not characterize it as "time consuming".

ctrladel commented 2 years ago

I also landed here looking for a way to import a single file and think this should be reopened.

To say that a single cp is sufficient because it's not "time consuming" assumes a lot about what is trying to be done and where the command is trying to be run. When trying to run a command against a remote host finding a writeable directory and making a temp directory is quite time consuming. For my current situation I'm trying to correct a tricky order of operations config import bug on Acquia so to do "one cp" I have to

It's a non trivial amount of work for something that feels like it should already be supported by the --partial flag.

Lancelight commented 2 years ago

Agree with @ctrladel. The answer given by @weitzman is very assume'ish and also seems to include an uneducated opinion at the tail end stating that it's "just a cp". Assuming that it's "just a cp" is pretty ridiculous when you toss in 21st century hosting methods, git repositories (pull requests and the like), etc. Not every site is hosted on your own machine. Making multiple copies of something just to limp around lack of a required feature can be quite an involved process depending upon the hosting situation.

The correct fix is to actually implement a single config import.

IE: Drupal console has this in the form of config:import:single (alias of cis), drush is lagging behind in a lot of areas compared to drupal console: https://drupalconsole.com/docs/vn/commands/config-import-single

weitzman commented 1 year ago

PRs welcome for this.

weitzman commented 1 year ago

Until then config:set can replace a whole config object https://www.drush.org/latest/commands/config_set/. Satisfies some use cases.

Lancelight commented 1 year ago

As far as I am aware, config:set doesnt work with files or does it? I am pretty certain config:set is just a command line only command which has nothing to do with the contents of a config yml file. Am I missing something? Maybe there is some sort of way to get it to load from file, idk. Would be welcome news to me :D Perhaps this might work? drush config:set --input-format=yaml < some_filename.yml I have no idea if that would even work or not.

EDIT: Nope, that didnt work either :/ It still asks for a key argument.

gitressa commented 1 year ago

The --partial flag does work, but I had trouble finding the right command, until I found How to import a specific config files map with drush. It is not clear that you need to supply both --partial and --source:

$ drush config:import -h
Import config from a config directory.

Options:
 --source=SOURCE An arbitrary directory that holds the configuration files.                                        
 --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). No config transformation happens. 

So the full command which works for me in Lando is:

drush config:import --partial --source=/app/config

With this structure:

drupal10/
├── composer.json
├── composer.lock
├── config
├── vendor
└── web
weitzman commented 1 year ago

I added the example from @gitressa to config:import.

As for config:import for a single file without creating a directory, I just added documentation to both config:import and config:set that shows how to do this.

Both these docs improvements are in #5543

weitzman commented 1 year ago

The docs in #3343 should work in earlier versions of Drush as well as Drush 12.

gitressa commented 1 year ago

Thanks for maintaining Drush @weitzman and @greg-1-anderson, I appreciate it very much. It's an incredible tool.

Paulmicha commented 1 year ago

If anyone else needed a port of drupal console config:import:single in drush :

<?php

namespace Drupal\drush_cis\Drush\Commands;

use Consolidation\OutputFormatters\StructuredData\RowsOfFields;
use Drush\Attributes as CLI;
use Drush\Commands\DrushCommands;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\config\StorageReplaceDataWrapper;
use Drupal\Core\Config\CachedStorage;
use Drupal\Core\Config\ConfigImporter;
use Drupal\Core\Config\ConfigImporterException;
use Drupal\Core\Config\ConfigManager;
use Drupal\Core\Config\StorageComparer;
use Symfony\Component\Yaml\Parser;
use Webmozart\PathUtil\Path;

/**
 * A Drush commandfile.
 *
 * In addition to this file, you need a drush.services.yml
 * in root of your module, and a composer.json file that provides the name
 * of the services file to use.
 */
final class DrushCisCommands extends DrushCommands {

  /**
   * Imports given config file(s).
   */
  #[CLI\Command(name: 'drush_cis:config-import-single', aliases: ['cis'])]
  #[CLI\Option(name: 'file', description: 'Repeatable. Specify 1 or more paths to files as needed.')]
  #[CLI\Usage(name: 'drush cis --file=path/to/config.file.yml', description: 'Imports given config file.')]
  public function configImportSingle($options = ['file' => ['default']]) {
    $configStorage = \Drupal::service('config.storage');
    $sourceStorage = new StorageReplaceDataWrapper($configStorage);
    $names = [];

    foreach ($options['file'] as $configFile) {
      if (!file_exists($configFile)) {
        $this->logger()->error(dt('Error : config file does not exist') . " : '$configFile'");
        return 1;
      }
      $name = Path::getFilenameWithoutExtension($configFile);
      $ymlFile = new Parser();
      $value = $ymlFile->parse(file_get_contents($configFile));
      $sourceStorage->replaceData($name, $value);
      $names[] = $name;
    }

    $storageComparer = new StorageComparer(
      $sourceStorage,
      $configStorage,
      \Drupal::service('config.manager')
    );

    $configImporter = new ConfigImporter(
      $storageComparer,
      \Drupal::service('event_dispatcher'),
      \Drupal::service('config.manager'),
      \Drupal::lock(),
      \Drupal::service('config.typed'),
      \Drupal::moduleHandler(),
      \Drupal::service('module_installer'),
      \Drupal::service('theme_handler'),
      \Drupal::service('string_translation'),
      \Drupal::service('extension.list.module')
    );

    if ($configImporter->alreadyImporting()) {
      $this->logger()->warning(dt('Already importing.'));
      return 0;
    }

    try {
      if ($configImporter->validate()) {
        $sync_steps = $configImporter->initialize();
        foreach ($sync_steps as $step) {
          $context = [];
          do {
            $configImporter->doSyncStep($step, $context);
          }
          while ($context['finished'] < 1);
        }
      }
    }
    catch (ConfigImporterException $e) {
      $feedback = "Error: unable to import specified config file(s)."
        . PHP_EOL
        . strip_tags(implode(PHP_EOL, $configImporter->getErrors()))
        . PHP_EOL;
      $this->logger()->error($feedback);
      return 2;
    }
    catch (\Exception $e) {
      $this->logger()->error($e->getMessage());
      return 3;
    }

    $this->logger()->success(
      dt('Config file(s) successfully imported. Config names imported :')
      . " "
      . join(', ', $names)
    );
  }

}
loopy3025 commented 12 months ago

Just thought I'd throw a friendly note that, if you're running this with --source, you're likely going to have to specify a level down to your config folder because drush is technically executing from docroot.

fin drush config-import --partial --source="../config/migrate/"