.. program:: jetstream server

Triggers
========

Overview
--------

Each JetStream service will react to certain events and execute user-supplied scripts. The server
accepts a :option:`--trigger-dir` command line parameter, which should point to a directory containing
the trigger scripts.

The name of the trigger script file must correspond exactly to the name of the event. For example,
if the JetStream service was started with :option:`--trigger-dir` set to ``/var/lib/jetstream/triggers``,
JetStream would look for and execute script :file:`/var/lib/jetstream/triggers/recv.transferComplete`
once a transfer has been completely received.

.. tip::

  A trigger script must be an executable file.

  On Linux and macOS, make sure to set the *executable* bit on each trigger script (e.g. with :command:`chmod`).

.. tip::

  Including the :option:`--trigger-log` command line parameter will log additional information
  about triggers.

Each trigger script will be passed predefined parameters, depending on which event occurred.

Event Triggers
--------------

launch
~~~~~~

.. index::
  pair: trigger; launch

Required file name: ``launch``

Triggered when the server successfully launches and begins listening on the control port.

Parameters:
  #. ``port``: The control port used by the JetStream server.

.. code-block:: bash

    #!/bin/sh
    echo "JetStream server Launched on port "$1 >> log.txt

.. tip::

    You can using this trigger to handle startup configuration, like adding destinations:

.. code-block:: bash

  #!/bin/sh
  export JETSTREAM_API_PASSWORD=secret
  /usr/local/bin/jetstream api --user <username> createDestination --destinationAddress <destination_address> --sendRateMax=100000


send.destinationCreated
~~~~~~~~~~~~~~~~~~~~~~~

.. index::
  pair: trigger; send.destinationCreated

Required file name: ``send.destinationCreated``

Triggered on a successful :meth:`~jetstream.sendinterface.SendInterface.createDestination` API call.

Parameters:
  #. ``destination``: The address and port of the new destination, in the format ``<ip>:<port>``.
  #. ``sender``: The address and port of the sender that created the destination, in the format ``<ip>:<port>``.

.. code-block:: bash

    #!/bin/sh
    echo "Sender "$2" created destination "$1 >> log.txt

send.destinationDeleted
~~~~~~~~~~~~~~~~~~~~~~~

.. index::
  pair: trigger; send.destinationDeleted

Required file name: ``send.destinationDeleted``

Triggered on successful :meth:`~jetstream.sendinterface.SendInterface.deleteDestination` API call.

Parameters:
  #. ``destination``: The address and port of the deleted destination, in the format ``<ip>:<port>``.
  #. ``sender``: The address and port of the sender that deleted the destination, in the format ``<ip>:<port>``.

.. code-block:: bash

    #!/bin/sh
    echo "Sender "$2" deleted destination "$1 >> log.txt

recv.transferComplete
~~~~~~~~~~~~~~~~~~~~~

.. index::
  pair: trigger; recv.transferComplete

Required file name: ``recv.transferComplete``

Triggered when a transfer is done and files have been written to disk.

Parameters:
  #. ``transfer_id``: A transfer ID. Transfer IDs are returned from
     :meth:`~.jetstream.sendinterface.SendInterface.createTransfer`, and used in other methods to refer to
     a specific transfer.
  #. ``username``: User that was used to authorize the transfer.
  #. ``token``: Token name if a user token was used to authorize the transfer. Otherwise empty.

.. versionchanged:: 1.5.0
  - Removed ``sandbox_path`` parameter.

.. versionchanged:: 1.7.0
  - Removed ``destination_directory``, ``transfer_index``, ``transfer_count``, ``truncated_file_count`` parameters.
  - Added ``username`` parameter.

.. versionchanged:: 2.6.5
  - Added ``token`` parameter.

.. code-block:: bash

    #!/bin/sh
    echo "Transfer ID ${1} initiated by ${2} is complete." >> log.txt

Transfer File List
^^^^^^^^^^^^^^^^^^

In addition to its input parameters, the ``recv.transferComplete`` trigger also has access to the list of files in the completed transfer, and the path of the files on disk. These are provided in ``stdin``. The file list is presented as absolute paths to where the files were written (see :doc:`/using_jetstream/where_do_transfers_go`). This can be used for further operations on the files.

.. code-block:: bash

  #!/bin/sh
  while IFS="	" read -r manifest_file file_path || [ -n "${manifest_file}" ]; do
    echo manifest file: ${manifest_file}, file path: ${file_path} >> log.txt
  done

recv.transferComplete.json
~~~~~~~~~~~~~~~~~~~~~~~~~~

.. index::
  pair: trigger; recv.transferComplete.json

Required file name: ``recv.transferComplete.json``

Triggered when a transfer is done and files have been written to disk.

Parameters:
  #. ``transfer_id``: A transfer ID. Transfer IDs are returned from
     :meth:`~.jetstream.sendinterface.SendInterface.createTransfer`, and used in other methods to refer to
     a specific transfer.
  #. ``username``: User that was used to authorize the transfer.
  #. ``token``: Token name if a user token was used to authorize the transfer. Otherwise empty.

.. versionchanged:: 2.3.0
  - Trigger added

