config_preview_deploy-1.0.0-alpha3/src/Controller/ProductionController.php

src/Controller/ProductionController.php
<?php

declare(strict_types=1);

namespace Drupal\config_preview_deploy\Controller;

use Drupal\config_preview_deploy\ProductionConfigDeployer;
use Drupal\config_preview_deploy\ConfigVerifier;
use Drupal\config_preview_deploy\HashVerification;
use Drupal\config_preview_deploy\ConfigExporter;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\File\FileSystemInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Drupal\Core\Config\ConfigManagerInterface;

/**
 * Production endpoints controller.
 */
class ProductionController extends ControllerBase {

  /**
   * The config verifier service.
   */
  protected ConfigVerifier $configVerifier;

  /**
   * The production config deployer service.
   */
  protected ProductionConfigDeployer $productionConfigDeployer;


  /**
   * The file system service.
   */
  protected FileSystemInterface $fileSystem;

  /**
   * The hash verification service.
   *
   * @var \Drupal\config_preview_deploy\HashVerification
   */
  protected HashVerification $hashVerification;

  /**
   * The config manager service.
   *
   * @var \Drupal\Core\Config\ConfigManagerInterface
   */
  protected ConfigManagerInterface $configManager;

  /**
   * The config exporter.
   *
   * @var \Drupal\config_preview_deploy\ConfigExporter
   */
  protected ConfigExporter $configExport;

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    $instance = parent::create($container);
    $instance->configVerifier = $container->get('config_preview_deploy.config_verifier');
    $instance->productionConfigDeployer = $container->get('config_preview_deploy.production_config_deployer');
    $instance->fileSystem = $container->get('file_system');
    $instance->hashVerification = $container->get('config_preview_deploy.hash_verification');
    $instance->configManager = $container->get('config.manager');
    $instance->configExport = $container->get('config_preview_deploy.config_export');
    return $instance;
  }

  /**
   * Deploys configuration diff endpoint.
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The request object.
   *
   * @return \Symfony\Component\HttpFoundation\JsonResponse
   *   JSON response with deployment result.
   */
  public function deploy(Request $request): JsonResponse {
    try {
      // Parse JSON request body.
      $content = $request->getContent();
      $data = json_decode($content, TRUE);

      if (!is_array($data)) {
        return new JsonResponse([
          'error' => $this->t('Invalid request format')->render(),
        ], 400);
      }

      // Validate required fields.
      $requiredFields = ['diff', 'environment', 'auth_hash', 'timestamp'];
      foreach ($requiredFields as $field) {
        if (!isset($data[$field])) {
          return new JsonResponse([
            'error' => $this->t('Missing required field: @field', ['@field' => $field])->render(),
          ], 400);
        }
      }

      $diff = $data['diff'];
      $environment = $data['environment'];
      $authHash = $data['auth_hash'];
      $timestamp = (int) $data['timestamp'];

      // Validate authentication including timestamp.
      if (!$this->hashVerification->verifyHash($authHash, $timestamp)) {
        $this->getLogger('config_preview_deploy')->warning('Invalid authentication in deployment request from @env', [
          '@env' => $environment,
        ]);
        return new JsonResponse([
          'error' => $this->t('Invalid authentication')->render(),
          'success' => FALSE,
        ], 403);
      }

      try {
        // Deploy the configuration diff.
        // The deployDiff method now handles validation internally
        // using ConfigDiff.
        $result = $this->productionConfigDeployer->deployDiff(
          $diff,
          $environment
        );

        // Update last change timestamp on successful deployment.
        $this->configVerifier->updateLastChange($environment);

        $this->getLogger('config_preview_deploy')->notice('Configuration deployed from @env with checkpoint @checkpoint on @prod_env', [
          '@env' => $environment,
          '@checkpoint' => $result['checkpoint_id'],
          '@prod_env' => $this->configVerifier->getEnvironmentName(),
        ]);

        return new JsonResponse($result);
      }
      catch (\RuntimeException $e) {
        // Deployment validation errors return 400.
        return new JsonResponse([
          'error' => $e->getMessage(),
          'success' => FALSE,
        ], 400);
      }
      catch (\Exception $e) {
        $this->getLogger('config_preview_deploy')->error('Deployment endpoint error: @message', [
          '@message' => $e->getMessage(),
        ]);

        return new JsonResponse([
          'error' => $this->t('Internal server error')->render(),
          'success' => FALSE,
        ], 500);
      }
    }
    catch (\Exception $e) {
      // This outer catch should never be reached now.
      return new JsonResponse([
        'error' => $this->t('Unexpected error')->render(),
        'success' => FALSE,
      ], 500);
    }
  }

  /**
   * Exports the active configuration as a tarball.
   *
   * This endpoint provides the same format as Drupal core's config export.
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The request object.
   *
   * @return \Symfony\Component\HttpFoundation\Response
   *   Response with configuration tarball.
   */
  public function exportConfig(Request $request): Response {
    try {
      // Verify hash authentication from request headers.
      $authHash = $request->headers->get('X-Config-Deploy-Hash');
      $timestamp = (int) $request->headers->get('X-Config-Deploy-Timestamp');

      if (!$authHash || !$timestamp) {
        return new JsonResponse(['error' => 'Missing authentication headers'], 401);
      }

      // Validate the hash.
      if (!$this->hashVerification->verifyHash($authHash, $timestamp)) {
        return new JsonResponse(['error' => 'Invalid authentication hash'], 403);
      }

      // Export configuration using the dedicated service.
      $tarballContent = $this->configExport->exportConfigTarball();

      // Return the tarball as a response.
      $response = new Response($tarballContent);
      $response->headers->set('Content-Type', 'application/gzip');
      $response->headers->set('Content-Disposition', 'attachment; filename="config.tar.gz"');
      $response->headers->set('Content-Length', (string) strlen($tarballContent));

      $this->getLogger('config_preview_deploy')->notice('Configuration exported for environment via API');

      return $response;

    }
    catch (\Exception $e) {
      $this->getLogger('config_preview_deploy')->error('Config export error: @message', [
        '@message' => $e->getMessage(),
      ]);

      return new JsonResponse([
        'error' => $this->t('Export failed: @error', ['@error' => $e->getMessage()])->render(),
      ], 500);
    }
  }

  /**
   * Returns production configuration status with token authentication.
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The request object with timestamp and token parameters.
   *
   * @return \Symfony\Component\HttpFoundation\JsonResponse
   *   JSON response with production configuration status.
   */
  public function getStatus(Request $request): JsonResponse {
    try {
      $timestamp = $request->query->get('timestamp');
      $token = $request->query->get('token');

      // Check if required parameters are present.
      if ($timestamp === NULL || $token === NULL) {
        return new JsonResponse(['error' => 'Missing required parameters'], 403);
      }

      $timestamp = (int) $timestamp;

      // Validate token using production host + timestamp.
      if (!$this->hashVerification->verifyHash($token, $timestamp)) {
        return new JsonResponse(['error' => 'Invalid verification hash'], 403);
      }

      // Return production configuration status.
      $lastChange = $this->state()->get('config_preview_deploy.last_change', 0);
      $lastDeployedFrom = $this->state()->get('config_preview_deploy.last_deployed_from', '');

      $responseData = [
        'last_change' => $lastChange,
        'last_deployed_from' => $lastDeployedFrom,
        'environment' => $this->configVerifier->getEnvironmentName(),
        'timestamp' => time(),
      ];

      return new JsonResponse($responseData);

    }
    catch (\Exception $e) {
      $this->getLogger('config_preview_deploy')->error('Status endpoint error: @message', [
        '@message' => $e->getMessage(),
      ]);

      return new JsonResponse([
        'error' => $this->t('Internal server error')->render(),
      ], 500);
    }
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc