Commit 448da85e authored by Ben Mullins's avatar Ben Mullins Committed by Tim Plunkett
Browse files

Issue #3245770 by tedbow, kunal.sachdev, bnjmnm, narendraR, gaurav.kapoor,...

Issue #3245770 by tedbow, kunal.sachdev, bnjmnm, narendraR, gaurav.kapoor, bakulahluwalia, grasmash, chrisfromredfin, tim.plunkett, fjgarlin, phenaproxima: Create a service to composer install via package_manager from Automatic Updates
parent f0e706c8
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -17,6 +17,9 @@
        "composer/semver": "^3.2",
        "ext-simplexml": "*"
    },
    "require-dev": {
        "drupal/automatic_updates": "2.0.0"
    },
    "config": {
        "optimize-autoloader": true,
        "sort-packages": true
+3 −0
Original line number Diff line number Diff line
@@ -57,6 +57,9 @@ build:

          - sed -i '/.*FINAL_STATUS" == "1".*/i cd $TOP_LEVEL' modules/contrib/project_browser/commit-code-check.sh
          - sed -i '/.*FINAL_STATUS" == "1".*/i printf "\n"' modules/contrib/project_browser/commit-code-check.sh
          # \Drupal\package_manager\Validator\ComposerExecutableValidator will only validate 2.3.5 and above. We could
          # remove this when https://www.drupal.org/project/automatic_updates/issues/3302524 is fixed.
          - composer self-update 2.3.5

          # DEBUG: AFTER
          # - cat modules/contrib/project_browser/commit-code-check.sh
+109 −0
Original line number Diff line number Diff line
<?php

namespace Drupal\project_browser\ComposerInstaller;

use Drupal\package_manager\Event\StageEvent;
use Drupal\package_manager\Exception\ApplyFailedException;
use Drupal\package_manager\Exception\StageValidationException;
use Drupal\package_manager\Stage;
use Drupal\project_browser\Exception\InstallException;

/**
 * Defines a service to perform installs.
 *
 * @internal
 *   This is an internal part of Package Manager and may be changed or removed
 *   at any time without warning. External code should not interact with this
 *   class.
 */
final class Installer extends Stage {

  /**
   * Begins the install operation.
   *
   * @param string[] $package_versions
   *   The versions of the package versions to install, keyed by package name.
   * @param int|null $timeout
   *   (optional) How long to allow the file copying operation to run before
   *   timing out, in seconds, or NULL to never time out. Defaults to 300
   *   seconds.
   *
   * @return string
   *   The unique ID of the stage.
   *
   * @throws \InvalidArgumentException
   *   Thrown if no package versions are provided.
   */
  public function begin(array $package_versions, ?int $timeout = 300): string {
    if (empty($package_versions)) {
      throw new \InvalidArgumentException("No packages to begin the install");
    }

    // Ensure that package versions are available to pre-create event
    // subscribers. We can't use ::setMetadata() here because it requires the
    // stage to be claimed, but that only happens during ::create().
    $this->tempStore->set(static::TEMPSTORE_METADATA_KEY, [
      'packages' => [
        'production' => $package_versions,
        'dev' => [],
      ],
    ]);
    return $this->create($timeout);
  }

  /**
   * Returns the package versions that will be required during the install.
   *
   * @return string[][]
   *   An array with two sub-arrays: 'production' and 'dev'. Each is a set of
   *   package versions, where the keys are package names and the values are
   *   version constraints understood by Composer.
   */
  public function getPackageVersions(): array {
    return $this->getMetadata('packages');
  }

  /**
   * Stages the install.
   */
  public function stage(): void {
    $this->checkOwnership();

    // Convert an associative array of package versions, keyed by name, to
    // command-line arguments in the form `vendor/name:version`.
    $map = function (array $versions): array {
      $requirements = [];
      foreach ($versions as $package => $version) {
        $requirements[] = "$package:$version";
      }
      return $requirements;
    };
    $versions = array_map($map, $this->getPackageVersions());
    $this->require($versions['production']);
  }

  /**
   * {@inheritdoc}
   */
  protected function dispatch(StageEvent $event, callable $on_error = NULL): void {
    try {
      parent::dispatch($event, $on_error);
    }
    catch (StageValidationException $e) {
      throw new InstallException($e->getResults(), $e->getMessage(), $e->getCode(), $e);
    }
  }

  /**
   * {@inheritdoc}
   */
  public function apply(?int $timeout = 600): void {
    try {
      parent::apply($timeout);
    }
    catch (ApplyFailedException $exception) {
      throw new InstallException([], 'The install operation failed to apply. The install may have been partially applied. It is recommended that the site be restored from a code backup.', $exception->getCode(), $exception);
    }
  }

}
+11 −0
Original line number Diff line number Diff line
<?php

namespace Drupal\project_browser\Exception;

use Drupal\package_manager\Exception\StageValidationException;

/**
 * Defines a custom exception for a failure during an install.
 */
class InstallException extends StageValidationException {
}
+34 −0
Original line number Diff line number Diff line
<?php

namespace Drupal\project_browser;

use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\ServiceProviderBase;
use Drupal\project_browser\ComposerInstaller\Installer;
use Symfony\Component\DependencyInjection\Reference;

class ProjectBrowserServiceProvider extends ServiceProviderBase {

  /**
   * {@inheritdoc}
   */
  public function alter(ContainerBuilder $container) {
    if (array_key_exists('package_manager', $container->getParameter('container.modules'))) {
      parent::register($container);
      $container->register('project_browser.installer')
        ->setClass(Installer::class)
        ->setArguments([
          new Reference('config.factory'),
          new Reference('package_manager.path_locator'),
          new Reference('package_manager.beginner'),
          new Reference('package_manager.stager'),
          new Reference('package_manager.committer'),
          new Reference('file_system'),
          new Reference('event_dispatcher'),
          new Reference('tempstore.shared'),
          new Reference('datetime.time'),
        ]);
    }
  }

}
Loading