A recipe should install new modules in the same way/result as modules installed via the UI or CLI.

Created on 5 October 2024, 3 months ago

Problem/Motivation

As a module maintainer/creator, I expect a recipe to install a module in
the same way as it would be installed via the UI or CLI.

Expectations include...

  • A module's install hook should be triggered without the $is_sync flag.
  • Any config entities in config/rewrite should be created as expected and available to module's install hook.

Thoughts

If a recipe does not use `config.import.module: '*'`, a module could be updated to include a new config entity, but it would not be installed via the recipe. This issue is noted via ✨ Exclude rather than include configuration Closed: won't fix

Steps to reproduce

Below are my steps to test the attached example module and the nuances that occur when a recipe installs the same module.

# Clean install, enable the module, 
# and confirm that the module's weight is set 99999.
ddev drush -y site:install;
ddev drush en -y recipe_install_example;
ddev drush config:get core.extension module.recipe_install_example;

# Clean install, apply the recipe, 
# and confirm that the module's weightis NOT set 99999.
ddev drush -y site:install;
ddev drush recipe modules/sandbox/recipe_install_example;
ddev drush config:get core.extension module.recipe_install_example;

Below is the install hook from the attached example.module.

function recipe_install_example_install(bool $is_syncing): void {
  // Set initial module weight but don't set it when config is syncing because
  // someone might want to change the module weight post module installation
  // but pre-deployment (and config sync) to production.
  if (!$is_syncing) {
    module_set_weight('recipe_install_example', 99999);
  }

  // Load example node type and confirm it exists.
  $node_type = \Drupal\node\Entity\NodeType::load('example');
  if ($node_type) {
    \Drupal::logger('recipe_install_example')->info('Example node type exists');
  }
  else {
    // NOTE: Recipes current DO NOT display log message via Drush.
    \Drupal::logger('recipe_install_example')->error('Example node type does not exist.');
  }
}

Proposed resolution

Adjust how recipes install modules or document how recipes install modules differently than the UI and CLI.

Remaining tasks

TBD

User interface changes

TBD

API changes

Any tweaks to a recipe's module installation would probably have to start
via \Drupal\Core\Recipe\RecipeRunner::installModule

Data model changes

TBD

πŸ› Bug report
Status

Active

Version

10.3

Component

Code

Created by

