tapis_job-1.4.1-alpha1/src/Plugin/WebformHandler/LaunchAppWebformHandler.php

src/Plugin/WebformHandler/LaunchAppWebformHandler.php
<?php

namespace Drupal\tapis_job\Plugin\WebformHandler;

use Drupal\Component\Uuid\UuidInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\ImmutableConfig;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\Url;
use Drupal\file\Entity\File;
use Drupal\tapis_app\DrupalIds as AppDrupalIds;
use Drupal\tapis_auth\TapisProvider\TapisTokenProviderInterface;
use Drupal\tapis_job\TapisProvider\TapisJobProviderInterface;
use Drupal\tapis_system\DrupalIds as SystemDrupalIds;
use Drupal\tapis_system\DrupalIds;
use Drupal\tapis_system\TapisProvider\TapisSystemProviderInterface;
use Drupal\webform\Plugin\WebformHandlerBase;
use Drupal\webform\WebformSubmissionInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Launch an app (OSP) from Webform Submissions.
 *
 * @WebformHandler(
 *   id = "tapis_job_osp_webform_launch_app",
 *   label = @Translation("Launch an app (OSP)"),
 *   category = @Translation("One Science Place (OSP)"),
 *   description = @Translation("Launch an app from Webform Submissions."),
 *   cardinality = \Drupal\webform\Plugin\WebformHandlerInterface::CARDINALITY_UNLIMITED,
 *   results = \Drupal\webform\Plugin\WebformHandlerInterface::RESULTS_PROCESSED,
 *   submission = \Drupal\webform\Plugin\WebformHandlerInterface::SUBMISSION_REQUIRED,
 * )
 */
class LaunchAppWebformHandler extends WebformHandlerBase {

  /**
   * The current user.
   *
   * @var \Drupal\Core\Session\AccountProxyInterface
   */
  protected AccountProxyInterface $currentUser;

  /**
   * The configuration factory.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface|\Drupal\Core\Config\ImmutableConfig
   */
  protected ConfigFactoryInterface|ImmutableConfig $config;

  /**
   * The Tapis job provider.
   *
   * @var \Drupal\tapis_job\TapisProvider\TapisJobProviderInterface
   */
  protected TapisJobProviderInterface $tapisJobProvider;

  /**
   * The Tapis system provider.
   *
   * @var \Drupal\tapis_system\TapisProvider\TapisSystemProviderInterface
   */
  protected TapisSystemProviderInterface $tapisSystemProvider;

  /**
   * The Tapis token provider.
   *
   * @var \Drupal\tapis_auth\TapisProvider\TapisTokenProviderInterface
   */
  protected TapisTokenProviderInterface $tapisTokenProvider;

  /**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * The messenger interface.
   *
   * @var \Drupal\Core\Messenger\MessengerInterface
   */
  protected $messenger;

