cloud-8.x-2.0-beta1/modules/tools/k8s_to_s3/k8s_to_s3.module

modules/tools/k8s_to_s3/k8s_to_s3.module
<?php

/**
 * @file
 * K8s to S3 module.
 *
 * This module transfers definitions of K8s resources to AWS S3 bucket.
 */

use Drupal\aws_cloud\Service\Ecr\EcrServiceInterface;
use Drupal\aws_cloud\Service\S3\S3ServiceInterface;
use Drupal\cloud\Entity\CloudContentEntityBase;
use Drupal\Component\Serialization\Yaml;
use Drupal\Core\Messenger\Messenger;
use Drupal\docker\Service\DockerServiceInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\cloud\Entity\CloudServerTemplate;

/**
 * Implements hook_cron().
 */
function k8s_to_s3_cron() {
  if (k8s_to_s3_validate_config() == TRUE) {
    k8s_to_s3_export_entities();

    $config = \Drupal::config('k8s_to_s3.settings');
    if ($config->get('enable_automatic_ecr_import_export') == TRUE) {
      $lock = \Drupal::lock();
      $lock_name = $lock->getLockId('k8s_to_s3_export_server_template');
      if ($lock->acquire($lock_name)) {
        k8s_to_s3_export_server_templates();
        $lock->release($lock_name);
      }
    }
  }
}

/**
 * Validate if the k8s and aws configuration is available.
 *
 * @return bool
 *   TRUE if valid, else FALSE.
 */
function k8s_to_s3_validate_config() {
  $logger = \Drupal::logger('k8s_to_s3');
  $config = \Drupal::config('k8s_to_s3.settings');
  $k8s_clusters = empty($config->get('k8s_clusters'))
    ? []
    : json_decode($config->get('k8s_clusters'), TRUE);
  if (empty($k8s_clusters)) {
    $logger->error('The configuration k8s_clusters is empty. Please select K8s Clusters in admin setting page.');
    return FALSE;
  }

  $aws_cloud_context = $config->get('aws_cloud');
  if (empty($aws_cloud_context)) {
    $logger->error('The configuration aws_cloud is empty. Please select an AWS Cloud Provider in admin setting page.');
    return FALSE;
  }

  $s3_bucket = $config->get('s3_bucket');
  if (empty($s3_bucket)) {
    $logger->error('The configuration s3_bucket is empty. Please input a S3 Bucket in admin setting page.');
    return FALSE;
  }
  return TRUE;
}

/**
 * Copy K8s yaml templates up to S3.
 */
function k8s_to_s3_export_entities() {
  $config = \Drupal::config('k8s_to_s3.settings');

  $k8s_clusters = empty($config->get('k8s_clusters'))
    ? []
    : json_decode($config->get('k8s_clusters'), TRUE);

  $aws_cloud_context = $config->get('aws_cloud');
  $s3_bucket = $config->get('s3_bucket');
  $entity_types = [
    'k8s_namespace',
    'k8s_pod',
    'k8s_deployment',
  ];
  $cloud_config_plugin_manager = \Drupal::service('plugin.manager.cloud_config_plugin');
  $entity_type_manager = \Drupal::entityTypeManager();
  foreach (array_keys($k8s_clusters) as $cloud_context) {
    k8s_update_resources($cloud_context);

    foreach ($entity_types as $entity_type) {
      $entities = $entity_type_manager->getStorage($entity_type)->loadByProperties(
        ['cloud_context' => [$cloud_context]]
      );

      $s3_service = \Drupal::service('aws_cloud.s3');
      $s3_service->setCloudContext($aws_cloud_context);
      $s3_service->deleteMatchingObjects(
        [
          'Bucket' => $s3_bucket,
          'Prefix' => $cloud_context . '/' . $entity_type,
        ]
      );
      foreach ($entities as $entity) {
        $key = k8s_to_s3_build_k8s_s3_key($entity, $entity_type);
        if ($entity_type == 'k8s_namespace') {
          $body = Yaml::encode([
            'metadata' => [
              'name' => $entity->getName(),
            ],
          ]);
        }
        else {
          $body = $entity->getCreationYaml();
        }
        if (!empty($body)) {
          k8s_to_s3_put_object($s3_service, $s3_bucket, $key, $body);
        }
      }
    }
  }
}

