Problem/Motivation

As part of phase 2 of #2940731: Automatic Updates Initiative overview and roadmap we need Readiness Checks for automatic updates.

These checks would ensure that a site is ready to have automatic updates applied.

The Automatic Updates contrib module has a readiness check system built on tagged services.

These Readiness checks are particular to the process of Automatic Updates in the contrib module and should also work for the proposed Composer based updates for Drupal core.

Because Drupal core will introduce the Automatic Updates in an experimental module and Readiness Checks will be checking for this yet to be implemented functionality it does not make sense to add the Readiness Checks directly to the existing Update module. For more info on this see this comment on the roadmap issue.

Proposed resolution

Add the Readiness check infrastructure and 1 Readiness Check service from the contrib module to a new experimental module.

Remaining tasks

  1. Improve UX when a checker has many, many messages. The contrib module has checkers that produce many messages, like a message for every file that has changed. We may want to only display summary message like "Your site has files changed which may affect auto-updates" on the report page and the admin pages. The have detailed page with the checkers organized with all there messages seperated.

    There also the problem where there is not a way to tell which messages come from which checkers

  2. Port initial functionality from the contrib module
  3. Determine what changes should be made from the contrib code
  4. Ensure that modules that have new Readiness Checkers trigger a recheck.
  5. Determine minimum space needed now that we will be using composer. Using 100 MB for now. Follow up #3166416: Determine how many megabytes of free disk space should required for automatic updates using Composer
  6. Determine if a larger minimum disk space value could ever cause random failures on Drupal CI. Right now this not happening with the contrib module but it only uses 10mb. If we change this to something like 200mb would this ever fail in DrupalCi and cause a test failure? Confirmed with @mixologic this won't be a problem
  7. Use Batch API for running checks for scaling. Do we actually need this? Currently all the contrib checks can take a little as 200 msec
  8. Ensure that \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManager::run() does not throw an exception if it is run for a category that has no checkers or ensure it does not run in this case. Services can be altered so we don't know for sure what the existing checkers will be or which category they will be in. No longer applies
  9. Determine if a single checker should be able provide both warnings and messages
  10. Change free space checker to factor in that root/vendor/tmp may all be on same are partition or all on different partition.
  11. Create a kernel test for ReadinessCheckerManager that test deleting the key/value results works as expected, refreshing, providing checker services dynamically
  12. Review

Questions

  1. What value should we use for \Drupal\automatic_updates\ReadinessChecker\DiskSpace::MINIMUM_DISK_SPACE?
    10mb was used for the contrib module but that was with patch workflow. Now that we will use composer it will probably be different.

    Using 1 GB follow to confirm #3166416: Determine how many megabytes of free disk space should required for automatic updates using Composer
  2. Currently each "checker" tagged service has a "category" tag. This means they can only have messages in 1 category. Should they instead be able to return message in either category?

    For instance the DiskSpace check might return an "error" message "Your disk space is too low" and a "warning" message "Your free disk space is nearing the limit for automatic updates".

Fixes/Questions for the contrib module

  1. the way the FileSystemBase is written if produces and error all checkers that extend it will return the same messages.

User interface changes

There will be message on the Status Report page that tells you if your site is ready for Automatic Updates

API changes

TBD

Data model changes

None

Release notes snippet

TBD

CommentFileSizeAuthor
#68 3162655-68.patch45.14 KBtedbow
#68 interdiff-66-68.txt23.6 KBtedbow
#66 3162655-66.patch45.97 KBtedbow
#66 interdiff-61-66.txt15.17 KBtedbow
#61 3162655-61.patch50 KBtedbow
#61 interdiff-59-61.txt13.57 KBtedbow
#59 3162655-59.patch47.73 KBtedbow
#59 interdiff-57-59.txt1.6 KBtedbow
#57 3162655-57.patch47.44 KBtedbow
#57 interdiff-55-57.txt22.84 KBtedbow
#55 3162655-55.patch48.59 KBtedbow
#55 interdiff-50-55.txt24.06 KBtedbow
#50 3162655-50.patch48.68 KBtedbow
#50 interdiff-48-50.txt5.56 KBtedbow
#48 3162655-48.patch49.15 KBtedbow
#48 interdiff-46-48.txt9.89 KBtedbow
#46 3162655.45_46.interdiff.txt867 bytesdww
#46 3162655-46.patch48.91 KBdww
#45 3162655.44_45.actual-diff.txt940 bytesdww
#45 3162655.44_45.interdiff.txt3.13 KBdww
#45 3162655-45.patch48.91 KBdww
#44 3162655-44.patch49.73 KBtedbow
#44 interdiff-41-44.txt723 bytestedbow
#41 3162655-41.patch49.69 KBtedbow
#41 interdiff-40-41.txt12.17 KBtedbow
#40 interdiff-38_40.txt572 bytesressa
#40 3162655-40.patch48.83 KBressa
#38 3162655.33_38.interdiff.txt26.15 KBdww
#38 3162655-38.patch48.71 KBdww
#33 3162655-33.patch49.33 KBtedbow
#33 interdiff-31-33.txt13 KBtedbow
#31 interdiff-30-31.txt16.7 KBtedbow
#31 3162655-31.patch48.06 KBtedbow
#30 3162655-30.patch43.9 KBtedbow
#30 interdiff-29-30.txt18.31 KBtedbow
#29 3162655-29.patch43.76 KBtedbow
#29 interdiff-28-29.txt7.76 KBtedbow
#28 3162655-28.patch43.59 KBtedbow
#28 interdiff-26-28.txt17.91 KBtedbow
#26 3162655-26.patch36.52 KBtedbow
#26 interdiff-24-26.txt6.62 KBtedbow
#24 3162655-24.patch36.77 KBtedbow
#24 interdiff-22-24.txt2.71 KBtedbow
#22 3162655-22.patch35.26 KBtedbow
#22 interdiff-17-22.txt16.16 KBtedbow
#17 3162655-17.patch29.6 KBtedbow
#17 interdiff-16-17.txt28.7 KBtedbow
#16 3162655-16.patch30.25 KBtedbow
#16 interdiff-14-16.txt13.97 KBtedbow
#14 3162655-4.patch30.75 KBtedbow
#14 interdiff-2-4.txt12.39 KBtedbow
#2 3162655-2-contrib-copied.patch30.62 KBtedbow

Issue fork drupal-3162655

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

tedbow created an issue. See original summary.

tedbow’s picture

Status: Active » Needs review
FileSize
30.62 KB

Here is the initial port from the contrib module. I just copied over files needed and tried to remove all part not necessary for the Readiness Checks.

I have not given this a review. I will do that now.

Status: Needs review » Needs work

The last submitted patch, 2: 3162655-2-contrib-copied.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

tedbow credited bdragon.

tedbow credited ckrina.

tedbow credited eiriksm.

tedbow credited heddn.

tedbow credited mbaynton.

tedbow credited rkoller.

tedbow’s picture

Status: Needs work » Needs review
FileSize
12.39 KB
30.75 KB

Fixing the test and some nits.

Also attempting to credit everyone that was credited on the #3043521: [META] Update readiness checks for autoupdate (pre-flight check) and other issues as this code was copied from there.

