.. _sticky-modules-rcp:

Sticky modules
==============

When providing a configurable environment to users, site's staff may require
that some part of this environment remain loaded whatever the user does.

Such feature is for instance useful when every details of the user environment
are configured through the use of modulefiles. Even the core setup that is
usually configured through the :file:`/etc/profile.d` initialization scripts.
But by using modulefiles for core initialization, end users can fully see how
things are setup on their environment by using
:subcmd:`module display<display>`. When the environment core setup is achieved
by loading a specific modulefile, it is important that such module remains
loaded to keep this initial setup on whatever the module actions the user
performs over its environment.

This recipe describes how to keep modulefiles loaded by forbidding their
unload. Such unloadable modules are called *sticky modules*.

Implementation
--------------

:ref:`Sticky modules` are simply modules that cannot be unloaded once loaded.
Such behavior could be achieved by basically breaking the modulefile
evaluation when attempting to unload the sticky module:

.. code-block:: tcl

    if {[module-info mode unload]} {
        break
    }

Using the ``break`` Tcl command to stop the modulefile evaluation does not
require to install a recent version of Modules to get a basic sticky
mechanism.

To get a smoother sticky mechanism with two different level of stickyness,
allowing to reload environment or to swap a sticky module by another
version of the same module name, the *sticky* and *super-sticky* module tags
have been introduced in Modules v4.7.

A modulefile is declared *sticky* by applying it the ``sticky`` tag with the
:mfcmd:`module-tag` modulefile command. Such sticky module cannot be unloaded,
unless if the unload action is forced or if the module reloads after being
unloaded.

Modulefile can also be defined ``super-sticky`` by applying the corresponding
module tag. *Super-sticky* module cannot be unloaded even if the unload action
is forced. It can only be unloaded if the module reloads afterward.

In case the stickyness applies to the generic module name (and does not target
a specific module version or version-set), one version of the sticky or
super-sticky module can be swapped by another version of this same module.

**Compatible with Modules v4.7+**

Usage examples
--------------

For this recipe, a *core* module acts as the initial setup of user's
environment. This module must not be unloaded otherwise user's environment may
be considered broken.

So this *core* module is tagged *super-sticky* with the :mfcmd:`module-tag`
modulefile command in :file:`core/.modulerc` file:

.. code-block:: tcl

    module-tag super-sticky core

Once module got loaded, it cannot be unloaded even if these unload actions are
forced.

.. parsed-literal::

    :ps:`$` module list
    Currently Loaded Modulefiles:
     1) :sgrss:`core/1.0`  

    Key:
    :sgrss:`super-sticky`  
    :ps:`$` module unload core
    Unloading :sgrhi:`core/1.0`
      :sgrer:`ERROR`: Unload of super-sticky module 'core/1.0' skipped
    :ps:`$` module purge -f
    Unloading :sgrhi:`core/1.0`
      :sgrer:`ERROR`: Unload of super-sticky module 'core/1.0' skipped
    :ps:`$` module list
    Currently Loaded Modulefiles:
     1) :sgrss:`core/1.0`  

    Key:
    :sgrss:`super-sticky`  

However it is still possible to change version of this *super-sticky* module.

.. parsed-literal::

    :ps:`$` module switch core/2.0
    :ps:`$` module list
    Currently Loaded Modulefiles:
     1) :sgrss:`core/2.0`  

    Key:
    :sgrss:`super-sticky`  

In this recipe environment, the *compiler* module provides several flavors:
*compA* and *compB*. Site's staff have decided that user's environment should
always have a compiler module loaded by default.

So the *compiler* module is set *sticky* with the :mfcmd:`module-tag`
modulefile command in :file:`compiler/.modulerc` file:

.. code-block:: tcl

    module-tag sticky compiler

As stickyness is defined over the generic *compiler* name, users can switch
between available compiler flavors:

.. parsed-literal::

    :ps:`$` module list
    Currently Loaded Modulefiles:
     1) :sgrss:`core/2.0`   2) :sgrs:`compiler/compB/2.1`  

    Key:
    :sgrss:`super-sticky`  :sgrs:`sticky`  
    :ps:`$` module switch compiler/compA
    :ps:`$` module list
    Currently Loaded Modulefiles:
     1) :sgrss:`core/2.0`   2) :sgrs:`compiler/compA/1.2`  

    Key:
    :sgrss:`super-sticky`  :sgrs:`sticky`  

Unload attempt fails by default:

.. parsed-literal::

    :ps:`$` module unload compiler
    Unloading :sgrhi:`compiler/compA/1.2`
      :sgrer:`ERROR`: Unload of sticky module 'compiler/compA/1.2' skipped

However if a user really wants to get rid of the *compiler* module, the unload
action can be forced:

.. parsed-literal::

    :ps:`$` module unload -f compiler
    Unloading :sgrhi:`compiler/compA/1.2`
      :sgrwa:`WARNING`: Unload of sticky module 'compiler/compA/1.2' forced
    :ps:`$` module list
    Currently Loaded Modulefiles:
     1) :sgrss:`core/2.0`  

    Key:
    :sgrss:`super-sticky`  

Last but not least, the sticky modules should get loaded when the user's shell
session initializes. So the *core* and *compiler* modules should be defined
for load in the initialization RC file |file etcdir_initrc|:

.. code-block:: tcl

    #%Module
    module use --append .../example/sticky-modules/modulefiles
    module load core
    module load compiler/compB