/**
 * Export server template to s3.
 */
function k8s_to_s3_export_server_templates() {
  $config = \Drupal::config('k8s_to_s3.settings');
  $s3_bucket = $config->get('s3_bucket');
  $aws_cloud_context = $config->get('aws_cloud');
  $k8s_clusters = empty($config->get('k8s_clusters'))
    ? []
    : json_decode($config->get('k8s_clusters'), TRUE);

  /* @var \Drupal\aws_cloud\Service\Ecr\EcrServiceInterface $ecr */
  $ecr = \Drupal::service('aws_cloud.ecr');

  /* @var \Drupal\docker\Service\DockerServiceInterface $docker */
  $docker = \Drupal::service('docker');

  /* @var \Drupal\aws_cloud\Service\S3\S3ServiceInterface $s3_service */
  $s3_service = \Drupal::service('aws_cloud.s3');

  if ($docker->isDockerUp() == FALSE) {
    \Drupal::logger('k8s_to_s3')->info(t('Docker unavailable. Skipping cloud server template export.'));
    return;
  }

  $operations = [];

  foreach (array_keys($k8s_clusters) as $cloud_context) {
    $entities = \Drupal::entityTypeManager()
      ->getStorage('cloud_server_template')
      ->loadByProperties(
        [
          'cloud_context' => [$cloud_context],
        ]
      );
    $s3_service->setCloudContext($aws_cloud_context);
    $s3_service->deleteMatchingObjects(
      [
        'Bucket' => $s3_bucket,
        'Prefix' => $cloud_context . '/cloud_server_template',
      ]
    );
    foreach ($entities as $entity) {
      $yaml = Yaml::decode($entity->get('field_detail')->value);
      $containers = [];
      $object = $entity->get('field_object')->value;
      // Extract the containers array.
      switch ($object) {
        case 'deployment':
          $containers =& $yaml['spec']['template']['spec']['containers'];
          break;

        case 'pod':
          $containers =& $yaml['spec']['containers'];
          break;

        default:
          break;
      }

      // Use batch operation for transfer the images.
      foreach ($containers as $key => $container) {
        // With the image, download image.
        if (isset($container['image'])) {
          $image = $container['image'];
          $operations[] = [
            'k8s_to_s3_transfer_image',
            [
              $ecr,
              $docker,
              $image,
              $aws_cloud_context,
            ],
          ];
          $target_tag = k8s_to_s3_get_ecr_target($docker, $ecr, $image, $aws_cloud_context);

          // Replace image location with the ECR version.
          $containers[$key]['image'] = $target_tag;
        }
      }
      // If there are transfer operations, then copy the yaml up.
      if (count($operations)) {
        $operations[] = [
          'k8s_to_s3_export_entity_batch',
          [
            Yaml::encode($yaml),
            $s3_bucket,
            $aws_cloud_context,
            $entity,
          ],
        ];
      }
    }
  }

  if (count($operations)) {
    k8s_to_s3_run_batch($operations);
  }

}

/**
 * Run batch operations to copy docker container images to Ecr.
 *
 * @param array $operations
 *   Array of operations for batch to perform.
 */
function k8s_to_s3_run_batch(array $operations) {
  if (count($operations)) {
    $batch = [
      'title' => t('Transferring k8s cloud server template images to ECR and S3'),
      'operations' => $operations,
      'init_message' => t('Start processing'),
      'progress_message' => t('Processed @current out of @total. Estimated time: @estimate.'),
      'error_message' => t('The transfer process has encountered an error.'),
    ];
    $start = time();

    batch_set($batch);
    $batch = &batch_get();
    $batch['progressive'] = FALSE;
    batch_process();

    $end = time();
    \Drupal::logger('k8s_to_s3')->info(
      t(
        'Docker image operations transfer took @time seconds.',
        [
          '@time' => $end - $start,
        ]
      )
    );
  }
}

/**
 * Export an entity to S3 bucket.
 *
 * @param string $yaml
 *   The yaml body.
 * @param string $s3_bucket
 *   The S3 bucket.
 * @param string $cloud_context
 *   The aws cloud context.
 * @param \Drupal\cloud\Entity\CloudServerTemplate $entity
 *   The cloud server template entity.
 * @param array $context
 *   The batch process context that gets passed.
 */