johnwebdev’s picture

  1. +++ b/core/modules/automatic_updates/automatic_updates.install
    @@ -0,0 +1,75 @@
    +    return NULL;
    

    Should maybe return an empty array here?

  2. +++ b/core/modules/automatic_updates/automatic_updates.install
    @@ -0,0 +1,75 @@
    +  _automatic_updates_checker_requirements($requirements);
    ...
    +function _automatic_updates_checker_requirements(array &$requirements) {
    

    Why not have this directly in the hook_requirements?

  3. +++ b/core/modules/automatic_updates/automatic_updates.install
    @@ -0,0 +1,75 @@
    +    'value' => t('Your site is ready to for <a href="@readiness_checks">automatic updates</a>.', ['@readiness_checks' => 'https://www.drupal.org/docs/8/update/automatic-updates#readiness-checks']),
    

    /s/to for/for

  4. +++ b/core/modules/automatic_updates/automatic_updates.install
    @@ -0,0 +1,75 @@
    +  $error_results = $checker->getResults(ReadinessCheckerManagerInterface::ERROR);
    +  $warning_results = $checker->getResults(ReadinessCheckerManagerInterface::WARNING);
    

    Maybe ::getResults() could be extended to accept bitwise operators. getResults(ReadinessCheckerManagerInterface::ERROR | ReadinessCheckerManagerInterface::Warning).

  5. +++ b/core/modules/automatic_updates/automatic_updates.install
    @@ -0,0 +1,75 @@
    +    $requirements['automatic_updates_readiness']['severity'] = $error_results ? REQUIREMENT_ERROR : REQUIREMENT_WARNING;
    

    Aha. That makes the suggestion above harder, but that could be solved by returning getResults() returning a Result object instead of an array.

  6. +++ b/core/modules/automatic_updates/automatic_updates.install
    @@ -0,0 +1,75 @@
    +        '@link' => $readiness_check->toString(),
    ...
    +        '@link' => $readiness_check->toString(),
    

    @ should not be used within HTML attributes.

    https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Component%21Rend...

  7. +++ b/core/modules/automatic_updates/automatic_updates.module
    @@ -0,0 +1,58 @@
    +use Drupal\Core\Url;
    +/**
    

    Nit: Missing a blankspace between.

  8. +++ b/core/modules/automatic_updates/automatic_updates.module
    @@ -0,0 +1,58 @@
    +  if ($admin_context->isAdminRoute($route_match->getRouteObject()) && \Drupal::currentUser()->hasPermission('administer site configuration')) {
    

    Is this the right permission?

  9. +++ b/core/modules/automatic_updates/automatic_updates.module
    @@ -0,0 +1,58 @@
    +        '@link' => $readiness_settings->toString(),
    

    Should use ":" here.

  10. +++ b/core/modules/automatic_updates/config/install/automatic_updates.settings.yml
    @@ -0,0 +1 @@
    +enable_readiness_checks: true
    
    +++ b/core/modules/automatic_updates/config/schema/automatic_updates.schema.yml
    @@ -0,0 +1,7 @@
    +    enable_readiness_checks:
    

    When do you want to disable readiness checks?

  11. +++ b/core/modules/automatic_updates/src/Controller/ReadinessCheckerController.php
    @@ -0,0 +1,63 @@
    + * Class ReadinessCheckerController.
    

    Can be marked internal.

  12. +++ b/core/modules/automatic_updates/src/Controller/ReadinessCheckerController.php
    @@ -0,0 +1,63 @@
    +   * @return \Symfony\Component\HttpFoundation\RedirectResponse
    +   *   A redirect
    

    Maybe: A redirect to the automatic updates settings page.

  13. +++ b/core/modules/automatic_updates/src/Controller/ReadinessCheckerController.php
    @@ -0,0 +1,63 @@
    +    $messages = array_merge(...$messages);
    

    Maybe a comment here to explain that we're flattening the array?

  14. +++ b/core/modules/automatic_updates/src/Form/SettingsForm.php
    @@ -0,0 +1,130 @@
    +  /**
    

    Nit: Missing a blank space.

  15. +++ b/core/modules/automatic_updates/src/Form/SettingsForm.php
    @@ -0,0 +1,130 @@
    +    $form['readiness']['ignored_paths'] = [
    

    ignored_paths is not present in the schema.

  16. +++ b/core/modules/automatic_updates/src/Form/SettingsForm.php
    @@ -0,0 +1,130 @@
    +      '#description' => $this->t('Paths relative to %drupal_root. One path per line. Automatic Updates is intentionally limited to Drupal core. It is recommended to ignore paths to contrib extensions.', ['%drupal_root' => $this->drupalRoot]),
    

    "It is recommended to ignore paths to contrib extensions"... why?

  17. +++ b/core/modules/automatic_updates/src/ReadinessChecker/DiskSpace.php
    @@ -0,0 +1,67 @@
    +use Drupal\Component\FileSystem\FileSystem as FileSystemComponent;
    ...
    +class DiskSpace extends Filesystem {
    
    +++ b/core/modules/automatic_updates/src/ReadinessChecker/Filesystem.php
    @@ -0,0 +1,99 @@
    +abstract class Filesystem implements ReadinessCheckerInterface {
    

    Why does this checker extend the Filesystem? Couldn't it just get it as a dependency through the constructor?

    Oh... nevermind. Maybe we should suffix checker classes with "Checker"

  18. +++ b/core/modules/automatic_updates/src/ReadinessChecker/DiskSpace.php
    @@ -0,0 +1,67 @@
    +   * Minimum disk space (in bytes) is 10mb.
    

    What is this based on?

  19. +++ b/core/modules/automatic_updates/src/ReadinessChecker/ReadinessCheckerManager.php
    @@ -0,0 +1,137 @@
    +class ReadinessCheckerManager implements ReadinessCheckerManagerInterface {
    

    Do we really need the "Manager" suffix. I think "ReadinessChecker" is fine.

  20. +++ b/core/modules/automatic_updates/src/ReadinessChecker/ReadinessCheckerManager.php
    @@ -0,0 +1,137 @@
    +  public function addChecker(ReadinessCheckerInterface $checker, $category = 'warning', $priority = 0) {
    

    Wondering if a checker should be able to return messages for multiple categories instead? i.e. The DiskSpace checker could then have a warning "Your disk space is close to being too low, etc."

  21. +++ b/core/modules/automatic_updates/src/ReadinessChecker/ReadinessCheckerManagerInterface.php
    @@ -0,0 +1,85 @@
    +   *   (optional) The priority of the checker being added.
    

    Is "0" low priority, and a higher number is higher priority?

  22. +++ b/core/modules/automatic_updates/tests/src/Kernel/ReadinessChecker/DiskSpaceTest.php
    @@ -0,0 +1,70 @@
    +   * Override the default free disk space minimum to an insanely high number.
    

    Nit: Maybe this comment should be in the class doc comment instead.

tedbow’s picture

Issue summary: View changes
FileSize
13.97 KB
30.25 KB

@johnwebdev thanks for the review!

    administer site configuration
  1. This is because the contrib module had other types of requirements it was checking and had multiple function calls here. But the PSA functionality will likely go directly into the Update module so it will not be needed here. removing _automatic_updates_checker_requirements()
  2. fixed
  3. I could be missing something but these are strings would this work?
  4. Leaving for now. I think returning the strings is more understandable.
  5. Fixed and changed to ":url" because is used more in core than ":link". Also fixed other occurences.
  6. fixed
  7. Adding a todo in the patch to check on this. I want to see why this was chosen because it is used multiple places in the contrib module.
  8. fixed
  9. Eventually the module will have the ability to actually do the automatic updates. You may want to turn off this functionality but leave the Readiness checks on. But you may want to turn them too if you know there is issue with the site and don't want to see the message again and again.
  10. fixed
  11. fixed
  12. added a comment but moved the array_merge into the if since we don't need it again.
  13. fixed
  14. This is for another check in the contrib module that we will add later. Removing
  15. Removed but the initial support will on be for core
  16. I am inclined to not add "checker" because it is redundant with the namespace but I could be persuaded.
  17. Add this as a question in the summary. The contrib module used a different patch based system so we would probably need a higher number
  18. Well we already have ReadinessCheckerInterface which each check will implement. This manages all of those checkers
  19. Good question. I have put this as a question in the summary to get feedback from original contrib authors as to why it was architected this way.

    I could definitely see 1 checker has messages in both categories.

  20. Looks that way because of krsort($checkers);
    I expanded the comment.
  21. yep fixed.
tedbow’s picture

Assigned: tedbow » Unassigned
FileSize
28.7 KB
29.6 KB

This patch changes the machine name for the module from automatic_updates to auto_updates

This is prevent composer problems for sites that already have contrib module used as drupal/automatic_updates.

I chatted with @xjm, @gabesullice, and @Wim Leers about this because they were involved with adding jsonapi to core which also already had a contrib module with the same machine name. They recommended using a different machine name.

Status: Needs review » Needs work

The last submitted patch, 17: 3162655-17.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

tedbow’s picture

Assigned: Unassigned » tedbow

Fixing the test fails and working on functional tests.

I think one problem currently is

  1. You install automatic updates
  2. The updates are run on cron
  3. The status report says your site is ready
  4. you install another module that has a checker service that will not pass
  5. You reload the status report
  6. The message still says your sites is ready because nothing triggered the checks to run again.
heddn’s picture

Re #20: we could add cache tags and cache invalidation in a more traditional manner.

Re: MINIMUM_DISK_SPACE
We'll have to do some calculating. But anything less then 100MB seems valid to me. Empirical testing will help.

Re: checkers returning multiple categories
These checkers are really simple. I don't see any benefit to having a single hunk of code do 2 things. It follows the spirit of single-responsibility principle (SRP) much better that way.

heddn’s picture

  1. +++ b/core/modules/auto_updates/auto_updates.module
    @@ -0,0 +1,61 @@
    +  // @todo Is 'administer site configuration' the correct permission?
    +  //   get feedback from contrib module maintainers.
    

    This is the same permission we use in update.module. I'd suggest we keep the same and (if necessary) open a follow-up to see if we want to change that permission. Because if we change it here, we'd want to change it there too. But I'm not advocating we pick a new permission because an update to the site's code is fully in the wheelhouse of a site's administrator.

  2. +++ b/core/modules/auto_updates/auto_updates.routing.yml
    @@ -0,0 +1,18 @@
    +auto_updates.update_readiness:
    +  path: '/admin/config/auto_updates/readiness'
    +  defaults:
    +    _controller: '\Drupal\auto_updates\Controller\ReadinessCheckerController::run'
    

    I suggest we use the batch API for this instead of a controller callback. It would scale better. It was easier/faster to do it in a controller, but I wished we'd done batch.

  3. +++ b/core/modules/auto_updates/src/Form/SettingsForm.php
    @@ -0,0 +1,119 @@
    +    $instance->drupalRoot = (string) $container->get('app.root');
    +    $instance->updateManager = $container->get('update.manager');
    +    $instance->updateProcessor = $container->get('update.processor');
    

    I think a few of these are not needed until later. The variables aren't in use.

  4. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManager.php
    @@ -0,0 +1,138 @@
    +      // @todd Adding only 1 checker so will error for "warning".
    

    spelling of todo.

  5. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManager.php
    @@ -0,0 +1,138 @@
    +  protected function getSortedCheckers() {
    

    The amount of documentation for the sorting is on par with the rest of these types of things in core. However, adding a comment on how we weight things is probably a good idea so its more clear if/when folks start playing around with weights.

tedbow’s picture

Issue summary: View changes
Status: Needs work » Needs review
FileSize
16.16 KB
35.26 KB

@heddn thanks for the review!

re #20

  1. Added ensuring that newly installed modules with Readiness Checkers are handled correctly to the remaining tasks
  2. Added a remaining task and @todo in the patch to figure out minimum space number so we don't forget.

    I also added an item in the remaining tasks to determine a larger minimum value would ever cause random failures on Drupal Ci.

  3. These checkers are really simple. I don't see any benefit to having a single hunk of code do 2 things.

    I guess I am not sure if this is always 2 things. With the disk space example I could see it as 1 check for how much disk space is available. But whether it is "warning" or "error" depends on how much space is left. \Drupal\auto_updates\ReadinessChecker\DiskSpace::diskSpaceCheck() returns multiple messages. We want each of those messages to be a "warning" if there is > 200 && < 100mb left but an "error" if there is < 100 mb left. I could see this case because you want to warn site admin if it is getting close to <100mb but you don't want stop an update unless it is less than 100mb(which you need an "error" to do). Otherwise would have to do 2 Readiness checkers to achieve this and it actually becomes more complicated.

    Leaving for now for more opinions and thought. I am still undecided on this.

re #21

  1. I agree this is the right permission because this is the same permission that is on /admin/reports/status which allow you to see these same messages.

    But I'm not advocating we pick a new permission because an update to the site's code is fully in the wheelhouse of a site's administrator.

    Well at least to know about the readiness to updates. The system module already has the permission administer software updates which the current contrib module and the core Update module use for the actual updates.

  2. Added Batch API to remaining tasks
  3. fixed and expanded comment. Also added to Remaining tasks
  4. Added a @todo because this patch already has a bunch of other changes.

other changes since #17

  1. fixed existing test failures that were because I didn't update the file names to use "auto_updates" in the "config" directory
  2. '

  3. added work in progress from #3157833: ReadinessCheckerManager::timestamp() returns the time the site installed if checks have never been run. which @heddn and myself were working on for the contrib module
  4. +++ b/core/modules/auto_updates/auto_updates.services.yml
    @@ -0,0 +1,14 @@
    +  logger.channel.auto_updates:
    +    parent: logger.channel_base
    +    arguments: ['auto_updates']
    

    This service is not used in this patch.

  5. +++ b/core/modules/auto_updates/src/Form/SettingsForm.php
    @@ -0,0 +1,119 @@
    +    $instance->drupalRoot = (string) $container->get('app.root');
    +    $instance->updateManager = $container->get('update.manager');
    +    $instance->updateProcessor = $container->get('update.processor');
    

    We don't use these yet. Removing.

    Found this because new test failed on this because the module is dependent on the Update module yet.

  6. Added functional tests that currently fail because of #19

Status: Needs review » Needs work

The last submitted patch, 22: 3162655-22.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

tedbow’s picture

Status: Needs work » Needs review
FileSize
2.71 KB
36.77 KB
  1. When I first created the patch from the contrib module I forgot to bring over the hook_cron implementation. adding that and a test for it.
  2. The test failures in #22 were expected

Status: Needs review » Needs work

The last submitted patch, 24: 3162655-24.patch, failed testing. View results

tedbow’s picture

Status: Needs work » Needs review
FileSize
6.62 KB
36.52 KB
  1. +++ b/core/modules/auto_updates/auto_updates.module
    @@ -0,0 +1,59 @@
    +    $results = $checker->getResults('warning');
    

    This should use \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManagerInterface::WARNING

  2. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManager.php
    @@ -0,0 +1,138 @@
    +    $this->keyValue->set("readiness_check_results.$category", $messages);
    

    Back to #20 about the checkers being restricted to providing messages in just 1 category.

    Since \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManagerInterface::run() is called with a $category argument at least the API provides a way to run checkers for just 1 category. But we record a single timestamp. So if it was called only for "warning" then we would have no way of knowing this after the fact that readiness_check_timestamp was just when "warning" checkers were run.
    We could change \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManagerInterface::run()to not have an argument and just run both(all) categories. This way the readiness_check_timestamp would actually be the last time all the checkers were run.

    I have local patch for this but not posting because of the next point

  3. So I realize that maybe why \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManagerInterface::run() is called with a $category argument is because the in the contrib module only "error" category is run before an InPlaceUpdate.

    We could just run all checks and only not update if there are error messages but maybe the contrib module was made this way to avoid doing extra checks before an InPlaceUpdate?

  4. +++ b/core/modules/auto_updates/auto_updates.install
    @@ -0,0 +1,70 @@
    +    $requirements['auto_updates_readiness']['description'] = [
    +      '#theme' => 'item_list',
    +      '#items' => $checker_results,
    +    ];
    +  }
    +  if ($last_check_timestamp === NULL ||  \Drupal::time()->getRequestTime() > $last_check_timestamp + ReadinessCheckerManagerInterface::LAST_CHECKED_WARNING) {
    

    If we leave things the way they are in the contrib module with InPlaceUpdate only running "error" checks then if you come status report after a failed InPlaceUpdate then $last_check_timestamp could be very recent but the "warnings" checkers might not have been run in hours. So this would mean the checker results would be display with the "errors" being very recent results and the "warning" results being old.

    This makes me think we should always run all checkers. So I will add a patch for that.

Status: Needs review » Needs work

The last submitted patch, 26: 3162655-26.patch, failed testing. View results

tedbow’s picture

Assigned: tedbow » Unassigned
Status: Needs work » Needs review
FileSize
17.91 KB
43.59 KB
  1. +++ b/core/modules/auto_updates/auto_updates.module
    @@ -0,0 +1,78 @@
    +  $last_check = $state->get('auto_updates.cron_last_check', 0);
    +  // Only allow cron to run once every hour.
    +  if (($request_time - $last_check) < 3600) {
    +    return;
    +  }
    ...
    +  $state->set('auto_updates.cron_last_check', \Drupal::time()->getCurrentTime());
    

    I don't think we actually need auto_updates.cron_last_check

    Do we actually care when cron last ran the checks or just when they were last run? If a user just ran the checks and then cron runs 5 minutes after do we actually want to run them again.

    I think the intent here if the checks haven't been run in an hour run them. We can do that by relying on \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManager::timestamp().

  2. Implemented hook_install() to run readiness checks on module install. Is there any reason we shouldn't be doing this? All the checkers in the contrib module take 200 msecs for me to run
  3. Implemented hook_modules_installed() to run readiness checks again if the available checkers have changed.
tedbow’s picture

+++ b/core/modules/auto_updates/src/Controller/ReadinessCheckerController.php
@@ -0,0 +1,61 @@
+   * @var \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManagerInterface
+   */
+  protected $checker;

+++ b/core/modules/auto_updates/src/Form/SettingsForm.php
@@ -0,0 +1,97 @@
+   * @var \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManagerInterface
+   */
+  protected $checker;

+++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManager.php
@@ -0,0 +1,169 @@
+   * @var \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerInterface[][][]
+   */
+  protected $checkers = [];
...
+    foreach ($sorted_checkers as $category => $checkers) {
+      foreach ($checkers as $checker) {
+        if ($messages = $checker->run()) {

We are calling the manager a "checker" and but also a list of checkers "checkers". We should name this checkerManager to be clear.

tedbow’s picture

  1. +++ b/core/modules/auto_updates/auto_updates.install
    @@ -0,0 +1,79 @@
    +  $requirements['auto_updates_readiness'] = [
    +    'title' => t('Update readiness checks'),
    +    'severity' => REQUIREMENT_OK,
    +    'value' => t('Your site is ready to for <a href=":readiness_checks">automatic updates</a>.', [':readiness_checks' => 'https://www.drupal.org/docs/8/update/automatic-updates#readiness-checks']),
    +  ];
    +  $error_results = $checker_manager->getResults(ReadinessCheckerManagerInterface::ERROR);
    +  $warning_results = $checker_manager->getResults(ReadinessCheckerManagerInterface::WARNING);
    +  $checker_results = array_merge($error_results, $warning_results);
    +  if (!empty($checker_results)) {
    +    $requirements['auto_updates_readiness']['severity'] = $error_results ? REQUIREMENT_ERROR : REQUIREMENT_WARNING;
    +    $requirements['auto_updates_readiness']['value'] = new PluralTranslatableMarkup(count($checker_results), '@count check failed:', '@count checks failed:');
    +    $requirements['auto_updates_readiness']['description'] = [
    +      '#theme' => 'item_list',
    +      '#items' => $checker_results,
    +    ];
    +  }
    

    I refactored auto_updates_requirements() because writing the test it was confusing to see that actual behavior is.

    Longer than this snippet but with the current if/else logic $requirements['auto_updates_readiness']['description'] could be set and then re-set 2 more times.

    IMHO I think the refactor is more readable.

    I expanded the test coverage also and the tests pass before and after refactor.

  2. +++ b/core/modules/auto_updates/auto_updates.install
    @@ -0,0 +1,79 @@
    +      $requirements['auto_updates_readiness']['description'] = t('Readiness checks were last run @time ago.', ['@time' => $time_ago]);
    ...
    +        $requirements['auto_updates_readiness']['description'] = t('Last run @time ago. <a href="@link">Run readiness checks</a> manually.', [
    

    In 1 case we use "Readiness checks were last run @time ago" and another "Last run @time ago"

    I think these should be the same. Changed to the longer

  3. +++ b/core/modules/auto_updates/tests/src/Functional/ReadinessCheckerTest.php
    @@ -0,0 +1,183 @@
    +    $assert->pageTextContains('Your site is ready to for automatic updates.');
    +    $assert->pageTextNotContains('Run readiness checks manually');
    

    change this test to assert the complete text on the status report not what is there and not. It is much simpler.

tedbow’s picture

  1. +++ b/core/modules/auto_updates/auto_updates.services.yml
    @@ -0,0 +1,11 @@
    +  auto_updates.readiness_checker:
    

    This service should be suffixed with "_manager"

  2. +++ b/core/modules/auto_updates/src/Controller/ReadinessCheckerController.php
    @@ -0,0 +1,61 @@
    +    if (!array_filter($this->checkerManager->run())) {
    +      $this->messenger()->addStatus($this->t('No issues found. Your site is ready for <a href=":readiness_checks">automatic updates</a>.', [':readiness_checks' => 'https://www.drupal.org/docs/8/update/auto-updates#readiness-checks']));
    +    }
    

    Currently if the checkers have been disabled and user clicks an old link to run the checkers or if they enter the address in manually this message will be displayed.

    We should had a _custom_access check to the route so that it is only accessible if the checkers are enabled.

  3. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerInterface.php
    @@ -0,0 +1,18 @@
    + * Interface for objects capable of readiness checking.
    

    Changing to more common pattern I see in core "Defines an interface for * services."

  4. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManager.php
    @@ -0,0 +1,169 @@
    +  public function isEnabled() {
    +    return $this->configFactory->get('auto_updates.settings')->get('enable_readiness_checks');
    +  }
    

    Adding test coverage for disabling readiness checks.

    The item should not be status report and readiness checks should not run on cron.

  5. +++ b/core/modules/auto_updates/tests/src/Kernel/ReadinessChecker/DiskSpaceTest.php
    @@ -0,0 +1,68 @@
    +    $this->assertCount(2, $messages);
    ...
    +    $this->assertCount(3, $messages);
    

    We should also assert the actual message texts.

  6. Added extra test coverage and fixed comments in tests
heddn’s picture

Status: Needs review » Needs work

This is coming along very well. Just a few small nits and questions.

  1. +++ b/core/modules/auto_updates/auto_updates.install
    @@ -0,0 +1,80 @@
    +  elseif (\Drupal::time()->getRequestTime() > $last_check_timestamp + ReadinessCheckerManagerInterface::LAST_CHECKED_WARNING) {
    
    +++ b/core/modules/auto_updates/auto_updates.module
    @@ -0,0 +1,88 @@
    +    if (\Drupal::time()->getRequestTime() > $last_check_timestamp + ReadinessCheckerManagerInterface::LAST_CHECKED_WARNING) {
    

    Does it make sense to add a helper method that calculates this in a single place? Date math is always complicated and centralizing it would make it easier to test.

  2. +++ b/core/modules/auto_updates/auto_updates.module
    @@ -0,0 +1,88 @@
    +  if ($checker_manager->clearResultsStaleResults()) {
    

    Maybe call this clearStaleResults?

  3. +++ b/core/modules/auto_updates/auto_updates.routing.yml
    @@ -0,0 +1,19 @@
    +    _permission: 'administer software updates'
    +    _custom_access: '\Drupal\auto_updates\Controller\ReadinessCheckerController::access'
    

    +1

  4. +++ b/core/modules/auto_updates/src/ReadinessChecker/DiskSpace.php
    @@ -0,0 +1,71 @@
    +   * @todo Determine how much the minimum should be now that we will be using
    +   *   Composer.
    

    Do we want to open a follow-up for this?

  5. +++ b/core/modules/auto_updates/src/ReadinessChecker/FilesystemBase.php
    @@ -0,0 +1,99 @@
    +  protected function getVendorPath() {
    +    if (!$this->vendorPath) {
    +      $this->vendorPath = $this->getRootPath() . DIRECTORY_SEPARATOR . 'vendor';
    +    }
    +    return $this->vendorPath;
    

    This all works in D8 and tarball when we don't support a relocated docroot for auto updates. But in D9 and composer, we need to support this. Is this a good time to introduce webflo/drupal-finder. I'd also support punting that to a follow-up and just getting this in AS-IS.

  6. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManager.php
    @@ -0,0 +1,169 @@
    +   * @todo Add more detail to @return.
    

    Needs an issue opened or we should address here, no?

tedbow’s picture

Status: Needs work » Needs review
FileSize
13 KB
49.33 KB

@heddn thank again for the review

  1. Added \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManager::hasRunRecently()

    Now we are not using LAST_CHECKED_WARNING outside of the manager class so making that private const

  2. whoops. fixed
  3. 👍
  4. Created #3166416: Determine how many megabytes of free disk space should required for automatic updates using Composer. I changed the current patch to use 100mb. We can discuss in that issue if that is enough or not. That issue can either be a follow up or if it has consensus before this issue is committed we can update the patch here. But better place to have a focused discussion.
  5. Good point. I think we should remove the ability to have vendor path elsewhere for now and just thrown an error if it is not a directory. i think this should also be the case for the Root path. currently it will alway set set from the container parameter via the service constructor. I am removing logic in getRootPath() to handle if it not set.

    Created #3166435: Support alternate 'vendor' folder locations in auto_updates module as a follow up.

  6. fixed and removed the todo
heddn’s picture

Status: Needs review » Reviewed & tested by the community

Assuming this comes back green, I'm +1 on RTBC. I'm a big fan of commit often and early. This has had a lot of good review, let's land it and iterate on any potential improvements in follow-ups.

dww’s picture

Status: Reviewed & tested by the community » Needs work

This mostly looks great. I love how thorough the test coverage is! Bravo.

As usual, I have a bunch of minor cosmetic things, and a handful of questions of substance.

General point (flagged below in detail): since this is all new code in 9.1.x only, just about everything could use return types now.

Also general question on terminology with "checker" vs. "check". I didn't flag it everywhere.

Anyway, here's the gory list. ;)

Thanks/sorry,
-Derek

Update: Most of this is fixed in comment #38.

  1. +++ b/core/modules/auto_updates/auto_updates.install
    @@ -0,0 +1,80 @@
    +use Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManagerInterface;
    +use Drupal\Core\StringTranslation\PluralTranslatableMarkup;
    +use Drupal\Core\Url;
    

    Shouldn't 'auto_updates' be after 'Core'?

  2. +++ b/core/modules/auto_updates/auto_updates.install
    @@ -0,0 +1,80 @@
    +  $checker_manager = \Drupal::service('auto_updates.readiness_checker_manager');
    

    'readiness_check_manager'?

    "checker manager" seems clumsy.

  3. +++ b/core/modules/auto_updates/auto_updates.install
    @@ -0,0 +1,80 @@
    +  elseif (!$checker_manager->hasRunRecently()) {
    +    $requirements['auto_updates_readiness']['severity'] = REQUIREMENT_ERROR;
    

    Should this be warning instead of error?

  4. +++ b/core/modules/auto_updates/auto_updates.install
    @@ -0,0 +1,80 @@
    +    $requirements['auto_updates_readiness']['value'] = t('Your site has not recently checked if it is ready to apply <a href="@readiness_checks">automatic updates</a>.', ['@readiness_checks' => 'https://www.drupal.org/docs/8/update/automatic-updates#readiness-checks']);
    ...
    +        'value' => t('Your site is ready for <a href=":readiness_checks">automatic updates</a>.', [':readiness_checks' => 'https://www.drupal.org/docs/8/update/automatic-updates#readiness-checks']),
    

    @readiness_checks vs. :readiness_checks -- why different in the two places?

  5. +++ b/core/modules/auto_updates/auto_updates.install
    @@ -0,0 +1,80 @@
    +  /** @var \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManagerInterface $checker_manager */
    +  $checker_manager = \Drupal::service('auto_updates.readiness_checker_manager');
    +  $checker_manager->run();
    

    Do we need the local var? Why not just:
    \Drupal::service('auto_updates.readiness_checker_manager')->run(); ?

  6. +++ b/core/modules/auto_updates/auto_updates.links.menu.yml
    @@ -0,0 +1,5 @@
    +  route_name: auto_updates.settings
    

    We should mention this in the .info.yml file, too.

  7. +++ b/core/modules/auto_updates/auto_updates.module
    @@ -0,0 +1,87 @@
    +use Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManagerInterface;
    +use Drupal\Core\Url;
    

    Core before auto_updates

  8. +++ b/core/modules/auto_updates/auto_updates.module
    @@ -0,0 +1,87 @@
    +      \Drupal::messenger()->addError(t('Your site is currently failing readiness checks for automatic updates. It cannot be <a href=":readiness_checks">automatically updated</a> until further action is performed:', [':readiness_checks' => 'https://www.drupal.org/docs/8/update/auto-updates#readiness-checks']));
    ...
    +      \Drupal::messenger()->addWarning(t('Your site does not pass some readiness checks for automatic updates. Depending on the nature of the failures, it might effect the eligibility for <a href=":readiness_checks">automatic updates</a>.', [':readiness_checks' => 'https://www.drupal.org/docs/8/update/auto-updates#readiness-checks']));
    

    Could perhaps tweak these with a UX review, but not needed before commit. However, we could at least be consistent about ending with : vs. . for now.

  9. +++ b/core/modules/auto_updates/auto_updates.module
    @@ -0,0 +1,87 @@
    +  /** @var \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManagerInterface $checker_manager */
    +  $checker_manager = \Drupal::service('auto_updates.readiness_checker_manager');
    

    Definitely don't need these two lines again. We already have a $checker_manager here.

  10. +++ b/core/modules/auto_updates/auto_updates.routing.yml
    @@ -0,0 +1,19 @@
    +    _title: 'Update readiness checking...'
    

    ... ? ;)

  11. +++ b/core/modules/auto_updates/src/Controller/ReadinessCheckerController.php
    @@ -0,0 +1,72 @@
    +   * The readiness checker.
    ...
    +   *   The readiness checker manager.
    

    Shall these match?

  12. +++ b/core/modules/auto_updates/src/Controller/ReadinessCheckerController.php
    @@ -0,0 +1,72 @@
    +   * Run the readiness checkers.
    

    "Run the readiness checks.", right? I don't get what "checkers" means here.

  13. +++ b/core/modules/auto_updates/src/Controller/ReadinessCheckerController.php
    @@ -0,0 +1,72 @@
    +      $this->messenger()->addStatus($this->t('No issues found. Your site is ready for <a href=":readiness_checks">automatic updates</a>.', [':readiness_checks' => 'https://www.drupal.org/docs/8/update/auto-updates#readiness-checks']));
    

    In this case, should it really skip down to the section on the readiness_checks, or should it just send you to the top of that page?

  14. +++ b/core/modules/auto_updates/src/Form/SettingsForm.php
    @@ -0,0 +1,97 @@
    +    $form['readiness'] = [
    +      '#type' => 'details',
    +      '#title' => $this->t('Readiness checks'),
    +      '#open' => TRUE,
    +    ];
    

    Since this settings form has a single checkbox with a description, why are we wrapping it in a details container? I thought the UX guidelines say if a form only has 1 thing, don't wrap it inside a fieldset/details.

  15. +++ b/core/modules/auto_updates/src/ReadinessChecker/DiskSpace.php
    @@ -0,0 +1,71 @@
    +class DiskSpace extends FilesystemBase {
    
    --- /dev/null
    +++ b/core/modules/auto_updates/src/ReadinessChecker/FilesystemBase.php
    
    +++ b/core/modules/auto_updates/src/ReadinessChecker/FilesystemBase.php
    @@ -0,0 +1,96 @@
    +abstract class FilesystemBase implements ReadinessCheckerInterface {
    

    FileSystemBase

  16. +++ b/core/modules/auto_updates/src/ReadinessChecker/DiskSpace.php
    @@ -0,0 +1,71 @@
    +  protected function doCheck() {
    +    return $this->diskSpaceCheck();
    +  }
    

    Why not just put the body of diskSpaceCheck() directly into doCheck() itself?

  17. +++ b/core/modules/auto_updates/src/ReadinessChecker/DiskSpace.php
    @@ -0,0 +1,71 @@
    +  protected function diskSpaceCheck() {
    

    I believe this wants a return type now.

  18. +++ b/core/modules/auto_updates/src/ReadinessChecker/DiskSpace.php
    @@ -0,0 +1,71 @@
    +    if (!$this->areSameLogicalDisk($this->getRootPath(), $this->getVendorPath())) {
    ...
    +    elseif (disk_free_space($this->getRootPath()) < static::MINIMUM_DISK_SPACE) {
    ...
    +    if (disk_free_space($temp) < static::MINIMUM_DISK_SPACE) {
    

    Seems weird that we have a single value for MINIMUM_DISK_SPACE that applies to each partition / filesystem separately, yet temp space doesn't care where it lives. So if I have /tmp $root and $vendor each on their own partitions, in theory the MINIMUM for each should be less than if all 3 are on the same partition and /tmp is competing for space with $root an $vendor. This logic doesn't totally make sense.

    Seems either we want separate minimums for each of these 3 things, and we want to enforce them such that if any of them are on the same partition, that disk must have enough space for both/all...

  19. +++ b/core/modules/auto_updates/src/ReadinessChecker/FilesystemBase.php
    @@ -0,0 +1,96 @@
    +  public function run() {
    

    Return type.

  20. +++ b/core/modules/auto_updates/src/ReadinessChecker/FilesystemBase.php
    @@ -0,0 +1,96 @@
    +    if (!file_exists($this->getRootPath() . DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, ['core', 'core.api.php']))) {
    

    If you're gonna implode(), why not go all the way? ;)

    !file_exists(implode(DIRECTORY_SEPARATOR, [$this->getRootPath(), 'core', 'core.api.php']))

  21. +++ b/core/modules/auto_updates/src/ReadinessChecker/FilesystemBase.php
    @@ -0,0 +1,96 @@
    +    if (!is_dir($this->getVendorPath())) {
    

    If we check all the way for core/core.api.php as a file in root, do we want to check for a specific file from vendor?

    If not, why not just check for is_dir(getRootPath() . DIR_SEP . 'core') ?

  22. +++ b/core/modules/auto_updates/src/ReadinessChecker/FilesystemBase.php
    @@ -0,0 +1,96 @@
    +      $messages[] = $this->t('Vendor folder "@vendor" is not a valid directory. Alternate vendor folder locations are not currently supported.', [
    

    Before this is stable, I'd love a UX review of all these messages. ;) E.g. this one seems like it wants to link to a handbook page somewhere. Seems very non-actionable to non-technical users.

  23. +++ b/core/modules/auto_updates/src/ReadinessChecker/FilesystemBase.php
    @@ -0,0 +1,96 @@
    +   * Perform checks.
    

    Performs

  24. +++ b/core/modules/auto_updates/src/ReadinessChecker/FilesystemBase.php
    @@ -0,0 +1,96 @@
    +   * Get the root file path.
    ...
    +   * Get the vendor file path.
    

    Gets

  25. +++ b/core/modules/auto_updates/src/ReadinessChecker/FilesystemBase.php
    @@ -0,0 +1,96 @@
    +  protected function getRootPath() {
    

    Return type.

  26. +++ b/core/modules/auto_updates/src/ReadinessChecker/FilesystemBase.php
    @@ -0,0 +1,96 @@
    +  protected function getRootPath() {
    ...
    +  protected function getVendorPath() {
    ...
    +  protected function areSameLogicalDisk(string $root, string $vendor) {
    

    Return type

  27. +++ b/core/modules/auto_updates/src/ReadinessChecker/FilesystemBase.php
    @@ -0,0 +1,96 @@
    +   * Determine if the root and vendor file system are the same logical disk.
    

    Determines if the root and vendor directories are on the same logical disk.

  28. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerInterface.php
    @@ -0,0 +1,18 @@
    +   * Run check.
    

    Runs a check.

  29. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerInterface.php
    @@ -0,0 +1,18 @@
    +   *   An array of translatable strings or an empty array.
    

    The comment above for doCheck() seems more clear:

    "An array of translatable strings if any checks fail."

  30. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerInterface.php
    @@ -0,0 +1,18 @@
    +  public function run();
    

    Return type.

  31. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManager.php
    @@ -0,0 +1,192 @@
    + * Defines a chained readiness checker implementation combining multiple checks.
    

    Is that really what this is still for?

  32. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManager.php
    @@ -0,0 +1,192 @@
    +  /**
    +   * Last checked ago warning (in seconds).
    +   */
    +  private const LAST_CHECKED_WARNING = 3600 * 24;
    

    A) "Time (in seconds) since the last check after which we generate a warning." (or something).

    B) Probably it's obvious, but maybe "Defaults to 1 day." and/or use 60 * 60 * 24?

  33. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManager.php
    @@ -0,0 +1,192 @@
    +  public function addChecker(ReadinessCheckerInterface $checker, string $category = 'warning', $priority = 0) {
    

    Return type -- but what's the correct syntax for that when a function returns $this? Do we just use the raw class name?

  34. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManager.php
    @@ -0,0 +1,192 @@
    +  public function run() {
    ...
    +  public function getResults($category) {
    ...
    +  public function clearStaleResults() {
    ...
    +  public function timestamp() {
    ...
    +  public function isEnabled() {
    ...
    +  public function getCategories() {
    ...
    +  protected function getSortedCheckers() {
    ...
    +  public function hasRunRecently() {
    

    return types

  35. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManager.php
    @@ -0,0 +1,192 @@
    +    $this->keyValue->set("readiness_check_results",
    

    ' not "

  36. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManager.php
    @@ -0,0 +1,192 @@
    +   *   objects in the category order by priority.
    

    ordered by

  37. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManager.php
    @@ -0,0 +1,192 @@
    +   *   A concatenated list of checker service Ids.
    

    Ids delimited by '::'.

  38. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManager.php
    @@ -0,0 +1,192 @@
    +    return !($this->time->getRequestTime() > $this->timestamp() + self::LAST_CHECKED_WARNING);
    

    instead of ! and >, why not just <= ?

    return getRequestTime() <= timestamp() + last_checked

    ?

  39. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManagerInterface.php
    @@ -0,0 +1,98 @@
    +interface ReadinessCheckerManagerInterface {
    

    Every method on this interface could define its return type.

  40. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManagerInterface.php
    @@ -0,0 +1,98 @@
    +   *   (optional) The category of check.
    

    ...of the check. Defaults to 'warning'.

  41. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManagerInterface.php
    @@ -0,0 +1,98 @@
    +   *   (optional) The priority of the checker being added. If not provided the
    +   *   default priority is 0. Readiness checkers with larger priorities will run
    

    s/If not provided the default priority is 0./Defaults to 0./

  42. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManagerInterface.php
    @@ -0,0 +1,98 @@
    +   * Run readiness checks.
    

    s/Run/Runs/

  43. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManagerInterface.php
    @@ -0,0 +1,98 @@
    +   *   A nested array readiness check messages. The top level array is keyed by
    

    s/array readiness/array of readiness/

  44. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManagerInterface.php
    @@ -0,0 +1,98 @@
    +   *   category and the next level array is is an array of translatable strings
    

    s/is is/is/

  45. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManagerInterface.php
    @@ -0,0 +1,98 @@
    +   * Get results of most recent run.
    

    Gets the results of the most recent run.

  46. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManagerInterface.php
    @@ -0,0 +1,98 @@
    +   *   An array of translatable strings.
    

    Translatable messages if any checks fail, otherwise an empty array.
    (or something)

  47. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManagerInterface.php
    @@ -0,0 +1,98 @@
    +   * Get timestamp of most recent run.
    

    Gets the timestamp of the most recent run.

  48. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManagerInterface.php
    @@ -0,0 +1,98 @@
    +   *   A unix timestamp of most recent completed run if one exists or NULL if no
    

    The timestamp of the most recently completed run, or NULL if...

  49. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManagerInterface.php
    @@ -0,0 +1,98 @@
    +  public function timestamp();
    

    getTimestamp() ?

  50. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManagerInterface.php
    @@ -0,0 +1,98 @@
    +   * Determine if readiness checks is enabled.
    

    Determines if readiness checks are enabled.

  51. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManagerInterface.php
    @@ -0,0 +1,98 @@
    +   * Get the checker categories.
    

    Gets

  52. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManagerInterface.php
    @@ -0,0 +1,98 @@
    +   *   Return TRUE if the results were cleared, otherwise returns FALSE.
    

    TRUE if the results were cleared, otherwise FALSE.

  53. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManagerInterface.php
    @@ -0,0 +1,98 @@
    +   *   True if the checkers have been run recently, otherwise false.
    

    s/True/TRUE/
    s/false/FALSE/

  54. +++ b/core/modules/auto_updates/tests/modules/auto_updates_test/src/ReadinessChecker/TestChecker.php
    @@ -0,0 +1,27 @@
    + * The disk space readiness checker.
    

    A test readiness checker.

  55. +++ b/core/modules/auto_updates/tests/src/Functional/ReadinessCheckerTest.php
    @@ -0,0 +1,261 @@
    +   * @var bool|\Drupal\user\Entity\User
    ...
    +   * @var bool|\Drupal\user\Entity\User
    

    Why bool| ?

  56. +++ b/core/modules/auto_updates/tests/src/Functional/ReadinessCheckerTest.php
    @@ -0,0 +1,261 @@
    +    $this->container->get('module_installer')->uninstall(['automated_cron']);
    +    $this->container->get('module_installer')->install(['auto_updates', 'auto_updates_test']);
    

    I guess we don't want auto_updates* installed until after cron is disabled? Maybe a comment about why not just use self::$modules for these?

  57. +++ b/core/modules/auto_updates/tests/src/Functional/ReadinessCheckerTest.php
    @@ -0,0 +1,261 @@
    +    // If the the site is ready for updates the users will see the same output
    

    A) s/the the/the/
    B) add comma after "updates"

  58. +++ b/core/modules/auto_updates/tests/src/Functional/ReadinessCheckerTest.php
    @@ -0,0 +1,261 @@
    +      $this->assertReadinessReportMatches('Your site is ready for automatic updates.');
    

    Would be helpful to pass the user to this so an assertion failure could tell you which user it was using at the time.

  59. +++ b/core/modules/auto_updates/tests/src/Functional/ReadinessCheckerTest.php
    @@ -0,0 +1,261 @@
    +    //   This is how 'Run cron" works.
    

    ' vs. "

  60. +++ b/core/modules/auto_updates/tests/src/Functional/ReadinessCheckerTest.php
    @@ -0,0 +1,261 @@
    +    $assert->pageTextNotContains("Access denied");
    

    ' not "

  61. +++ b/core/modules/auto_updates/tests/src/Functional/ReadinessCheckerTest.php
    @@ -0,0 +1,261 @@
    +    $assert->pageTextNotContains('>run the readiness checks');
    

    why '>' in here?

  62. +++ b/core/modules/auto_updates/tests/src/Functional/ReadinessCheckerTest.php
    @@ -0,0 +1,261 @@
    +    $assert->pageTextContains('Access denied');
    

    Could also assert the response code.

  63. +++ b/core/modules/auto_updates/tests/src/Functional/ReadinessCheckerTest.php
    @@ -0,0 +1,261 @@
    +    $this->container->get('module_installer')->install(['color']);
    

    Should we use something not actively being moved towards deprecation and removal from core? ;)

  64. +++ b/core/modules/auto_updates/tests/src/Functional/ReadinessCheckerTest.php
    @@ -0,0 +1,261 @@
    +    // Tests that running cron after 1 hour of the checkers running during cron
    ...
    +    $this->setFakeTime('+125 minutes');
    

    Originally flagged this since I didn't understand 125 vs. "1 hour" in this comment. Re-reading it, I see the comment threw me off. Maybe:

    "// Tests that running cron again over an hour after the previous cron run will run them again."
    (or something).

  65. +++ b/core/modules/auto_updates/tests/src/Kernel/ReadinessChecker/DiskSpaceTest.php
    @@ -0,0 +1,80 @@
    + * Test checker with the free disk space minimum set to an insanely high number.
    

    s/an insanely/a very/
    (more inclusive for folks with mental health issues -- some people take offense at such language).

dww’s picture

p.s. I just tried and https://www.drupal.org/docs/8/update/auto-updates is a 404. Tagging for some doc updates to have a page at that location, or to fix the links if that page already exists somewhere else.

Thanks!
-Derek

dww’s picture

Assigned: Unassigned » dww

I'll fix most of this now, stay tuned.

dww’s picture

This fixes everything from #35 except:

2, 3, 4, 12, 13, 14, 16, 18, 21, 22, 31, 55, 56, 58, 61, 63

Fxed but worth confirming the fix:
33, 64

Now I don't feel so sorry for the 65 point review. ;)

Cheers,
-Derek

ressa’s picture

Great work @tedbow and @dww, thanks! Just one comment:

+++ b/core/modules/auto_updates/src/ReadinessChecker/DiskSpace.php
@@ -0,0 +1,71 @@
+   * Minimum disk space (in bytes) is 100mb.

Technically, one megabyte is written as 1 MB, so perhaps 100mb should be written as 100 MB (with space) in stead?

https://web.stanford.edu/class/cs101/bits-gigabytes.html
https://www.npl.co.uk/si-units

ressa’s picture

A patch for the 100 MB comment above.

tedbow’s picture

@dww thanks for the review!

re #35

  1. I would vote to leave it as is. We have readiness checkers and this the class that manages them.
  2. '

  3. I think an error is good. When we actually have this module providing automatic security updates if it is not checking frequently to that it can actually apply the updates then the site could easily be in a situation where a critical security fix is released and it will not able to be applied to the site.
  4. Fixed when used inside href we should always use ':'
  5. I prefer to assign services to a local variable because then with the @var line IDE are able to find the call to the method. This makes automated refactoring easier for method name changes and also for method removals. It also just makes it easier to find all the times a method is called.

    changing back for now.

  6. I think the idea here was that this title you would only see briefly, if at all, as the checking was in progress before the redirect. Not changing back though
  7. The classes like "DiskSpace" are "Readiness Checkers". The DiskSpace checker performs multiple checks. This comes from the contrib module where there are many checkers, https://git.drupalcode.org/project/automatic_updates/-/tree/8.x-1.x/src/.... We will add them in later issues.

    The naming seems fine with me but we could change it all places if others think we should call the classes "Readiness Checks"

  8. I am thinking we should just remove all these documentation links because they link to the docs for the contrib module. We want to do everything we can to not confuse people and make them think the current state of this module actually has update functionality.

    but should we link to some page provided by the module explain this. The module is hidden by default for now so I don't think this is likely but still we don't want anyone to think they will get security updates now.

    leaving for now.

  9. This was from the contrib module that had more elements. Removing. We can add it back when there are more elements on the form
  10. I am not sure. Maybe it was so the diskSpaceCheck() could have a more descriptive comment? But I think if we make the class comment better then "doCheck()" say enough.
    fixiing.
  11. Agreed. Added this to remaining task. Might related to this question #3166416: Determine how many megabytes of free disk space should required for automatic updates using Composer. I think Composer 2 by default will download and extract the packages inside the vendor/composer directory and not temp before it moves them to their final location. So this might factor into which folders we should check.

    The current logic was based the contrib workflow which explicitly copies files into \Drupal\Component\FileSystem\FileSystem::getOsTemporaryDirectory() but it is not Composer based.

  12. I update this to check for vendor/autoload.php
    That will always be there correct?
  13. Updated
  14. This what just what IDE generated because this is what \Drupal\Tests\user\Traits\UserCreationTrait::createUser() returns but there is no need for it. Fixed
  15. Update the comment
  16. I think it would just be simpler to remove the foreach loops I put in to loop through the 2 users. Then if assertReadinessReportMatches() fails will be able to tell the user from the call line number in the error.

    fixed

  17. Just a copy/paste error. fixed
  18. Switched to help

re #40. Thanks, looks good!

UPDATE: whoops made all that a <ul> fixed.

Status: Needs review » Needs work

The last submitted patch, 41: 3162655-41.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

tedbow’s picture

Issue summary: View changes

Updating remaining tasks

tedbow’s picture

Status: Needs work » Needs review
FileSize
723 bytes
49.73 KB

Fixing the coding standard issue.
#41 was just a unrelated random Javascript test failure. I miss those, good times 😜

dww’s picture

Re: #41: Thanks, @tedbow! All makes sense. +1 to your changes / answers there.

  1. Per #36 The linked page doesn't exist: https://www.drupal.org/docs/8/update/auto-updates -- so either we need to create that page, or change all the links. NW I guess.
  2. Okay, cool. So where does that leave us for getting this into core for now? Open a follow-up, add @todo to this patch, and punt? Address it here before initial commit?
  3. (which is really #35.21) +1 to checking for vendor/autoload.php. Makes sense. However -1 to using implode() for a single delimiter. ;) Fixed here.

Everything else looks great. Interdiff is happy and addresses all the points.

So it seems we're very close, and down to these 2 points. Looks like many of the remaining tasks are already done. Can you confirm and cross off everything that's complete? Seems like points 1, 2, 3 and 8 are all done, right? Do we need a follow-up for 5? Are the "questions" there still relevant?

Thanks!
-Derek

p.s. interdiff is a bit confused since your Git is renaming a big_pipe_test module and mine isn't. So here's also the local commit I made that's all I really changed from #44.

dww’s picture

FileSize
48.91 KB
867 bytes

Whoops, stray space snuck in. ;)

dww’s picture

Re: #36: Oh, I see. It's https://www.drupal.org/docs/8/update/auto-updates (link in the patch) vs. https://www.drupal.org/docs/8/update/automatic-updates (actual page).

So yeah, definitely NW. Not sure how to best approach it.

  1. Add a new page for core autoupdates that this module can link to.
  2. Fix the existing page to have separate giant header sections for "What's in core" vs. "Available via the contrib project" or whatever.
  3. Remove all the links from this patch, open a follow-up about adding them back once the docs are sorted out.
  4. Other?

Cheers,
-Derek

tedbow’s picture

  1. re #32.18. I removed the check for the temp directory. This was added to the contrib module because it explicitly save the files there. I added question in #3166416: Determine how many megabytes of free disk space should required for automatic updates using Composer to confirm that Composer 2 always downloads packages first to /vendor/composer before it extracts them to their final location. This is what I found in testing with https://github.com/heddn/php-signify-composer-integration which takes advantage of the new Composer 2 event that lets you validate the packages after they are downloaded and before they are extracted.

    I also bumped up the minimum disk space to 1 GB. On #3166416 it was pointed out that even shared hosting plans have 50 GB or space. It is probably better to start high.

    I also changed the check to so that if the root and vendor are on 2 different logical disks it will check for minimum space/2 for each.

  2. re #47 went with option 3. Removed the links and added todo's to the new issue #3168405: Provide links to documentation when showing user Readiness Check messages
  3. We have 1 remaining question in the summary

    Determine if a single checker should be able provide both warnings and messages

    I think the services should not have the "category" tag but return a nested array like \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManagerInterface::run() does.

    My reasoning for this is because the disk space checker for example might want to return a "warning" if you have < 500 MB but an error if you have < 1 GB. To do this with the current architecture you would have to make DiskSpaceWarning and DiskSpaceError.
    @heddn has said we should stick with the current method. Other opinions?

  4. Oh also

    Determine if a larger minimum disk space value could ever cause random failures on Drupal CI. Right now this not happening with the contrib module but it only uses 10mb. If we change this to something like 200mb would this ever fail in DrupalCi and cause a test failure?

    I think if this patch passes we can cross that off. I ping @mixologic about this to see if he thinks it would be problem with DrupalCI

tedbow’s picture

Issue summary: View changes

Confirmed with @mixologic that #48.4 won't be a problem

tedbow’s picture

Some nits, and comments

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

voleger’s picture

#33 Added comment for #3166435: Support alternate 'vendor' folder locations in auto_updates module. Maybe we can try to update the patch as the module not commited jet.

phenaproxima’s picture

Partial review without any manual testing...

  1. +++ b/core/modules/auto_updates/auto_updates.info.yml
    @@ -0,0 +1,7 @@
    +package: Core (Experimental)
    +version: VERSION
    +hidden: true
    

    😳Hidden and experimental...

  2. +++ b/core/modules/auto_updates/auto_updates.install
    @@ -0,0 +1,86 @@
    +  $readiness_check = Url::fromRoute('auto_updates.update_readiness');
    

    Nit: Maybe $readiness_check_url would be a more accurate name for this variable.

  3. +++ b/core/modules/auto_updates/auto_updates.install
    @@ -0,0 +1,86 @@
    +    $requirements['auto_updates_readiness']['severity'] = REQUIREMENT_ERROR;
    

    This seems a bit severe to me. Maybe REQUIREMENT_WARNING would be more appropriate?

  4. +++ b/core/modules/auto_updates/auto_updates.install
    @@ -0,0 +1,86 @@
    +    if ($readiness_check->access()) {
    +      $requirements['auto_updates_readiness']['description'] = t('<a href=":link">Run readiness checks</a> manually.', [
    +        ':link' => $readiness_check->toString(),
    +      ]);
    +    }
    

    IMHO, the word "manually" implies that it'll take a bunch of work on the part of the user. How about replacing "manually" with "now" instead? Also, a supernit: :url would be a more accurate placeholder for the URL.

  5. +++ b/core/modules/auto_updates/auto_updates.install
    @@ -0,0 +1,86 @@
    +    else {
    +      $requirements['auto_updates_readiness']['description'] = t('Readiness checks were last run @time ago.', ['@time' => $time_ago]);
    +    }
    

    So...this is not actionable. We're treating it as an error, but there's nothing for the user to do or click on to correct the problem. That's not super great :)

  6. +++ b/core/modules/auto_updates/auto_updates.install
    @@ -0,0 +1,86 @@
    +    $error_results = $checker_manager->getResults(ReadinessCheckerManagerInterface::ERROR);
    +    $warning_results = $checker_manager->getResults(ReadinessCheckerManagerInterface::WARNING);
    

    These feel like they should just be two different methods of ReadinessCheckerManagerInterface: getErrors() and getWarnings(). Is there a reason that interface constants are preferable?

  7. +++ b/core/modules/auto_updates/auto_updates.install
    @@ -0,0 +1,86 @@
    +function auto_updates_install($is_syncing) {
    

    Nit: $is_syncing is not used.

  8. +++ b/core/modules/auto_updates/auto_updates.install
    @@ -0,0 +1,86 @@
    +  /** @var \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManagerInterface $checker_manager */
    +  $checker_manager = \Drupal::service('auto_updates.readiness_checker_manager');
    +  $checker_manager->run();
    

    Supernit: technically there is no need for the $checker_manager variable, but I do understand why it's there :)

  9. +++ b/core/modules/auto_updates/auto_updates.module
    @@ -0,0 +1,88 @@
    +  if ($admin_context->isAdminRoute($route_match->getRouteObject()) && \Drupal::currentUser()->hasPermission('administer site configuration')) {
    

    Wrapping all of this in a check for the administer site configuration permission feels a bit wobbly. Maybe we could at least add a comment here explaining why we're using that specific permission to cordon all of this stuff off?

  10. +++ b/core/modules/auto_updates/auto_updates.module
    @@ -0,0 +1,88 @@
    +    if (in_array(\Drupal::routeMatch()->getRouteName(), $disabled_routes, TRUE)) {
    

    Nit: You can just use $route_match->getRouteName() here.

  11. +++ b/core/modules/auto_updates/auto_updates.module
    @@ -0,0 +1,88 @@
    +function auto_updates_modules_installed($modules) {
    

    $modules should be type hinted as an array.

    But more to the point -- do we maybe also want to run the checks during hook_modules_uninstalled()?

  12. +++ b/core/modules/auto_updates/auto_updates.services.yml
    @@ -0,0 +1,11 @@
    +      - { name: readiness_checker, category: error}
    

    Not a huge deal, but IMHO, "severity" would be a better term than "category" to describe the nature of the check; it would be keeping with our existing concepts and language in hook_requirements().

  13. +++ b/core/modules/auto_updates/config/install/auto_updates.settings.yml
    @@ -0,0 +1 @@
    +enable_readiness_checks: true
    

    Given that the whole point of this module is to do readiness checks, I kinda wonder if there's any real point in making this a configuration flag. If people don't want to see readiness checks, why couldn't they just uninstall the module wholesale?

  14. +++ b/core/modules/auto_updates/src/Controller/ReadinessCheckerController.php
    @@ -0,0 +1,75 @@
    +    $this->stringTranslation = $string_translation;
    

    Nit: We should use $this->setStringTranslation() to keep the abstraction non-leaky. :)

  15. +++ b/core/modules/auto_updates/src/Controller/ReadinessCheckerController.php
    @@ -0,0 +1,75 @@
    +  public function run() {
    

    Why no return type hint on this method?

  16. +++ b/core/modules/auto_updates/src/Form/SettingsForm.php
    @@ -0,0 +1,90 @@
    +/**
    + * Settings form for Automatic Updates.
    + */
    +class SettingsForm extends ConfigFormBase {
    

    Any reason why we shouldn't mark this class as internal?

  17. +++ b/core/modules/auto_updates/src/ReadinessChecker/DiskSpace.php
    @@ -0,0 +1,54 @@
    +  /**
    +   * Minimum disk space (in bytes) is 1 GB.
    +   *
    +   * @todo Determine how much the minimum should be now that we will be using
    +   *   Composer in https://www.drupal.org/node/3166416.
    +   */
    +  const MINIMUM_DISK_SPACE = 1073741824;
    +
    +  /**
    +   * Megabyte divisor.
    +   */
    +  const MEGABYTE_DIVISOR = 1000000;
    

    Any reason not to make these protected or private?

  18. +++ b/core/modules/auto_updates/src/ReadinessChecker/FileSystemBase.php
    @@ -0,0 +1,96 @@
    +    return $root_statistics && $vendor_statistics && $root_statistics['dev'] === $vendor_statistics['dev'];
    

    If stat() has an error, PHP's documentation says it will return FALSE. In that case, this method will report something that isn't necessarily true -- it'll say that the root and vendor aren't on the same logical disk, when in fact they might be, but we're just not aware of it due to an error condition.

    So, IMHO, the right thing to do here is to throw an exception, or at least log a warning to watchdog, if stat() returns FALSE for either $root_statistics or $vendor_statistics. That'll leave a paper trail for intrepid debuggers to follow.

  19. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManagerInterface.php
    @@ -0,0 +1,99 @@
    +  /**
    +   * Gets the timestamp of the most recent run.
    +   *
    +   * @return int|null
    +   *   The timestamp of the most recently completed run, or NULL if no run has
    +   *   been completed.
    +   */
    +  public function getTimestamp();
    

    This method name feels kinda vague. How about getMostRecentRunTime() or similar?

  20. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManagerInterface.php
    @@ -0,0 +1,99 @@
    +  /**
    +   * Determines whether the readiness checkers have been run recently.
    +   *
    +   * @return bool
    +   *   TRUE if the checkers have been run recently, otherwise FALSE.
    +   */
    +  public function hasRunRecently(): bool;
    

    So I really like the method name here, because it makes the intent clear. But "recently" is a vague term and we need to define it more clearly in the doc comment at the very least.

    Also, this class is supposed to run multiple readiness checks, right? Depending on their implementation, what if certain ones have been run recently, but others haven't? Is that a potential problem? How do we keep track of this in a unified way? Maybe I'm overthinking it.

phenaproxima’s picture

A few more thoughts.

  1. +++ b/core/modules/auto_updates/auto_updates.services.yml
    @@ -0,0 +1,11 @@
    +  auto_updates.readiness_checker_manager:
    +    class: Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManager
    +    arguments: ['@keyvalue', '@config.factory', '@datetime.time']
    

    Since the results of the readiness checks to eventually become stale, maybe we should use the expirable key-value store (keyvalue.expirable).

  2. +++ b/core/modules/auto_updates/src/ReadinessChecker/DiskSpace.php
    @@ -0,0 +1,54 @@
    +  /**
    +   * Megabyte divisor.
    +   */
    +  const MEGABYTE_DIVISOR = 1000000;
    

    This comment is kinda useless :)

  3. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManager.php
    @@ -0,0 +1,194 @@
    +  public function run(): array {
    

    I don't hugely love the fact that run() returns an array, which calling code needs to examine to determine if there are any readiness problems. That means the calling code needs to understand the details of how readiness checks work. I'd rather hide those details -- IMHO, something like $manager->isReadyForUpdates() would be easier to use, and also less prone to being used incorrectly. For example, what I envision:

    if (!$manager->isReady()) {
      $errors = $manager->getErrors();
      $warnings = $manager->getWarnings();
      // ...do stuff...
    }
    
  4. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManagerInterface.php
    @@ -0,0 +1,99 @@
    +interface ReadinessCheckerManagerInterface {
    

    So, to be honest...I'm not 100% certain that there is much benefit to having an interface here. This is a fairly specialized domain object in an experimental module; it doesn't seem like an API we'd want other code to implement. Personally, I feel like the interface is not necessary and should be removed.

tedbow’s picture

@voleger thanks I haven't had a chance to read that yet but will. Also I open to using a different checker as the first checker to add with this patch but I am not sure of simpler one.

@phenaproxima, thanks for the review!

  1. Yeah we probably don't need hidden since this only be the dev release until we actually update ability. removed
  2. fixed
  3. fixed
  4. fixed
  5. I changed this to a warning. I am not sure if we shouldn't show the message at all if the user doesn't have access to run readiness checks.
  6. Yes this sounds like a good idea going to wait on this though`. I was wondering though if you had an opinion on related points#15.20, #20.3, #22.3. Basically I think a checkers should be able to return messages and warning not just 1 or the other.

    If we changes this it might be better to refactor at once.

  7. For some reason I thought we left arguments in when they were in the hook signature even if we didn't use them but see for this hook it isn't that way. Removed
  8. I personally like to assign to a variable first so we can use @var or there is no way for an IDE or other automated tools to determine that ->run() is
    \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManagerInterface::run()
    

    I think it makes the code more understandable and discoverable. when I am looking at the interface I like to be able to quickly find all the instances of the methods. This allows that. Since it is only on module install I don't think the performance hit makes a difference.

  9. This permission is used tons of places, DateFormat admin permission, BookSettingsForm, color module, seeing errors on \Drupal\system\Controller\SystemController::overview(), 14 routes in the system module. I think it reasonable to use here. Generally all these uses of don't explain why this 1 permission is so powerful so I don't think we need more info here.
  10. fixed
  11. Do we actually need $modules here? I thought we did because it is in the hook signature but if don't need $is_syncing in 7) then we don't need $modules here for the same reason. I removed it.

    In this case we don't care which modules where installed we just know the new module might have a readiness checker so we should run the checks.

    As for hook_modules_uninstalled() I guess module that was uninstalled could have checker and the message from the module being uninstalled could be now stored in readiness_check_results. Obviously we don't want to show a message for module that was already uninstalled so that would be we should all run this in hook_modules_uninstalled().

    Another option would be to not have both \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManager::getResults() and \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManager::run() but only run() which returns the results any ways. We could use KeyValueExpirableFactory instead of KeyValueFactory which we are using now. This way run() could just check if readiness_check_results had a value or had expired or if $this->getCurrentCheckerIds() didn't match the checkers in readiness_check_results and only then actually run the checkers. Otherwise just return the previous result. Then we won't need \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManager::clearStaleResults() either and probably won't need auto_updates_modules_installed() either. It would just mean that on the status report page if we hadn't run checkers or if the results expired it would actually run them not just get previous results. I am convinced yet they are that expensive to make that a problem.

    So I am not changing anything for this right now because there will already be a bunch of nits in patch anyways I would rather a refactor like that in its own patch.

    I am going to add a new failing test testReadinessCheckerUninstall() to ensure we solve this problem either by implementing hook_modules_uninstalled() or doing the refactor I suggested.

  12. See my question/suggestion in 6) which would mean we won't need this tag at all. But I agree severity is better description but also makes be think we shouldn't be categorizing the services like this.
  13. Well for now it is the point but this is just the first step. The point of the module is to actually do the updates. Just trying to not make a mega patch
  14. forgot that was StringTranslationTrait, fixed
  15. fixed
  16. nope, fixed
  17. setting to protected. I could see want to override these if you think you site needs more space to the updates, or less
  18. good catch, throwing an exception
  19. fixed
  20. \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManager::run() runs all the checkers at once so they all ran the same time. But with my suggestion in 11) maybe won't need this at all. But that would mean anybody who could view the status report could run the requirement checkers(if they hadn't been run recently on cron). But is that problem? The could already see the results and the checker should not be actually making changes, just "checking". If they aren't expensive maybe this isn't a problem

re #54

  1. Yep see my comment about #53.11. Will wait for the bigger refactor if that seems the right thing to do.
  2. I thought our coding standards need comment but maybe not. removed
  3. That might make more sense will wait on refactor mentioned above.
  4. Agreed removing

Status: Needs review » Needs work

The last submitted patch, 55: 3162655-55.patch, failed testing. View results

tedbow’s picture

Assigned: Unassigned » tedbow
Status: Needs work » Needs review
FileSize
22.84 KB
47.44 KB

Here is refactor I suggested in #55. I will wait till is uploaded so I can do a self review by commenting on the interdiff to explain

Assigning to myself because I will do that shortly

Status: Needs review » Needs work

The last submitted patch, 57: 3162655-57.patch, failed testing. View results

tedbow’s picture

Assigned: tedbow » Unassigned
Issue summary: View changes
Status: Needs work » Needs review
Related issues: +#3113971: Replace REQUEST_TIME in services
FileSize
1.6 KB
47.73 KB
  1. +++ b/core/modules/auto_updates/auto_updates.info.yml
    @@ -4,3 +4,4 @@ description: 'Experimental module to develop automatic updates. Currently the mo
    +hidden: true
    

    Added this back because it caused some testing problems. Right now there is no update functionality so I think it confusing to have a "readiness" state for something that is not existing functionality.

    Also we only have 1 checker so we can't say it is "Ready" anyways.

    we can decide later when want show it in the UI.

  2. +++ b/core/modules/auto_updates/auto_updates.install
    @@ -82,5 +81,5 @@ function auto_updates_requirements($phase) {
    -  $checker_manager->run();
    +  $checker_manager->getErrors();
    

    I made run() protected. I think the only drawback of this is that you have to call getErrors() even when you don't want the results. Here we calling it on hook_install() make sure it is run once.

    UPDATE: I think maybe we should move this to hook_modules_installed(). If you are installing auto_updates along with other modules that also provider readiness checkers running it in hook_install() may run before the other modules are installed(if they are dependent on auto_updates because they provider unrelated functionality too).

    Right now you still won't get stale results because run() makes sure the results were run with the same set of checkers. But it will mean it actually be run 2x when maybe it only has be run 1x.

    Because run() checks if checker services have changed this will not actually call the checkers if the modules don't provide new checkers.

    fixed

  3. +++ b/core/modules/auto_updates/auto_updates.module
    @@ -68,21 +67,5 @@ function auto_updates_cron() {
    -  $last_check = $checker_manager->getMostRecentRunTime();
    -  // Only allow cron to run once every hour.
    -  if ($last_check && ($request_time - $last_check) < 3600) {
    -    return;
    -  }
    -  $checker_manager->run();
    -}
    -
    -/**
    - * Implements hook_modules_installed().
    - */
    -function auto_updates_modules_installed() {
    -  /** @var \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManager $checker_manager */
    -  $checker_manager = \Drupal::service('auto_updates.readiness_checker_manager');
    -  if ($checker_manager->clearStaleResults()) {
    -    $checker_manager->run();
    -  }
    +  $checker_manager->getErrors();
    

    Hare to tell from this diff now we don't check when the checkers were last run in cron because we use \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface::setWithExpire() we don't have worry about it outside of the manager.

  4. +++ b/core/modules/auto_updates/auto_updates.services.yml
    @@ -3,9 +3,9 @@ services:
    -      - { name: readiness_checker, category: error}
    +      - { name: readiness_checker}
    

    Removed having checkers only be able return messages in 1 category, errors or warnings. I think this especially makes sense when you realize we don't call this "category" anywhere else in core. These are really severity levels.

  5. +++ b/core/modules/auto_updates/src/Controller/ReadinessCheckerController.php
    @@ -55,7 +55,7 @@ public static function create(ContainerInterface $container) {
    -    if (!array_filter($this->checkerManager->run())) {
    +    if (!array_filter($this->checkerManager->getErrors(TRUE))) {
    

    When the user is explicitly running the checkers we send the optional $refresh argument to delete the cache version.

  6. +++ b/core/modules/auto_updates/src/ReadinessChecker/DiskSpace.php
    @@ -20,7 +20,19 @@ class DiskSpace extends FileSystemBase {
    +  public function getWarnings(): array {
    +    return [];
    +  }
    

    Now each checker has to implement getErrors() and getWarnings() even if they don't provide both. We could make a ReadinessCheckerBase to implement both and returning empty arrays.

  7. +++ b/core/modules/auto_updates/src/ReadinessChecker/DiskSpace.php
    @@ -20,7 +20,19 @@ class DiskSpace extends FileSystemBase {
    +  public function getErrors(): array {
    +    if ($messages = parent::getErrors()) {
    +      // @todo Reimplemenet so that every class that extends FileSystem base
    +      // will not the same error.
    +      return $messages;
    +    }
    

    I figured the way the FileSystemBase is written if produces and error all checkers that extend it will return the same messages.

    I have confirm the contrib module has the same problem

  8. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManager.php
    @@ -90,80 +77,59 @@ public function __construct(KeyValueFactoryInterface $key_value, ConfigFactoryIn
    +      // If the checkers have not changed return the results.
    +      if ($results && $results['checkers'] === $this->getCurrentCheckerIds()) {
    +        return $results['messages'];
           }
    

    This allows removing clearStateResults(). run() will not never return cached results if checkers have changed.

    This now makes testReadinessCheckerUninstall() pass 🎉

    But it also means that any module removes or adds checker services dynamically stale results won't be returned.

    @todo Do we need a test for this?

  9. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManager.php
    @@ -90,80 +77,59 @@ public function __construct(KeyValueFactoryInterface $key_value, ConfigFactoryIn
    +    $this->keyValueExpirable->setWithExpire(
    +      'readiness_check_results',
    ...
    +      ],
    +      3600
    

    Because we use setWithExpire() outside code doesn't have to worry about this

  10. \Drupal\Tests\auto_updates\Functional\ReadinessCheckerTest::testCronRun() will now fail until #3113971: Replace REQUEST_TIME in services because you can't fake time when using setWithExpire()
    But since auto_updates_cron() doesn't have any logic now we probably don't need this test.

    We probably need a new kernel for ReadinessCheckerManager to ensure that works expected if the key/value is manually deleted with a follow to add the cron test back when #3113971 is fixed.

Status: Needs review » Needs work

The last submitted patch, 59: 3162655-59.patch, failed testing. View results

tedbow’s picture

Issue summary: View changes
Status: Needs work » Needs review
FileSize
13.57 KB
50 KB
  1. Adding a kernel test for ReadinessCheckerManager
  2. Removing explicit cron test but adding test that deletes keyvalue results
phenaproxima’s picture

  1. +++ b/core/modules/auto_updates/auto_updates.info.yml
    @@ -0,0 +1,7 @@
    +description: 'Experimental module to develop automatic updates. Currently the module does not provide update functionality.'
    

    Although the module is hidden, I think we might want to change the description to accurately describe what it does, rather than just say "this module doesn't do any updating". How about: "Experimental module to develop automatic updates. Currently runs automated checks to ensure your site can be automatically updated in the future, but not does not actually perform updates"?

  2. +++ b/core/modules/auto_updates/auto_updates.install
    @@ -0,0 +1,76 @@
    +  if (!$checker_manager->isEnabled()) {
    +    return [];
    +  }
    

    I may be missing something here...but I'm still not super clear on why we need ReadinessCheckerManager::isEnabled(). Why would anyone ever want to disable the readiness checks, but not the auto_updates module itself? Even if we did allow people to do that, we'd be putting them in a potentially dangerous position (run updates -- which are major surgery -- but don't verify your condition at all beforehand). IMHO, we should lose this method; if people want to disable readiness checks, they should just uninstall the module entirely.

    @tedbow tells me that this might be something the contrib version of this module has. I was not involved in writing that module, so maybe someone who was, can explain the rationale here?

  3. +++ b/core/modules/auto_updates/auto_updates.install
    @@ -0,0 +1,76 @@
    +  $requirements['auto_updates_readiness']['title'] = t('Update readiness checks');
    

    A relatively minor stylistic complaint here, so feel free to ignore it, but: for better readability, and less repetition of keys, maybe we could structure this function like:

    $requirement['title'] = t('Update readiness checks');
    // do stuff with $requirement
    $requirements['auto_updates_readiness'] = $requirement;
    return $requirements;
    
  4. +++ b/core/modules/auto_updates/auto_updates.install
    @@ -0,0 +1,76 @@
    +      $requirements['auto_updates_readiness']['description'] = t('<a href=":link">Run readiness checks</a> manually.', [
    +        ':link' => $readiness_check_url->toString(),
    +      ]);
    

    s/manually/now, to match the other place we changed this :)

  5. +++ b/core/modules/auto_updates/auto_updates.install
    @@ -0,0 +1,76 @@
    +    $error_results = $checker_manager->getErrors();
    +    $warning_results = $checker_manager->getWarnings();
    +    $checker_results = array_merge($error_results, $warning_results);
    +    if (!empty($checker_results)) {
    +      $requirements['auto_updates_readiness']['severity'] = $error_results ? REQUIREMENT_ERROR : REQUIREMENT_WARNING;
    +      $requirements['auto_updates_readiness']['value'] = new PluralTranslatableMarkup(count($checker_results), '@count check failed:', '@count checks failed:');
    +      $requirements['auto_updates_readiness']['description'] = [
    +        '#theme' => 'item_list',
    +        '#items' => $checker_results,
    +      ];
    +    }
    

    What if getErrors() and getWarnings() returned render arrays, rather than simple arrays of strings? I can imagine some scenarios where a checker might want to return more detailed information than just a string. Discussion with @tedbow confirms that this is a definite possibility, but it introduces UX issues that @DyanneNova is looking into. So IMHO this needs some sort of plan.

  6. +++ b/core/modules/auto_updates/auto_updates.links.menu.yml
    @@ -0,0 +1,5 @@
    +auto_updates.settings:
    +  title: 'Automatic updates'
    +  route_name: auto_updates.settings
    +  description: 'Configure automatic update settings.'
    +  parent: system.admin_config_system
    

    If we remove ReadinessCheckerManager::isEnabled(), do we still need this link (and the associated settings form code)?

  7. +++ b/core/modules/auto_updates/auto_updates.module
    @@ -0,0 +1,84 @@
    +  $admin_context = \Drupal::service('router.admin_context');
    +  $route_match = \Drupal::routeMatch();
    +  if ($admin_context->isAdminRoute($route_match->getRouteObject()) && \Drupal::currentUser()->hasPermission('administer site configuration')) {
    

    It doesn't seem like we need to pass the route object to isAdminRoute(); its documentation says it will fall back to the current route if none is passed.

  8. +++ b/core/modules/auto_updates/auto_updates.module
    @@ -0,0 +1,84 @@
    +    $disabled_routes = [
    +      'update.theme_update',
    +      'system.theme_install',
    +      'update.module_update',
    +      'update.module_install',
    +      'update.status',
    +      'update.report_update',
    +      'update.report_install',
    +      'update.settings',
    +      'system.status',
    +      'update.confirmation_page',
    +    ];
    

    I kinda wish this were configurable in some way (maybe a flag on the route object, which we could add to these routes via a route subscriber). Not a big deal for the time being, though.

  9. +++ b/core/modules/auto_updates/auto_updates.module
    @@ -0,0 +1,84 @@
    +    if (!$checker_manager->hasRunRecently()) {
    +      $readiness_settings = Url::fromRoute('auto_updates.settings');
    +      \Drupal::messenger()->addError(t('Your site has not recently run an update readiness check. <a href=":url">Administer automatic updates</a> and run readiness checks manually.', [
    +        ':url' => $readiness_settings->toString(),
    +      ]));
    +    }
    

    Not sure the word "manually" is what we want here. Maybe we should just remove it outright?

    Also, the bigger issue here IMHO is it's confusing that we are linking to the settings form here. Why would we do that? Don't we want to just provide a link to run the checks directly?

  10. +++ b/core/modules/auto_updates/auto_updates.module
    @@ -0,0 +1,84 @@
    +    $results = $checker_manager->getErrors();
    +    if ($results) {
    +      // @todo Link "automatic updates" to documentation in
    +      //    https://www.drupal.org/node/3168405.
    +      \Drupal::messenger()->addError(t('Your site is currently failing readiness checks for automatic updates. It cannot be automatically updated until further action is performed.'));
    +      foreach ($results as $message) {
    +        \Drupal::messenger()->addError($message);
    +      }
    +    }
    

    Not sure how I feel about this verbiage. We're saying "you can't auto-update until you take further action", but there's no link or anything, and we can't guarantee that the readiness checkers will provide anything actionable here. Maybe we should just strike the second sentence, or change it to a passive voice, like "The following issues must be resolved before your site can be automatically updated"?

  11. +++ b/core/modules/auto_updates/auto_updates.module
    @@ -0,0 +1,84 @@
    +      \Drupal::messenger()->addWarning(t('Your site does not pass some readiness checks for automatic updates. Depending on the nature of the failures, it might effect the eligibility for automatic updates.'));
    

    "The eligibility for automatic updates" feels like detached, kinda bureaucratic phrasing :) How about something like, simply: "These may affect your site's ability to apply automatic updates"?

  12. +++ b/core/modules/auto_updates/auto_updates.module
    @@ -0,0 +1,84 @@
    +  $request_time = \Drupal::time()->getRequestTime();
    

    This doesn't appear to be used?

  13. +++ b/core/modules/auto_updates/auto_updates.routing.yml
    @@ -0,0 +1,19 @@
    +auto_updates.settings:
    +  path: '/admin/config/auto_updates'
    +  defaults:
    +    _form: '\Drupal\auto_updates\Form\SettingsForm'
    +    _title: 'Automatic Updates'
    +  requirements:
    +    _permission: 'administer software updates'
    +  options:
    +    _admin_route: TRUE
    +auto_updates.update_readiness:
    +  path: '/admin/config/auto_updates/readiness'
    +  defaults:
    +    _controller: '\Drupal\auto_updates\Controller\ReadinessCheckerController::run'
    +    _title: 'Update readiness checking'
    +  requirements:
    +    _permission: 'administer software updates'
    +    _custom_access: '\Drupal\auto_updates\Controller\ReadinessCheckerController::access'
    +  options:
    +    _admin_route: TRUE
    

    Why do these routes need to explicitly specify the _admin_route option? Won't the router automatically figure that out from the /admin prefix?

  14. +++ b/core/modules/auto_updates/auto_updates.services.yml
    @@ -0,0 +1,11 @@
    +  auto_updates.disk_space_checker:
    +    class: Drupal\auto_updates\ReadinessChecker\DiskSpace
    +    arguments: ['%app.root%']
    +    tags:
    +      - { name: readiness_checker}
    

    Supernit: there needs to be a space after readiness_checker in the tag name.

heddn’s picture

62.2: We intentionally added it to the contrib module so someone could have it installed and a future core module installed at the same time and not get duplicate messaging. I imagined a day someday, when we'd have the PSA notification but someone wouldn't want to have auto updates enabled. With how we are splitting things up so we are putting PSA into update.module and the auto updates into its own separate module, that need (as is pointed out) is less of an issue. So removing that config option is probably acceptable.

phenaproxima’s picture

  1. +++ b/core/modules/auto_updates/src/Controller/ReadinessCheckerController.php
    @@ -0,0 +1,76 @@
    +/**
    + * A controller for running Readiness Checkers.
    

    Supernit: "Readiness Checkers" does not need title-case capitalization.

  2. +++ b/core/modules/auto_updates/src/Controller/ReadinessCheckerController.php
    @@ -0,0 +1,76 @@
    +  public function run(): RedirectResponse {
    +    if (!array_filter($this->checkerManager->getErrors(TRUE))) {
    +      // @todo Link "automatic updates" to documentation in
    +      //   https://www.drupal.org/node/3168405.
    +      $this->messenger()->addStatus($this->t('No issues found. Your site is ready for automatic updates'));
    +    }
    +    return $this->redirect('auto_updates.settings');
    +  }
    

    I'm confused. Why aren't we handling the case when errors (or warnings) DO come back from the readiness checkers?

  3. +++ b/core/modules/auto_updates/src/ReadinessChecker/DiskSpace.php
    @@ -0,0 +1,63 @@
    +  protected const MEGABYTE_DIVISOR = 1000000;
    

    I think the coding standards demand a comment here. Maybe just something a little more descriptive than "Megabyte divisior" :)

  4. +++ b/core/modules/auto_updates/src/ReadinessChecker/DiskSpace.php
    @@ -0,0 +1,63 @@
    +    if ($messages = parent::getErrors()) {
    +      // @todo Reimplemenet so that every class that extends FileSystem base
    +      // will not the same error.
    +      return $messages;
    +    }
    

    I'm very confused by this block; I don't understand what it's doing or why. Can we refactor this now to clarify?

  5. +++ b/core/modules/auto_updates/src/ReadinessChecker/DiskSpace.php
    @@ -0,0 +1,63 @@
    +    $minimum_megabytes = static::MINIMUM_DISK_SPACE / static::MEGABYTE_DIVISOR;
    

    Is this the only place we use static::MEGABYTE_DIVISOR? If so, we should hard-code it. I don't think the mathematical definition of a megabyte needs to be overridden by subclasses, or should be :)

  6. +++ b/core/modules/auto_updates/src/ReadinessChecker/DiskSpace.php
    @@ -0,0 +1,63 @@
    +    if (!$this->areSameLogicalDisk($this->getRootPath(), $this->getVendorPath())) {
    

    Micro-optimization: maybe we should call getRootPath() and getVendorPath() once at the top of the method, both for readability and so we don't need to keep calling them.

  7. +++ b/core/modules/auto_updates/src/ReadinessChecker/DiskSpace.php
    @@ -0,0 +1,63 @@
    +      if (disk_free_space($this->getRootPath()) < (static::MINIMUM_DISK_SPACE / 2)) {
    +        $messages[] = $this->t('Drupal root filesystem "@root" has insufficient space. There must be at least @space megabytes free.', [
    +          '@root' => $this->getRootPath(),
    +          '@space' => $minimum_megabytes,
    +        ]);
    +      }
    +      if (disk_free_space($this->getVendorPath()) < (static::MINIMUM_DISK_SPACE / 2)) {
    +        $messages[] = $this->t('Vendor filesystem "@vendor" has insufficient space. There must be at least @space megabytes free.', [
    +          '@vendor' => $this->getVendorPath(),
    +          '@space' => $minimum_megabytes,
    +        ]);
    +      }
    

    Maybe I'm missing something, but shouldn't $minimum_megabytes be divided by 2 in both of these cases?

  8. +++ b/core/modules/auto_updates/src/ReadinessChecker/DiskSpace.php
    @@ -0,0 +1,63 @@
    +    elseif (disk_free_space($this->getRootPath()) < static::MINIMUM_DISK_SPACE) {
    

    According to my research, disk_free_space() can return FALSE on failure, which would break this if statement (and all the other ones too). We should probably have a wrapper method to call disk_free_space() and throw an exception if it returns FALSE.

  9. +++ b/core/modules/auto_updates/src/ReadinessChecker/FileSystemBase.php
    @@ -0,0 +1,91 @@
    +/**
    + * Base class for file system checkers.
    + */
    +abstract class FileSystemBase implements ReadinessCheckerInterface {
    

    Might be useful to expand the doc comment a bit to explain why code would want to extend this class, and why it's useful.

  10. +++ b/core/modules/auto_updates/src/ReadinessChecker/FileSystemBase.php
    @@ -0,0 +1,91 @@
    +  /**
    +   * Gets the root file path.
    +   *
    +   * @return string
    +   *   The root file path.
    +   */
    +  protected function getRootPath(): string {
    +    return $this->rootPath;
    +  }
    

    I don't think this doc comment is super useful. Maybe "Gets the absolute path at which Drupal is installed"?

  11. +++ b/core/modules/auto_updates/src/ReadinessChecker/FileSystemBase.php
    @@ -0,0 +1,91 @@
    +  /**
    +   * Get the vendor file path.
    +   *
    +   * @return string
    +   *   The vendor file path.
    +   */
    +  protected function getVendorPath(): string {
    +    // @todo Support finding the 'vendor' directory dynamically in
    +    // https://www.drupal.org/node/3166435.
    +    return $this->getRootPath() . DIRECTORY_SEPARATOR . 'vendor';
    +  }
    

    Correct me if I'm wrong, but won't this method not work for Composer-native installations that keep vendor next to docroot? If so, we really need to document that, here and in other visible places.

  12. +++ b/core/modules/auto_updates/src/ReadinessChecker/FileSystemBase.php
    @@ -0,0 +1,91 @@
    +    if ($root_statistics === FALSE || $vendor_statistics === FALSE) {
    +      throw new \Exception('Unable to determine if the root and vendor directories are on the same logic disk.');
    +    }
    

    IMHO this should be RuntimeException.

  13. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerInterface.php
    @@ -0,0 +1,28 @@
    +  /**
    +   * Gets the warnings.
    +   *
    +   * @return array
    +   *   An array of translatable strings if any checks fail, otherwise an empty
    +   *   array.
    +   */
    +  public function getWarnings(): array;
    +
    +  /**
    +   * Gets the errors.
    +   *
    +   * @return array
    +   *   An array of translatable strings if any checks fail, otherwise an empty
    +   *   array.
    +   */
    +  public function getErrors(): array;
    

    I'm not a huge fan of the short descriptions for these methods, but I can't think of better ones at this moment.

  14. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManager.php
    @@ -0,0 +1,218 @@
    +/**
    + * Defines a manager to run readiness checkers.
    + */
    +class ReadinessCheckerManager {
    

    IMHO, this should itself implement ReadinessCheckerInterface. It is a meta-checker.

  15. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManager.php
    @@ -0,0 +1,218 @@
    +  /**
    +   * Time (in seconds) since the last check after which we generate a warning.
    +   *
    +   * Defaults to 1 day.
    +   */
    +  private const LAST_CHECKED_WARNING = 60 * 60 * 24;
    

    This is a non-overridable constant, so it doesn't really "default" to anything. I wonder if this is worth making configurable (although IMHO we don't need to expose a UI for it).

  16. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManager.php
    @@ -0,0 +1,218 @@
    +  /**
    +   * The key/value storage.
    +   *
    +   * @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface
    +   */
    +  protected $keyValueExpirable;
    

    Isn't this actually KeyValueStoreExpirableInterface?

  17. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManager.php
    @@ -0,0 +1,218 @@
    +  public function addChecker(ReadinessCheckerInterface $checker, $priority = 0): ReadinessCheckerManager {
    

    $priority can be type hinted as an int.

  18. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManager.php
    @@ -0,0 +1,218 @@
    +   * @param bool $refresh
    +   *   Whether to refresh the results.
    

    We need to mention that it defaults to FALSE, and that if it is FALSE, cached results may be returned.

  19. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManager.php
    @@ -0,0 +1,218 @@
    +  /**
    +   * Gets the timestamp of the most recent run.
    +   *
    +   * @return int|null
    +   *   The timestamp of the most recently completed run, or NULL if no run has
    +   *   been completed.
    +   */
    +  public function getMostRecentRunTime(): int {
    +    return $this->keyValueExpirable->get('readiness_check_timestamp');
    +  }
    

    If this can return an int or null, the return type hint needs to be ?int.

  20. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManager.php
    @@ -0,0 +1,218 @@
    +  /**
    +   * Gets the current checker service Ids.
    +   *
    +   * @return string
    +   *   A concatenated list of checker service Ids delimited by '::'.
    +   */
    

    Supernit: "Ids" should be "IDs".

phenaproxima’s picture

  1. +++ b/core/modules/auto_updates/tests/modules/auto_updates_test/auto_updates_test.services.yml
    @@ -0,0 +1,8 @@
    +  auto_updates_test.checker:
    +    class: Drupal\auto_updates_test\ReadinessChecker\TestChecker
    +    tags:
    +      - { name: readiness_checker, priority: 2}
    

    Supernit: need a space after priority: 2.

  2. +++ b/core/modules/auto_updates/tests/modules/auto_updates_test/src/Datetime/TestTime.php
    @@ -0,0 +1,32 @@
    +/**
    + * Test service for altering the request time.
    + */
    +class TestTime extends Time {
    

    The update_test module already has something like this, so I kinda wish there were a generic time_test module somewhere which had this. Not in scope for this issue, though. Maybe a follow-up is in order?

  3. +++ b/core/modules/auto_updates/tests/modules/auto_updates_test/src/Datetime/TestTime.php
    @@ -0,0 +1,32 @@
    +  /**
    +   * The time format to for setting the test time.
    +   */
    +  const TIME_FORMAT = 'U';
    +
    +  /**
    +   * The state key for setting and getting the test time.
    +   */
    +  const STATE_KEY = 'auto_updates_test.mock_date_time';
    

    Do we really need to have these constants? It would probably be cleaner to just expose a public static method on this class which sets the fake time, thus keeping all the implementation details internal.

  4. +++ b/core/modules/auto_updates/tests/modules/auto_updates_test/src/ReadinessChecker/TestChecker.php
    @@ -0,0 +1,52 @@
    +  /**
    +   * Gets the test messages set in state.
    +   *
    +   * @return string[][]
    +   *   The test messages.
    +   *
    +   * @see \Drupal\Tests\auto_updates\Kernel\ReadinessChecker\TestCheckerTrait::setTestMessages()
    +   */
    +  private static function getMessages() {
    +    return \Drupal::state()->get(
    +        static::STATE_KEY,
    +        []
    +      ) + [
    +        'errors' => [],
    +        'warnings' => [],
    +      ];
    +  }
    

    Is there a reason this needs to be static? Also, it's a bit hard to read, can it maybe be reformatted a little?

  5. +++ b/core/modules/auto_updates/tests/src/Functional/ReadinessCheckerTest.php
    @@ -0,0 +1,230 @@
    +  /**
    +   * A user how can view the status report and run readiness checkers.
    

    s/how/who

  6. +++ b/core/modules/auto_updates/tests/src/Functional/ReadinessCheckerTest.php
    @@ -0,0 +1,230 @@
    +    // Disable automated_cron before installing auto_updates. This ensures we
    +    // are testing that auto_updates runs the checkers when the module itself
    +    // is installed and they weren't run on cron.
    +    $this->container->get('module_installer')->uninstall(['automated_cron']);
    

    Not a big deal, but the testing profile doesn't enable automated_cron, so this line isn't really doing anything. But it makes sense to have this there as future-proofing in case automated_cron does get added in the future.

  7. +++ b/core/modules/auto_updates/tests/src/Functional/ReadinessCheckerTest.php
    @@ -0,0 +1,230 @@
    +    // Confirm a user without the permission to run readiness checks does not
    +    // have a link to run the checks when the checks need to be run again.
    +    $this->setFakeTime('+2 days');
    +    $this->drupalLogin($this->reportViewerUser);
    +    $this->drupalGet('admin/reports/status');
    +    $this->assertReadinessReportMatches('Your site has not recently checked if it is ready to apply automatic updates. Readiness checks were last run %s ago.');
    

    Maybe we should also assert the absence of the link?

  8. +++ b/core/modules/auto_updates/tests/src/Functional/ReadinessCheckerTest.php
    @@ -0,0 +1,230 @@
    +    $this->assertReadinessReportMatches('Your site has not recently checked if it is ready to apply automatic updates.'
    +      . ' Readiness checks were last run %s ago. Run readiness checks now.');
    

    Why is this split across lines?

  9. +++ b/core/modules/auto_updates/tests/src/Functional/ReadinessCheckerTest.php
    @@ -0,0 +1,230 @@
    +    $this->setTestMessages(['OMG 🚒. Your server is on 🔥!']);
    

    Oh, man. Embedding emoji directly in tests? You shouldn't give me such ideas...

  10. +++ b/core/modules/auto_updates/tests/src/Functional/ReadinessCheckerTest.php
    @@ -0,0 +1,230 @@
    +    // @todo If coming from the status report page should you be redirected there?
    +    //   This is how 'Run cron' works.
    

    Er...redirected where, exactly?

  11. +++ b/core/modules/auto_updates/tests/src/Functional/ReadinessCheckerTest.php
    @@ -0,0 +1,230 @@
    +    $assert->checkboxChecked('enable_readiness_checks');
    +    $assert->pageTextNotContains('Access denied');
    

    The second assertion is basically redundant here.

  12. +++ b/core/modules/auto_updates/tests/src/Functional/ReadinessCheckerTest.php
    @@ -0,0 +1,230 @@
    +    // @todo Should we always show when the checks were last run and a link to
    +    //   run when there is an error?
    

    Yes, IMHO. Both of these things could help the user clear up the errors.

  13. +++ b/core/modules/auto_updates/tests/src/Functional/ReadinessCheckerTest.php
    @@ -0,0 +1,230 @@
    +    $this->setTestMessages(['OMG 🔌. Some one unplugged the server! How is this site even running?']);
    

    😂 ERR: System currently running on duct tape and prayer.

  14. +++ b/core/modules/auto_updates/tests/src/Functional/ReadinessCheckerTest.php
    @@ -0,0 +1,230 @@
    +    /** @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface $keyValue */
    +    $keyValue = $this->container->get('keyvalue.expirable')->get('auto_updates');
    +    $keyValue->delete('readiness_check_results');
    

    I think that we should expose a method on ReadinessCheckerManager which explicitly clears the cached results, and call it here.

  15. +++ b/core/modules/auto_updates/tests/src/Functional/ReadinessCheckerTest.php
    @@ -0,0 +1,230 @@
    +    // Confirm that new checker message is not displayed because the checker was
    +    // not run again.
    +    $this->assertReadinessReportMatches('1 check failed: 😿Oh no! A hacker now owns your files!');
    

    I feel like also asserting the absence of the bogus message would be good here, no? I guess it depends on what assertReadinessReportMatches() does, which I haven't yet seen at this point in my review.

  16. +++ b/core/modules/auto_updates/tests/src/Functional/ReadinessCheckerTest.php
    @@ -0,0 +1,230 @@
    +    $this->setTestMessages(['😲Your site is running on Commodore 64! Not powerful enough to do updates!']);
    

    That's what you think. 😎

  17. +++ b/core/modules/auto_updates/tests/src/Functional/ReadinessCheckerTest.php
    @@ -0,0 +1,230 @@
    +    $text = $this->getSession()->getPage()->find(
    +      'css',
    +      'details.system-status-report__entry:contains("Update readiness checks")'
    +    )->getText();
    

    This is probably fine as it is, but it will target any details element that contains the words "Update readiness checks". Usually I try to find the summary with those words, then call $summary->getParent()->getText(), for a bit more safety in this regard.

tedbow’s picture

#62.2

This patch removes the setting enable/disable readiness checkers

  1. as @heddn says this setting probably doesn't make sense sense the new module is just for auto updates
  2. Remove the settings from as we no longer have a setting
  3. Added new "status report" which also allows running the update manually like the settings from did.

Status: Needs review » Needs work

The last submitted patch, 66: 3162655-66.patch, failed testing. View results

tedbow’s picture

Issue summary: View changes
Status: Needs work » Needs review
Issue tags: +Needs usability review
FileSize
23.6 KB
45.14 KB

Test failures in #66

  1. Removing the configure link from the info.yml file

@phenaproxima thanks for the reviews!
re #62

  1. updated description
  2. fixed in #66
  3. good suggestion. fixed
  4. fixed
  5. I added a item under remaining items. We many want more changes to API like have a summary message and then full list. I think we should run this by the UX team.
  6. fixed in #66
  7. fixed
  8. This is the same as we do in update_page_top(), we have a hard-coded list there.

    Looks like this has been this way all of Drupal 8 and I am guessing longer. I think if we haven't had need for the list of disabled routes for available updates messages to be easily configurable then we won't need it for this.
    I don't think we should add any complexity until some has a real world not hypothetical use case for it.

  9. I removed the settings form above but changed it into a controller that just allows you to run the checkers.

    But I don't think it serves much purpose now since we can run the checkers from the status page.

    So adding ability to run the checkers directly here.

  10. lets leave this until the UX review
  11. leaving for UX review because it is already confusing that you could have both "Your site is currently failing readiness checks" and "'Your site does not pass some readiness checks" on a single page
  12. yep left over from previous changes. fixed
  13. fixed but also we are down to only 1 route here
  14. fixed

re #64

  1. fixed
  2. Currently this can be run 2 places.

    1) as of this patch on any admin page that displays the messages that the checker has not be run recently
    2) on the status report page

    In both case if there are any messages they will be displayed on the page anyways.

    I also updated this to check for warnings or errors

  3. added a comment
  4. Basically in the contrib module we had a few checkers extending FileSystemBase. All of them need a valid to webroot and vendor directory to do their checks. So they would all call the parent to check if these can be located first. If there were any errors they would not be able do their own checks so they just returned the parents checks.

    The problem is that if you have more than one class extending FileSystemBase then in the case the errors come from the parent they would all have exactly the same message. So the use would see it duplicated.

    I refactored this here. Hopefully the new message to the user makes it more clear.

  5. agreed. in the contrib module there are more uses of it with more checkers but we can add it back if we need it later
  6. fixed
  7. fixed.
  8. fixed.
  9. fixed.
  10. fixed.
  11. I personally think since we have a follow up to determine this and the module is experimental and hidden this should be enough. When the follow up is done we would have no need for any documentation because the limitation would not exist.
  12. fixed.
  13. me neither.
  14. I don't think so. We have no need for the manager to be an api. I don't see the advantage and I think would only lead to confusion
  15. change the comment to say it equals a day
  16. fixed.
  17. fixed.
  18. fixed.
  19. fixed.

Stopping now but will do 65 next

Status: Needs review » Needs work

The last submitted patch, 68: 3162655-68.patch, failed testing. View results

voleger’s picture

Addressed "Support finding the 'vendor' directory dynamically". Also added optional argument of vendor dir path for FileSystemBase class constructor.
And fixed typo in the method name - hasValidVendorPatch -> hasValidVendorPath

benjifisher’s picture

Issue tags: -Needs usability review

Usability review

We discussed this issue at #3188068: Drupal Usability Meeting 2020-12-18, so I am removing the tag for a usability review. I have a feeling that it will be added back as this issue gets closer to being done.

I apologize for not reading the earlier comments on this issue. I may be repeating things that have been said before.

Some requirements:

  1. Since this module will add a message to every admin page (when there are errors or warnings), it is important to keep that message short.
  2. That message should have a link to more information, but too many links can cause decision paralysis. Ideally, provide just one link.

Suggestions:

  1. Because of Requirement 1, do not add a warning message if there is already an error message. Instead, add to the error message something like "There are also warning messages from ..." and then list the checkers that produced warnings.
  2. Because of Requirement 2, individual checkers should not add links to the status message. The main module should have a link to the site Status report. Possibly a second link to the same page: the first link goes to the errors from this module and the second goes to the warnings.
  3. On the Status report, consider using a <details> element (initially closed) for each checker that reports an error or warning. If there are both errors and warnings, and you follow the previous suggestion, then the labels on these Details elements will match the checkers listed at the end of the error message, which will help orient the site administrator.
  4. At the meeting, no one objected to adding messages on the Status report to the existing section for Drupal core update status.
  5. At the meeting, we agreed with the suggestion to reverse the options compared to the existing contrib module: first enable automatic updates for security releases; if selected, then enable the option to enable automatic updates for all releases.

Thinking some more about the last point, my personal opinion is that this is not a good place to use progressive disclosure. Consider three radio buttons: info only, automatic security updates, automatic all updates.

benjifisher’s picture

We discussed one more thing at today's Usability meeting: what message to show when Drupal cannot connect to the PSA feed from drupal.org. It is difficult to give actionable advice about how to configure a server that cannot make outgoing requests, but it is easy to give a link to Security advisories > Public service announcements. (Look, I just did it!)

tedbow’s picture

Assigned: Unassigned » tedbow
  1. @benjifisher thanks for writing up the details from the UX meeting. Very useful! Assigning to myself to work on this
  2. re
    At the meeting, no one objected to adding messages on the Status report to the existing section for Drupal core update status.

    I didn't think about this at the meeting but currently this is not possible without #309040: Add hook_requirements_alter() because this is separate module without altering the update module. But because this is experimental module I don't think we should do this.

    I will put a todo in for #309040.

  3. @voleger thanks for the update regarding finding the vendor directory directly.

    We had created #3166435: Support alternate 'vendor' folder locations in auto_updates module as a follow-up for this but probably we can just address it here. I think your solution is good but if others think we should do it another way or if it is more complicated than it seems then I think we should push it back to the other issue as follow-up.

voleger’s picture

#74.3 sure, I agree. Just for clarifying: currently applied detection of the folder based on the presence of the composer Loader class which is always present in the vendor directory. That will introduce accurate vendor directory detection even if the location and the name of the vendor directory were changed through configuration from the composer.json file. That's mean the vendor folder location detection will always respect the current project composer configuration (See an example at the follow-up issue comment). Also, test coverage forced me to introduce an optional manual set of composer directory in the FileSystemBase constructor. From the current perspective not sure when and where it would be useful but at least we have an option to define the location of the vendor dir manually.

phenaproxima’s picture

  1. +++ b/core/modules/auto_updates/auto_updates.info.yml
    @@ -0,0 +1,6 @@
    +description: 'Experimental module to develop automatic updates. Currently the module provides checks for update readiness but does not yet provide update functionality.'
    

    I think we should reword this to explain what the module actually does -- not what it will one day do -- but it can wait for now.

  2. +++ b/core/modules/auto_updates/auto_updates.links.menu.yml
    @@ -0,0 +1,6 @@
    +
    +auto_updates.status:
    

    Looks like there's an errant blank line here?

  3. +++ b/core/modules/auto_updates/auto_updates.module
    @@ -0,0 +1,83 @@
    +  if ($admin_context->isAdminRoute() && \Drupal::currentUser()->hasPermission('administer site configuration')) {
    

    The auto_updates.update_readiness route needs the "administer software updates" permission, but we're showing this message to people with the "administer site configuration" permission. If they click the "Run readiness checks" link, won't they get a 403 if they don't have the "administer software updates" permission?

    I could be missing something, of course, but if I'm not, then maybe we should have test coverage of this.

  4. +++ b/core/modules/auto_updates/auto_updates.module
    @@ -0,0 +1,83 @@
    +function auto_updates_cron() {
    +  /** @var \Drupal\auto_updates\ReadinessChecker\ReadinessCheckerManager $checker_manager */
    +  $checker_manager = \Drupal::service('auto_updates.readiness_checker_manager');
    +  $checker_manager->getErrors();
    +}
    

    This could use a comment to explain why we're getting errors, but not doing anything with them.

  5. +++ b/core/modules/auto_updates/auto_updates.module
    @@ -0,0 +1,83 @@
    +  // Run the readiness checkers when any modules are installed in case they have
    +  // provide readiness checker services. Because ReadinessCheckerManager caches
    

    Either the word "have", or the word "provide", shouldn't be there.

  6. +++ b/core/modules/auto_updates/auto_updates.services.yml
    @@ -0,0 +1,11 @@
    +      - { name: service_collector, tag: readiness_checker, call: addChecker }
    

    Maybe we should namespace the tag, like auto_updates.readiness_checker?

  7. +++ b/core/modules/auto_updates/src/Controller/ReadinessCheckerController.php
    @@ -0,0 +1,65 @@
    +      // that site is ready. If there are messages the page will display them.
    

    Should say "If there are messages, the status page will display them."

    But I'm wondering...the status page will also display an "okay" message if everything is hunky-dory, right? So why wouldn't we just call getErrors() here, and immediately redirect to the status report?

  8. +++ b/core/modules/auto_updates/src/Controller/ReadinessCheckerController.php
    @@ -0,0 +1,65 @@
    +      $this->messenger()->addStatus($this->t('No issues found. Your site is ready for automatic updates'));
    

    If we're keeping this, it should have a period at the end of the sentence.

  9. +++ b/core/modules/auto_updates/src/ReadinessChecker/DiskSpace.php
    @@ -0,0 +1,84 @@
    +  protected static function getFreeSpace(string $path) {
    +    if (!disk_free_space($path)) {
    +      throw new \RuntimeException('disk_free_space() failed.');
    +    }
    +  }
    

    This method's name is misleading. The "get" suggests that it will return something, but it doesn't. We should probably rename this and make the doc comment more detailed.

  10. +++ b/core/modules/auto_updates/src/ReadinessChecker/DiskSpace.php
    @@ -0,0 +1,84 @@
    +    if (!$has_valid_root && !$has_valid_vendor) {
    +      return [$this->t('Free disk space cannot be determined because the web root and vendor directories could not be located.')];
    +    }
    

    Why do we need this? Aren't we kind of duplicating this logic immediately below?

  11. +++ b/core/modules/auto_updates/src/ReadinessChecker/FileSystemBase.php
    @@ -0,0 +1,98 @@
    +  protected function hasValidRootPath() {
    

    Should probably have a bool return type?

  12. +++ b/core/modules/auto_updates/src/ReadinessChecker/FileSystemBase.php
    @@ -0,0 +1,98 @@
    +  protected function hasValidVendorPatch() {
    

    This should be "path", not "patch". Also, should it have a

    bool</code return type?
    </li>
    
    <li>
    <code>
    +++ b/core/modules/auto_updates/src/ReadinessChecker/FileSystemBase.php
    @@ -0,0 +1,98 @@
    +   * @throws \Exception
    +   *   Thrown if the an error is found trying get the directory information.
    

    Should be \RuntimeException.

  13. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManager.php
    @@ -0,0 +1,206 @@
    +    else {
    +      $results = $this->keyValueExpirable->get('readiness_check_results');
    +
    +      // If the checkers have not changed return the results.
    +      if ($results && $results['checkers'] === $this->getCurrentCheckerIds()) {
    +        return $results['messages'];
    +      }
    +      $this->keyValueExpirable->delete('readiness_check_results');
    +    }
    

    Why are we deleting the cached results in this case?

  14. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManager.php
    @@ -0,0 +1,206 @@
    +  public function getMostRecentRunTime():?int {
    

    Nit: Missing a space?

  15. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManager.php
    @@ -0,0 +1,206 @@
    +   * Gets the current checker service Ids.
    

    Nit: s/Ids/IDs

  16. +++ b/core/modules/auto_updates/src/ReadinessChecker/ReadinessCheckerManager.php
    @@ -0,0 +1,206 @@
    +
    +  /**
    +   * Get error messages.
    +   *
    +   * @param bool $refresh
    +   *   Whether to refresh the results.
    +   *
    +   * @return string[]
    +   *   The error messages.
    +   */
    +  public function getErrors(bool $refresh = FALSE) {
    +    return $this->run($refresh)['errors'];
    +  }
    +
    +  /**
    +   * Get warning messages.
    +   *
    +   * @param bool $refresh
    +   *   Whether to refresh the results.
    +   *
    +   * @return string[]
    +   *   The warning messages.
    +   */
    +  public function getWarnings(bool $refresh = FALSE) {
    +    return $this->run($refresh)['warnings'];
    +  }
    

    Why no return type hints?

tedbow’s picture

  1. re #76 @phenaproxima thanks for the review but I think you reviewing the last patch not the fork branch now that this issue is using a merge request.
  2. I am working on the changes needed for the UX feedback. Right now I have change the API to deal with summaries and not just messages.

    Currently I just got the only test passing using the changes to ReadinessCheckerManager and ReadinessCheckerInterface. Since we are now caching not just string messages but summaries too I have create ReadinessCheckerResult which is value object for storing results from checkers

  3. I will not work on actually implementing the UX changes that need the summaries but if anyone wants to look at the changes to the in regards to the files I mentioned in 2) that would be appreciated.

    Leaving assigned to myself as I am currently working on this

tedbow’s picture

Status: Needs work » Needs review

re #75

from @voleger

Also, test coverage forced me to introduce an optional manual set of composer directory in the FileSystemBase constructor. From the current perspective not sure when and where it would be useful but at least we have an option to define the location of the vendor dir manually.

I think we should do what we can not to have this option argument we don't really need.

I have a new TestDiskSpaceInvalidVendor class that has an invalid vendor directory like we already have to. Will push up this change

tedbow’s picture

Assigned: tedbow » Unassigned
  1. unassigning myself to get feedback for MR comment above "25 January 2021 at 22:40"
    @phenaproxima if you agree will this change I can make and add unit tests.
  2. for #78 I pushed the commit, it is above the comment

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

tedbow’s picture

I removed the DiskSpace checker from this merge request. I think it should be added in it's own issue. I created #3214654: [PP-1] Create diskspace readiness checker as a follow up.

We have the test readiness checkers that prove the api works.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

tedbow’s picture

Status: Needs review » Closed (duplicate)
Related issues: +#3253158: [PP-1] Add Alpha level Experimental Automatic Updates module

Sorry I did not update this issue sooner.

Since we worked on this issue it was decided for various reasons explained #3220205: Proposal: As pathway to Drupal core use new 2.x.x branch for Composer Based Updates that we would use the 8.x-2.x branch in contrib.

We have since done and created a issue to opened this #3253158: [PP-1] Add Alpha level Experimental Automatic Updates module

Marking this a duplicate of that issue