From 1872867b3023d484bed4f75b87365c306af16d6a Mon Sep 17 00:00:00 2001 From: MontrealSergiy Date: Mon, 24 Mar 2025 18:52:18 -0400 Subject: [PATCH 1/9] Refactor singularity into apptainer --- .../app/controllers/bourreaux_controller.rb | 2 +- .../controllers/tool_configs_controller.rb | 8 +- BrainPortal/app/models/bourreau.rb | 6 +- BrainPortal/app/models/cluster_task.rb | 142 +++++++++--------- .../models/sing_bindmount_data_provider.rb | 52 +++---- .../app/models/sing_squashfs_data_provider.rb | 79 +++++----- BrainPortal/app/models/tool_config.rb | 50 +++--- BrainPortal/app/views/bourreaux/show.html.erb | 6 +- .../_dp_types_explained.html.erb | 6 +- .../views/tool_configs/_form_fields.html.erb | 20 +-- .../app/views/tool_configs/show.html.erb | 6 +- .../singularity_image/singularity_image.rb | 27 ++-- .../singularity_image/views/_info.html.erb | 8 +- .../config/console_rc/lib/pretty_view.rb | 4 +- ...8164549_rename_singularity_to_apprainer.rb | 7 + BrainPortal/db/schema.rb | 6 +- BrainPortal/lib/boutiques_ext3_capturer.rb | 2 +- .../schema_task_generator.rb | 6 +- BrainPortal/spec/models/tool_config_spec.rb | 11 +- 19 files changed, 235 insertions(+), 213 deletions(-) create mode 100644 BrainPortal/db/migrate/20240408164549_rename_singularity_to_apprainer.rb diff --git a/BrainPortal/app/controllers/bourreaux_controller.rb b/BrainPortal/app/controllers/bourreaux_controller.rb index 7219f07f2..362cfbcdc 100644 --- a/BrainPortal/app/controllers/bourreaux_controller.rb +++ b/BrainPortal/app/controllers/bourreaux_controller.rb @@ -769,7 +769,7 @@ def bourreau_params #:nodoc: :cms_default_queue, :cms_extra_qsub_args, :cms_shared_dir, :workers_instances, :workers_chk_time, :workers_log_to, :workers_verbose, :help_url, :rr_timeout, :proxied_host, :spaced_dp_ignore_patterns, :support_email, :system_from_email, :external_status_page_url, - :docker_executable_name, :docker_present, :singularity_executable_name, :singularity_present, + :docker_executable_name, :docker_present, :apptainer_executable_name, :apptainer_present, :small_logo, :large_logo, :license_agreements, :activity_workers_instances ) diff --git a/BrainPortal/app/controllers/tool_configs_controller.rb b/BrainPortal/app/controllers/tool_configs_controller.rb index b36540403..12ea9f0cd 100644 --- a/BrainPortal/app/controllers/tool_configs_controller.rb +++ b/BrainPortal/app/controllers/tool_configs_controller.rb @@ -206,7 +206,7 @@ def update #:nodoc: @tool_config.container_image_userfile_id = other_tc.container_image_userfile_id @tool_config.container_exec_args = other_tc.container_exec_args.presence @tool_config.container_index_location = other_tc.container_index_location - @tool_config.singularity_overlays_specs = other_tc.singularity_overlays_specs + @tool_config.apptainer_overlays_specs = other_tc.apptainer_overlays_specs @tool_config.extra_qsub_args = other_tc.extra_qsub_args @tool_config.cloud_disk_image = other_tc.cloud_disk_image @tool_config.cloud_vm_user = other_tc.cloud_vm_user @@ -243,7 +243,7 @@ def update #:nodoc: container_image_userfile_id containerhub_image_name container_engine container_index_location container_exec_args inputs_readonly - singularity_overlays_specs singularity_use_short_workdir + apptainer_overlays_specs apptainer_use_short_workdir boutiques_descriptor_path ) ) @@ -318,8 +318,8 @@ def tool_config_params #:nodoc: :version_name, :description, :tool_id, :bourreau_id, :env_array, :script_prologue, :script_epilogue, :group_id, :ncpus, :container_image_userfile_id, :containerhub_image_name, :container_index_location, :inputs_readonly, - :container_engine, :extra_qsub_args, :singularity_overlays_specs, :container_exec_args, - :singularity_use_short_workdir, + :container_engine, :extra_qsub_args, :apptainer_overlays_specs, :container_exec_args, + :apptainer_use_short_workdir, :boutiques_descriptor_path, # The configuration of a tool in a VM managed by a # ScirCloud Bourreau is defined by the following diff --git a/BrainPortal/app/models/bourreau.rb b/BrainPortal/app/models/bourreau.rb index 6e8664fb8..7f0aba9e0 100644 --- a/BrainPortal/app/models/bourreau.rb +++ b/BrainPortal/app/models/bourreau.rb @@ -80,11 +80,11 @@ def docker_present? #:nodoc: alias docker_present docker_present? #:nodoc: - def singularity_present? #:nodoc: - singularity_executable_name.present? + def apptainer_present? #:nodoc: + apptainer_executable_name.present? end - alias singularity_present singularity_present? #:nodoc: + alias apptainer_present apptainer_present? #:nodoc: diff --git a/BrainPortal/app/models/cluster_task.rb b/BrainPortal/app/models/cluster_task.rb index 78e02a74c..cf18041c3 100644 --- a/BrainPortal/app/models/cluster_task.rb +++ b/BrainPortal/app/models/cluster_task.rb @@ -439,7 +439,7 @@ def make_available(userfile, file_path, userfile_sub_path = nil, start_dir = nil userfile = Userfile.find(userfile) unless userfile.is_a?(Userfile) # Detect if we're trying to access a userfile content that is - # containerized in a singularity overlay. + # containerized in an Apptainer overlay. is_local = nil if self.tool_config .data_providers_with_overlays @@ -447,7 +447,7 @@ def make_available(userfile, file_path, userfile_sub_path = nil, start_dir = nil .include? userfile.data_provider_id userfile_path = Pathname.new(userfile.provider_full_path) # path inside container is_local = true - self.addlog("Input file '#{userfile.name}' is on a local Singularity overlay") + self.addlog("Input file '#{userfile.name}' is on a local Apptainer overlay") else userfile.sync_to_cache userfile_path = Pathname.new(userfile.cache_full_path) @@ -633,10 +633,10 @@ def tool_config_system(command) # Build script script = "" - # flag to guaranty propagation of env variables to the singularity/apptainer + # flag to guaranty propagation of env variables to the Apptainer # as far I know only needed to reverse effect of --cleanenv option, and otherwise all vars are copied to the container # yet potentially more cases may be identified - propagate = self.use_singularity? + propagate = self.use_apptainer? # Add prologues in specialization order script += bourreau_glob_config.to_bash_prologue propagate if bourreau_glob_config script += tool_glob_config.to_bash_prologue propagate if tool_glob_config @@ -1796,14 +1796,14 @@ def submit_cluster_job # Joined version of all the lines in the scientific script command_script = commands.join("\n") - # In case of Docker or Singularity, we rewrite the scientific script inside + # In case of Docker or Apptainer, we rewrite the scientific script inside # yet another wrapper script. if self.use_docker? command_script = wrap_new_HOME(command_script, self.full_cluster_workdir) command_script = self.docker_commands(command_script) - elsif self.use_singularity? - load_singularity_image - command_script = self.singularity_commands(command_script) # note: invokes wrap_new_HOME itself + elsif self.use_apptainer? + load_apptainer_image + command_script = self.apptainer_commands(command_script) # note: invokes wrap_new_HOME itself else command_script = wrap_new_HOME(command_script, self.full_cluster_workdir) end @@ -1818,9 +1818,9 @@ def submit_cluster_job # by ClusterTask # #{ClusterTask.revision_info.to_s} -#{bourreau_glob_config ? bourreau_glob_config.to_bash_prologue(self.use_singularity?) : ""} -#{tool_glob_config ? tool_glob_config.to_bash_prologue(self.use_singularity?) : ""} -#{tool_config ? tool_config.to_bash_prologue(self.use_singularity?) : ""} +#{bourreau_glob_config ? bourreau_glob_config.to_bash_prologue(self.use_apptainer?) : ""} +#{tool_glob_config ? tool_glob_config.to_bash_prologue(self.use_apptainer?) : ""} +#{tool_config ? tool_config.to_bash_prologue(self.use_apptainer?) : ""} #{self.supplemental_cbrain_tool_config_init} # CbrainTask '#{self.name}' commands section @@ -1867,9 +1867,9 @@ def submit_cluster_job # Record runtime environment bash #{Rails.root.to_s.bash_escape}/vendor/cbrain/bin/runtime_info.sh > #{runtime_info_basename} -# With apptainer/singularity jobs, we sometimes get an error booting the container, +# With Apptainer jobs, we sometimes get an error booting the container, # so we try up to five times. -for singularity_attempts in 1 2 3 4 5 ; do # note: the number 5 is used a bit below in an 'if' +for apptainer_attempts in 1 2 3 4 5 ; do # note: the number 5 is used a bit below in an 'if' SECONDS=0 # this is a special bash variable, see the doc # stdout and stderr captured below will be re-substituted in @@ -1879,20 +1879,20 @@ def submit_cluster_job test $status -eq 0 && break # all is good - # Detect failed boot of singularity container + # Detect failed boot of apptainer container if ! grep -i 'FATAL.*container.*creation.*failed' #{science_stderr_basename} >/dev/null ; then break # move on, for any other error or even non zero successes fi # Detect that final attempt to boot failed - if test $singularity_attempts -eq 5 ; then + if test $apptainer_attempts -eq 5 ; then echo "Apptainer container boot attempts all failed, giving up." status=99 # why not break fi # Cleanup and try again - echo "Apptainer boot attempt number $singularity_attempts failed, trying again." + echo "Apptainer boot attempt number $apptainer_attempts failed, trying again." grep -v -i 'FATAL.*container.*creation.*failed' < #{science_stderr_basename} > #{science_stderr_basename}.clean mv -f #{science_stderr_basename}.clean #{science_stderr_basename} done @@ -2334,34 +2334,34 @@ def load_docker_image_cmd #:nodoc: ################################################################## - # Singularity support methods + # Apptainer support methods ################################################################## - # Returns true if the task's ToolConfig is configured to point to a singularity image + # Returns true if the task's ToolConfig is configured to point to a Apptainer image # for the task's processing. - def use_singularity? - return self.tool_config.use_singularity? + def use_apptainer? + return self.tool_config.use_apptainer? end - # Return the 'singularity' command to be used for the task; this is fetched - # from the Bourreau's own attribute. Default: "singularity". - def singularity_executable_name - return self.bourreau.singularity_executable_name.presence || "singularity" + # Return the 'apptainer' command to be used for the task; this is fetched + # from the Bourreau's own attribute. Default: "apptainer". + def apptainer_executable_name + return self.bourreau.apptainer_executable_name.presence || "apptainer" end # Returns true if the admin has configured this option in the # task's ToolConfig attributes. - def use_singularity_short_workdir? - self.tool_config.singularity_use_short_workdir + def use_apptainer_short_workdir? + self.tool_config.apptainer_use_short_workdir end # Returns the command line(s) associated with the task, wrapped in - # a Singularity call if a Singularity image has to be used. +command_script+ + # a Apptainer call if a Apptainer image has to be used. +command_script+ # is the raw scientific bash script. - def singularity_commands(command_script) + def apptainer_commands(command_script) - # Basename of the singularity wrapper script - singularity_wrapper_basename = ".singularity.#{self.run_id}.sh" + # Basename of the apptainer wrapper script + apptainer_wrapper_basename = ".apptainer.#{self.run_id}.sh" # Values we substitute in our script: # Numbers in (paren) correspond to the comment @@ -2370,9 +2370,9 @@ def singularity_commands(command_script) # (7) The path to the task's work directory task_workdir = self.full_cluster_workdir # a string short_workdir = "/T#{self.id}" # only used in short workdir mode - effect_workdir = use_singularity_short_workdir? ? short_workdir : task_workdir + effect_workdir = use_apptainer_short_workdir? ? short_workdir : task_workdir - # (1) additional singularity execution command options defined in ToolConfig + # (1) additional Apptainer execution command options defined in ToolConfig container_exec_args = self.tool_config.container_exec_args.presence # (2) The root of the DataProvider cache @@ -2408,7 +2408,7 @@ def singularity_commands(command_script) # Some of them might be patterns (e.g. /a/b/data*.squashfs) that need to # be resolved locally. # This will be a string "--overlay=path:ro --overlay=path:ro" etc. - overlay_paths = self.tool_config.singularity_overlays_full_paths.map do |path, knd| + overlay_paths = self.tool_config.apptainer_overlays_full_paths.map do |path, knd| paths = Dir.glob(path) # assume no glob expression in overlay files cb_error "Can't find any local file matching overlay '#{path}'" if paths.blank? paths.each do |f| @@ -2423,8 +2423,8 @@ def singularity_commands(command_script) # Wrap new HOME environment command_script = wrap_new_HOME(command_script, effect_workdir) - # Set singularity command - singularity_commands = <<-SINGULARITY_COMMANDS + # Set apptainer command + apptainer_commands = <<-APPTAINER_COMMANDS # Note to developers: # During a standard CBRAIN task, this script is invoked with no arguments @@ -2434,26 +2434,26 @@ def singularity_commands(command_script) # These two variables control the mode switching at the end of the script. mode="exec" -sing_basename=./#{singularity_wrapper_basename.bash_escape} # note: the ./ is necessary +apptainer_basename=./#{apptainer_wrapper_basename.bash_escape} # note: the ./ is necessary # In 'shell' mode we replace them with other things. if test $# -eq 1 -a "X$1" = "Xshell" ; then mode="shell" - sing_basename="" + apptainer_basename="" fi -# Build a local wrapper script to run in a singularity container -cat << \"SINGULARITYJOB\" > #{singularity_wrapper_basename.bash_escape} +# Build a local wrapper script to run in a apptainer container +cat << \"APPTAINERJOB\" > #{apptainer_wrapper_basename.bash_escape} #!/bin/bash -# Singularity wrapper script created automatically for #{self.class.to_s} +# Apptainer wrapper script created automatically for #{self.class.to_s} # #{self.class.revision_info.to_s} # by ClusterTask # #{ClusterTask.revision_info.to_s} # CBRAIN internal consistency test 1: must run under proper UID if test "$UID" -ne "#{Process.uid}" ; then - echo "Singularity internal script running with wrong UID (expected UID=#{Process.uid})" + echo "Apptainer internal script running with wrong UID (expected UID=#{Process.uid})" echo "Runtime IDs: `id`" exit 2 fi @@ -2489,7 +2489,7 @@ def singularity_commands(command_script) # CBRAIN internal consistency test 6: all mounted ext3 filesystems should be # on a device different from the task's workdir. Otherwise something went -# wrong with the mounts. Singularity or Apptainer can sometimes do that +# wrong with the mounts. Apptainer can sometimes do that # if the command is improperly built (order of mounts args etc). workdir_devid=$(stat -c %d .) # dev number of task workdir for mount in #{capture_basenames.map(&:bash_escape).join(" ")} ; do @@ -2507,12 +2507,12 @@ def singularity_commands(command_script) # Scientific commands start here #{command_script} -SINGULARITYJOB +APPTAINERJOB # Make sure it is executable -chmod 755 #{singularity_wrapper_basename.bash_escape} +chmod 755 #{apptainer_wrapper_basename.bash_escape} -# Invoke Singularity with our wrapper script above. +# Invoke Apptainer with our wrapper script above. # Tricks used here: # 1) we supply (if any) additional options for the exec command # 2) we mount the local data provider cache root directory @@ -2522,8 +2522,8 @@ def singularity_commands(command_script) # 4) we mount each (if any) of the root directories for local data providers # 5) we mount (if any) other fixed file system overlays # 6) we mount (if any) capture ext3 filesystems -# 7) with -H we set the task's work directory as the singularity $HOME directory -#{singularity_executable_name} \\ +# 7) with -H we set the task's work directory as the apptainer $HOME directory +#{apptainer_executable_name} \\ $mode \\ #{container_exec_args} \\ -B #{cache_dir.bash_escape} \\ @@ -2535,11 +2535,11 @@ def singularity_commands(command_script) #{esc_capture_mounts} \\ -H #{effect_workdir.bash_escape} \\ #{container_image_name.bash_escape} \\ - $sing_basename + $apptainer_basename - SINGULARITY_COMMANDS + APPTAINER_COMMANDS - return singularity_commands + return apptainer_commands end @@ -2623,7 +2623,7 @@ def install_ext3fs_filesystem(filename,size) #:nodoc: ################################################################## - # Singularity load + # Apptainer load ################################################################## private @@ -2635,56 +2635,56 @@ def container_image_name #:nodoc: ".container-#{self.id}.img" end - # Load the singularity image either from a repository or from a CBRAIN file - def load_singularity_image #:nodoc: + # Load the Apptainer image either from a repository or from a CBRAIN file + def load_apptainer_image #:nodoc: if self.tool_config.container_image - load_singularity_image_from_userfile + load_apptainer_image_from_userfile elsif self.tool_config.containerhub_image_name - load_singularity_image_from_repo + load_apptainer_image_from_repo else - cb_error "Cannot find source for singularity image..." + cb_error "Cannot find source for Apptainer image..." end end # Create a link to our image; the image is a registered CBRAIN file - def load_singularity_image_from_userfile #:nodoc: - singularity_image = self.tool_config.container_image - self.addlog("Syncing the singularity image '#{singularity_image.name}'") + def load_apptainer_image_from_userfile #:nodoc: + apptainer_image = self.tool_config.container_image + self.addlog("Syncing the Apptainer image '#{apptainer_image.name}'") # Sync the userfile content - singularity_image.sync_to_cache + apptainer_image.sync_to_cache # Create the symlink to the cached image. - cachename = singularity_image.cache_full_path + cachename = apptainer_image.cache_full_path image_name = container_image_name safe_symlink(cachename,image_name) end - # Perform the singularity build; the image will be cached + # Perform the Apptainer build; the image will be cached # as a special, hidden userfile on the ScratchDataProvider. - def load_singularity_image_from_repo #:nodoc: - singularity_image_name = self.tool_config.containerhub_image_name - singularity_index_location = self.tool_config.container_index_location.presence || "shub://" + def load_apptainer_image_from_repo #:nodoc: + apptainer_image_name = self.tool_config.containerhub_image_name + apptainer_index_location = self.tool_config.container_index_location.presence || "shub://" - self.addlog("Building singularity image '#{singularity_image_name}'") + self.addlog("Building Apptainer image '#{apptainer_image_name}'") # Find or create the userfile holding the image content. - scratch_name = "Singularity-build-" + singularity_image_name.gsub(/[^a-z0-9_\.\-]+/i,"_") # must respect userfile convention. + scratch_name = "Apptainer-build-" + apptainer_image_name.gsub(/[^a-z0-9_\.\-]+/i,"_") # must respect userfile convention. scratch_name.sub!(/(\.img)?$/i, ".img") scratch_userfile = SingularityImage.find_or_create_as_scratch(:name => scratch_name) do |cache_path| # Optimization: if another find_or_create_as_scratch has already beaten us to the punch # and dowloaded the file, just skip this block altogether. The way SyncStatus works, several # of these blocks can be scheduled to run, but only one will execute at at any given time. next if File.exists?(cache_path.to_s) && File.size(cache_path.to_s) > 0 - # Run singularity build command - out, err = tool_config_system("umask 000; #{singularity_executable_name} build #{cache_path.to_s.bash_escape} #{singularity_index_location.bash_escape}#{singularity_image_name.bash_escape}") + # Run Apptainer build command + out, err = tool_config_system("umask 000; #{apptainer_executable_name} build #{cache_path.to_s.bash_escape} #{apptainer_index_location.bash_escape}#{apptainer_image_name.bash_escape}") # Check that all is OK; if not we store the captured outputs for investigation if ! File.exists?(cache_path.to_s) || File.size(cache_path.to_s) < 500.kilobytes - capfile = "singularity_build_traces-#{self.run_id}.txt" + capfile = "apptainer_build_traces-#{self.run_id}.txt" File.open(capfile,"w") do |fh| fh.write("=== Stdout ===\n#{out}\n=== Stderr ===\n#{err}\n=== ====== ===\n") end - cb_error "Cannot build singularity image. Captured outputs are in #{capfile}" + cb_error "Cannot build apptainer image. Captured outputs are in #{capfile}" end end diff --git a/BrainPortal/app/models/sing_bindmount_data_provider.rb b/BrainPortal/app/models/sing_bindmount_data_provider.rb index 5757638e3..5e6822b3d 100644 --- a/BrainPortal/app/models/sing_bindmount_data_provider.rb +++ b/BrainPortal/app/models/sing_bindmount_data_provider.rb @@ -22,22 +22,22 @@ # This class implements a Data Provider which fetches # files out of a single filesystem file. The -# implementation requires Singularity 3.2 or better to -# be installed on the host, as well as a singularity +# implementation requires Apptainer 1.1 or better +# be installed on the host, as well as an Apptainer # container image that contains the basic Linux # commands and the 'rsync' command too. # # For the moment the DP is read-only. Allowing # write access would require a mechanism in CBRAIN -# to prevent more than one singularity process to +# to prevent more than one Apptainer process to # be accessing the mounted file at any one time. # # Bind mounts are performed using a command such as: # -# singularity shell -B filesystem.img:/my/mount/point:image-src=/inside/dir,ro sing_squashfs.simg +# apptainer shell -B filesystem.img:/my/mount/point:image-src=/inside/dir,ro sing_squashfs.simg # # where: -# 1- sing_squashfs.simg is a singularity image file, +# 1- sing_squashfs.simg is an Apptainer image file, # 2- filesystem.img is either a ext3 or squashfs filesystem, # 3- /inside/dir is a path inside that filesystem, # 4- /my/mount/point is a path where the data will be mounted under @@ -51,7 +51,7 @@ # # The attribute 'remote_dir' must be a full path to the location of # the filesystem image 'filesystem.img' (even though it is not a 'dir'). -# The singularity image file will be expected to be called 'sing_squashfs.simg' +# The Apptainer image file will be expected to be called 'sing_squashfs.simg' # and be located in the same subdirectory as 'filesystem.img'. class SingBindmountDataProvider < SshDataProvider @@ -64,14 +64,14 @@ class SingBindmountDataProvider < SshDataProvider # TODO: Change this is the DP is ever made writable! BROWSE_CACHE_EXPIRATION = 2.months #:nodoc: - # This is the basename of the singularity image + # This is the basename of the Apptainer image # we use to access the remote filesystem; we # expect this image to be installed in the same # directory that contains it. Its minimum # requirements are that 1) basic UNIX commands # exist on it 2) the rsync command is installed # in it too. - SINGULARITY_IMAGE_BASENAME = 'sing_squashfs.simg' + APPTAINER_IMAGE_BASENAME = 'sing_squashfs.simg' # We use this to point to the directory INSIDE the container # were the root of the data is stored. @@ -96,7 +96,7 @@ def read_only=(val) #:nodoc: # This returns the category of the data provider def self.pretty_category_name #:nodoc: - "Singularity Bind Mount" + "Apptainer Bind Mount" end # This uses the new CBRAIN capability of registering @@ -114,20 +114,20 @@ def impl_is_alive? #:nodoc: # Raise an exception with a message indicating what is wrong with the config. # This method is not part of the official method API def check_remote_config! #:nodoc: - # Check we have one singularity image file - remote_cmd = "cd #{self.real_remote_dir.to_s.bash_escape} && test -f #{SINGULARITY_IMAGE_BASENAME} && echo OK-Exists" + # Check we have one Apptainer image file + remote_cmd = "cd #{self.real_remote_dir.to_s.bash_escape} && test -f #{APPTAINER_IMAGE_BASENAME} && echo OK-Exists" text = self.remote_bash_this(remote_cmd) # The following check will also make sure the remote shell is clean! - cb_error "No installed singularity image #{SINGULARITY_IMAGE_BASENAME}, or remote shell is unclean" unless text =~ /\AOK-Exists\s*\z/ + cb_error "No installed apptainer image #{APPTAINER_IMAGE_BASENAME}, or remote shell is unclean" unless text =~ /\AOK-Exists\s*\z/ # Check we have the remote filesystem file remote_cmd = "test -f #{self.remote_dir.to_s.bash_escape} && echo OK-Exists" cb_error "Filesystem file '#{self.remote_dir}' not found" unless text =~ /\AOK-Exists\s*\z/ - # Check we have singularity version 3.7 or better + # Check we have Apptainer 1.1 or better remote_cmd = "(singularity --version 2>/dev/null || apptainer --version 2>/dev/null)" text = self.remote_bash_this(remote_cmd) - cb_error "Can't find singularity version number on remote host" unless text =~ /^((singularity|apptainer) version )?(\d+)\.(\d+)/ + cb_error "Can't find apptainer version number on remote host" unless text =~ /^((singularity|apptainer) version )?(\d+)\.(\d+)/ _, _, tool, major, minor = Regexp.last_match.to_a major = major.to_i minor = minor.to_i @@ -139,7 +139,7 @@ def check_remote_config! #:nodoc: # Check that inside the container, the containerized path exists checkdir = "test -d #{self.containerized_path.bash_escape} && echo OK-Exists" - text = remote_in_sing_bash_this(checkdir) + text = remote_in_apptainer_bash_this(checkdir) cb_error "No path '#{self.containerized_path}' inside container" unless text =~ /\AOK-Exists\s*\z/ # Well, we passed all the tests @@ -184,7 +184,7 @@ def impl_provider_list_all(user=nil, browse_path=nil) #:nodoc: file_infos = Rails.cache.fetch(cache_key, :expires_in => BROWSE_CACHE_EXPIRATION) do dir = Pathname.new(self.containerized_path) + browse_path.to_s - text = remote_in_sing_stat_all(dir,".",true) + text = remote_in_apptainer_stat_all(dir,".",true) stat_reports_to_fileinfos(text) end @@ -216,7 +216,7 @@ def impl_provider_collection_index(userfile, directory = :all, allowed_types = : type_opt = allowed_types == [ :regular ] ? "f" : allowed_types == [ :directory ] ? "d" : nil # we can still filter for other combinations on Ruby side - text = remote_in_sing_stat_all(basedir, subdir, one_level, type_opt) + text = remote_in_apptainer_stat_all(basedir, subdir, one_level, type_opt) file_infos = stat_reports_to_fileinfos(text) # Apply more complex filters if necessary @@ -272,8 +272,8 @@ def impl_provider_rename(userfile,newname) #:nodoc: # Returns true if we have to use 'ssh' to # connect to the remote server. Returns false - # when we can optimize requests by running - # singularity locally. The local situation is + # when we can optimize requests by running the + # Apptainer locally. The local situation is # detected pretty much like in the Smart DP # module: if the hostname is the same as *remote_host* # or *alternate_host*, and if the *remote_dir* exists @@ -299,8 +299,8 @@ def provider_is_remote #:nodoc: @provider_is_remote end - def singularity_exec_prefix #:nodoc: - "cd #{self.real_remote_dir.to_s.bash_escape} && singularity -s exec #{self.local_bind_opt} #{SINGULARITY_IMAGE_BASENAME}" + def apptainer_exec_prefix #:nodoc: + "cd #{self.real_remote_dir.to_s.bash_escape} && singularity -s exec #{self.local_bind_opt} #{APPTAINER_IMAGE_BASENAME}" end def local_bind_opt #:nodoc: @@ -308,7 +308,7 @@ def local_bind_opt #:nodoc: end def remote_rsync_command #:nodoc: - "#{singularity_exec_prefix} rsync" + "#{apptainer_exec_prefix} rsync" end def real_remote_dir #:nodoc: @@ -338,12 +338,12 @@ def rsync_over_ssh_prefix rsync end - def remote_in_sing_bash_this(com) #:nodoc: - newcom = "#{singularity_exec_prefix} bash -c #{com.bash_escape}" + def remote_in_apptainer_bash_this(com) #:nodoc: + newcom = "#{apptainer_exec_prefix} bash -c #{com.bash_escape}" remote_bash_this(newcom) end - def remote_in_sing_stat_all(basedir, subdir, one_level = true, find_type = nil) #:nodoc: + def remote_in_apptainer_stat_all(basedir, subdir, one_level = true, find_type = nil) #:nodoc: max_depth = one_level ? "-maxdepth 1" : "" type_opt = find_type ? "-type #{find_type}" : "" # Linux 'stat' command formats: @@ -359,7 +359,7 @@ def remote_in_sing_stat_all(basedir, subdir, one_level = true, find_type = nil) # Linux 'find' command format: find_format = "E=%y,%m,%s,%U,%u,%G,%g,%A@,%T@,%C@,%p\\n" com = "cd #{basedir.to_s.bash_escape} && find #{subdir.to_s.bash_escape} #{max_depth} #{type_opt} -printf \"#{find_format}\"" - remote_in_sing_bash_this(com) + remote_in_apptainer_bash_this(com) end # Given a text file report such as this: diff --git a/BrainPortal/app/models/sing_squashfs_data_provider.rb b/BrainPortal/app/models/sing_squashfs_data_provider.rb index 2ebeb411a..adf1ec2d2 100644 --- a/BrainPortal/app/models/sing_squashfs_data_provider.rb +++ b/BrainPortal/app/models/sing_squashfs_data_provider.rb @@ -22,8 +22,8 @@ # This class implements a Data Provider which fetches # files out of one or several SquashFS files. The -# implementation requires Singularity 3.2 or better to -# be installed on the host, as well as a singularity +# implementation requires Apptainer 1.1 or better to +# be installed on the host, as well as an Apptainer # container image that contains the basic Linux # commands and the 'rsync' command too. class SingSquashfsDataProvider < SshDataProvider @@ -35,14 +35,14 @@ class SingSquashfsDataProvider < SshDataProvider # be forever, really. BROWSE_CACHE_EXPIRATION = 6.months #:nodoc: - # This is the basename of the singularity image + # This is the basename of the Apptainer image # we use to access the squashfs filesystems; we # expect this image to be installed in the same # directory that contain them. Its minimum # requirements are that 1) basic UNIX commands # exist on it 2) the rsync command is installed # in it too. - SINGULARITY_IMAGE_BASENAME = 'sing_squashfs.simg' + APPTAINER_IMAGE_BASENAME = 'sing_squashfs.simg' # We use this to point to the directory INSIDE the container # were the root of the data is stored @@ -77,37 +77,48 @@ def impl_is_alive? #:nodoc: false end + # Check we have Apptainer 1.1 or better + def apptainer_executable_name + return @_tool if @_tool # cached name of executable + + remote_cmd = "( apptainer --version 2>/dev/null || singularity --version 2>/dev/null )" + # Apptainer is preferable so it comes first in the command + # also works if an old Singularity and uptodate Apptainer + # todo loop over list of several candidate executables + text = self.remote_bash_this(remote_cmd) + cb_error "Can't find apptainer version number on remote host" unless text =~ /^((singularity|apptainer) version )?(\d+)\.(\d+)/ + _, _, @_tool, major, minor = Regexp.last_match.to_a + major = major.to_i + minor = minor.to_i + if @_tool == 'singularity' + cb_error "Singularity version number on remote host is less than 3.7" if major < 3 || (major == 3 && minor < 7) + else # tool == 'apptainer' + cb_error "Apptainer version number on remote host is less than 1.1" if major < 1 || (major == 1 && minor < 1) + end + + return @_tool + + end + + + # Raise an exception with a message indicating what is wrong with the config. # This method is not part of the official method API def check_remote_config! #:nodoc: - # Check we have one singularity image file - remote_cmd = "cd #{self.remote_dir.bash_escape};test -f #{SINGULARITY_IMAGE_BASENAME} && echo OK-Exists" + # Check we have one Apptainer image file + remote_cmd = "cd #{self.remote_dir.bash_escape};test -f #{APPTAINER_IMAGE_BASENAME} && echo OK-Exists" text = self.remote_bash_this(remote_cmd) # The following check will also make sure the remote shell is clean! - cb_error "No installed singularity image #{SINGULARITY_IMAGE_BASENAME}, or remote shell is unclean" unless text =~ /\AOK-Exists\s*\z/ + cb_error "No installed Apptainer image #{APPTAINER_IMAGE_BASENAME}, or remote shell is unclean" unless text =~ /\AOK-Exists\s*\z/ # Check we have at least one .squashfs file in the remote_dir sq_files = get_squashfs_basenames cb_error "No .squashfs files found" unless sq_files.present? - - # Check we have singularity version 3.2 or better - remote_cmd = "(singularity --version 2>/dev/null || apptainer --version 2>/dev/null)" - text = self.remote_bash_this(remote_cmd) - cb_error "Can't find singularity version number on remote host" unless text =~ /^((singularity|apptainer) version )?(\d+)\.(\d+)/ - _, _, tool, major, minor = Regexp.last_match.to_a - major = major.to_i - minor = minor.to_i - if tool == 'singularity' - cb_error "singularity version number on remote host is less than 3.7" if major < 3 || (major == 3 && minor < 7) - else # tool == 'apptainer' - cb_error "apptainer version number on remote host is less than 1.1" if major < 1 || (major == 1 && minor < 1) - end - # Check that inside the container all_sq_files = @sq_files @sq_files = [ @sq_files.first ] # To speed up check, use only the first squashfs file checkdir = "test -d #{self.containerized_path.bash_escape} && echo OK-Exists" - text = remote_in_sing_bash_this(checkdir) + text = remote_in_apptainer_bash_this(checkdir) @sq_files = all_sq_files # return it to proper full list cb_error "No path '#{self.containerized_path}' inside container" unless text =~ /\AOK-Exists\s*\z/ @@ -158,7 +169,7 @@ def impl_provider_list_all(user=nil,browse_path=nil) #:nodoc: file_infos = Rails.cache.fetch(cache_key, :expires_in => BROWSE_CACHE_EXPIRATION) do sourcedir = Pathname.new(self.containerized_path) sourcedir += browse_path if browse_path.present? - text = remote_in_sing_stat_all(sourcedir.to_s, "." ,true) + text = remote_in_apptainer_stat_all(sourcedir.to_s, "." ,true) stat_reports_to_fileinfos(text) end @@ -190,7 +201,7 @@ def impl_provider_collection_index(userfile, directory = :all, allowed_types = : type_opt = allowed_types == [ :regular ] ? "f" : allowed_types == [ :directory ] ? "d" : nil # we can still filter for other combinations on Ruby side - text = remote_in_sing_stat_all(basedir, subdir, one_level, type_opt) + text = remote_in_apptainer_stat_all(basedir, subdir, one_level, type_opt) file_infos = stat_reports_to_fileinfos(text) # Apply more complex filters if necessary @@ -245,7 +256,7 @@ def impl_provider_rename(userfile,newname) #:nodoc: public # Returns the full paths to the overlays - def singularity_overlays_full_paths #:nodoc: + def apptainer_overlays_full_paths #:nodoc: self.get_squashfs_basenames.map do |basename| Pathname.new(self.remote_dir) + basename end.map(&:to_s) @@ -256,7 +267,7 @@ def singularity_overlays_full_paths #:nodoc: # Returns true if we have to use 'ssh' to # connect to the remote server. Returns false # when we can optimize requests by running - # singularity locally. The local situation is + # Apptainer locally. The local situation is # detected pretty much like in the Smart DP # module: if the hostname is the same as *remote_host* # or *alternate_host*, and if the *remote_dir* exists @@ -271,7 +282,7 @@ def provider_is_remote #:nodoc: .map(&:presence) .compact - # Or test is biased so that we try local only if we have a local dir + # Our test is biased so that we try local only if we have a local dir # and the hostname match. if dp_hostnames.include?(Socket.gethostname) && File.directory?(self.remote_dir) @provider_is_remote = false @@ -296,14 +307,14 @@ def get_squashfs_basenames(force = false) #:nodoc: @sq_files end - def singularity_exec_prefix #:nodoc: + def apptainer_exec_prefix #:nodoc: sq_files = get_squashfs_basenames overlay_opts = sq_files.map { |f| "--overlay=#{f.bash_escape}:ro" }.join(" ") - "cd #{self.remote_dir.bash_escape} && singularity -s exec #{overlay_opts} #{SINGULARITY_IMAGE_BASENAME}" + "cd #{self.remote_dir.bash_escape} && #{apptainer_executable_name} -s exec #{overlay_opts} #{APPTAINER_IMAGE_BASENAME}" end def remote_rsync_command #:nodoc: - "#{singularity_exec_prefix} rsync" + "#{apptainer_exec_prefix} rsync" end # Builds a prefix for a +rsync+ command, such as @@ -325,12 +336,12 @@ def rsync_over_ssh_prefix rsync end - def remote_in_sing_bash_this(com) #:nodoc: - newcom = "#{singularity_exec_prefix} bash -c #{com.bash_escape}" + def remote_in_apptainer_bash_this(com) #:nodoc: + newcom = "#{apptainer_exec_prefix} bash -c #{com.bash_escape}" remote_bash_this(newcom) end - def remote_in_sing_stat_all(basedir, subdir, one_level = true, find_type = nil) #:nodoc: + def remote_in_apptainer_stat_all(basedir, subdir, one_level = true, find_type = nil) #:nodoc: max_depth = one_level ? "-maxdepth 1" : "" type_opt = find_type ? "-type #{find_type}" : "" # Linux 'stat' command formats: @@ -346,7 +357,7 @@ def remote_in_sing_stat_all(basedir, subdir, one_level = true, find_type = nil) # Linux 'find' command format: find_format = "E=%y,%m,%s,%U,%u,%G,%g,%A@,%T@,%C@,%p\\n" com = "cd #{basedir.to_s.bash_escape} && find #{subdir.to_s.bash_escape} #{max_depth} #{type_opt} -printf \"#{find_format}\"" - remote_in_sing_bash_this(com) + remote_in_apptainer_bash_this(com) end # Given a text file report such as this: diff --git a/BrainPortal/app/models/tool_config.rb b/BrainPortal/app/models/tool_config.rb index b6c5ba068..6de420c37 100644 --- a/BrainPortal/app/models/tool_config.rb +++ b/BrainPortal/app/models/tool_config.rb @@ -195,10 +195,10 @@ def extended_environment # Generates a partial BASH script that initializes environment # variables and is followed a the script prologue stored in the - # object. For singularity prologues, special prefixes are added to + # object. For Apptainer prologues, special prefixes are added to # variable names to ensure they will be propagated to the container # even in presence of --cleanenv parameteres and such - def to_bash_prologue(singularity=false) + def to_bash_prologue(apptainer=false) tool = self.tool bourreau = self.bourreau group = self.group @@ -239,15 +239,14 @@ def to_bash_prologue(singularity=false) ENV_HEADER script += vars_to_export_script - if singularity + if apptainer script += <<-ENV_HEADER #--------------------------------------------------- # Ensuring that environment variables are propagated:#{env.size == 0 ? " (NONE DEFINED)" : ""} #--------------------------------------------------- ENV_HEADER - script += vars_to_export_script("SINGULARITYENV_") - script += vars_to_export_script("APPTAINERENV_") # SINGULARITYENV is to be depricated + script += vars_to_export_script("APPTAINERENV_") end script += "\n" if env.size > 0 @@ -341,9 +340,10 @@ def is_at_least_version(version) end end - # true if singularity image is defined - def use_singularity? - return self.container_engine == "Singularity" && + # true if Apptainer image is defined + def use_apptainer? + return (self.container_engine == "Singularity" || # Currently Boutiques keeps singularity only + self.container_engine == "Apptainer") && # Singularity will be probably deprecated by Boutiques soon ( self.containerhub_image_name.present? || self.container_image_userfile_id.present? ) end @@ -381,7 +381,7 @@ def cbrain_task_class self.tool.cbrain_task_class end - # Returns a hash of full paths to the Singularity overlay files that + # Returns a hash of full paths to the Apptainer overlay files that # need to be mounted along with explanation of their origin. # Some of paths might # be patterns and will need to be resolved at run time. The dsl is @@ -393,7 +393,7 @@ def cbrain_task_class # userfile:1234 # # A ext3 capture filesystem, will NOT be returned here as an overlay # ext3capture:basename=12G - def singularity_overlays_full_paths + def apptainer_overlays_full_paths specs = parsed_overlay_specs specs.map do |knd, id_or_name| @@ -401,7 +401,7 @@ def singularity_overlays_full_paths when 'dp' dp = DataProvider.where_id_or_name(id_or_name).first cb_error "Can't find DataProvider #{id_or_name} for fetching overlays" if ! dp - dp_ovs = dp.singularity_overlays_full_paths rescue nil + dp_ovs = dp.apptainer_overlays_full_paths rescue nil cb_error "DataProvider #{id_or_name} does not have any overlays configured." if dp_ovs.blank? dp_ovs.map do |dp_path| { dp_path => "Data Provider" } @@ -424,7 +424,7 @@ def singularity_overlays_full_paths end # Returns an array of the data providers that are - # specified in the attribute singularity_overlays_specs, + # specified in the attribute apptainer_overlays_specs, # ignoring all other overlay specs for normal files. def data_providers_with_overlays return @_data_providers_with_overlays_ if @_data_providers_with_overlays_ @@ -452,7 +452,7 @@ def ext3capture_basenames # Validate some rules for container_engine, container_image_userfile_id, containerhub_image_name def validate_container_rules #:nodoc: # Should only have one container_engine of particular type - available_engine = ["Singularity","Docker"] + available_engine = ["Apptainer","Singularity","Docker"] if self.container_engine.present? && available_engine.exclude?(self.container_engine) errors[:container_engine] = "is not valid" end @@ -470,9 +470,9 @@ def validate_container_rules #:nodoc: errors[:container_engine] = "a container hub image name or a container image userfile ID should be set when the container engine is set" end - if self.container_engine.present? && self.container_engine == "Singularity" + if self.container_engine.present? && (self.container_engine == "Apptainer" || self.container_engine == "Singularity") if self.container_index_location.present? && self.container_index_location !~ /\A[a-z0-9]+\:\/\/\z/i - errors[:container_index_location] = "is invalid for container engine Singularity. Should end in '://'." + errors[:container_index_location] = "is invalid for container engine Apptainer. Should end in '://'." end elsif self.container_engine.present? && self.container_engine == "Docker" if self.container_index_location.present? && self.container_index_location !~ /\A[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,6}\z/i @@ -484,7 +484,7 @@ def validate_container_rules #:nodoc: # breaks down overlay spec onto a list of overlays def parsed_overlay_specs - specs = self.singularity_overlays_specs + specs = self.apptainer_overlays_specs return [] if specs.blank? lines = specs.split(/^#.*|\s+#.*|\n/) # split on lines and drop comments lines.map(&:presence).compact.map do |spec| @@ -513,24 +513,24 @@ def validate_overlays_specs #:nodoc: case kind # different validations rules for file, userfile and dp specs when 'file' # full path specification for a local file, e.g. "file:/myfiles/c.sqs" if id_or_name !~ /^\/\S+\.(sqs|sqfs|squashfs)$/ - self.errors.add(:singularity_overlays_specs, + self.errors.add(:apptainer_overlays_specs, " contains invalid #{kind} named '#{id_or_name}'. It should be a full path that ends in .squashfs, .sqs or .sqfs") end when 'userfile' # db-registered file spec, e.g. "userfile:42" if id_or_name !~ /\A\d+\z/ - self.errors.add(:singularity_overlays_specs, + self.errors.add(:apptainer_overlays_specs, %{" contains invalid userfile id '#{id_or_name}'. The userfile id should be an integer number."} ) else userfile = SingleFile.where(:id => id_or_name).first end if ! userfile - self.errors.add(:singularity_overlays_specs, + self.errors.add(:apptainer_overlays_specs, %{" contains invalid userfile id '#{id_or_name}'. The file with id '#{id_or_name}' is not found."} ) elsif userfile.name.to_s !~ /\.(sqs|sqfs|squashfs)$/ - self.errors.add(:singularity_overlays_specs, + self.errors.add(:apptainer_overlays_specs, " contains invalid userfile with id '#{id_or_name}' and name '#{userfile.name}'. File name should end in .squashfs, .sqs or .sqfs") # todo maybe or/and check file type? end @@ -538,20 +538,20 @@ def validate_overlays_specs #:nodoc: when 'dp' # DataProvider specs: "dp:name" or "dp:ID" dp = DataProvider.where_id_or_name(id_or_name).first if !dp - self.errors.add(:singularity_overlays_specs, "contains invalid DP '#{id_or_name}' (no such DP)") + self.errors.add(:apptainer_overlays_specs, "contains invalid DP '#{id_or_name}' (no such DP)") elsif ! dp.is_a?(SingSquashfsDataProvider) - self.errors.add(:singularity_overlays_specs, "DataProvider '#{id_or_name}' is not a SingSquashfsDataProvider") + self.errors.add(:apptainer_overlays_specs, "DataProvider '#{id_or_name}' is not a SingSquashfsDataProvider") end when 'ext3capture' # ext3 filesystem as a basename with an initial size # The basename is limited to letters, digits, numbers and dashes; the =SIZE suffix must end with G or M if id_or_name !~ /\A\w[\w\.-]+=([1-9]\d*)[mg]\z/i - self.errors.add(:singularity_overlays_specs, "contains invalid ext3capture specification (must be like ext3capture:basename=1g or 2m etc)") + self.errors.add(:apptainer_overlays_specs, "contains invalid ext3capture specification (must be like ext3capture:basename=1g or 2m etc)") end else # Other errors - self.errors.add(:singularity_overlays_specs, "contains invalid specification '#{kind}:#{id_or_name}'") + self.errors.add(:apptainer_overlays_specs, "contains invalid specification '#{kind}:#{id_or_name}'") end end @@ -662,7 +662,7 @@ def self.create_from_descriptor(bourreau, tool, descriptor, record_path=false) container_engine = container_info['type'].presence.try(:capitalize) container_engine = "Singularity" if (container_engine == "Docker" && !bourreau.docker_present? && - bourreau.singularity_present? + bourreau.apptainer_present? ) container_index = container_info['index'].presence container_index = 'docker://' if container_index == 'index.docker.io' # old convention diff --git a/BrainPortal/app/views/bourreaux/show.html.erb b/BrainPortal/app/views/bourreaux/show.html.erb index 73f2dec0b..43c24208d 100644 --- a/BrainPortal/app/views/bourreaux/show.html.erb +++ b/BrainPortal/app/views/bourreaux/show.html.erb @@ -475,9 +475,9 @@
Name of the Docker executable available on the machines where tasks will run. It should always be set if Docker is present.
<% end %> - <% t.edit_cell :singularity_executable_name, :header => "Singularity executable" do |f| %> - <%= f.text_field :singularity_executable_name, :size => 60 %>
-
Name of the Singularity executable available on the machines where tasks will run. It should always be set if Singularity is present.
+ <% t.edit_cell :apptainer_executable_name, :header => "Apptainer executable" do |f| %> + <%= f.text_field :apptainer_executable_name, :size => 60 %>
+
Name of the Apptainer or Singularity executable available on the machines where tasks will run. It should always be set if Singularity is present.
<% end %> <% end %> <% end %> diff --git a/BrainPortal/app/views/data_providers/_dp_types_explained.html.erb b/BrainPortal/app/views/data_providers/_dp_types_explained.html.erb index 951970e11..73070cfb8 100644 --- a/BrainPortal/app/views/data_providers/_dp_types_explained.html.erb +++ b/BrainPortal/app/views/data_providers/_dp_types_explained.html.erb @@ -114,12 +114,12 @@ acts like a FlatDataProvider.

