tapis_job-1.4.1-alpha1/src/Plugin/WebformHandler/DryRunAppWebformHandler.php
src/Plugin/WebformHandler/DryRunAppWebformHandler.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;
use Drupal\tapis_system\DrupalIds as SystemDrupalIds;
use Drupal\tapis_system\TapisProvider\TapisSystemProviderInterface;
use Drupal\webform\Plugin\WebformHandlerBase;
use Drupal\webform\WebformSubmissionInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Dry run an app (OSP) from Webform Submissions.
*
* @WebformHandler(
* id = "tapis_job_osp_webform_dry_run_app",
* label = @Translation("Dry run app - THIS DOES NOT SUBMIT THE JOB (OSP)"),
* category = @Translation("One Science Place (OSP)"),
* description = @Translation("Dry-run an app from Webform Submissions. THIS DOES NOT SUBMIT THE JOB."),
* cardinality = \Drupal\webform\Plugin\WebformHandlerInterface::CARDINALITY_UNLIMITED,
* results = \Drupal\webform\Plugin\WebformHandlerInterface::RESULTS_PROCESSED,
* submission = \Drupal\webform\Plugin\WebformHandlerInterface::SUBMISSION_REQUIRED,
* )
*/
class DryRunAppWebformHandler 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) {
// For restart forms, set the system id to the original job's system id
// and disable the element
// this is so that the user cannot change the system id
// when restarting a job.
// $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) {
// $batch_logical_queue = \Drupal::entityTypeManager()->getStorage('storage')->load($batch_logical_queue_id);
/** @var \Drupal\storage\Entity\Storage $batch_logical_queue */
$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) {
// $config = \Drupal::config('tapis_job.config');
$upload_files_to_tapis = $this->config->get("upload_files_to_tapis");
// /** @var \Drupal\tapis_job\TapisProvider\TapisJobProviderInterface */
// $tapisJobProvider = \Drupal::service("tapis_job.tapis_job_provider");
// $nodeStorage = \Drupal::entityTypeManager()->getStorage('node');
$nodeStorage = $this->entityTypeManager->getStorage('node');
$webform_submission_id = $webform_submission->id();
// $tapisSystemProvider =
// \Drupal::service("tapis_system.tapis_system_provider");
// 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);
$tapisSystemTenantId = $tapisSystemObject['tenant'];
$tapisSystemJobWorkingDir = $tapisSystemObject['jobWorkingDir'];
// Get the system credential to use for this user and system,
// get its loginUser value, and replace [loginUser] with it.
// $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, "/");
// In dry-run, we don't actually upload the file to the system
// $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]));
}
}
/**
* 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;
}
}
