Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
meltmedia/meltconsole / src / App / Commands / CreateEnvironmentCommand.php
Size: Mime:
<?php

namespace MeltConsole\App\Commands;

use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Question\ConfirmationQuestion;

/**
 * Handles the functionality to create new environments on the server.
 *
 * @package MeltConsole\App\Commands
 */
class CreateEnvironmentCommand extends MeltCommand {

  /**
   * Question helper.
   *
   * @var \Symfony\Component\Console\Helper\QuestionHelper
   */
  private $question;

  /**
   * {@inheritdoc}
   */
  protected function configure() {

    // Initial config for command.
    $this
      ->setName('melt:create-environment')
      ->setAliases(['melt:ce'])
      ->setDescription('Creates new environment on the remote server.')
      ->setHelp('This command allows you to create a user...')
      ->addOption('env', 'e', InputOption::VALUE_REQUIRED, 'Environment e.g dev, qa, stage')
      ->addOption('branch', 'b', InputOption::VALUE_REQUIRED, 'Branch to use');
  }

  /**
   * {@inheritdoc}
   */
  protected function interact(InputInterface $input, OutputInterface $output) {
    parent::interact($input, $output);

    $env = NULL;
    $this->question = $this->getHelper('question');

    if (!$input->getOption('env')) {
      // Ask the user some stuff.
      $helper = $this->getHelper('question');
      $question = new Question('Enter the environment name you\'d like to create e.g. dev, stage, qa, prod: ');
      $answer = $helper->ask($input, $output, $question);
      $env = strtolower(preg_replace('/\s+/', '_', trim($answer)));

      if (empty($env)) {
        throw new \Exception('Please enter a value for environment name.');
      }

      // Set options based on users answer.
      $input->setOption('env', $env);
    }

    if ($env && !$input->getOption('branch')) {
      $remotes = $this->getRemoteBranches();
      $question = new ChoiceQuestion("Which branch would you like deployed to <info>{$env}</info> environment? ", $remotes);
      $branch = $this->question->ask($input, $output, $question);
      $input->setOption('branch', $branch);

    }

  }

  /**
   * {@inheritdoc}
   */
  protected function execute(InputInterface $input, OutputInterface $output) {

    $env = $input->getOption('env');
    $branch = $input->getOption('branch');

    if (!$this->validateEnvironmentExistsOnServer($env)) {
      $dirname = "{$this->projectName}-{$env}";
      $helper = $this->getHelper('question');

      // Ask questions.
      $question1 = new ConfirmationQuestion(
        "Do you really want to create a <info>{$dirname}</info> environment? (n/Y) ",
        TRUE
      );
      $proceed = $helper->ask($input, $output, $question1);

      $serverEnvs = $this->getEnvironmentsFromServer();
      $chosenEnv = NULL;

      if (!empty($serverEnvs)) {
        $serverEnvs[] = 'none';
        $question2 = new ChoiceQuestion(
          'What environment would you like to sync with?',
          $serverEnvs
        );
        $chosenEnv = $helper->ask($input, $output, $question2);
      }

      if ($proceed) {

        // Clone project directory.
        $output->writeln("Creating <info>{$dirname}</info> environment...");
        $this->cloneNewEnvironment($env, $branch);
        $this->setupServer($env);

        // Install dependencies.
        $this->runServerCommands([
          "cd /{$this->serverRoot}/{$dirname}",
          'composer install --no-dev --optimize-autoloader',
        ]);

        // Sync database and files if they chose an existing environment
        if ($chosenEnv && $chosenEnv !== 'none') {
          // Sync database with another environment.
          $output->writeln("Syncing database with <info>{$this->projectName}-{$chosenEnv}</info>, this may take a while...");
          $this->syncDatabaseWithAnotherEnvironment($chosenEnv, $env);

          // Sync files with another environment.
          $output->writeln('Syncing files now...');
          $this->syncFilesWithAnotherEnvironment($chosenEnv, $env);

          $output->writeln("Syncing complete!");
        }

        // @TODO Create brand new environment if they
        // commenting this out for now because we may not need to do this
//        if($chosenEnv == 'none') {
//           // @TODO automatically install drupal for user. Figure out a better way.
//          $this->runServerCommands([
//            "cd /{$this->serverRoot}/{$dirname}",
//            'drush si',
//          ]);
//        }

        $output->writeln("Creation of <info>{$dirname}</info> complete!");

        // Add drush alias.
        $output->writeln("Adding drush alias for <info>${dirname}</info>...");
        $this->addDrushAliasForEnv($env);
        $output->writeln("Baddabing baddaboom! Drush alias created!!! Go check <info>{$this->pathToDrushAlias}</info>. You're welcome!");

        // Useful output.
        $output->writeln([
          '----------------------',
          "Environment URL: <info>https://{$this->projectName}-{$env}.drupal.meltdemo.com</info>",
          "Drush alias example: <info>lando drush @melt.{$env} ssh lando drush pm-list</info>",
          '----------------------',
        ]);
      }

    }
    else {
      throw new \Exception(sprintf(
        '%s environment already exists on the server. Please choose another environment name.',
        $env
      ));
    }
  }