-SingSquashfsDataProvider This class connects to a set of +ApptainerSquashfsDataProvider This class connects to a set of one or several squashfs files (all named with .squashfs extensions) through -a Singularity container handler. The requirements are:
+a Apptainer container handler. The requirements are:

diff --git a/BrainPortal/app/views/tool_configs/_form_fields.html.erb b/BrainPortal/app/views/tool_configs/_form_fields.html.erb index fb166cb6a..aac81f17f 100644 --- a/BrainPortal/app/views/tool_configs/_form_fields.html.erb +++ b/BrainPortal/app/views/tool_configs/_form_fields.html.erb @@ -194,10 +194,10 @@ <% t.edit_cell(:container_index_location, :header => "Index of the container image") do |f| %> <%= f.text_field :container_index_location %>
- The index (url) of the container image in which the docker or singularity container is + The index (url) of the container image in which the docker or Apptainer/Singularity container is accessible through. Examples for Docker are: quay.io, index.docker.io (default). - Examples for Singularity are: docker://, shub:// (default). + Examples for Apptainer are: docker://, shub:// (default).
<% end %> @@ -219,11 +219,11 @@ <% end %> - <% t.edit_cell(:singularity_overlays_specs, :header => "Singularity Overlays", :show_width => 2, :content => full_description(@tool_config.singularity_overlays_specs)) do |f| %> - <%= f.text_area :singularity_overlays_specs, :rows => 6, :cols => 120 %> + <% t.edit_cell(:apptainer_overlays_specs, :header => "Apptainer/Singularity Overlays", :show_width => 2, :content => full_description(@tool_config.apptainer_overlays_specs)) do |f| %> + <%= f.text_area :apptainer_overlays_specs, :rows => 6, :cols => 120 %>
This field can contain one or several specifications for data overlays - to be included when the task is started with Singularity. + to be included when the task is started with Apptainer (formerly known as Singularity). A specification can be either a full path (e.g. file:/a/b/data.squashfs), a path with a pattern (e.g. file:/a/b/data*.squashfs), @@ -237,18 +237,18 @@
<% end %> - <% t.edit_cell(:container_exec_args, :header => "Misc Singularity Options", :show_width => 2) do |f| %> + <% t.edit_cell(:container_exec_args, :header => "Misc Apptainter Options", :show_width => 2) do |f| %> <%= f.text_field :container_exec_args, :size => 60 %>
- This field can contain singularity exec command options. Please use appropriate quotation or escaping + This field can contain apptainer/singularity exec command options. Please use appropriate quotation or escaping For example, --cleanenv --env MYPATH='/My Documents'.
<% end %> - <% t.boolean_edit_cell('tool_config[singularity_use_short_workdir]', - (@tool_config.singularity_use_short_workdir ? "1" : "0"), + <% t.boolean_edit_cell('tool_config[apptainer_use_short_workdir]', + (@tool_config.apptainer_overlays_specs ? "1" : "0"), "1", "0", - :header => "Use short workdirs inside Singularity") + :header => "Use short workdirs inside Apptainer") %> <% end %> diff --git a/BrainPortal/app/views/tool_configs/show.html.erb b/BrainPortal/app/views/tool_configs/show.html.erb index 6b570a903..ff02fd003 100644 --- a/BrainPortal/app/views/tool_configs/show.html.erb +++ b/BrainPortal/app/views/tool_configs/show.html.erb @@ -69,9 +69,9 @@
 
