github.com/mmatczuk/gohan@v0.0.0-20170206152520-30e45d9bdb69/docs/js_extension.md (about)

     1  # Javascript extension
     2  
     3  Gohan support a extension written by JavaScript thanks to Otto project.
     4  In the gohan extension code, you need to register context using
     5  gohan_register_handler function.
     6  gohan_register_handler talkes event_type (string)_ and handler (function(context)).
     7  
     8  ```
     9    gohan_register_handler("pre_show", function(context){
    10      context.resp = "pre_show event"
    11    });
    12  
    13    gohan_register_handler("pre_update", function(context){
    14      context.resp = "pre_update event "
    15    });
    16  ```
    17  
    18  context has following items
    19  
    20    context.schema : schema information
    21    context.path : url path
    22    context.params : query params
    23    context.role : user role
    24    context.auth : auth_context information
    25    context.http_request : Go HTTP request object
    26    context.http_response : Go HTTP response writer object
    27  
    28  
    29  ## Build in exception types
    30  
    31  In an effort to simplify writing extensions for validation Gohan supports
    32  throwing some exceptions and handles them internally.
    33  Gohan provides the following exception types.
    34  
    35  - BaseException(msg)
    36  
    37  The base type of exception, should never be raised as itself, only extended.
    38  
    39  - CustomException(msg, code)
    40  
    41  A BaseException with an additional code property. When thrown will result in
    42  an http response with the provided code and message being written.
    43  
    44  One can extend the CustomException. An example follows.
    45  
    46  ```
    47    function ValidationException(msg) {
    48      CustomException.call(this, msg, 400);
    49      this.name = "ValidationException";
    50    }
    51    ValidationException.prototype = Object.create(CustomException.prototype);
    52  ```
    53  
    54  ### Build in javascript functions
    55  
    56  Gohan extension supports some build-in functions.
    57  
    58  - ``gohan_log_critical(message)``
    59  - ``gohan_log_error(message)``
    60  - ``gohan_log_warning(message)``
    61  - ``gohan_log_notice(message)``
    62  - ``gohan_log_info(message)``
    63  - ``gohan_log_debug(message)``
    64  
    65    log ``message`` in Gohan log.
    66  
    67    ``gohan_log_<lowercase level>(message)`` is equivalent to
    68    ``gohan_log(MODULE, LOG_LEVEL.<uppercase level>, message)``.
    69  
    70  - ``gohan_log(module, log_level, message)``
    71  
    72    log ``message`` in Gohan log (general version).
    73  
    74    - ``module``
    75      The module to be used for logging. You can use ``LOG_MODULE`` for
    76      the current log module. See ``gohan_log_module_push``.
    77  
    78    - ``log_level``
    79      One of ``LOG_LEVEL.CRITICAL``, ``LOG_LEVEL.ERROR``,
    80      ``LOG_LEVEL.WARNING``, ``LOG_LEVEL.NOTICE``, ``LOG_LEVEL.INFO``,
    81      ``LOG_LEVEL.DEBUG``.
    82  
    83    Example usage::
    84  
    85      gohan_log(LOG_MODULE, DEBUG, "It works");
    86  
    87    This will print something like the following::
    88  
    89      17:52:40.921 gohan.extension.network.post_list_in_transaction DEBUG  It works
    90  
    91  - ``gohan_log_module_push(new_module) : <old log module>``
    92    Appends ``new_module`` to the current ``LOG_MODULE``.
    93  
    94  - ``gohan_log_module_restore(old_module)``
    95    Restores ``LOG_MODULE`` to ``old_module``. Example usage::
    96  
    97      old_module = gohan_log_module_push("low_level");
    98      try {
    99          ...
   100      } finally {
   101          gohan_restore_log_module(old_module)
   102      }
   103  
   104  - gohan_http(method, url, headers, data, opaque, timeout)
   105  
   106  fetch data from url
   107  method : GET | POST | PUT | DELETE
   108  url : destination url
   109  headers : additional headers (eg. AUTH_TOKEN)
   110  data : post or put data
   111  opaque (optional) : boolean - whether to parse URL or to treat it as raw
   112  timeout (optional) : int - timeout in milliseconds. Default is no timeout.
   113  
   114  - gohan_raw_http(method, url, headers, data)
   115  
   116  Fetch data from url. It uses GO RoundTripper instead of Client (as in gohan_http),
   117  which allows for more control.
   118  method : GET | POST | PUT | DELETE
   119  url : destination url
   120  headers : additional headers (eg. AUTH_TOKEN)
   121  data : request data (string)
   122  
   123  - gohan_config(key, default_value)
   124  
   125  get value from Gohan config. If key is not found, default value is returned.
   126  Key should be specified as
   127  `JSON Pointer <http://tools.ietf.org/html/draft-ietf-appsawg-json-pointer-07>`_.
   128  
   129  - gohan_db_list(transaction, schema_id, filter_object[, order_key[, limit[, offset]]])
   130  
   131  retrive all data from database
   132  
   133  - gohan_db_fetch(transaction, schema_id, id, tenant_id)
   134  
   135  get one data from db
   136  
   137  - gohan_db_query(transaction, schema_id, query_string, arguments)
   138  
   139    Retrieve data with a raw query
   140  
   141    - transaction: The transaction to use. When null will use a new one.
   142    - schema_id: The ID of the schema of which the query result populates instances
   143    - query_string: Raw query string such as a SQL SELECT query
   144  
   145      - You can put a "?" as the placeholder of variables in your query string
   146  
   147    - arguments: An array of actual values that replace place holders in query_string
   148  
   149  - gohan_db_create(transaction, schema_id, object)
   150  
   151  create data in db
   152  
   153  - gohan_db_update(transaction, schema_id, object)
   154  
   155  update data in db
   156  
   157  - gohan_db_state_update(transaction, schema_id, object)
   158  
   159  update data in db without informing etcd
   160  
   161  - gohan_db_delete(transaction, schema_id, object)
   162  
   163  delete data in db
   164  
   165  - gohan_db_transaction()
   166  
   167  start a new DB transaction. You are responsible for managing tranansactions created by this function. Call .Close() or .Commit() after using the return value.
   168  
   169  
   170  - gohan_model_list(context, schema_id, filter)
   171  
   172    Retrieve data through Gohan.
   173  
   174    - context: You need to have transaction in this dictionary which you can get from given context
   175    - schema_id: The id of the schema of the objects we want to retrieve.
   176    - filter: How to filter retrieved objects. Should be a dictionary with each key being either:
   177  
   178      - A property of the schema we are retrieving. Then the value has to either be a string or an array of strings.
   179        The response is then filtered by removing all entries that do not have the value for the given key in the provided array.
   180      - Any of the strings 'sort_key', 'sort_order', 'limit', 'offset'. These are interpreted with their values as query parameters.
   181  
   182  - gohan_model_fetch(context, schema_id, resource_ids)
   183  
   184    Retrieve a specific resource through Gohan.
   185  
   186    - context: You need to have transaction in this dictionary which you can get from given context
   187    - schema_id: The id of the schema of the object we want to retrieve.
   188    - resource_id: The id of the object we want to retrieve.
   189    - tenant_ids: allowed tenant id
   190  
   191  - gohan_model_create(context, schema_id, data)
   192  
   193    Create an object through Gohan.
   194  
   195    - context: You need to have transaction in this dictionary which you can get from given context
   196    - schema_id: The id of the schema of the object we want to create.
   197    - data: The data needed to create the object, in the form of a dictionary.
   198  
   199  - gohan_model_update(context, schema_id, resource_id, data, tenant_ids)
   200  
   201    Update an object through Gohan.
   202  
   203    - context: You need to have transaction in this dictionary which you can get from given context
   204    - schema_id: The id of the schema of the object we want to update.
   205    - resource_id: The id of the object we want to update.
   206    - data: The data needed to update the object, in the form of a dictionary.
   207    - tenant_ids: allowed tenant id
   208  
   209  - gohan_model_delete(context, schema_id, resource_id)
   210  
   211    Delete an object through Gohan.
   212  
   213    - context: You need to have transaction in this dictionary which you can get from given context
   214    - schema_id: The id of the schema of the object we want to delete.
   215    - resource_id: The id of the object we want to delete.
   216  
   217  - gohan_schemas()
   218  
   219  returns all registered schemas
   220  
   221  - gohan_schema_url(schema)
   222  
   223  returns the url for the schema
   224  
   225  - gohan_policies()
   226  
   227  returns all policies
   228  
   229  - gohan_uuid()
   230  
   231  generate uuid v4
   232  
   233  - gohan_sleep(time)
   234  
   235  sleep time (ms)
   236  
   237  - gohan_execute(comand_name, args)
   238  
   239  execute shell command
   240  
   241  - gohan_template(template_string, variables)
   242  
   243  apply go style template
   244  
   245  - gohan_netconf_open(hostname, username)
   246  
   247  open netconf session.
   248  (Note: you need set up ssh key configuraion
   249  on both of gohan and target node.)
   250  In gohan, you need to setup ssh/key_file
   251  configuraion.
   252  
   253  - gohan_netconf_exec(session, command)
   254  
   255  execute netconf command
   256  
   257  - gohan_netconf_close(session)
   258  
   259  close netconf session
   260  
   261  - gohan_ssh_open(hostname, username)
   262  
   263  open ssh session.
   264  (Note: you need set up ssh key configuraion
   265  on both of gohan and target node.)
   266  In gohan, you need to setup ssh/key_file
   267  configuraion.
   268  
   269  - gohan_ssh_exec(session, command)
   270  
   271  execute command on ssh session
   272  
   273  - gohan_ssh_close(session)
   274  
   275  close ssh session
   276  
   277  - require(module)
   278  
   279  Dynamically load modules loaded in source code or
   280  installed via npm in node_modules at working directory
   281  
   282  - gohan_file_list(dir)
   283  
   284  List files in dir
   285  
   286  - gohan_file_read(path)
   287  
   288  Read file from path
   289  
   290  - gohan_file_dir(path)
   291  
   292  Check if dir
   293  
   294  - gohan_sync_fetch(path)
   295  
   296  Fetch a given path from Sync   
   297  
   298  - gohan_sync_watch(path, timeout, revision)
   299  
   300  Watch a given path in Sync starting from a given revision. This call is blocking no longer
   301  than a given timeout in milliseconds. If no event occurs in the given timeout, the function
   302  returns an empty object.
   303  
   304  # Testing javascript extensions
   305  
   306  You can test extensions using a testing tool bundled with Gohan with the command
   307  ``test_extensions`` (or ``test_ex`` for short). Build and install Gohan, then
   308  run ``gohan test_extensions <paths to files/directories to test>``. The
   309  framework will walk through files and recursively through directories, running
   310  tests in files named ``test_*.js``.
   311  
   312  By default, the framework doesn't show logs and results for passing tests, so
   313  you won't see any output if all the tests pass. If you pass a
   314  ``-v``/``--verbose`` flag, it will show these messages, and an additional ``All
   315  tests have passed.`` message if all the tests pass.
   316  
   317  ## Test file contents
   318  
   319  Each test file must specify schema and path for preloading extensions:
   320  
   321  * var SCHEMA - path to the schema that stores extensions to be tested
   322  * var PATH - path for preloading extensions
   323  
   324  Additionally each file can specify:
   325  
   326  * one setUp() function that will be called before each test
   327  * one tearDown() function that will be called after each test
   328  * multiple test_<name>() functions that will be called by the framework
   329  * multiple helper functions and variables, with names not starting with prefix
   330    ``test_``
   331  
   332  ## Framework API
   333  
   334  Test framework provides all built in function mentioned in subsection
   335  describing `gohan built in functions`_.
   336  
   337  In unit tests, you can use mocks of ``gohan_http``, ``gohan_config`` and
   338  ``gohan_db_transaction``. You can pass values that will be returned for given
   339  arguments during subsequent calls by calling
   340  ``gohan_*.Expect(argument, ...).Return(value)``. One call to
   341  ``gohan_*.Expect(arguments, ...).Return(value)`` provides one response of
   342  ``gohan_*`` (FIFO queue). If no return value, or wrong arguments are provided
   343  for a call then an unexpected call is assumed, which will result in test failures.
   344  
   345  In addition to the abovementioned functions, the framework provides the
   346  following API:
   347  
   348  * ``Fail(format_string, ...)`` - stop execution of a single test case and
   349    return an error
   350  
   351  * ``GohanTrigger(event_type, context) : <new context>`` - triggers a specified
   352    type of Gohan event
   353  
   354    * ``event_type`` - one of the event types recognized by Gohan (see
   355      event_ subsection)
   356  
   357    *  ``context`` - context passed to the event handler
   358  
   359  * ``MockTransaction(is_new) : <mock transaction>`` - return a mock transaction that
   360    can be used with built-in Gohan methods. Each test is run using a separate
   361    database that is deleted after ``tearDown()``, so there is no need to
   362    clean up the database between tests. Multiple calls to ``MockTransaction()``
   363    within a single ``setUp()``, test, ``tearDown()`` routine when no call to
   364    ``CommitMockTransaction()`` has been made will yield the same transaction.
   365    ``MockTransaction(true)`` returns forcibly new transaction.
   366  
   367  * ``CommitMockTransaction()`` - commit and close the last nonclosed
   368    transaction. After this call any calls to ``MockTransaction()`` return
   369    a new transaction.
   370  
   371  * ``MockPolicy() : <mock policy>`` - return a mock policy that
   372    can be used with built-in Gohan methods.
   373  
   374  * ``MockAuthorization() : <mock authorization>`` - return a mock authorization that
   375    can be used with built-in Gohan methods.
   376  
   377  ## Example
   378  A sample test may look like this:
   379  
   380  ```
   381    // Schema file containing extensions to be tested
   382    var SCHEMA = "../test_schema.yaml";
   383  
   384    /**
   385     * Sample contents of test_schema.yaml:
   386     *
   387     * extensions:
   388     * - id: network
   389     *   path: /v2.0/network.*
   390     *   url: file://./etc/examples/neutron/network.js
   391     * - id: exceptions
   392     *   path: ""
   393     *   url: file://./etc/examples/neutron/exceptions.js
   394     * - id: urls
   395     *   path: /gohan/v0.1/schema.*
   396     *   url: file://./etc/examples/url.js
   397     * schemas:
   398     * - description: Network
   399     *   id: network
   400     *   parent: ""
   401     *   plural: networks
   402     *   schema:
   403     *     properties:
   404     *       id:
   405     *         format: uuid
   406     *         permission:
   407     *         - create
   408     *         title: ID
   409     *         type: string
   410     *         unique: true
   411     *       tenant_id:
   412     *         format: uuid
   413     *         permission:
   414     *         - create
   415     *         title: Tenant id
   416     *         type: string
   417     *         unique: false
   418     *     propertiesOrder:
   419     *     - name
   420     *     - id
   421     *     - tenant_id
   422     *   singular: network
   423     *   title: Network
   424     */
   425  
   426    // With the following PATH, "network" and "exceptions" extensions will be loaded
   427    var PATH = "/v2.0/networks";
   428  
   429    /**
   430     * Sample contents of network.js:
   431     *
   432     * // filter removes the network with the unwanted id
   433     * gohan_register_handler("post_list", function filter(context) {
   434     *     // This call will be mocked, see testNetworkListFilter below
   435     *     response = gohan_http("GET", "http://whatisunwanted.com", {}, null);
   436     *
   437     *     for (var i = 0; i < context.response.networks.length; i++) {
   438     *         if (context.response.networks[i].id == response.unwanted) {
   439     *             context.response.networks.splice(i, 1);
   440     *             break;
   441     *         }
   442     *     }
   443     * });
   444     */
   445  
   446    var context;
   447    var network;
   448  
   449    function setUp() {
   450        var network_to_create = {
   451            'id': 'new',
   452            'tenant_id': 'azerty'
   453        };
   454        network = gohan_db_create(MockTransaction(), "network", network_to_create);
   455        context = {
   456            'schema': { /* ... */ },
   457            'http_request': { /* ... */ },
   458            'http_response': { /* ... */ },
   459            'path': '/gohan/v0.1/schema',
   460            'response': {
   461                'networks': [
   462                    network,
   463                    {
   464                        'id': 'foo',
   465                        'tenant_id': 'xyz'
   466                    }
   467                 ]
   468             }
   469        }
   470    }
   471  
   472    function tearDown() {
   473      gohan_db_delete(MockTransaction(), "network", "new");
   474    }
   475  
   476    function testNetworkListFilter() {
   477        // First call to gohan_http will return {'unwanted': 'foo'}
   478        gohan_http.Expect("GET", "http://whatisunwanted.com", {}, null).Return({'unwanted': 'foo'});
   479        // Second call to gohan_http will return empty response
   480        gohan_http.Expect("GET", "http://whatisunwanted.com", {}, null).Return({});
   481        // Subsequent calls to gohan_http will fail since they are not expected
   482        var new_context = GohanTrigger('post_list', context);
   483  
   484        if (new_context.response.networks.length != 1) {
   485           Fail('Expected 1 network but %d found.', new_context.response.networks.length);
   486        }
   487  
   488        if (new_context.response.networks[0].id != network.id) {
   489           Fail('Expected network with id "%s" but "%s" found.', network.id, new_context.response.networks[0].id);
   490        }
   491    }
   492  
   493    function testSomethingElse() {
   494        /* ... */
   495    }
   496  ```