  /**
   * Create's new directory on server by cloning the git repo.
   *
   * @param string $env
   *   Directory name.
   * @param string $branch
   *   Git branch name.
   */
  private function cloneNewEnvironment(string $env, string $branch) {
    $config = $this->getConfig();
    $dirname = "{$this->projectName}-{$env}";
    $giturl = $config['melt']['git'];

    $this->runServerCommands([
      "cd /{$this->serverRoot}",
      "git clone -b {$branch} {$giturl} {$dirname}",
      "cd {$dirname}"
    ]);
  }

  /**
   * Helper for `melt:ss` command.
   *
   * @param string $env
   *  Environment name
   */
  private function setupServer(string $env) {
    $dirname = "{$this->projectName}-{$env}";

    // Need to `source ~/.meltconsole.env` so we can read environment variables
    $this->runServerCommands([
      "source ~/.meltconsole.env; cd /{$this->serverRoot}/{$dirname}",
      "meltconsole melt:ss --projectName={$dirname} --projectRoot=/{$this->serverRoot}/{$dirname}",
    ]);
  }

  /**
   * Syncs files between environments.
   *
   * --TODO-- may need to refactor this to use some other mechanism of importing
   * files from one environment to another. This works for now since they're
   * on the same server.
   *
   * @param string $srcEnv
   *   Source environment.
   * @param string $destEnv
   *   Destination environment.
   */
  private function syncFilesWithAnotherEnvironment(string $srcEnv, string $destEnv) {
    // Create destination dirname.
    $dest = "{$this->projectName}-{$destEnv}";
    $filesDir = 'docroot/sites/default/files';
    $src = "{$this->projectName}-{$srcEnv}";

    $this->runServerCommands([
      // Make sure `files` dir exists before rsyncing
      "[ -d /{$this->serverRoot}/{$src}/{$filesDir} ]", // Make sure `files` dir exists before rsyncing

      // Rsync files from source directory to destination
      "rsync -az /{$this->serverRoot}/{$src}/{$filesDir}/ /{$this->serverRoot}/{$dest}/{$filesDir}/",
    ]);

  }

  /**
   * Syncs database between environments.
   *
   * --TODO-- may need to refactor this to use some other mechanism of importing
   * a database from one environment to another. This works for now since
   * they're on the same server.
   *
   * @param string $srcEnv
   *   Source environment.
   * @param string $destEnv
   *   Destination environment.
   */
  private function syncDatabaseWithAnotherEnvironment(string $srcEnv, string $destEnv) {

    // Create destination dirname.
    $dest = "{$this->projectName}-{$destEnv}";

    $src = "{$this->projectName}-{$srcEnv}";
    // Year month day hour minute.
    $timestamp = date('Ymdhi');
    $backup_name = "{$src}-backup-{$timestamp}.sql";
    $backup_dir = '/opt/backups';

    $this->runServerCommands([
      // Change into src directory.
      "cd /{$this->serverRoot}/{$src}",

      // Add to backups directory
      "drush sql:dump --gzip --result-file={$backup_dir}/{$backup_name}",

      // Copy backup to destination.
      "cp {$backup_dir}/{$backup_name}.gz /{$this->serverRoot}/{$dest}/",

      // Change to destination directory.
      "cd /{$this->serverRoot}/{$dest}",

      // Unzip database dump
      "gunzip {$backup_name}.gz",

      // Import database dump using `drush sql-cli` command
      "drush sqlc < {$backup_name}",

      // Remove the src's backup from the destination.
      "rm {$backup_name}",
    ]);
  }

  /**
   * Adds a new alias to the @var $pathToDrushAlias.
   *
   * @param string $env
   *   Environment name.
   */
  protected function addDrushAliasForEnv(string $env) {
    $aliases = $this->getDrushAliases();

    // Copy an alias for our new alias.
    $new_alias[$env] = reset($aliases);

    // @TODO figure out a better way to set host and root.
    $new_alias[$env]['uri'] = "{$this->projectName}-{$env}.drupal.meltdemo.com";
    $new_alias[$env]['root'] = "/{$this->serverRoot}/{$this->projectName}-{$env}";

    // Combine new alias with the current.
    $updated_aliases = array_merge($aliases, $new_alias);
    $this->writeToYamlFile($updated_aliases, $this->pathToDrushAlias);
  }

}