-<%= @bourreau_glob_config.to_bash_prologue @tool_local_config&.use_singularity? if @bourreau_glob_config %>
-<%= @tool_glob_config.to_bash_prologue     @tool_local_config&.use_singularity? if @tool_glob_config     %>
-<%= @tool_local_config.to_bash_prologue    @tool_local_config&.use_singularity? if @tool_local_config    %>
+<%= @bourreau_glob_config.to_bash_prologue @tool_local_config&.use_apptainer? if @bourreau_glob_config %>
+<%= @tool_glob_config.to_bash_prologue     @tool_local_config&.use_apptainer? if @tool_glob_config     %>
+<%= @tool_local_config.to_bash_prologue    @tool_local_config&.use_apptainer? if @tool_local_config    %>
 ##########################################
 #### [Wrapped commands would be here] ####
 ##########################################
diff --git a/BrainPortal/cbrain_plugins/cbrain-plugins-base/userfiles/singularity_image/singularity_image.rb b/BrainPortal/cbrain_plugins/cbrain-plugins-base/userfiles/singularity_image/singularity_image.rb
index 8953c769e..1f155744e 100644
--- a/BrainPortal/cbrain_plugins/cbrain-plugins-base/userfiles/singularity_image/singularity_image.rb
+++ b/BrainPortal/cbrain_plugins/cbrain-plugins-base/userfiles/singularity_image/singularity_image.rb
@@ -32,28 +32,33 @@ def self.file_name_pattern #:nodoc:
   end
 
   def is_viewable? #:nodoc:
-    if ! self.has_singularity_support?
-      return [ "The local portal doesn't support inspecting Singularity images." ]
+    if ! self.has_apptainer_support?
+      return [ "The local portal doesn't support inspecting Apptainer (aka Singularity) images." ]
     elsif ! self.is_locally_synced?
-      return [ "Singularity image file not yet synchronized" ]
+      return [ "Apptainer (aka Singularity) image file not yet synchronized" ]
     else
       true
     end
   end
 
-  def has_singularity_support? #:nodoc:
-    self.class.has_singularity_support?
+  def has_apptainer_support? #:nodoc:
+    self.class.has_apptainer_support?
   end
 
-  # Detects if the system has the 'singularity' command.
+  # caches apptainer_executable_name (which does not change that often)
+  def self.apptainer_executable  #:nodoc:
+    @_executable ||= RemoteResource.current_resource.apptainer_executable_name.presence || "apptainer"
+  end
+
+  # Detects if the system has the Apptainer command.
+  # Singularity command is still supported on best effort basis
   # Caches the result in the class so it won't need to
   # be detected again after the first time, for the life
   # of the current process.
-  def self.has_singularity_support? #:nodoc:
-    return @_has_singularity_support if ! @_has_singularity_support.nil?
-    out = IO.popen("bash -c 'type -p singularity'","r") { |f| f.read }
-    @_has_singularity_support = out.present?
+  def self.has_apptainer_support? #:nodoc:
+    return @_has_apptainer_support if ! @_has_apptainer_support.nil?
+    out = IO.popen("bash -c 'type -p #{apptainer_executable}'","r") { |f| f.read }
+    @_has_apptainer_support = out.present?
   end
 
 end