function k8s_to_s3_export_entity_batch(
  $yaml,
  $s3_bucket,
  $cloud_context,
  CloudServerTemplate $entity,
  array &$context
) {
  $s3_service = \Drupal::service('aws_cloud.s3');
  $s3_service->setCloudContext($cloud_context);

  $key = "{$entity->getCloudContext()}/cloud_server_template/{$entity->getName()}.yaml";

  if (!empty($yaml)) {
    k8s_to_s3_put_object($s3_service, $s3_bucket, $key, $yaml);
    $context['results'][] = $key;
    $context['message'] = t(
      'Copied @key to S3',
      [
        '@key' => $key,
      ]
    );
  }
}

/**
 * Batch function for transferring image to S3.
 *
 * @param Drupal\aws_cloud\Service\Ecr\EcrServiceInterface $ecr
 *   Ecr service object.
 * @param Drupal\docker\Service\DockerServiceInterface $docker
 *   Docker server object.
 * @param string $image
 *   Image to transfer.
 * @param string $cloud_context
 *   Cloud context for ECR.
 * @param array $context
 *   The batch process context that gets passed.
 */
function k8s_to_s3_transfer_image_batch(
  EcrServiceInterface $ecr,
  DockerServiceInterface $docker,
  $image,
  $cloud_context,
  array &$context
) {
  $target = k8s_to_s3_transfer_image(
    $ecr,
    $docker,
    $image,
    $cloud_context
  );
  if ($target !== FALSE) {
    $context['results'][] = $image;
    $context['message'] = t(
      'Copied @image to Ecr as @target',
      [
        '@image' => $image,
        '@target' => $target,
      ]);
  }
}

/**
 * Return authentication tokens.
 *
 * @param \Drupal\aws_cloud\Service\Ecr\EcrServiceInterface $ecr
 *   EcrServiceInterface to use.
 * @param string $cloud_context
 *   The cloud context to use with Ecr.
 *
 * @return array
 *   Auth array.
 */
function k8s_to_s3_get_auth_token(EcrServiceInterface $ecr, $cloud_context) {
  $ecr->setCloudContext($cloud_context);
  $token = $ecr->getAuthorizationToken();
  $token = base64_decode($token);
  $token_parts = explode(':', $token);
  $auth = [];
  if (count($token_parts) == 2) {
    $auth = [
      'username' => $token_parts[0],
      'password' => $token_parts[1],
      'serveraddress' => $ecr->getEcrEndpoint(),
    ];
  }
  return $auth;
}

/**
 * Transfer image to S3.
 *
 * @param Drupal\aws_cloud\Service\Ecr\EcrServiceInterface $ecr
 *   Ecr service object.
 * @param Drupal\docker\Service\DockerServiceInterface $docker
 *   Docker server object.
 * @param string $image
 *   Image to transfer.
 * @param string $cloud_context
 *   Cloud context for ECR.
 *
 * @return bool|string
 *   Target image uri or false if not transferred.
 */
function k8s_to_s3_transfer_image(
  EcrServiceInterface $ecr,
  DockerServiceInterface $docker,
  $image,
  $cloud_context
) {

  $auth = k8s_to_s3_get_auth_token($ecr, $cloud_context);
  $target = FALSE;
  if (count($auth)) {
    $info = $docker->parseImage($image);
    $endpoint = $ecr->getEcrEndpoint();

    // Create the repository if it doesn't exist.
    if ($ecr->doesRepositoryExists($info['full_repository']) === FALSE) {
      $ecr->createRepository($info['full_repository']);
    }

    $target_tag = k8s_to_s3_get_ecr_target($docker, $ecr, $image, $cloud_context);
    // Docker pull from repo, tag and push to target sequence.sequence.
    if ($docker->pullImage($info['name']) == TRUE) {
      $tag = $docker->tagImage($image, $target_tag, $info['tag']);
      $docker->pushImage($target_tag, $auth);
      // Check to see if the image got pushed.  If error, log it.
      if ($ecr->doesImageExist($info['full_repository'], $info['tag']) == FALSE) {
        \Drupal::logger('k8s_to_s3')->error(
          t(
            'Image @image not pushed to Ecr @endpoint',
            [
              '@image' => $info['name'],
              '@endpoint' => $endpoint,
            ])
        );
      }
    }
    else {
      \Drupal::logger('k8s_to_s3')->error(
        t(
          'Unable to pull image @image for cloud server template transfer.',
          [
            '@image' => $info['name'],
          ]
        )
      );
    }
  }
  return $target_tag;
}