πŸ‡ΊπŸ‡ΈUnited States jrockowitz Brooklyn, NY

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

  • Issue created by @jrockowitz
  • πŸ‡ΊπŸ‡ΈUnited States jrockowitz Brooklyn, NY

    I'm opting to put my praise in the first comment. This is the only major issue I could find with recipes, and I am experimenting with some complex recipes and config actions. Understanding the public API took very little effort, and the internals of the Recipe and ConfigAction code are pristine. Outstanding and inspiring work!!!

    I'm switching this issue's priority to 'major' because this could cause some unexpected issues/challenges as more contrib modules are included in recipes. At the same time, if people know about this problem and it is an accepted nuance, feel free to change it back to 'Normal.'

  • πŸ‡ΊπŸ‡ΈUnited States jrockowitz Brooklyn, NY
  • πŸ‡ΊπŸ‡ΈUnited States jrockowitz Brooklyn, NY
  • πŸ‡ΊπŸ‡ΈUnited States jrockowitz Brooklyn, NY

    I tried my best to find similar tickets before I created this ticket. Now, I am finding a few more related tickets to this ticket.

    πŸ“Œ Document responsibility for required extension-provided configuration Needs review provides background on how recipes install modules and configuration.

  • πŸ‡ΊπŸ‡ΈUnited States jrockowitz Brooklyn, NY

    I have worked with recipes and the install behavior for the past few days. A possible compromise could be to allow a recipe to declare modules as 'dependencies' and modules that will be 'installed' (and overridden).

    # The modules below must be enabled before the recipe is applied.
    dependencies:
      - field
      - node
      - taxonomy
      - views
    
    # The modules below will be installed and overridden as a recipe is applied.
    install:
      - pathauto
    

    I know I am weighing in on something that has been thought about and debated for a . I hope this feedback helps.

  • πŸ‡¬πŸ‡§United Kingdom alexpott πŸ‡ͺπŸ‡ΊπŸŒ

    Thanks for filing the issue and thanks for comment #2.

    WRT to the issue summary - setting module weight should never be inside a config sync check - the use-case for someone else setting module weight and having it different in different environments is a recipe for production only bugs.

    The way recipes install modules is different and yes we have had these discussion before. The way a recipe installs a module without it's configuration entities is I guess surprising but this is exactly what happens when a module is installed during a config sync so a module should support it. The ability to not install a module's provided configuration entities is key to recipe functionality and therefore the decisions made that underpin this are unlikely to be changed.

    I guess the issue here is that this surprised you and therefore this points to the fact that we should make this clearer, perhaps in the recipe author guide - https://git.drupalcode.org/project/distributions_recipes/-/blob/1.0.x/do....

  • πŸ‡ΊπŸ‡ΈUnited States jrockowitz Brooklyn, NY

    I appreciate your explanation because I did not fully recognize how a module is installed via config sync and UI as two different situations a module maintainer needs to account for.

    As a recipe author, I would want some modules installed as expected; for others, I want full control over how a module is installed.

    For example, I want to know if the module (like paragraphs) is installed AS-IS so that I can create a new paragraph type. I don't want to worry about the default configuration entities related to the paragraph.

    The ability to not install a module's provided configuration entities is key to recipe functionality and therefore the decisions made that underpin this are unlikely to be changed.

    We should not change the current 'config sync install' workflow; I am suggesting offering the other 'default install' workflow.

  • πŸ‡ΊπŸ‡ΈUnited States thejimbirch Cape Cod, Massachusetts

    If a recipe does not use `config.import.module: '*'`, a module could be updated to include a new config entity, but it would not be installed via the recipe. This issue is noted via #3311155: Exclude rather than include configuration

    Just because a module is updated to include a new config entity doesn't mean the recipe author absolutely wants to include it if they did not use the * wildcard to import the config. It would be up to the recipe maintainer to decide to include it or not. Recipes are very declarative with that respect.

    WRT the example module attached.

    For clarity, I just want to point out that modules should not contain recipes. Recipes should be in their own packages and kept along side other recipes. We suggest in a recipes folder above webroot.

  • πŸ‡ΊπŸ‡ΈUnited States jrockowitz Brooklyn, NY

    Just because a module is updated to include a new config entity doesn't mean the recipe author absolutely wants to include it if they did not use the * wildcard to import the config.

    I agree; yet, as a recipe author, for some modules, I want to know if the module is already installed or have it installed, and do not care about the installation configuration details. For other modules, I want to override and tweak the default installation configuration details.

    πŸ› Error when installing a recipe that has configuration files already in the system, even if there is no difference Postponed: needs info feels like a related issue but is only addressing identical configuration files.

    I will do my best to come up with other examples.

  • πŸ‡ΊπŸ‡ΈUnited States thejimbirch Cape Cod, Massachusetts

    For other modules, I want to override and tweak the default installation configuration details.

    That is what config actions are for.

    πŸ› Recipes' imported config is validated too strictly by default Active was merged this morning. This will help out a lot making recipe application more lenient.

    This issue above will help close a lot of issues, but you may find more discussions and similarities on our Roadmap, #3446089: [Meta] Recipes Phase 2 Roadmap β†’

  • πŸ‡ΊπŸ‡ΈUnited States thejimbirch Cape Cod, Massachusetts
  • πŸ‡ΊπŸ‡ΈUnited States jrockowitz Brooklyn, NY

    @thejimbirch I switched to 10.4, and πŸ› Recipes' imported config is validated too strictly by default Active solved most of my issues. Thank you.

    I am still working through the issue in #6 where some modules are not installing as expected because a recipe installs a module via a 'config sync workflow" and not the UI/Drush 'install workflow'.

    I will post more information as I work through the problem.

  • πŸ‡ΊπŸ‡ΈUnited States thejimbirch Cape Cod, Massachusetts

    Great to hear!

  • πŸ‡¬πŸ‡§United Kingdom lstirk

    We are also facing an issue with install hooks not running due to the $is_sync flag. We are wanting to create a recipe for the domain modules. The domain module install hook adds field to all content entity bundles. I don't think we can save that config into the recipe as each site may have different content types. Is there a way around this?

  • πŸ‡ΊπŸ‡ΈUnited States jrockowitz Brooklyn, NY

    I am currently experimenting with a not-great workaround: I am using a config action to trigger the install hook without $is_sync=TRUE.

    I am sharing the workaround to help illustrate the problem; I do not feel this is a good solution.

    Here is the SchemaDotOrgExecuteInstallHook config action and here is an example of it being used to trigger schemadotorg_taxonomy_install(FALSE);.

    install:
      - schemadotorg_taxonomy
    config:
      strict: false
      import:
        schemadotorg_taxonomy: '*'
      actions:
        core.extension:
          executeInstallHook:
            - schemadotorg_taxonomy
    

    @see https://git.drupalcode.org/sandbox/jrockowitz-3479651/-/blob/1.0.x/schem...

  • πŸ‡ΊπŸ‡ΈUnited States thejimbirch Cape Cod, Massachusetts

    The domain module install hook adds field to all content entity bundles. I don't think we can save that config into the recipe as each site may have different content types. Is there a way around this?

    First, create or import the field storage config(s)

    Next, use the addToAllBundles config action to add that field storage to all the content types essentially creating the field instances.

    Lastly, use a wildcard on the form/display configs core.entity_form_display.node.*.default: That will hit the default form on all content types for example. You can

    Here is how I did it in the SEO recipe in Drupal CMS.

      actions:
        # Add SEO fields to all content types that are installed.
        field.storage.node.field_seo_analysis:
          addToAllBundles:
            label: 'SEO Analysis'
            description: 'Pick the main keyword or phrase this page is about.'
        field.storage.node.field_seo_description:
          addToAllBundles:
            label: 'SEO Description'
            description: 'Write a description for search engines and social media sites.'
        field.storage.node.field_seo_image:
          addToAllBundles:
            label: 'SEO Image'
            description: 'Upload an image to be used when the page is shared on social media sites.'
        field.storage.node.field_seo_title:
          addToAllBundles:
            label: 'SEO Title'
            description: 'Use this field to overwrite the default HTML page title for SEO.'
        # Configure the fields on the edit forms.
        core.entity_form_display.node.*.default:
          # Configure field group.
          setThirdPartySettings:
            - module: field_group
              key: group_seo
              value:
                children:
                  - field_seo_title
                  - field_seo_description
                  - field_seo_image
                  - field_seo_analysis
                label: 'Search Engine Optimization (SEO) Information'
                region: content
                parent_name: ''
                weight: 50
                format_type: details
                format_settings:
                  classes: ''
                  show_empty_fields: false
                  id: group_seo
                  label_as_html: false
                  open: false
                  description: ''
                  required_fields: false
          setComponents:
            -
              name: field_seo_analysis
              options:
                type: yoast_seo_widget
                weight: 18
                region: content
                settings:
                  render_theme: olivero
                  render_view_mode: default
                  edit_title: 0
                  edit_description: 0
            -
              name: field_seo_description
              options:
                type: string_textarea
                weight: 16
                region: content
                settings:
                  rows: 5
                  placeholder: ''
            -
              name: field_seo_image
              options:
                type: media_library_widget
                weight: 17
                region: content
                settings:
                  media_types: {  }
            -
              name: field_seo_title
              options:
                type: string_textfield
                weight: 15
                region: content
                settings:
                  size: 60
                  placeholder: ''
    
    
  • πŸ‡¬πŸ‡§United Kingdom lstirk

    Thanks @thejimbirch that approach worked

Production build 0.71.5 2024