-
diff --git a/BrainPortal/cbrain_plugins/cbrain-plugins-base/userfiles/singularity_image/views/_info.html.erb b/BrainPortal/cbrain_plugins/cbrain-plugins-base/userfiles/singularity_image/views/_info.html.erb
index 1f45c8ff0..0c49d8934 100644
--- a/BrainPortal/cbrain_plugins/cbrain-plugins-base/userfiles/singularity_image/views/_info.html.erb
+++ b/BrainPortal/cbrain_plugins/cbrain-plugins-base/userfiles/singularity_image/views/_info.html.erb
@@ -27,21 +27,21 @@
 
 
Image Inspect
-
<%= cat.("singularity inspect #{path}") %>
+
<%= cat.("#{SingularityImage.apptainer_executable} inspect #{path}") %>
SIF Header
-
<%= cat.("singularity sif header #{path}") %>
+
<%= cat.("#{SingularityImage.apptainer_executable} sif header #{path}") %>
SIF List
-
<%= text = cat.("singularity sif list #{path}") %>
+
<%= text = cat.("#{SingularityImage.apptainer_executable} sif list #{path}") %>
SIF Items
<% text.split(/\n/).map { |line| line[/^(\d+)/] }.compact.each do |id| %> -
<%= cat.("singularity sif info #{id} #{path}") %>