/**
 * Derive docker target image url.
 *
 * @param \Drupal\docker\Service\DockerServiceInterface $docker
 *   Docker service.
 * @param \Drupal\aws_cloud\Service\Ecr\EcrServiceInterface $ecr
 *   The Ecr service.
 * @param string $image
 *   Image to derive.
 * @param string $cloud_context
 *   The cloud context for the Ecr service.
 *
 * @return string
 *   The derived target.
 */
function k8s_to_s3_get_ecr_target(
  DockerServiceInterface $docker,
  EcrServiceInterface $ecr,
  $image,
  $cloud_context
) {
  $ecr->setCloudContext($cloud_context);
  $endpoint = $ecr->getEcrEndpoint();
  $info = $docker->parseImage($image);
  $target_tag = $endpoint . '/' . $info['full_repository'];
  $target_tag .= !empty($info['tag']) ? ":{$info['tag']}" : ':latest';
  return $target_tag;
}

/**
 * Build the k8s key.
 *
 * @param \Drupal\cloud\Entity\CloudContentEntityBase $entity
 *   The k8s entity.
 * @param string $entity_type
 *   The entity type.
 *
 * @return string
 *   The built key.
 */
function k8s_to_s3_build_k8s_s3_key(CloudContentEntityBase $entity, $entity_type) {
  $key = "{$entity->getCloudContext()}/$entity_type";
  $key .= method_exists($entity, 'getNamespace') ? "/{$entity->getNamespace()}" : '';
  $key .= "/{$entity->getName()}.yaml";
  return $key;
}

/**
 * Implements hook_entity_delete().
 */
function k8s_to_s3_entity_delete(EntityInterface $entity) {
  if (k8s_to_s3_validate_config() == TRUE) {
    $entity_types = [
      'k8s_namespace',
      'k8s_pod',
      'k8s_deployment',
    ];
    $entity_type = $entity->getEntityTypeId();
    if (!in_array($entity_type, $entity_types)) {
      return;
    }

    $logger = \Drupal::logger('k8s_to_s3');
    $config = \Drupal::config('k8s_to_s3.settings');
    $k8s_clusters = empty($config->get('k8s_clusters'))
      ? []
      : json_decode($config->get('k8s_clusters'), TRUE);
    $aws_cloud_context = $config->get('aws_cloud');
    $s3_bucket = $config->get('s3_bucket');

    if (!in_array($entity->getCloudContext(), $k8s_clusters)) {
      return;
    }

    $key = k8s_to_s3_build_k8s_s3_key($entity, $entity_type);

    $s3_service = \Drupal::service('aws_cloud.s3');
    $s3_service->setCloudContext($aws_cloud_context);

    k8s_to_s3_put_object($s3_service, $s3_bucket, $key, '');
  }
}

/**
 * Put object to S3.
 *
 * @param \Drupal\aws_cloud\Service\S3\S3ServiceInterface $s3_service
 *   The S3 service.
 * @param string $s3_bucket
 *   The S3 bucket.
 * @param string $key
 *   The key of S3 object.
 * @param string $body
 *   The body of S3 object.
 */
function k8s_to_s3_put_object(S3ServiceInterface $s3_service, $s3_bucket, $key, $body) {
  $result = $s3_service->putObject([
    'Bucket' => $s3_bucket,
    'Key' => $key,
    'Body' => $body,
  ]);

  // Output error messages in messenger to the logger.
  if (empty($result)) {
    $messages = \Drupal::messenger()->messagesByType(Messenger::TYPE_ERROR);
    foreach ($messages as $message) {
      \Drupal::logger('k8s_to_s3')->error($message);
    }
    \Drupal::logger('k8s_to_s3')->error("Failed to export an entity to s3://$s3_bucket/$key.");
  }
}

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

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