Hard failure after module install if composer is not found

Created on 16 February 2023, almost 2 years ago
Updated 3 March 2023, over 1 year ago

Problem/Motivation

Affects 2.7 and 3.x

If composer is not found then on modules install we get this error

The website encountered an unexpected error. Please try again later.

PhpTuf\ComposerStager\Domain\Exception\LogicException: The "composer" executable cannot be found. Make sure it's installed and in the $PATH. in PhpTuf\ComposerStager\Infrastructure\Service\Finder\ExecutableFinder->find() (line 40 of /Users/ted.bowman/sites/test-2-16-23/vendor/php-tuf/composer-stager/src/Infrastructure/Service/Finder/ExecutableFinder.php).
Drupal\package_manager\ExecutableFinder->find('composer') (Line: 36)
PhpTuf\ComposerStager\Infrastructure\Service\Precondition\ComposerIsAvailable->isFulfilled(Object, Object, NULL) (Line: 17)
PhpTuf\ComposerStager\Infrastructure\Service\Precondition\AbstractPrecondition->assertIsFulfilled(Object, Object) (Line: 73)
Drupal\package_manager\Validator\ComposerExecutableValidator->validateStagePreOperation(Object, 'Drupal\package_manager\Event\StatusCheckEvent', Object)
call_user_func(Array, Object, 'Drupal\package_manager\Event\StatusCheckEvent', Object) (Line: 111)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object) (Line: 45)
Drupal\automatic_updates\Validation\StatusChecker->runStatusCheck(Object, Object, 1) (Line: 72)
Drupal\automatic_updates\Validation\StatusChecker->run() (Line: 204)
automatic_updates_modules_installed(Array, )
call_user_func_array(Object, Array) (Line: 409)
Drupal\Core\Extension\ModuleHandler->Drupal\Core\Extension\{closure}(Object, 'automatic_updates') (Line: 388)
Drupal\Core\Extension\ModuleHandler->invokeAllWith('modules_installed', Object) (Line: 416)
Drupal\Core\Extension\ModuleHandler->invokeAll('modules_installed', Array) (Line: 373)
Drupal\Core\Extension\ModuleInstaller->install(Array, 1) (Line: 83)
Drupal\Core\ProxyClass\Extension\ModuleInstaller->install(Array) (Line: 502)
Drupal\system\Form\ModulesListForm->submitForm(Array, Object)
call_user_func_array(Array, Array) (Line: 114)
Drupal\Core\Form\FormSubmitter->executeSubmitHandlers(Array, Object) (Line: 52)
Drupal\Core\Form\FormSubmitter->doSubmitForm(Array, Object) (Line: 595)
Drupal\Core\Form\FormBuilder->processForm('system_modules', Array, Object) (Line: 323)
Drupal\Core\Form\FormBuilder->buildForm(Object, Object) (Line: 73)
Drupal\Core\Controller\FormController->getContentResult(Object, Object)
call_user_func_array(Array, Array) (Line: 123)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 124)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext(Array, Array) (Line: 97)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 163)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 74)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 106)
Drupal\page_cache\StackMiddleware\PageCache->pass(Object, 1, 1) (Line: 85)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\StackedHttpKernel->handle(Object, 1, 1) (Line: 681)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
require('/Users/ted.bowman/sites/test-2-16-23/web/index.php') (Line: 234)

and viewing the status report

PhpTuf\ComposerStager\Domain\Exception\LogicException: The "composer" executable cannot be found. Make sure it's installed and in the $PATH. in PhpTuf\ComposerStager\Infrastructure\Service\Finder\ExecutableFinder->find() (line 40 of /Users/ted.bowman/sites/test-2-16-23/vendor/php-tuf/composer-stager/src/Infrastructure/Service/Finder/ExecutableFinder.php).
Drupal\package_manager\ExecutableFinder->find('composer') (Line: 36)
PhpTuf\ComposerStager\Infrastructure\Service\Precondition\ComposerIsAvailable->isFulfilled(Object, Object, NULL) (Line: 17)
PhpTuf\ComposerStager\Infrastructure\Service\Precondition\AbstractPrecondition->assertIsFulfilled(Object, Object) (Line: 73)
Drupal\package_manager\Validator\ComposerExecutableValidator->validateStagePreOperation(Object, 'Drupal\package_manager\Event\StatusCheckEvent', Object)
call_user_func(Array, Object, 'Drupal\package_manager\Event\StatusCheckEvent', Object) (Line: 111)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object) (Line: 45)
Drupal\automatic_updates\Validation\StatusChecker->runStatusCheck(Object, Object, 1) (Line: 72)
Drupal\automatic_updates\Validation\StatusChecker->run() (Line: 57)
Drupal\automatic_updates\Validation\StatusCheckRequirements->getRequirements() (Line: 29)
automatic_updates_requirements('runtime')
call_user_func_array(Object, Array) (Line: 409)
Drupal\Core\Extension\ModuleHandler->Drupal\Core\Extension\{closure}(Object, 'automatic_updates') (Line: 388)
Drupal\Core\Extension\ModuleHandler->invokeAllWith('requirements', Object) (Line: 416)
Drupal\Core\Extension\ModuleHandler->invokeAll('requirements', Array) (Line: 109)
Drupal\system\SystemManager->listRequirements() (Line: 49)
Drupal\system\Controller\SystemInfoController->status()
call_user_func_array(Array, Array) (Line: 123)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 580)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 124)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext(Array, Array) (Line: 97)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 163)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 74)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 106)
Drupal\page_cache\StackMiddleware\PageCache->pass(Object, 1, 1) (Line: 85)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\StackedHttpKernel->handle(Object, 1, 1) (Line: 681)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
require('/Users/ted.bowman/sites/test-2-16-23/web/index.php') (Line: 234)

both of these involving running the status checks and might because we are not catching the correct exception

I think the error might be coming from another validator other than ComposerExecutableValidator but there is cache in composer-stager that causes the wrong backtrace to show up. That is being addressed

This is probably actually a problem in \Drupal\package_manager\Validator\ComposerPluginsValidator::validateStagePreOperation because is only catching \PhpTuf\ComposerStager\Domain\Exception\RuntimeException not \PhpTuf\ComposerStager\Domain\Exception\LogicException also.

But it could just catch \Exception like \Drupal\package_manager\Validator\ComposerSettingsValidator::validateStagePreOperation is doing

Steps to reproduce