+

<%= cat.("#{SingularityImage.apptainer_executable} sif info #{id} #{path}") %>

<% end %> diff --git a/BrainPortal/config/console_rc/lib/pretty_view.rb b/BrainPortal/config/console_rc/lib/pretty_view.rb index 77fa24e99..8945f1d0b 100644 --- a/BrainPortal/config/console_rc/lib/pretty_view.rb +++ b/BrainPortal/config/console_rc/lib/pretty_view.rb @@ -234,7 +234,7 @@ def pretview Created: %s Updated: %s Docker: %s - Singularity: %s + Apptainer: %s Flags: %s %s %s VIEW sprintf report, @@ -266,7 +266,7 @@ def pretview ConsoleCtx.send(:pretty_past_date,created_at), ConsoleCtx.send(:pretty_past_date,updated_at), docker_executable_name.presence || "", - singularity_executable_name.presence || "", + apptainer_executable_name.presence || "", (online? ? "Online" : "Offline"), (read_only? ? "ReadOnly" : "R/W"), (portal_locked? ? "LOCKED" : "") diff --git a/BrainPortal/db/migrate/20240408164549_rename_singularity_to_apprainer.rb b/BrainPortal/db/migrate/20240408164549_rename_singularity_to_apprainer.rb new file mode 100644 index 000000000..ee000e29d --- /dev/null +++ b/BrainPortal/db/migrate/20240408164549_rename_singularity_to_apprainer.rb @@ -0,0 +1,7 @@ +class RenameSingularityToApprainer < ActiveRecord::Migration[5.0] + def change + rename_column :tool_configs, :singularity_overlays_specs, :apptainer_overlays_specs + rename_column :tool_configs, :singularity_use_short_workdir, :apptainer_use_short_workdir + rename_column :remote_resources, :singularity_executable_name, :apptainer_executable_name + end +end diff --git a/BrainPortal/db/schema.rb b/BrainPortal/db/schema.rb index f8e0f11f7..5f7ca44c9 100644 --- a/BrainPortal/db/schema.rb +++ b/BrainPortal/db/schema.rb @@ -315,7 +315,7 @@ t.string "nh_system_from_email" t.string "external_status_page_url" t.string "docker_executable_name" - t.string "singularity_executable_name" + t.string "apptainer_executable_name" t.string "small_logo" t.string "large_logo" t.index ["type"], name: "index_remote_resources_on_type", using: :btree @@ -485,8 +485,8 @@ t.string "containerhub_image_name" t.string "container_engine" t.string "container_index_location" - t.text "singularity_overlays_specs", limit: 65535 - t.boolean "singularity_use_short_workdir", default: false, null: false + t.text "apptainer_overlays_specs", limit: 65535 + t.boolean "apptainer_use_short_workdir", default: false, null: false t.string "container_exec_args" t.boolean "inputs_readonly", default: false t.string "boutiques_descriptor_path" diff --git a/BrainPortal/lib/boutiques_ext3_capturer.rb b/BrainPortal/lib/boutiques_ext3_capturer.rb index 1345eb6e2..239b0ea86 100644 --- a/BrainPortal/lib/boutiques_ext3_capturer.rb +++ b/BrainPortal/lib/boutiques_ext3_capturer.rb @@ -22,7 +22,7 @@ # This module adds automatic setting up of mounted # ext3 filesystem as subdirectories of a task, provided -# the tool works in Singularity/Apptainer. +# the tool works in Apptainer. # It is the exact equivalent of adding an ext3 overlay # configuration entry in the task's tool config. # diff --git a/BrainPortal/lib/cbrain_task_generators/schema_task_generator.rb b/BrainPortal/lib/cbrain_task_generators/schema_task_generator.rb index 48edeed38..42cb0ecd2 100644 --- a/BrainPortal/lib/cbrain_task_generators/schema_task_generator.rb +++ b/BrainPortal/lib/cbrain_task_generators/schema_task_generator.rb @@ -259,10 +259,10 @@ def register(task) return if container_engine.blank? # Singularity or Docker return if container_image.blank? # Container name or url container_engine.capitalize! - return if container_engine == "Singularity" && !resource.singularity_present? - return if container_engine == "Docker" && (!resource.docker_present? && !resource.singularity_present?) + return if container_engine == ("Singularity" || "Apptainer") && !resource.apptainer_present? + return if container_engine == "Docker" && (!resource.docker_present? && !resource.apptainer_present?) - # If Docker engine isn't present use Singularity + # If Docker engine isn't present use Apptainer container_engine = "Singularity" if (container_engine == "Docker" && !resource.docker_present?) container_index = 'docker://' if container_index == 'index.docker.io' # old convention diff --git a/BrainPortal/spec/models/tool_config_spec.rb b/BrainPortal/spec/models/tool_config_spec.rb index 731851f1f..9e788da6d 100644 --- a/BrainPortal/spec/models/tool_config_spec.rb +++ b/BrainPortal/spec/models/tool_config_spec.rb @@ -159,13 +159,13 @@ context "fill HEADER" do it "should print 'Configuration: tool_config.id'" do expect(tool_config.to_bash_prologue).to match(/Configuration\s?:\s+#\s+#{tool_config.id}/) - expect(tool_config.to_bash_prologue(singularity: true)).to match(/Configuration\s?:\s+#\s+#{tool_config.id}/) + expect(tool_config.to_bash_prologue(true)).to match(/Configuration\s?:\s+#\s+#{tool_config.id}/) end it "should print 'Tool: ALL' if specific tool is not defined" do tool_config.tool = nil expect(tool_config.to_bash_prologue).to match(/Tool\s?:\s+ALL/) - expect(tool_config.to_bash_prologue(singularity: true)).to match(/Tool\s?:\s+ALL/) + expect(tool_config.to_bash_prologue(apptainer: true)).to match(/Tool\s?:\s+ALL/) end it "should print 'Tool: tool_config.tool.name' if specific tool is defined" do @@ -225,16 +225,16 @@ expect(tool_config.to_bash_prologue).to match(/Environment variables\s?:\n\#\-+\n\n#{script}/) end - it "should not print 'Environment variables: export SINGULARITYENV_name1=\"value1\".... if config has no singularity" do + it "should not print 'Environment variables: export APPTAINERENV_name1=\"value1\".... if config has no singularity" do tool_config.env_array = [["name1", "value1"],["name2","value2"]] expect(tool_config.to_bash_prologue).not_to match(/(SINGULARITYENV|APPTAINERENV)/) end - it "should print 'export SINGULARITYENV_name1=\"value1\".... if env is not empty and config uses singularity" do + it "should print 'export APPTAINERENV_name1=\"value1\".... if env is not empty and config uses singularity" do tool_config.env_array = [["name1", "value1"],["name2","value2"]] script = "" tool_config.env_array.each do |name_val| - name = "SINGULARITYENV_" + name_val[0].strip + name = "APPTAINERENV_" + name_val[0].strip val = name_val[1] script += "export #{name}=\\\"#{val}\\\"\\n" end @@ -279,4 +279,3 @@ end end - From 3ed1da8747d6747b2b55fe17c27d375f73a3d28e Mon Sep 17 00:00:00 2001 From: MontrealSergiy Date: Mon, 24 Mar 2025 19:05:14 -0400 Subject: [PATCH 2/9] Refactor singularity into apptainer - update migration timestamp --- ...ainer.rb => 20250324164549_rename_singularity_to_apprainer.rb} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename BrainPortal/db/migrate/{20240408164549_rename_singularity_to_apprainer.rb => 20250324164549_rename_singularity_to_apprainer.rb} (100%) diff --git a/BrainPortal/db/migrate/20240408164549_rename_singularity_to_apprainer.rb b/BrainPortal/db/migrate/20250324164549_rename_singularity_to_apprainer.rb similarity index 100% rename from BrainPortal/db/migrate/20240408164549_rename_singularity_to_apprainer.rb rename to BrainPortal/db/migrate/20250324164549_rename_singularity_to_apprainer.rb From 7930f636c82a2904fda41e503907c4d0deed86da Mon Sep 17 00:00:00 2001 From: MontrealSergiy Date: Mon, 24 Mar 2025 19:06:18 -0400 Subject: [PATCH 3/9] Refactor singularity into apptainer - update migration timestamp --- BrainPortal/db/schema.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BrainPortal/db/schema.rb b/BrainPortal/db/schema.rb index 5f7ca44c9..c42097873 100644 --- a/BrainPortal/db/schema.rb +++ b/BrainPortal/db/schema.rb @@ -11,7 +11,7 @@ # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20250417170914) do +ActiveRecord::Schema.define(version: 20250513170914) do create_table "access_profiles", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci" do |t| t.string "name", null: false From 8c58a750ed88fd5497a9ec59513eb2d74479969e Mon Sep 17 00:00:00 2001 From: MontrealSergiy Date: Wed, 2 Apr 2025 17:42:20 -0400 Subject: [PATCH 4/9] bugfix --- BrainPortal/lib/cbrain_task_generators/schema_task_generator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BrainPortal/lib/cbrain_task_generators/schema_task_generator.rb b/BrainPortal/lib/cbrain_task_generators/schema_task_generator.rb index 42cb0ecd2..9e6e596e4 100644 --- a/BrainPortal/lib/cbrain_task_generators/schema_task_generator.rb +++ b/BrainPortal/lib/cbrain_task_generators/schema_task_generator.rb @@ -259,7 +259,7 @@ def register(task) return if container_engine.blank? # Singularity or Docker return if container_image.blank? # Container name or url container_engine.capitalize! - return if container_engine == ("Singularity" || "Apptainer") && !resource.apptainer_present? + return if ( container_engine == "Singularity" || container_engine == "Apptainer") && !resource.apptainer_present? return if container_engine == "Docker" && (!resource.docker_present? && !resource.apptainer_present?) # If Docker engine isn't present use Apptainer From a70f3b028e1dcb9bd601a9f757a4390db918bc2b Mon Sep 17 00:00:00 2001 From: MontrealSergiy Date: Thu, 3 Apr 2025 17:15:24 -0400 Subject: [PATCH 5/9] drop a comment --- .../userfiles/singularity_image/singularity_image.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/BrainPortal/cbrain_plugins/cbrain-plugins-base/userfiles/singularity_image/singularity_image.rb b/BrainPortal/cbrain_plugins/cbrain-plugins-base/userfiles/singularity_image/singularity_image.rb index 1f155744e..ceda43bb9 100644 --- a/BrainPortal/cbrain_plugins/cbrain-plugins-base/userfiles/singularity_image/singularity_image.rb +++ b/BrainPortal/cbrain_plugins/cbrain-plugins-base/userfiles/singularity_image/singularity_image.rb @@ -45,7 +45,6 @@ def has_apptainer_support? #:nodoc: self.class.has_apptainer_support? end - # caches apptainer_executable_name (which does not change that often) def self.apptainer_executable #:nodoc: @_executable ||= RemoteResource.current_resource.apptainer_executable_name.presence || "apptainer" end From 41890ec68272ca5ee34813bf6d09e7d062a264d7 Mon Sep 17 00:00:00 2001 From: MontrealSergiy Date: Mon, 7 Apr 2025 10:44:33 -0400 Subject: [PATCH 6/9] shorten verbiage --- .../userfiles/singularity_image/singularity_image.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BrainPortal/cbrain_plugins/cbrain-plugins-base/userfiles/singularity_image/singularity_image.rb b/BrainPortal/cbrain_plugins/cbrain-plugins-base/userfiles/singularity_image/singularity_image.rb index ceda43bb9..ec51e74ae 100644 --- a/BrainPortal/cbrain_plugins/cbrain-plugins-base/userfiles/singularity_image/singularity_image.rb +++ b/BrainPortal/cbrain_plugins/cbrain-plugins-base/userfiles/singularity_image/singularity_image.rb @@ -35,7 +35,7 @@ def is_viewable? #:nodoc: if ! self.has_apptainer_support? return [ "The local portal doesn't support inspecting Apptainer (aka Singularity) images." ] elsif ! self.is_locally_synced? - return [ "Apptainer (aka Singularity) image file not yet synchronized" ] + return [ "Apptainer (Singularity) image file not yet synchronized" ] else true end From e60ac8a74ae09b5cd82bcf2efcacdb993d2f18b1 Mon Sep 17 00:00:00 2001 From: MontrealSergiy Date: Mon, 7 Apr 2025 10:50:20 -0400 Subject: [PATCH 7/9] roll back a change --- .../app/views/data_providers/_dp_types_explained.html.erb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/BrainPortal/app/views/data_providers/_dp_types_explained.html.erb b/BrainPortal/app/views/data_providers/_dp_types_explained.html.erb index 73070cfb8..34a15cdf1 100644 --- a/BrainPortal/app/views/data_providers/_dp_types_explained.html.erb +++ b/BrainPortal/app/views/data_providers/_dp_types_explained.html.erb @@ -114,12 +114,12 @@ acts like a FlatDataProvider.