  /**
   * The UUID service.
   *
   * @var \Drupal\Component\Uuid\UuidInterface
   */
  protected UuidInterface $uuidService;

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);

    $instance->currentUser = $container->get('current_user');
    $instance->config = $container->get('config.factory')->get('tapis_job.config');
    $instance->tapisJobProvider = $container->get('tapis_job.tapis_job_provider');
    $instance->tapisSystemProvider = $container->get('tapis_system.tapis_system_provider');
    $instance->tapisTokenProvider = $container->get('tapis_auth.tapis_token_provider');
    $instance->entityTypeManager = $container->get('entity_type.manager');
    $instance->messenger = $container->get('messenger');
    $instance->uuidService = $container->get('uuid');
    return $instance;
  }

  /**
   * {@inheritdoc}
   */
  public function prepareForm(WebformSubmissionInterface $webform_submission, $operation, FormStateInterface $form_state) {
    // Set the is_restart_form value from the webform submission data
    // into the form state
    // so that we can access it from the form state in alterElement.
    $webform_submission_data = $webform_submission->getData();
    $is_restart_form = $webform_submission_data['is_restart_form'] ?? FALSE;
    $form_state->set("is_restart_form", $is_restart_form);
    $tapis_job_id = $webform_submission_data['tapis_job_id'] ?? NULL;
    $form_state->set("tapis_job_id", $tapis_job_id);
  }

  /**
   * {@inheritdoc}
   */
  public function alterElement(array &$element, FormStateInterface $form_state, array $context) {
    $is_restart_form = $form_state->get("is_restart_form") ?? FALSE;
    $tapis_job_id = $form_state->get("tapis_job_id") ?? NULL;

    if ($element['#type'] === "managed_file") {
      // $currentUser = \Drupal::currentUser();
      if (!empty($this->currentUser)) {
        $currentUserID = $this->currentUser->getAccountName();
      }
      else {
        $currentUserID = "anonymous";
      }
      // Generate a UUID for the file.
      // $uuid = \Drupal::service('uuid')->generate();
      $uuid = $this->uuidService->generate();
      if (str_contains($element['#upload_location'], "_sid_")) {
        $file_location = str_replace("_sid_", "users/$currentUserID/$uuid", $element['#upload_location']);
      }
      else {
        $file_location = $element['#upload_location'] . "/users/$currentUserID/$uuid";
      }
      $element['#upload_location'] = $file_location;
    }

    if ($element['#webform_key'] === "osp_app_job_system_id") {
      $element['#ajax'] = [
        'callback' => 'osp_app_job_webform_refreshBatchLogicalQueue',
        'event' => 'change',
        'wrapper' => 'osp_app_job_system_info_wrapper',
      ];
      if ($is_restart_form) {
        // $original_tapis_job = \Drupal::entityTypeManager()->getStorage('tapis_job')->load($tapis_job_id);
        /** @var \Drupal\tapis_job\TapisJobInterface $original_tapis_job */
        $original_tapis_job = $this->entityTypeManager->getStorage('tapis_job')->load($tapis_job_id);
        $original_tapis_job_system_id = $original_tapis_job->getSystemId();
        $element['#default_value'] = $original_tapis_job_system_id;
        $element['#disabled'] = TRUE;
      }
    }
    elseif ($element['#webform_key'] === "osp_app_job_batch_logical_queue_id") {
      $element['#prefix'] = '<div id="osp_app_job_system_info_wrapper">';
      $element['#suffix'] = '</div>';
      $element['#after_build'][] = 'osp_app_job_batch_logical_queue_id_afterBuild';
    }
    elseif (str_starts_with($element['#webform_key'], "osp_app_job_name")) {
      if (!empty($element['#default_value'])) {
        $uniqueJobName = $this->tapisJobProvider->generateUniqueJobName($element['#default_value']);
        $element['#default_value'] = $uniqueJobName;
      }
    }

    if (!isset($element['#webform_plugin_id']) || strpos($element['#webform_plugin_id'], 'tapis_app_webform_') !== 0) {
      return;
    }

    $showOnlyOnRestart = boolval($element['#showOnlyOnRestart'] ?? FALSE);

    $is_restart_form = $form_state->get("is_restart_form") ?? FALSE;

    // If the element has the #showOnlyOnRestart key set to TRUE and
    // the form is not a restart form, then set the element's #access to FALSE.
    if ($showOnlyOnRestart && !$is_restart_form) {
      $element['#access'] = FALSE;
    }
  }

  /**
   * Validate the webform submission.
   *
   * By checking if the user has a system credential
   * for the system they selected, etc.
   *
   * @param array $form
   *   The form array.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state object.
   * @param \Drupal\webform\WebformSubmissionInterface $webform_submission
   *   The webform submission object.
   */
  public function validateForm(array &$form, FormStateInterface $form_state, WebformSubmissionInterface $webform_submission) {
    $jobNames = $this->tapisJobProvider->getAllJobNames();
    $values = $webform_submission->getData();
    $jobNameKey = 'osp_app_job_name';
    $jobNameValue = NULL;

    if (isset($values[$jobNameKey]) && !empty($values[$jobNameKey])) {
      $jobNameValue = $values[$jobNameKey];
    }
    else {
      foreach ($values as $key => $value) {
        if (str_starts_with($key, $jobNameKey) && !empty($value)) {
          $jobNameKey = $key;
          $jobNameValue = $value;
          break;
        }
      }
    }
    if (empty($jobNameValue) || in_array($jobNameValue, $jobNames)) {
      $form_state->setErrorByName($jobNameKey,
        $this->t("The job name, '@jobName', already exists. Please change it.",
          ['@jobName' => $jobNameValue]));
      $form_state->setRebuild();
      return;
    }

    $webform = $webform_submission->getWebform();
    $app_id = $webform->getThirdPartySetting('tapis_app', 'osp_app_id');
    $nodeStorage = $this->entityTypeManager->getStorage('node');
    $app = $nodeStorage->load($app_id);
    $useBatchScheduler = boolval($app->get(AppDrupalIds::APP_USE_BATCH_SCHEDULER)->getValue()[0]['value']);

    // If this is a batch app, load the selected batch logical queue
    // and then check if all the runtime parameters are valid.
    $batch_logical_queue_id = $values["osp_app_job_batch_logical_queue_id"];
    if ($useBatchScheduler && $batch_logical_queue_id) {
      /** @var \Drupal\storage\Entity\Storage $batch_logical_queue */
      // $batch_logical_queue = \Drupal::entityTypeManager()->getStorage('storage')->load($batch_logical_queue_id);
      $batch_logical_queue = $this->entityTypeManager->getStorage('storage')->load($batch_logical_queue_id);
      $batch_logical_queue_max_runtime = $batch_logical_queue->get(SystemDrupalIds::SYSTEM_BLQ_MAX_TIME)->getValue()[0]['value'];
      $batch_logical_queue_max_nodes = $batch_logical_queue->get(SystemDrupalIds::SYSTEM_BLQ_MAX_NODES)->getValue()[0]['value'];
      $batch_logical_queue_max_cores_per_node = $batch_logical_queue->get(SystemDrupalIds::SYSTEM_BLQ_MAX_CORES_PER_NODE)->getValue()[0]['value'];
      $batch_logical_queue_max_memory_per_node = $batch_logical_queue->get(SystemDrupalIds::SYSTEM_BLQ_MAX_MEMORY)->getValue()[0]['value'];

      $user_can_override_num_nodes = boolval($app->get(AppDrupalIds::OVERRIDE_NUM_NODES)->getValue()[0]['value']);
      $user_can_override_cores_per_node = boolval($app->get(AppDrupalIds::OVERRIDE_CORES_PER_NODE)->getValue()[0]['value']);
      $user_can_override_memory = boolval($app->get(AppDrupalIds::OVERRIDE_MEMORY)->getValue()[0]['value']);
      $user_can_override_max_runtime = boolval($app->get(AppDrupalIds::OVERRIDE_MAX_RUNTIME)->getValue()[0]['value']);

      if ($user_can_override_num_nodes) {
        $num_nodes = $values['osp_app_job_num_nodes'];
        if ($num_nodes > $batch_logical_queue_max_nodes) {
          $form_state->setErrorByName('osp_app_job_num_nodes', $this->t("The number of nodes you specified is greater than the maximum number of nodes allowed for this batch logical queue."));
        }
      }

      if ($user_can_override_cores_per_node) {
        $cores_per_node = $values['osp_app_job_cores_per_node'];
        if ($cores_per_node && ($cores_per_node > $batch_logical_queue_max_cores_per_node)) {
          $form_state->setErrorByName('osp_app_job_cores_per_node', $this->t("The number of cores per node you specified is greater than the maximum number of cores per node allowed for this batch logical queue."));
        }
      }

      if ($user_can_override_memory) {
        $memory = $values['osp_app_job_memory_mb'];
        if ($memory && ($memory > $batch_logical_queue_max_memory_per_node)) {
          $form_state->setErrorByName('osp_app_job_memory_mb', $this->t("The amount of memory you specified is greater than the maximum amount of memory allowed for this batch logical queue."));
        }
      }

      if ($user_can_override_max_runtime) {
        $max_runtime = $values['osp_app_job_max_minutes'];
        if ($max_runtime && ($max_runtime > $batch_logical_queue_max_runtime)) {
          $form_state->setErrorByName('osp_app_job_max_minutes', $this->t("The maximum runtime you specified is greater than the maximum runtime allowed for this batch logical queue."));
        }
      }
    }
  }

  /**
   * Launch the job.
   *
   * @param array $form
   *   The form array.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state object.
   * @param \Drupal\webform\WebformSubmissionInterface $webform_submission
   *   The webform submission object.
   */
  public function confirmForm(array &$form, FormStateInterface $form_state, WebformSubmissionInterface $webform_submission) {
    $upload_files_to_tapis = $this->config->get("upload_files_to_tapis");
    $nodeStorage = $this->entityTypeManager->getStorage('node');
    $webform_submission_id = $webform_submission->id();

    $original_tapis_job_id = $form_state->get("tapis_job_id") ?? NULL;

    // Get an array of the values from the submission.
    if ($webform_submission->getState() !== "completed") {
      return;
    }
    $values = $webform_submission->getData();

    $is_restart_form = $form_state->get("is_restart_form") ?? FALSE;

    $webform = $webform_submission->getWebform();
    $app_id = $webform->getThirdPartySetting('tapis_app', 'osp_app_id');
    $app = $nodeStorage->load($app_id);

    $useBatchScheduler = boolval($app->get(AppDrupalIds::APP_USE_BATCH_SCHEDULER)->getValue()[0]['value']);

    // Get runtime configuration details.
    $tapis_app_system_id = $values['osp_app_job_system_id'];
    $batch_logical_queue_id = $values['osp_app_job_batch_logical_queue_id'];
    if ($batch_logical_queue_id) {
      // $batch_logical_queue_entity = \Drupal::entityTypeManager()->getStorage('storage')->load($batch_logical_queue_id);
      /** @var \Drupal\storage\Entity\Storage $batch_logical_queue_entity */
      $batch_logical_queue_entity = $this->entityTypeManager->getStorage('storage')->load($batch_logical_queue_id);
      $batch_logical_queue_id = $batch_logical_queue_entity->getName();
    }

    $batch_allocation_id = $values['osp_app_job_batch_allocation_id'];
    $tapis_app_system_node = $nodeStorage->load($tapis_app_system_id);
    $tapis_app_system_ownerid = $tapis_app_system_node->getOwnerId();
    $tapis_app_system_tapis_id = $tapis_app_system_node->get(DrupalIds::SYSTEM_TAPIS_ID)->getValue()[0]['value'];

    $tenantId = $app->get(AppDrupalIds::APP_TENANT)->first()->getValue()['target_id'];

    // $tapisTokenProvider =
    // \Drupal::service("tapis_auth.tapis_token_provider");
    $currentTapisUsername = $this->tapisTokenProvider->getTapisUsername($tenantId, $this->currentUser->id());

    $tapisSystemId = $tapis_app_system_node->get(DrupalIds::SYSTEM_TAPIS_ID)->first()->getValue()['value'];
    $tapisSystemObject = $this->tapisSystemProvider->getSystem($tenantId, $tapisSystemId, $tapis_app_system_ownerid);
    $tapisSystemTenantId = $tapisSystemObject['tenant'];

    // $systemCredentialIds = \Drupal::entityQuery("tapis_system_credential")
    // ->condition("uid", \Drupal::currentUser()->id())
    $systemCredentialIds = $this->entityTypeManager->getStorage("tapis_system_credential")->getQuery()
      ->condition("uid", $this->currentUser->id())
      ->condition("system", $tapis_app_system_id)
      ->accessCheck(FALSE)
      ->execute();

    $systemCredential = NULL;
    $loginUser = NULL;

    // Check if $systemCredentialIds is not empty.
    if (!empty($systemCredentialIds)) {
      $systemCredentialId = reset($systemCredentialIds);

      // Load the tapis_system_credential entity.
      // $systemCredential = \Drupal::entityTypeManager()->getStorage('tapis_system_credential')->load($systemCredentialId);
      /** @var \Drupal\tapis_system\Entity\TapisSystemCredential $systemCredential */
      $systemCredential = $this->entityTypeManager->getStorage('tapis_system_credential')->load($systemCredentialId);

      // Get the login user field.
      $loginUser = $systemCredential->get("loginUser")->getValue()[0]['value'];
    }

    $tapis_app_num_nodes = $values['osp_app_job_num_nodes'];
    $tapis_app_cores_per_node = $values['osp_app_job_cores_per_node'];
    $tapis_app_memory_mb = $values['osp_app_job_memory_mb'];
    $tapis_app_max_minutes = $values['osp_app_job_max_minutes'];

    $app_runtime = $app->get(AppDrupalIds::APP_RUNTIME)->first()->getValue()['value'];
    $is_app_executable = ($app_runtime === "EXECUTABLE");

    $osp_webform_form_elements = $this->findOSPWebformElementsFromForm($form['elements'], $webform_submission);

    $full_cmd_prefix = $form_state->get("full_cmd_prefix") ?? '';
    $full_cmd_suffix = $form_state->get("full_cmd_suffix") ?? '';
    $ignore_element_keys = $form_state->get("ignore_element_keys") ?? [];

    $appArgs = [];
    $appArgIndicesToMoveToEnd = [];

    $envVars = [];
    $schedulerOptions = [];

    $jobFileInputArrays = [];

    foreach ($osp_webform_form_elements as $element) {
      $cmdValueType = trim($element['#valueType'] ?? 'cli_arg');
      $cmdPrefix = trim($element['#cmdPrefix'] ?? '');
      $cmdPrefix_trailing_space = $element['#cmdPrefix_trailing_space'] ?? TRUE;
      $cmdPrefix_trailing = $cmdPrefix_trailing_space ? ' ' : '';
      $cmdSuffix = rtrim($element['#cmdSuffix'] ?? '');
      $cmdSuffix_trailing_space = $element['#cmdSuffix_trailing_space'] ?? TRUE;
      $cmdSuffix_trailing = $cmdSuffix_trailing_space ? ' ' : '';
      $ignoreEmpty = boolval($element['#ignoreEmpty'] ?? FALSE);
      $ignoreValue = boolval($element['#ignoreValue'] ?? FALSE);
      $showOnlyOnRestart = boolval($element['#showOnlyOnRestart'] ?? FALSE);
      $repeatCmdArgs = boolval($element['#repeatCmdArgs'] ?? FALSE);

      // If the element has the #showOnlyOnRestart key set to TRUE
      // and the form is not a restart form, then skip it.
      if ($showOnlyOnRestart && !$is_restart_form) {
        continue;
      }

      $element_key = $element['#webform_key'];

      // If the element key is in the list of ignored element keys, skip it.
      if (in_array($element_key, $ignore_element_keys)) {
        continue;
      }

      // Check if we should ignore this element.
      if (!$ignoreValue) {
        // Get the webform plugin id.
        $webform_plugin_id = $element['#webform_plugin_id'];
        $element_data = $webform_submission->getElementData($element_key);

        if ($webform_plugin_id === "tapis_app_webform_checkbox") {
          if ($element_data === 1) {
            if ($cmdValueType === 'cli_arg') {
              $appArgs[] = $cmdPrefix . $cmdPrefix_trailing . $cmdSuffix . $cmdSuffix_trailing;
            }
            elseif ($cmdValueType === 'env_var') {
              $envVars[] = $cmdPrefix . $cmdPrefix_trailing . $cmdSuffix . $cmdSuffix_trailing;
            }
            elseif ($cmdValueType === 'scheduler_option') {
              $schedulerOptions[] = $cmdPrefix . $cmdPrefix_trailing . $cmdSuffix . $cmdSuffix_trailing;
            }
          }
          else {
            // don't add the argument when the checkbox is not checked.
          }
        }
        elseif ($webform_plugin_id === "tapis_app_webform_managed_file") {
          // This is a file upload element.
          $cmdLast = $element['#cmdLast'] ?? FALSE;
          $cmdValue = $element['#cmdValue'] ?? 'file_path';

          $files = $element_data;

          if (!$files) {
            // No files were uploaded.
            continue;
          }

          // If $files is not an array, make it an array.
          if (!is_array($files)) {
            $files = [$files];
          }

          if ($app_runtime === "DOCKER") {
            // For docker apps,
            // the file path is the path inside the container.
            $tapis_dest_filepath = "/TapisInput/";
          }
          else {
            // For executable and Singularity apps,
            // the file path is the path
            // on the execution system.
            $tapis_dest_filepath = "";
          }
          $tapisDestFilepaths = [];
          foreach ($files as $file_id) {
            // Load the file entity.
            $file = File::load($file_id);

            // Get the file path.
            $file_path = $file->getFileUri();

            // Read the file contents.
            $file_contents = file_get_contents($file->getFileUri());

            // Get the base file name with extension.
            $file_name = basename($file_path);

            // Remove private:// prefix.
            $dest_filepath = str_replace("private://", "/__{$tapisSystemTenantId}_webform_submission_uploads/$webform_submission_id/", $file_path);

            // Update the dest_filepath to be
            // <tapisSystemObject['jobWorkingDir']>/dest_filepath
            // make sure it's a valid path (the job working dir may or
            // may not end with a slash, etc.)
            $dest_filepath = rtrim($tapisSystemObject['jobWorkingDir'], "/") . "/" . ltrim($dest_filepath, "/");

            // Strip out leading slashes if the root dir ends with a slash.
            if (substr($tapisSystemObject['rootDir'], -1) === "/") {
              $dest_filepath = ltrim($dest_filepath, "/");
            }

            // Resolve macro vars in the dest_filepath.
            if (strpos($dest_filepath, '${EffectiveUserId}') !== FALSE) {
              // Check if the system is a dynamic effective user system.
              if ($tapisSystemObject['isDynamicEffectiveUser'] && $loginUser) {
                $dest_filepath = str_replace('${EffectiveUserId}', $loginUser, $dest_filepath);
              }
              elseif (!$tapisSystemObject['isDynamicEffectiveUser']) {
                $dest_filepath = str_replace('${EffectiveUserId}', $tapisSystemObject['effectiveUserId'], $dest_filepath);
              }
            }

            if (strpos($dest_filepath, '${JobOwner}') !== FALSE) {
              $dest_filepath = str_replace('${JobOwner}', $currentTapisUsername, $dest_filepath);
            }

            if ($cmdValue === "file_path") {
              $tapisDestFilepaths[] = $tapis_dest_filepath . $file_name;
            }

            if ($upload_files_to_tapis) {
              $jobFileInputArrays['sourceUrls'][] = "tapis://$tapis_app_system_tapis_id/" . ltrim($dest_filepath, "/");
              $jobFileInputArrays['targetDir'] = ".";

              $this->tapisJobProvider->uploadFileToSystem($tenantId, $tapis_app_system_tapis_id, $dest_filepath, $file_contents, -1, $tapis_app_system_ownerid);
            }
            else {
              // Generate the absolute url route where tapis can download
              // this input file.
              $file_download_jwt_token = $this->tapisJobProvider->generateJWTForJob($tenantId, [
                'sub' => $file_id,
                'file_id' => $file_id,
              ]);
              $input_file_download_route_url = Url::fromRoute('entity.tapis_job.get_input_file', [
                'file' => $file_id,
                'filename' => $file_name,
              ], ['https' => TRUE, 'absolute' => TRUE])->toString();
              if ($file_download_jwt_token) {
                $input_file_download_route_url .= '?' . http_build_query(['token' => $file_download_jwt_token]);
              }

              $jobFileInputArrays['sourceUrls'][] = $input_file_download_route_url;
              $jobFileInputArrays['targetDir'] = ".";
            }
          }

          // If cmdValue is "file_path" AND the number of files is 1,
          // then use the file path.
          if ($cmdValue === "file_path") {
            if ($repeatCmdArgs) {
              foreach ($tapisDestFilepaths as $tapis_dest_filepath) {
                if ($cmdValueType === "cli_arg") {
                  $appArgs[] = $cmdPrefix . $cmdPrefix_trailing . $tapis_dest_filepath . $cmdSuffix . $cmdSuffix_trailing;

                  if ($cmdLast) {
                    $appArgIndicesToMoveToEnd[] = count($appArgs) - 1;
                  }
                }
                elseif ($cmdValueType === "env_var") {
                  $envVars[] = $cmdPrefix . $cmdPrefix_trailing . $tapis_dest_filepath . $cmdSuffix . $cmdSuffix_trailing;
                }
                elseif ($cmdValueType === "scheduler_option") {
                  $schedulerOptions[] = $cmdPrefix . $cmdPrefix_trailing . $tapis_dest_filepath . $cmdSuffix . $cmdSuffix_trailing;
                }
              }
            }
            else {
              $tapis_dest_filepath = implode(" ", $tapisDestFilepaths);
              if ($cmdValueType === "cli_arg") {
                $appArgs[] = $cmdPrefix . $cmdPrefix_trailing . $tapis_dest_filepath . $cmdSuffix . $cmdSuffix_trailing;

                if ($cmdLast) {
                  $appArgIndicesToMoveToEnd[] = count($appArgs) - 1;
                }
              }
              elseif ($cmdValueType === "env_var") {
                $envVars[] = $cmdPrefix . $cmdPrefix_trailing . $tapis_dest_filepath . $cmdSuffix . $cmdSuffix_trailing;
              }
              elseif ($cmdValueType === "scheduler_option") {
                $schedulerOptions[] = $cmdPrefix . $cmdPrefix_trailing . $tapis_dest_filepath . $cmdSuffix . $cmdSuffix_trailing;
              }
            }
          }
          elseif ($cmdValue === "none") {
            if ($cmdValueType === "cli_arg") {
              $appArgs[] = $cmdPrefix . $cmdPrefix_trailing . $cmdSuffix . $cmdSuffix_trailing;

              if ($cmdLast) {
                $appArgIndicesToMoveToEnd[] = count($appArgs) - 1;
              }
            }
            elseif ($cmdValueType === "env_var") {
              $envVars[] = $cmdPrefix . $cmdPrefix_trailing . $cmdSuffix . $cmdSuffix_trailing;
            }
            elseif ($cmdValueType === "scheduler_option") {
              $schedulerOptions[] = $cmdPrefix . $cmdPrefix_trailing . $cmdSuffix . $cmdSuffix_trailing;
            }
          }
          elseif ($cmdValue === "file_directory") {
            if (!$is_app_executable) {
              // dirname($dest_filepath);
              $tapis_dest_folderpath = "/TapisInput";
            }
            else {
              $tapis_dest_folderpath = ".";
            }

            if ($cmdValueType === "cli_arg") {
              $appArgs[] = $cmdPrefix . $cmdPrefix_trailing . $tapis_dest_folderpath . $cmdSuffix . $cmdSuffix_trailing;

              if ($cmdLast) {
                $appArgIndicesToMoveToEnd[] = count($appArgs) - 1;
              }
            }
            elseif ($cmdValueType === "env_var") {
              $envVars[] = $cmdPrefix . $cmdPrefix_trailing . $tapis_dest_folderpath . $cmdSuffix . $cmdSuffix_trailing;
            }
            elseif ($cmdValueType === "scheduler_option") {
              $schedulerOptions[] = $cmdPrefix . $cmdPrefix_trailing . $tapis_dest_folderpath . $cmdSuffix . $cmdSuffix_trailing;
            }
          }
        }
        else {
          // This is not a checkbox nor file arg, so just add the argument
          // # TODO: how do we handle empty values?
          if ($ignoreEmpty && !$element_data) {
            // don't add the argument.
          }
          else {
            if ($webform_plugin_id === "tapis_app_webform_select") {
              // Make sure $element_data is NOT an array.
              if (is_array($element_data)) {
                $element_data = implode("", $element_data);
              }
            }
            else {
              // Make sure $element_data is NOT an array.
              if (is_array($element_data)) {
                $element_data = implode(" ", $element_data);
              }
            }
            if ($cmdValueType === "cli_arg") {
              $appArgs[] = $cmdPrefix . $cmdPrefix_trailing . $element_data . $cmdSuffix . $cmdSuffix_trailing;
            }
            elseif ($cmdValueType === "env_var") {
              $envVars[] = $cmdPrefix . $cmdPrefix_trailing . $element_data . $cmdSuffix . $cmdSuffix_trailing;
            }
            elseif ($cmdValueType === "scheduler_option") {
              $schedulerOptions[] = $cmdPrefix . $cmdPrefix_trailing . $element_data . $cmdSuffix . $cmdSuffix_trailing;
            }
          }
        }
      }
    }
    // If $jobFileInputArrays isn't empty, enclose it in an array.
    if (!empty($jobFileInputArrays)) {
      $jobFileInputArrays = [$jobFileInputArrays];
    }

    // Move the appArgs to the end of the array.
    $newAppArgs = [];

    // First add all the non-last arguments.
    foreach ($appArgs as $index => $appArg) {
      if (!in_array($index, $appArgIndicesToMoveToEnd)) {
        $newAppArgs[] = $appArg;
      }
    }

    // Then add all the last arguments.
    foreach ($appArgIndicesToMoveToEnd as $index) {
      $newAppArgs[] = $appArgs[$index];
    }

    $appArgs = $newAppArgs;
    $appArgsString = $full_cmd_prefix . trim(implode("", $appArgs)) . $full_cmd_suffix;

    // @todo custom job resources (we intentionally ignore if $user_can_override_job_resources)
    $customJobResources = [];

    // Num nodes.
    $customJobResources[AppDrupalIds::APP_NUM_NODES] = $tapis_app_num_nodes;

    // Cores per node.
    $customJobResources[AppDrupalIds::APP_CORES_PER_NODE] = $tapis_app_cores_per_node;

    // Max runtime.
    $customJobResources[AppDrupalIds::APP_MAX_MINUTES] = $tapis_app_max_minutes;

    // Max memory.
    $customJobResources[AppDrupalIds::APP_MEMORY_MB] = $tapis_app_memory_mb;

    // Show generated command.
    if (!empty($app->get(AppDrupalIds::APP_SHOW_GENERATED_COMMAND)->getValue())) {
      $generatedCommand = $app->get(AppDrupalIds::APP_SHOW_GENERATED_COMMAND)
        ->getValue()[0]['value'];
    }
    else {
      $generatedCommand = 0;
    }

    if ($generatedCommand) {
      $cmdExec = "";
      if (isset($app->get(AppDrupalIds::APP_CONTAINER_IMAGE_URI)
        ->getValue()[0]['value'])) {
        $cmdExec = $app->get(AppDrupalIds::APP_CONTAINER_IMAGE_URI)
          ->getValue()[0]['value'] . " ";
      }
      elseif (isset($app->get(AppDrupalIds::APP_EXECUTABLE_PATH)
        ->getValue()[0]['value'])) {
        $cmdExec = $app->get(AppDrupalIds::APP_EXECUTABLE_PATH)
          ->getValue()[0]['value'] . " ";
      }

      // @todo Convert this to using Messenger and only show if the current user is the app's owner OR the current user has the (newly created) permission "View generated app command upon webform submission"
      $this->messenger->addMessage($this->t('App command: @app_command', ['@app_command' => $cmdExec . $appArgsString]));
    }

    $appInputType = $app->get(AppDrupalIds::APP_INPUT_TYPE)->getValue()[0]['value'];
    if ($appInputType !== "flexible_parameters") {
      return;
    }
    $jobNameKey = 'osp_app_job_name';
    $jobName = $app->getTitle();

    if (isset($values[$jobNameKey]) && !empty($values[$jobNameKey])) {
      $jobName = $values[$jobNameKey];
    }
    else {
      foreach ($values as $key => $value) {
        if (str_starts_with($key, $jobNameKey) && !empty($value)) {
          $jobNameKey = $key;
          $jobName = $value;
          break;
        }
      }
    }

    // $jobName = $values['osp_app_job_name'] ?? $app->getTitle();
    $appType = $app->get(AppDrupalIds::APP_TYPE)->getValue()[0]['value'];

    // $job = \Drupal::entityTypeManager()->getStorage('tapis_job')->create([
    // 'uid' => ['target_id' => \Drupal::currentUser()->id()],
    /** @var \Drupal\tapis_job\Entity\TapisJob $job */
    $job = $this->entityTypeManager->getStorage('tapis_job')->create([
      'tenant' => ['target_id' => $tenantId],
      'app' => ['target_id' => $app_id],
      'system' => ['target_id' => $tapis_app_system_id],
      'webform_submission' => ['target_id' => $webform_submission->id()],
      'label' => $jobName,
      'uid' => ['target_id' => $this->currentUser->id()],
    ]);

    if ($original_tapis_job_id) {
      // $original_tapis_job = \Drupal::entityTypeManager()->getStorage('tapis_job')->load($original_tapis_job_id);
      /** @var \Drupal\tapis_job\TapisJobInterface $original_tapis_job */
      $original_tapis_job = $this->entityTypeManager->getStorage('tapis_job')->load($original_tapis_job_id);
      $job->setOriginalJobForRestart($original_tapis_job);

      $originalJobUUID = $original_tapis_job->getTapisUUID();
      $originalJobObject = $this->tapisJobProvider->getJob($tenantId, $originalJobUUID, $original_tapis_job->getOwnerId());
      $originalJobExecDir = $originalJobObject['execSystemExecDir'];

      $this->tapisJobProvider->backupJobFilesBeforeRestart($tenantId, $tapisSystemId, $originalJobUUID, $originalJobExecDir, $original_tapis_job->getOwnerId());
    }

    if ($appType === "web" || $appType === "vnc") {
      $proxyId = $this->tapisJobProvider->createNewSatelliteToken($tenantId);
      $job->setProxyId($proxyId);
    }

    if ($useBatchScheduler && $batch_logical_queue_id) {
      $job->setCustomProperties([
        'execSystemLogicalQueue' => $batch_logical_queue_id,
      ]);
    }

    if ($useBatchScheduler && $batch_allocation_id) {
      $job->setAllocationId($batch_allocation_id);
    }

    $job->setCustomJobResources($customJobResources);
    $tapisJobDefinition = $job->toJSON($appArgsString, $schedulerOptions, $envVars, $jobFileInputArrays);

    \Drupal::logger('tapis_job')->notice('Tapis Job Definition: ' . json_encode($tapisJobDefinition));

    $job->setTapisDefinition($tapisJobDefinition);
    $job->save();

    $form_state->setRedirect('entity.tapis_job.canonical', ['tapis_job' => $job->id()]);
  }

  /**
   * Get an array of all the OSP webform elements in the given form.
   *
   * @param array $form
   *   The form array.
   * @param \Drupal\webform\WebformSubmissionInterface $webform_submission
   *   The webform submission object.
   */
  private function findOSPWebformElementsFromForm(array &$form, WebformSubmissionInterface $webform_submission) {
    /*
     * $form is an associative array of form elements.
     *
     * For each key, value pair in $form:
     *  - If the value is an array:
     *    - check the array to see if it contains an #webform_plugin_id key
     *      whose value starts with 'tapis_app_webform_'.
     *    - If it does, add the current key & value to the list of
     *      OSP webform elements to return.
     *    - If it doesn't, recurse into the array.
     * - If the value is not an array, do nothing.
     */
    $osp_webform_elements = [];
    $ignore_element_keys = [];
    foreach ($form as $key => $value) {
      if (is_array($value)) {
        if (isset($value['#webform_plugin_id']) && strpos($value['#webform_plugin_id'], 'tapis_app_webform_') === 0) {
          // Check if this is an osp checkbox.
          if ($value['#webform_plugin_id'] === "tapis_app_webform_checkbox") {
            $ignoreElementKey = trim($value['#ignoreElementKey'] ?? '');
            $checked = $webform_submission->getElementData($key);
            if (!empty($ignoreElementKey) && $checked) {
              // If the checkbox is checked and
              // we're supposed to ignore a certain element key,
              // add the ignoreElementKey to the ignore_element_keys array.
              $ignore_element_keys[] = $ignoreElementKey;
            }
            if (!empty($ignoreElementKey)) {
              // If the ignoreElementKey is not empty, we skip this element
              // since the only purpose of this element is
              // to ignore another element.
              continue;
            }
            // dpm($value);
            // $ignore_element_keys[] = $key;.
          }

          if (str_starts_with($key, 'osp_app_job_name')) {
            continue;
          }

          if (!in_array($key,
            [
              "osp_app_job_system_id",
              "osp_app_job_batch_logical_queue_id",
              "osp_app_job_batch_allocation_id",
              "osp_app_job_num_nodes",
              "osp_app_job_cores_per_node",
              "osp_app_job_memory_mb",
              "osp_app_job_max_minutes",
            ])) {
            $osp_webform_elements[$key] = $value;
          }
        }
        else {
          $osp_webform_elements = array_merge($osp_webform_elements, $this->findOSPWebformElementsFromForm($value, $webform_submission));
        }
      }
    }

    // Remove the ignore_element_keys from the osp_webform_elements.
    foreach ($ignore_element_keys as $ignore_element_key) {
      if (isset($osp_webform_elements[$ignore_element_key])) {
        unset($osp_webform_elements[$ignore_element_key]);
      }
    }

    return $osp_webform_elements;
  }

}

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

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