.. versionchanged:: 2.6.5
  - Added ``token`` parameter.

Transfer Meta Data
^^^^^^^^^^^^^^^^^^

The ``recv.transferComplete.json`` trigger is an alternative to the ``recv.transferComplete`` trigger that includes additional meta data about transfers in JSON format provided in ``stdin``.

The JSON object contains the following keys:

  * **inputFiles**: (*list*) A list of file name written to disk, as seen by the sender. These paths are not sandboxed so may not be the absolute path on disk.
  * **outputFiles**: (*list*) A list of file name written to disk. These are the absolute file names after sandboxing has been applied.
  * **userData**: (*dict*) The user data set in :meth:`~.jetstream.sendinterface.SendInterface.createTransfer`.

.. note::

  The ``userData`` dictionary only includes user data included at the time of transfer creation (:meth:`~.jetstream.sendinterface.SendInterface.createTransfer`), and not user data added later using :meth:`~.jetstream.sendinterface.SendInterface.updateObjectUserData`.

.. code-block:: python

  #!/usr/bin/env python
  import json
  import os.path
  import sys

  log = os.path.join(os.path.dirname(__file__), 'log.txt')

  transferId, userName = sys.argv[1:]
  data = json.load(sys.stdin)
  with open(log, 'a') as stream:
      stream.write('Transfer: {!r}\n'.format(transferId))
      stream.write('UserName: {!r}\n'.format(userName))
      stream.write('Input Files: {!r}\n'.format(data['inputFiles']))
      stream.write('Output Files: {!r}\n'.format(data['outputFiles']))
      stream.write('User Data: {!r}\n'.format(data['userData']))

.. tip::

  Naturally, trigger scripts that need to act on the received files should have access to the file system used by the receiver server.

recv.transferStarted
~~~~~~~~~~~~~~~~~~~~

.. index::
  pair: trigger; recv.transferStarted

Required file name: ``recv.transferStarted``

Triggered when a transfer has started to be received.

Parameters:
  #. ``transfer_id``: A transfer ID. Transfer IDs are returned from
     :meth:`~.jetstream.sendinterface.SendInterface.createTransfer`, and used in other methods to refer to
     a specific transfer.
  #. ``username``: User that was used to authorize the transfer.

.. versionchanged:: 2.5.1
  - Trigger added

This trigger includes the same Transfer Meta Data as the ``recv.transferComplete.json`` trigger.


API Command Triggers
--------------------

.. index::
  pair: trigger; api triggers

If the :option:`--trigger-on-api` command line parameter was specified, then a trigger is called
after each API command is processed. The names of these triggers are :samp:`on.{command}`,
where `command` is the API command. For example, you may provide a trigger named 
``on.send.createTransfer`` which will be called after the server processes the ``send.createTransfer``
API command.

Four parameters are provided to an API command trigger:

  1. **command**: (*dict*) The command structure submitted to the server. This is a dictionary in JSON format.
  2. **status**: (*string*) A string describing status of the command. If the command succeeded, this will be "success".
  3. **message**: (*string*) A message returned from the server. Generally this is simply a confirmation that it has done as asked.
  4. **result**: (*dict*) The full result structure from the command. This is a dictionary in JSON format.

Since the command and result parameters are offered as dictionaries in JSON format, it may be
beneficial to process an API command trigger using a Python script, rather than bash.

.. code-block:: python

    #!/usr/bin/env python
    import json
    import os.path
    import sys

    log = os.path.join(os.path.dirname(__file__), 'log.txt')

    command, status, message, result = sys.argv[1:]
    command = json.loads(command)
    result = json.loads(result)
    with open(log, 'a') as stream:
        stream.write('Message: {!r}\n'.format(message))
        stream.write('Status: {!r}\n'.format(status))
        stream.write('Command: {!r}\n'.format(command))
        stream.write('Result: {!r}\n'.format(result))

.. code-block:: none

  Message: 'Manifest requested.'
  Status: 'success'
  Command: {u'request': u'send.createManifest', u'parameters': {u'fileMappings': {u'/src/foo.txt': u'/dst/bar.txt'}}}
  Result: {u'status': u'pending', u'processing': True, u'manifestId': u'ebec4994ea77a55c0100000000000022',
  u'totalFiles': None, u'totalSize': None, u'errorMessage': None}


Trigger Considerations
----------------------

There are some important details of the trigger execution model of which trigger authors should be aware:

  * *Execution is serial*.  A long-running trigger script will block further trigger scripts from
    running until it is complete, at which point the other trigger scripts will be run one by one.
    If a trigger never exits, it will block all further triggers.
  * *Execution is asynchronous*. Each trigger will be called some time after the triggering event
    occurred. Not only does JetStream not wait for the trigger to complete, it doesn't even wait
    for the trigger to start before moving on to the next task.
  * *Execution is silent*. No errors are reported from triggers, and output is suppressed. JetStream
    does not log trigger script failures or crashes. You may find it useful to do your own custom
    logging from within the trigger script. Specifying the :option:`--trigger-log` command line
    parameter will add some information about which triggers are being executed, and may print
    the output of a trigger in some cases, although this output will not be included in a log
    file if one was specified using the :option:`--log` command line option.