-ApptainerSquashfsDataProvider This class connects to a set of +SingSquashfsDataProvider This class connects to a set of one or several squashfs files (all named with .squashfs extensions) through -a Apptainer container handler. The requirements are:
+a Apptainer (Sigularity container handler. The requirements are:

From 1da44b00c9591b3c6b820700192f889fb71b96dd Mon Sep 17 00:00:00 2001 From: MontrealSergiy Date: Mon, 7 Apr 2025 11:43:43 -0400 Subject: [PATCH 8/9] Verbiage: more of Uppercase Apptainer in comments and messages, less aka --- BrainPortal/app/models/cluster_task.rb | 12 ++++++------ .../app/models/sing_bindmount_data_provider.rb | 6 +++--- .../app/models/sing_squashfs_data_provider.rb | 2 +- .../data_providers/_dp_types_explained.html.erb | 2 +- .../userfiles/singularity_image/singularity_image.rb | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/BrainPortal/app/models/cluster_task.rb b/BrainPortal/app/models/cluster_task.rb index cf18041c3..6b9011922 100644 --- a/BrainPortal/app/models/cluster_task.rb +++ b/BrainPortal/app/models/cluster_task.rb @@ -1879,7 +1879,7 @@ def submit_cluster_job test $status -eq 0 && break # all is good - # Detect failed boot of apptainer container + # Detect failed boot of Apptainer container if ! grep -i 'FATAL.*container.*creation.*failed' #{science_stderr_basename} >/dev/null ; then break # move on, for any other error or even non zero successes fi @@ -2360,7 +2360,7 @@ def use_apptainer_short_workdir? # is the raw scientific bash script. def apptainer_commands(command_script) - # Basename of the apptainer wrapper script + # Basename of the Apptainer wrapper script apptainer_wrapper_basename = ".apptainer.#{self.run_id}.sh" # Values we substitute in our script: @@ -2423,7 +2423,7 @@ def apptainer_commands(command_script) # Wrap new HOME environment command_script = wrap_new_HOME(command_script, effect_workdir) - # Set apptainer command + # Set Apptainer command apptainer_commands = <<-APPTAINER_COMMANDS # Note to developers: @@ -2442,7 +2442,7 @@ def apptainer_commands(command_script) apptainer_basename="" fi -# Build a local wrapper script to run in a apptainer container +# Build a local wrapper script to run in an Apptainer container cat << \"APPTAINERJOB\" > #{apptainer_wrapper_basename.bash_escape} #!/bin/bash @@ -2522,7 +2522,7 @@ def apptainer_commands(command_script) # 4) we mount each (if any) of the root directories for local data providers # 5) we mount (if any) other fixed file system overlays # 6) we mount (if any) capture ext3 filesystems -# 7) with -H we set the task's work directory as the apptainer $HOME directory +# 7) with -H we set the task's work directory as the Apptainer $HOME directory #{apptainer_executable_name} \\ $mode \\ #{container_exec_args} \\ @@ -2684,7 +2684,7 @@ def load_apptainer_image_from_repo #:nodoc: File.open(capfile,"w") do |fh| fh.write("=== Stdout ===\n#{out}\n=== Stderr ===\n#{err}\n=== ====== ===\n") end - cb_error "Cannot build apptainer image. Captured outputs are in #{capfile}" + cb_error "Cannot build Apptainer image. Captured outputs are in #{capfile}" end end diff --git a/BrainPortal/app/models/sing_bindmount_data_provider.rb b/BrainPortal/app/models/sing_bindmount_data_provider.rb index 5e6822b3d..aeb5b0542 100644 --- a/BrainPortal/app/models/sing_bindmount_data_provider.rb +++ b/BrainPortal/app/models/sing_bindmount_data_provider.rb @@ -118,7 +118,7 @@ def check_remote_config! #:nodoc: remote_cmd = "cd #{self.real_remote_dir.to_s.bash_escape} && test -f #{APPTAINER_IMAGE_BASENAME} && echo OK-Exists" text = self.remote_bash_this(remote_cmd) # The following check will also make sure the remote shell is clean! - cb_error "No installed apptainer image #{APPTAINER_IMAGE_BASENAME}, or remote shell is unclean" unless text =~ /\AOK-Exists\s*\z/ + cb_error "No installed Apptainer image #{APPTAINER_IMAGE_BASENAME}, or remote shell is unclean" unless text =~ /\AOK-Exists\s*\z/ # Check we have the remote filesystem file remote_cmd = "test -f #{self.remote_dir.to_s.bash_escape} && echo OK-Exists" @@ -127,14 +127,14 @@ def check_remote_config! #:nodoc: # Check we have Apptainer 1.1 or better remote_cmd = "(singularity --version 2>/dev/null || apptainer --version 2>/dev/null)" text = self.remote_bash_this(remote_cmd) - cb_error "Can't find apptainer version number on remote host" unless text =~ /^((singularity|apptainer) version )?(\d+)\.(\d+)/ + cb_error "Can't find Apptainer version number on remote host" unless text =~ /^((singularity|apptainer) version )?(\d+)\.(\d+)/ _, _, tool, major, minor = Regexp.last_match.to_a major = major.to_i minor = minor.to_i if tool == 'singularity' cb_error "singularity version number on remote host is less than 3.7" if major < 3 || (major == 3 && minor < 7) else # tool == 'apptainer' - cb_error "apptainer version number on remote host is less than 1.1" if major < 1 || (major == 1 && minor < 1) + cb_error "Apptainer version number on remote host is less than 1.1" if major < 1 || (major == 1 && minor < 1) end # Check that inside the container, the containerized path exists diff --git a/BrainPortal/app/models/sing_squashfs_data_provider.rb b/BrainPortal/app/models/sing_squashfs_data_provider.rb index adf1ec2d2..7763c06c4 100644 --- a/BrainPortal/app/models/sing_squashfs_data_provider.rb +++ b/BrainPortal/app/models/sing_squashfs_data_provider.rb @@ -86,7 +86,7 @@ def apptainer_executable_name # also works if an old Singularity and uptodate Apptainer # todo loop over list of several candidate executables text = self.remote_bash_this(remote_cmd) - cb_error "Can't find apptainer version number on remote host" unless text =~ /^((singularity|apptainer) version )?(\d+)\.(\d+)/ + cb_error "Can't find Apptainer version number on remote host" unless text =~ /^((singularity|apptainer) version )?(\d+)\.(\d+)/ _, _, @_tool, major, minor = Regexp.last_match.to_a major = major.to_i minor = minor.to_i diff --git a/BrainPortal/app/views/data_providers/_dp_types_explained.html.erb b/BrainPortal/app/views/data_providers/_dp_types_explained.html.erb index 34a15cdf1..79ce60137 100644 --- a/BrainPortal/app/views/data_providers/_dp_types_explained.html.erb +++ b/BrainPortal/app/views/data_providers/_dp_types_explained.html.erb @@ -116,7 +116,7 @@ acts like a FlatDataProvider. SingSquashfsDataProvider This class connects to a set of one or several squashfs files (all named with .squashfs extensions) through -a Apptainer (Sigularity container handler. The requirements are:
+a Apptainer (Singularity) container handler. The requirements are:
  • that all the squashfs files are in the Physical Data Location,
  • the Apptainer image is also there and named <%= SingSquashfsDataProvider::APPTAINER_IMAGE_BASENAME %>, diff --git a/BrainPortal/cbrain_plugins/cbrain-plugins-base/userfiles/singularity_image/singularity_image.rb b/BrainPortal/cbrain_plugins/cbrain-plugins-base/userfiles/singularity_image/singularity_image.rb index ec51e74ae..157dab731 100644 --- a/BrainPortal/cbrain_plugins/cbrain-plugins-base/userfiles/singularity_image/singularity_image.rb +++ b/BrainPortal/cbrain_plugins/cbrain-plugins-base/userfiles/singularity_image/singularity_image.rb @@ -33,7 +33,7 @@ def self.file_name_pattern #:nodoc: def is_viewable? #:nodoc: if ! self.has_apptainer_support? - return [ "The local portal doesn't support inspecting Apptainer (aka Singularity) images." ] + return [ "The local portal doesn't support inspecting Apptainer (Singularity) images." ] elsif ! self.is_locally_synced? return [ "Apptainer (Singularity) image file not yet synchronized" ] else From d9668abd03581d958f31694157c3156b5258fd26 Mon Sep 17 00:00:00 2001 From: MontrealSergiy Date: Tue, 13 May 2025 18:17:32 -0400 Subject: [PATCH 9/9] rebase, apply refactoring to new tool --- .../app/models/sing_squashfs_data_provider.rb | 2 +- BrainPortal/app/models/tool_config.rb | 2 +- .../boutiques_tool_configurator_handler.rb | 20 +++++++++---------- ...170914_rename_singularity_to_apptainer.rb} | 2 +- BrainPortal/db/schema.rb | 6 +++--- 5 files changed, 16 insertions(+), 16 deletions(-) rename BrainPortal/db/migrate/{20250324164549_rename_singularity_to_apprainer.rb => 20250513170914_rename_singularity_to_apptainer.rb} (82%) diff --git a/BrainPortal/app/models/sing_squashfs_data_provider.rb b/BrainPortal/app/models/sing_squashfs_data_provider.rb index 7763c06c4..365d0b584 100644 --- a/BrainPortal/app/models/sing_squashfs_data_provider.rb +++ b/BrainPortal/app/models/sing_squashfs_data_provider.rb @@ -83,7 +83,7 @@ def apptainer_executable_name remote_cmd = "( apptainer --version 2>/dev/null || singularity --version 2>/dev/null )" # Apptainer is preferable so it comes first in the command - # also works if an old Singularity and uptodate Apptainer + # also works if an old Singularity # todo loop over list of several candidate executables text = self.remote_bash_this(remote_cmd) cb_error "Can't find Apptainer version number on remote host" unless text =~ /^((singularity|apptainer) version )?(\d+)\.(\d+)/ diff --git a/BrainPortal/app/models/tool_config.rb b/BrainPortal/app/models/tool_config.rb index 6de420c37..8bedbb314 100644 --- a/BrainPortal/app/models/tool_config.rb +++ b/BrainPortal/app/models/tool_config.rb @@ -470,7 +470,7 @@ def validate_container_rules #:nodoc: errors[:container_engine] = "a container hub image name or a container image userfile ID should be set when the container engine is set" end - if self.container_engine.present? && (self.container_engine == "Apptainer" || self.container_engine == "Singularity") + if self.container_engine.present? && (self.container_engine == "Apptainer" || self.container_engine == "Singularity") if self.container_index_location.present? && self.container_index_location !~ /\A[a-z0-9]+\:\/\/\z/i errors[:container_index_location] = "is invalid for container engine Apptainer. Should end in '://'." end diff --git a/BrainPortal/cbrain_plugins/cbrain-plugins-base/cbrain_task/boutiques_tool_configurator_handler/portal/boutiques_tool_configurator_handler.rb b/BrainPortal/cbrain_plugins/cbrain-plugins-base/cbrain_task/boutiques_tool_configurator_handler/portal/boutiques_tool_configurator_handler.rb index 22bb3c3d3..a1be658f5 100644 --- a/BrainPortal/cbrain_plugins/cbrain-plugins-base/cbrain_task/boutiques_tool_configurator_handler/portal/boutiques_tool_configurator_handler.rb +++ b/BrainPortal/cbrain_plugins/cbrain-plugins-base/cbrain_task/boutiques_tool_configurator_handler/portal/boutiques_tool_configurator_handler.rb @@ -75,9 +75,9 @@ def refresh_form change.(:copy_env, old_tc.env_array.present?, "the flag to copy the environment variables") change.(:copy_bash, (old_tc.script_prologue.present? || old_tc.script_epilogue.present?), "the flag to copy the bash prologue and epilogue") change.(:copy_overlays, - (old_tc.singularity_overlays_specs.present? || + (old_tc.apptainer_overlays_specs.present? || old_tc.container_exec_args.present? || - (old_tc.singularity_use_short_workdir? != new_tc.singularity_use_short_workdir?) + (old_tc.apptainer_use_short_workdir? != new_tc.apptainer_use_short_workdir?) ), "the flag to copy miscellaneous Apptainer options") end @@ -209,8 +209,8 @@ def copy_attributes new_tc.script_epilogue = old_tc.script_epilogue end if bool.(invoke_params[:copy_overlays]) - new_tc.singularity_overlays_specs = old_tc.singularity_overlays_specs - new_tc.singularity_use_short_workdir = old_tc.singularity_use_short_workdir + new_tc.apptainer_overlays_specs = old_tc.apptainer_overlays_specs + new_tc.apptainer_use_short_workdir = old_tc.apptainer_use_short_workdir new_tc.container_exec_args = old_tc.container_exec_args end @@ -231,8 +231,8 @@ def copy_attributes env_array script_prologue script_epilogue - singularity_overlays_specs - singularity_use_short_workdir + apptainer_overlays_specs + apptainer_use_short_workdir container_exec_args container_image_userfile_id containerhub_image_name @@ -337,11 +337,11 @@ def add_all_input_notes(descriptor) #:nodoc: cp_ovrl = descriptor.input_by_id('copy_overlays') cp_ovrl.cbrain_input_notes = [] - cp_ovrl.cbrain_input_notes << "OLD ToolConfig has overlays" if old_tc&.singularity_overlays_specs.present? - cp_ovrl.cbrain_input_notes << "OLD ToolConfig uses short workdirs" if old_tc&.singularity_use_short_workdir? + cp_ovrl.cbrain_input_notes << "OLD ToolConfig has overlays" if old_tc&.apptainer_overlays_specs.present? + cp_ovrl.cbrain_input_notes << "OLD ToolConfig uses short workdirs" if old_tc&.apptainer_use_short_workdir? cp_ovrl.cbrain_input_notes << "OLD ToolConfig uses special container options" if old_tc&.container_exec_args.present? - cp_ovrl.cbrain_input_notes << "NEW ToolConfig has overlays" if new_tc&.singularity_overlays_specs.present? - cp_ovrl.cbrain_input_notes << "NEW ToolConfig uses short workdirs" if new_tc&.singularity_use_short_workdir? + cp_ovrl.cbrain_input_notes << "NEW ToolConfig has overlays" if new_tc&.apptainer_overlays_specs.present? + cp_ovrl.cbrain_input_notes << "NEW ToolConfig uses short workdirs" if new_tc&.apptainer_use_short_workdir? cp_ovrl.cbrain_input_notes << "NEW ToolConfig uses special container options" if new_tc&.container_exec_args.present? end diff --git a/BrainPortal/db/migrate/20250324164549_rename_singularity_to_apprainer.rb b/BrainPortal/db/migrate/20250513170914_rename_singularity_to_apptainer.rb similarity index 82% rename from BrainPortal/db/migrate/20250324164549_rename_singularity_to_apprainer.rb rename to BrainPortal/db/migrate/20250513170914_rename_singularity_to_apptainer.rb index ee000e29d..14c80b36b 100644 --- a/BrainPortal/db/migrate/20250324164549_rename_singularity_to_apprainer.rb +++ b/BrainPortal/db/migrate/20250513170914_rename_singularity_to_apptainer.rb @@ -1,4 +1,4 @@ -class RenameSingularityToApprainer < ActiveRecord::Migration[5.0] +class RenameSingularityToApptainer < ActiveRecord::Migration[5.0] def change rename_column :tool_configs, :singularity_overlays_specs, :apptainer_overlays_specs rename_column :tool_configs, :singularity_use_short_workdir, :apptainer_use_short_workdir diff --git a/BrainPortal/db/schema.rb b/BrainPortal/db/schema.rb index c42097873..f8ee642cd 100644 --- a/BrainPortal/db/schema.rb +++ b/BrainPortal/db/schema.rb @@ -11,7 +11,7 @@ # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20250513170914) do +ActiveRecord::Schema.define(version: 20250417170914) do create_table "access_profiles", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci" do |t| t.string "name", null: false @@ -485,8 +485,8 @@ t.string "containerhub_image_name" t.string "container_engine" t.string "container_index_location" - t.text "apptainer_overlays_specs", limit: 65535 - t.boolean "apptainer_use_short_workdir", default: false, null: false + t.text "apptainer_overlays_specs", limit: 65535 + t.boolean "apptainer_use_short_workdir" t.string "container_exec_args" t.boolean "inputs_readonly", default: false t.string "boutiques_descriptor_path"