Proposed resolution

  1. Change \Drupal\package_manager\Validator\ComposerPluginsValidator::validateStagePreOperation

    in

    try {
          // @see https://getcomposer.org/doc/06-config.md#allow-plugins
          $value = Json::decode($this->inspector->getConfig('allow-plugins', $dir));
        }
        catch (RuntimeException $exception) {
          $event->addErrorFromThrowable($exception, $this->t('Unable to determine Composer <code>allow-plugins

    setting.'));
    return;
    }

    Change this to just catch `\Exception` like \Drupal\package_manager\Validator\ComposerSettingsValidator::validateStagePreOperation

  2. To be consistant also add a try catch to \Drupal\package_manager\Validator\ComposerMinimumStabilityValidator::validateMinimumStability where you call $this->inspector->getConfig('minimum-stability', $dir);
🐛 Bug report
Status

Needs review

Version

3.0

Component

Code

Created by

🇺🇸United States tedbow Ithaca, NY, USA

Live updates comments and jobs are added and updated live.
  • Needs tests

    The change is currently missing an automated test that fails when run with the original code, and succeeds when the bug has been fixed.

  • Needs manual testing

    The change/bugfix cannot be fully demonstrated by automated testing, and thus requires manual testing in a variety of environments.

Sign in to follow issues

Comments & Activities

  • Issue created by @tedbow
  • 🇺🇸United States tedbow Ithaca, NY, USA
  • Status changed to Postponed: needs info almost 2 years ago
  • 🇺🇸United States tedbow Ithaca, NY, USA

    The latest release for Composer stager fixes the problem where the backtrace for the error was incorrect https://github.com/php-tuf/composer-stager/releases/tag/v1.2.1

    Updating the summary here with solution.

    I think 📌 Create an API for base requirement validators which run before other validators and stop event propagation if they fail Fixed is tricky and we should not wait for that one but yes related

  • Status changed to Active almost 2 years ago
  • 🇺🇸United States tedbow Ithaca, NY, USA
  • 🇧🇪Belgium wim leers Ghent 🇧🇪🇪🇺

    Attempting to make the title understandable.

  • Assigned to kunal.sachdev
  • @kunalsachdev opened merge request.
  • Issue was unassigned.
  • Status changed to Needs review almost 2 years ago
  • 🇮🇳India kunal.sachdev

    I have added the proposed resolution but can't test it as I am unable to reproduce the scenario locally. Also I think we should have a test for this scenario.

  • Status changed to Needs work almost 2 years ago
  • 🇧🇪Belgium wim leers Ghent 🇧🇪🇪🇺

    At minimum, I think you can simulate this by providing a stub implementation of \PhpTuf\ComposerStager\Infrastructure\Service\Finder\ExecutableFinderInterface which throws the same exception as was reported above. Hell, you could even swap out the Drupal\package_manager\ExecutableFinder service with yours, and then you can easily test it?

    Also, the changes you made are in ComposerMinimumStabilityValidator and ComposerPluginsValidator. Why? The stack trace clearly shows that it's ComposerExecutableValidator that's triggering this exception (by calling \Drupal\package_manager\ExecutableFinder), so I do not understand why we'd change any other validator?

  • Assigned to kunal.sachdev
  • 🇧🇪Belgium wim leers Ghent 🇧🇪🇪🇺
  • 🇮🇳India kunal.sachdev

    I just implemented the proposed resolution which requires changes in ComposerMinimumStabilityValidator and ComposerPluginsValidator. And also in summary we have I think the error might be coming from another validator other than ComposerExecutableValidator but there is cache in composer-stager that causes the wrong backtrace to show up.... so that's may be the reason why we need changes in ComposerMinimumStabilityValidator and ComposerPluginsValidator.

  • 🇮🇳India kunal.sachdev

    So, I tried to test it locally and I get hard failure

    The website encountered an unexpected error. Please try again later.
    
    PhpTuf\ComposerStager\Domain\Exception\LogicException: The "composer" executable cannot be found. Make sure it's installed and in the $PATH. in Drupal\package_manager\ExecutableFinder->find() (line 54 of modules/contrib/automatic_updates/package_manager/src/ExecutableFinder.php).
    PhpTuf\ComposerStager\Infrastructure\Service\ProcessRunner\AbstractRunner->findExecutable() (Line: 51)
    PhpTuf\ComposerStager\Infrastructure\Service\ProcessRunner\AbstractRunner->run(Array, Object) (Line: 87)
    Drupal\package_manager\ComposerInspector->getConfig('allow-plugins', '/Users/kunal.sachdev/www/ten_zero') (Line: 177)
    Drupal\package_manager\Validator\ComposerPluginsValidator->validateStagePreOperation(Object, 'Drupal\package_manager\Event\StatusCheckEvent', Object)
    call_user_func(Array, Object, 'Drupal\package_manager\Event\StatusCheckEvent', Object) (Line: 111)
    Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object) (Line: 49)
    Drupal\automatic_updates\Form\UpdateFormBase->runStatusCheck(Object, Object, 1) (Line: 204)
    Drupal\automatic_updates\Form\UpdaterForm->buildForm(Array, Object)
    call_user_func_array(Array, Array) (Line: 534)
    Drupal\Core\Form\FormBuilder->retrieveForm('automatic_updates_updater_form', Object) (Line: 281)
    Drupal\Core\Form\FormBuilder->buildForm(Object, Object) (Line: 73)
    Drupal\Core\Controller\FormController->getContentResult(Object, Object)
    call_user_func_array(Array, Array) (Line: 123)
    Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 580)
    Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 124)
    Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext(Array, Array) (Line: 97)
    Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 163)
    Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 74)
    Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
    Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
    Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 106)
    Drupal\page_cache\StackMiddleware\PageCache->pass(Object, 1, 1) (Line: 85)
    Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
    Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
    Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 51)
    Drupal\Core\StackMiddleware\StackedHttpKernel->handle(Object, 1, 1) (Line: 681)
    Drupal\Core\DrupalKernel->handle(Object) (Line: 19)

    and if I apply the fix we are doing here in ComposerPluginsValidator then I don't get a hard failure.

  • Issue was unassigned.
  • Status changed to Needs review almost 2 years ago
  • Assigned to kunal.sachdev
  • Status changed to Needs work almost 2 years ago
  • 🇧🇪Belgium wim leers Ghent 🇧🇪🇪🇺

    #12:

    I think the error might be coming from another validator other than ComposerExecutableValidator but there is cache in composer-stager that causes the wrong backtrace to show up..

    Aha!

    But that was fixed in https://github.com/php-tuf/composer-stager/releases/tag/v1.2.1! So rather than modifying other validators, we should just bump our version requirement 😊

    #13: Great that you were able to reproduce this! What version of php-tuf/composer-stager did you test that with? I bet (and hope!) that it was 1.2.0.

    Next step: update to 1.2.1 and verify that that fixed the problem.

  • 🇮🇳India kunal.sachdev

    So, I just checked and I am using 1.2.1 version of php-tuf/composer-stager and yes it fixes the cache problem and the back trace which I get is correct now i.e

    The website encountered an unexpected error. Please try again later.
    
    PhpTuf\ComposerStager\Domain\Exception\LogicException: The "composer" executable cannot be found. Make sure it's installed and in the $PATH. in Drupal\package_manager\ExecutableFinder->find() (line 54 of modules/contrib/automatic_updates/package_manager/src/ExecutableFinder.php).
    PhpTuf\ComposerStager\Infrastructure\Service\ProcessRunner\AbstractRunner->findExecutable() (Line: 51)
    PhpTuf\ComposerStager\Infrastructure\Service\ProcessRunner\AbstractRunner->run(Array, Object) (Line: 87)
    Drupal\package_manager\ComposerInspector->getConfig('allow-plugins', '/Users/kunal.sachdev/www/ten_zero') (Line: 177)
    Drupal\package_manager\Validator\ComposerPluginsValidator->validateStagePreOperation(Object, 'Drupal\package_manager\Event\StatusCheckEvent', Object)
    call_user_func(Array, Object, 'Drupal\package_manager\Event\StatusCheckEvent', Object) (Line: 111)
    Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object) (Line: 49)
    Drupal\automatic_updates\Form\UpdateFormBase->runStatusCheck(Object, Object, 1) (Line: 204)
    Drupal\automatic_updates\Form\UpdaterForm->buildForm(Array, Object)
    call_user_func_array(Array, Array) (Line: 534)
    Drupal\Core\Form\FormBuilder->retrieveForm('automatic_updates_updater_form', Object) (Line: 281)
    Drupal\Core\Form\FormBuilder->buildForm(Object, Object) (Line: 73)
    Drupal\Core\Controller\FormController->getContentResult(Object, Object)
    call_user_func_array(Array, Array) (Line: 123)
    Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 580)
    Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 124)
    Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext(Array, Array) (Line: 97)
    Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 163)
    Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 74)
    Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
    Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
    Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 106)
    Drupal\page_cache\StackMiddleware\PageCache->pass(Object, 1, 1) (Line: 85)
    Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
    Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
    Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 51)
    Drupal\Core\StackMiddleware\StackedHttpKernel->handle(Object, 1, 1) (Line: 681)
    Drupal\Core\DrupalKernel->handle(Object) (Line: 19)

    but we still need changes in ComposerPluginsValidator which solves this problem of hard failure.

  • Assigned to wim leers
  • Status changed to Needs review almost 2 years ago
  • Issue was unassigned.
  • Status changed to Postponed almost 2 years ago
  • 🇧🇪Belgium wim leers Ghent 🇧🇪🇪🇺

    Discussed in detail with @kunal.sachdev.

    The stack trace still clearly shows that ComposerPluginsValidator is triggering the LogicException. That is the problem!

    We need to make sure that ComposerExecutableValidator runs before all other validators that interact with composer (through ComposerInspector). That means we need to update \Drupal\package_manager\Validator\ComposerExecutableValidator::getSubscribedEvents() to make these callbacks have the highest possible priority and stop propagation!

    (Otherwise every single other validator would have to worry about this.)

    Ironically, that means this issue is now hard-blocked on 📌 Create an API for base requirement validators which run before other validators and stop event propagation if they fail Fixed — thanks @kunal.sachdev for pointing that out!

  • 🇧🇪Belgium wim leers Ghent 🇧🇪🇪🇺

    Discussed with @tedbow. Conclusion: do manual testing after 📌 Create an API for base requirement validators which run before other validators and stop event propagation if they fail Fixed is committed 👍

  • Assigned to kunal.sachdev
  • Status changed to Active over 1 year ago
  • 🇧🇪Belgium wim leers Ghent 🇧🇪🇪🇺

    Haha you beat me to it, @kunal.sachdev! Looking forward to reading your findings!

  • 🇮🇳India kunal.sachdev

    I tested it again with Automatic Updates 3.0.x and the module was installed successfully on the site with no hard failure. Then, I checked the status report page which is

  • Issue was unassigned.
  • Status changed to Needs review over 1 year ago
Production build 0.71.5 2024