Scripting¶
Capabilities¶
Capability |
Support |
Comment |
---|---|---|
Back-pressure |
||
Read/update values |
||
Logging |
See Logging |
|
Milestone trigger event |
See Milestone |
|
Importing script |
See Import scripts |
|
Scheduling executions |
||
Interact with alarms |
||
Interact with collections |
See Collections |
|
Sending notification (SMS/EMAIL) |
See Communications |
|
Get the list of users |
See Keycloak users |
|
Format JS dates |
||
Generate random UUID |
||
Sending SNMP TRAP |
||
Replaying SNMP TRAP |
||
HTTP request |
See HTTP requests |
Concepts¶
Overview¶
The osp-scripts plugin provides the ability to execute scripts written in Javascript and to interact with hierarchy elements and external systems.
Behavior¶
The osp-scripts provides a Javascript engine based on GraalVM, on which scripts are run on transient contexts, meaning that they do not have persistent state on their own (setting a value to a variable defined in the global scope of the script will be forgotten in the next script execution).
Scripts come in two forms :
Value scripts : Those scripts are bound to a value in the hierarchy and their result is used to generate the value content. They provide a way of computing a complex state and provide it to the hierarchy.
Detached scripts : Those scripts are detached from the hierarchy. They provide a way to execute actions on the hierarchy or on external systems.
Scripts executions follow the following constraints :
A script is guaranteed to have the view of the hierarchy as it was when it was triggered, independently of potential execution delays
If a value triggers the execution of multiple scripts, all scripts are guaranteed to have the same hierarchy view
Executions of different scripts are always run in parallel
Executions of the same value script are always run sequentially, for coherent update of their value
Executions of the same detached script are always run in parallel
Script execution¶
The script execution use a shared pool of threads, this pool must fit the need of the current installation with a tradeoff between :
Memory usage
Context switching
I/O duration
Warning
In general, the number of threads should be increased in case of a high number of scripts running at the same time. The more scripts are idle (like waiting on an external system), the more threads can be increased to increase throughput. But there is a drawback, each thread consumes memory and in case of too many context switches, the overall performance of the system can be affected.
Concurrency¶
A value script (describe in owner.scripts) is always executed in a concurrent way to avoid creating some edge cases on the impacted value.
A detached script can be concurrent so it’s must be taken into account that all interactions of the scripts on external system can arrive in any order :
For example a simple script logging in into a system and then performing a task can fail in concurrent way, if multiple login is not supported :
The intended script execution
The executed script in concurrency
Backpressure and buffering¶
When a script (value or detached) is called, it’s executed at most X times depending of the concurrency value set.
If the script execution is slower than the execution schedule frequency, the system will bufferize the newly added scripts executions.
When the buffer is full (each value has his own buffer), the system will use one of the backpressure strategy :
drop_older (drop the old values)
drop_latest (drop the new values)
Trigger information¶
Scripts can access the reason that triggered their execution by global variable trigger (trigger.id, trigger.content, …).
A trigger has the following structure :
id : the id of the value (string)
name : the name of the value (string)
description : the description of the value (string)
content (nullable) : the content of the value, can be null in case of reading errors (boolean, number or string depending on the type field)
error : the error in case of error, empty otherwise (string)
type : BOOLEAN, INTEGER, DECIMAL, TEXT (string)
timestamp : the timestamp attached to the value (integer)
Note
For detached scripts executed manually (i.e., not from callbacks, like an explicit call to scripts.run()), trigger
only provides following fields :
id : always contains internal.manual, useful to determine if script was triggered manually or by a callback (string)
parameters : parameters list that were provided to the script (string[])
Warning
Owner script can only be triggered by callbacks. Manual actions like Actions can’t trigger an owner script.
Read update values¶
Scripts can access values from the hierarchy :
Read a value with values.get(value_id: string)
Update a value (only works for variable values) with values.update(value_id: string, content: any)
Value scripts can access their current value using the global variable myself (myself.id, myself.content, …)
A value has the following structure :
id : the id of the value (string)
content (nullable) : the content of the value, can be null in case of reading errors (boolean, number or string depending on the type field)
error : the error in case of error, empty otherwise (string)
type : BOOLEAN, INTEGER, DECIMAL, TEXT (string)
timestamp : the timestamp attached to the value (integer)
Note
To be able to read a value, the script must first declare that value in its accessed values.
Logging¶
Logs information with log
with the following level:
trace
debug
info
warn
error
It is possible to include an element with the syntax log.warn("The error was {}", error)
log.info("Starting script.");
let message = { test: 1 };
log.warn("Message with parameter part {}", message);
Import scripts¶
Inside your scripts you may need some additional modules or functions. This section explain how to import them in your configuration. Here are some restrictions:
You need the source code you want to include (no package importation).
Only scripts with .mjs file extension can make use of the import keyword (it will be ignored if the extension is simply .js).
Objects or functions you want to import have to be marked with the export keyword.
Note
If you have a script with .js extension, you can freely rename it to .mjs.
A script can be imported with the following step:
Add you script in your configuration directory (we will call it <
script_path>
).Add an entry in the file :
modules/scripts/<module_script>/module.resources
:
"resources": [ { "source": "<script_path>", "destination": "<script_path>" } ]
Add an
import
statement in your script:
import { my_function } from "<script_path>";Note
The path to your script can be relative or absolute.
You may want to share a lib through all your configuration and for every script. The previous method is not very friendly and may result to an irrelevant file structure. For every library that may be shared between multiple scripts, the best practice is to place all of them outside the
root
folder :
Create a
lib
directory at the root of your project and put every scripts that you want to be global inside it :
<pre> ... ├── certs ├── keys ├── lib │ └── my_lib.mjs <-- this file ├── modules ├── offline-license-request ├── root ├── schema ├── stack ... </pre>
Then place every scripts that you need to be global inside the
lib
directory. Now we need to edit themodules/scripts/<module_script>/module.resources
file and link the folderlib
at in theroot
folder. Here is an example :
{ "resources": [ { "source": "lib/", "destination": "root/" } ] }
Finally, we can access to our script using the
import
statement like this:
import { my_function } from "my_lib.mjs";
Warning
When migrating a script from/to .mjs
, be careful to check your script. The JavaScript flavor used by the *.mjs
is ECMAScript 6.
Scheduled executions¶
Scripts executions can be scheduled periodically using cron expressions. The precision of those expressions is at the second, for example * * * * * ? *
will schedule a script every second.
Schedule execution is setup in either owner.scripts
or detached.scripts
with scheduledExecutions
key.
There is some good resources online for helping you generate cron entries Quartz generator help
Milestone¶
OnSphere scripts have features from module Milestone exposed.
All those features are available through the milestone
class
Hint
Milestone features require a milestone module.
Class description
Name
Attribute
Description
Return
triggerEvent(serverId, eventId)
serverId
is the ID of the milestone server that owns the event to trigger.eventId
is the Milestone event id (often called event “type”).Send an event trigger request to milestone server.
Return triggered event.
Example
The following example sends a trigger request to the c376083c-8e1a-4896-9e86-a94ec4ed66ab
event configured on the root.server
milestone server.
function main() {
const event = milestone.triggerEvent("root.server", "c376083c-8e1a-4896-9e86-a94ec4ed66ab")
log.info("{}", JSON.stringify(event))
}
main()
When the script is executed, the module sends a request to Milestone API triggering the specified event.
The event
variable will contain the triggered event.
Communications¶
A script can trigger communications with communications
Sends a notification with a static contact list with
sendStaticNotification(notificationId, originator, providers, contactId, trigger)
Parameters
notificationId
: The id of the notification to send.originator
: The notification source.providers
: The list of providers to use.contactId
: The id of the contact to send the notification totrigger
: An object containing arbitrary information that will be provided to the message generation.
Example:
communications.sendStaticNotification( "root.communication.notification", { smsSender: "Script", emailSenderAddress: "script@localhost", emailSenderName: "my super script" }, [ { providerId: "root.communication.provider.maildev" } ], "root.communication.directory", { name: "Script", content: true, timestamp: 0 } );
Sends a notification with a contacts list extracted from a collection with
sendCollectionNotification(notificationId, originator, providers, collectionId, collectionFilter, trigger)
Parameters
notificationId
: The id of the notifications to send.originator
: The notification source.providers
: The list of providers to use.collectionId
: The id of the collections from which to extract the contacts to whom the notification must be sent.collectionFilter
: The filter to apply on the collection when extracting contacts.trigger
: An object containing arbitrary information that will be provided to the message generation.
Sends a notification with a contacts list extracted from
Keycloak
withsendKeycloakGroupNotification(notificationId, originator, providers, wantedGroups, trigger)
Parameters
notificationId
: The id of the notification to send.originator
: The notification source.providers
: The list of providers to use.wantedGroups
: The list of groups to send the notification to. If empty, everyone will be notified.trigger
: An object containing arbitrary information that will be provided to the message generation.
Collections¶
Collections can be manipulated with collections
.
Each request except getUsername
and getUserAttributeFromPreferences
return a result object as following:
success
: if request is a success or not (true/false)message
: contains a message with details on why the request failed. Empty if request is successfulcontent
: data returned from a successful request. Type of the data is different for each request type. Each operation presented below explain what you should expect from the content of the request
Gets one or multiple elements from a collection:
list
: Returns the list of elements from one collection that match the given filter id from the schema configuration (can be omitted). Specifies the number of elements (pageSize) and the offset (pageNumber, starts at 0). Returned object contains onetotalCount
property with the number of elements found (unbound by the pageSize) andcollections
as an array of documents.collections.list(schema: String, pageSize: int, pageNumber: int)
collections.list(schema: String, pageSize: int, pageNumber: int, filterId: String)
listCustomFilter
: Returns the list of element from one collection matching the given custom filter provided. Same return type aslist
.collections.listCustomFilter(schema: String, pageSize: int, pageNumber: int, filterId: String)
get
: Returns the first element from one collection matching the given filter. Returns directly the document.collections.get(schema: String, documentId: String)
: Filter by document id.collections.getWithFilterId(schema: String, filter: String)
: Filter by filter from schema configuration.collections.getWithCustomFilter(schema: String, filter: String)
: Filter by custom filter.
Inserts/Updates an element from a collection. Each one of these request returns an object with documentId
and operation
(insert or update):
insert
: Inserts a new element into a collection.collections.insert(schema: String, data: Object)
update
: Updates a given element from a collection.collections.update(schema: String, documentId: String, data: List<UpdateMethod>)
: Updates by giving a list of updates to apply on the element. You can look at the collection documentation to see how to define an update object.collections.update(schema: String, data: Object)
: Updates by giving the complete updated object.
delete
: Deletes a given element from a collection.collections.delete(schema: String, documentId: String)
Get username of authentified user:
getUsername
: returns directly the username (string) of the authentified user. This information is accessible only if the script is trigger by a front-end action, like a menu for example. Otherwise, this will return an empty string.
Get an attribute from an user preferences:
getUserAttributeFromPreferences(attribute: String, defaultValue: any)
: get the value associated withattribute
from the currently authentified user preferences. If no value is found,defaultValue
is returned instead.defaultValue
is not a mandatory parameter. If not specified, null is returned instead.
Lookup¶
Gets information from a lookup file with lookup
:
has
: Returns a boolean indicating if the element exists (string filename, string key, int column).hasLine
: : Returns a boolean indicating if the line corresponding to the key exists (string filename, string key).get
: Returns a string corresponding to the key and column (string filename, string key, int column).getLine
: Returns a list of string corresponding to the key (string filename, string key).get
: Returns a string corresponding to the key and column if the column is missing the default value is returned (string filename, string key, int column, string defaultValue).getLine
: Returns a list of string corresponding to the key if the line is missing the default value is returned (string filename, string key, string[] defaultValues).
Example to check if a value exists and get it.
if (lookup.has("file.lookup", "35665", 0)) {
let infos = lookup.get("file", "35665", 0);
console.log(infos);
return true;
}
return false;
Sending SNMP TRAP¶
Sends snmp-trap with snmp
:
getTarget
: Returns a new target to use to send a trap (string host, int port, string community).createTrapV1
: Returns a trap for the snmp version 1 (string Entreprise oid, string source ip address).createTrapV2
: Returns a trap for the snmp version 2 (string Entreprise oid, string source ip address).createTrapV3
: Returns a trap for the snmp version 3 (string Entreprise oid, string source ip address).sendTrap
: Sends a trap (SnmpTrap trap, SnmpTarget target).
The target offers the following function:
setRetry
: Sets the number of retry (int retry).setTimeoutS
: Sets a new timeout in second (int second). Default: 1.5 seconds.setTimeoutMs
: Sets a new timeout in millisecond (int second). Default: 1500 milliseconds.
The trap offers the following function:
addOctetStringVariable
: Adds an OCTET_STRING variable to the trap (string oid, string content).addIntegerVariable
: Adds an INTEGER variable to the trap (string oid, int content).addCounter32Variable
: Adds a COUNTER_32 variable to the trap (string oid, int content).addCounter64Variable
: Adds a COUNTER_64 variable to the trap (string oid, long content).addGauges32Variable
: Adds a GAUGES_32 variable to the trap (string oid, int content).addTimeTicksVariable
: Adds a TIME_TICKS variable to the trap (string oid, int content).addUnsignedIntegerVariable
: Add an UNSIGNED_INTEGER variable to the trap (string oid, long content).
Example to send a trap.
let target = snmp.getTarget("10.110.0.116", 162, "public");
let trap = snmp.createTrapV2("1.3.6.1", "8.8.8.8");
trap.addOctetStringVariable("1.3.6.1.2.1.1.5", "My first trap");
trap.addOctetStringVariable("1.3.6.1.2.1.1.1", "The value is 10");
snmp.sendTrap(trap, target);
SNMP traps debug replay¶
It is possible to replay a trap that was produced by the osp-snmp-trap module if its raw data are available. This is typically done by enabling the trap raw data storage in the alarm on osp-snmp-trap (alarm.storeRawData = true
) and then accessing it using a menu interaction (alarm.additionalData._rawData
) that will trigger a detached script.
snmp.replayTrap(data: byte[])
: asks a running osp-snmp-trap module to replay the trap from its given raw data.
Here is an example of a replay trap script :
main();
function main() {
log.info("Replay trap requested");
snmp.replayTrap(trigger.parameters[0].raw);
}
and its triggering menu configuration interaction :
{
"label": "Replay trap",
"icon": "replay",
"context": [
{
"type": [
"AlarmTable",
"AlarmHistoryTable"
],
"action": "root.actions.run_script",
"condition": "${alarms.selected}.length === 0 && !!${alarms.clicked.additionalData} && !!${alarms.clicked.additionalData._rawData}",
"input": {
"scriptId": {
"expression": "'root.replay-trap'"
},
"arguments": {
"expression": "[{'raw': ${alarms.clicked.additionalData._rawData}}]"
}
}
}
]
}
HTTP requests¶
HTTP/HTTPS requests can be issued with http
. The CA certificate must be defined on certs/external
.
Gets a request object to fill
http.getRequest(): HttpScriptRequest
Fills a request
request.addHeaders(key: string, value: string): HttpScriptRequest
request.setBody(body: string): HttpScriptRequest
request.setBody(body: byte[]): HttpScriptRequest
request.setBody(body: string, mimeType: string): HttpScriptRequest
request.setBody(body: byte[], mimeType: string): HttpScriptRequest
setCredentials(username: string, password: string): HttpScriptRequest
setTimeout(timeoutInMilliSeconds: number): HttpScriptRequest
request.notFollowRedirects(follow: boolean): HttpScriptRequest
Sends requests
http.doGet(url: string): HttpScriptResponse
http.doGet(url: string, request: HttpScriptRequest): HttpScriptResponse
http.doPost(url: string): HttpScriptResponse
http.doPost(url: string, request: HttpScriptRequest): HttpScriptResponse
http.doPut(url: string): HttpScriptResponse
http.doPut(url: string, request: HttpScriptRequest): HttpScriptResponse
http.doDelete(url: string): HttpScriptResponse
http.doDelete(url: string, request: HttpScriptRequest): HttpScriptResponse
Accesses request response
response.isSuccess(): boolean
response.getStatusCode(): number
response.getHeader(): string
response.getBody(): string
response.getError(): string
Example to connect with a username and password.
let request = http.getRequest();
request.setCredential("test", "test");
let response = http.doGet(
"http://httpbin.org/digest-auth/auth/test/test",
request
);
if (response.getStatusCode() != 200) {
return false;
}
return true;
Alarms manipulation¶
Alarms can be created and manipulated with alarms
.
Creates an alarm (Directly added to the live collection without being processed by the pre-insertion rules)
alarms.create(severity: int | string, summary: string, location: string, source: string, serial: string, tags: string[])
alarms.create(severity: int | string, summary: string, location: string, source: string, serial: string, tags: string[], additionalData: object)
alarms.create(severity: int | string, summary: string, location: string, source: string, serial: string, tags: string[], hideUntil: long)
alarms.create(severity: int | string, summary: string, location: string, source: string, serial: string, tags: string[], additionalData: object, hideUntil: long)
Inserts an alarm (Added to the live collection after being processed by the pre-insertion rules)
alarms.insert(severity: int | string, summary: string, location: string, source: string, serial: string, tags: string[])
alarms.insert(severity: int | string, summary: string, location: string, source: string, serial: string, tags: string[], additionalData: object)
alarms.insert(severity: int | string, summary: string, location: string, source: string, serial: string, tags: string[], hideUntil: long)
alarms.insert(severity: int | string, summary: string, location: string, source: string, serial: string, tags: string[], additionalData: object, hideUntil: long)
Acknowledges alarms
alarms.acknowledge(id: string | string[])
alarms.unacknowledge(id: string | string[])
Escalates alarms
alarms.escalate(id: string | string[], severity: int | string)
Locks alarms severity
alarms.lockSeverity(id: string | string[], severity: int | string)
alarms.unlockSeverity(id: string | string[])
Removes alarms
alarms.clear(id: string | string[])
Tags alarms
alarms.tag(id: string | string[], tag: string)
alarms.untag(id: string | string[], tag: string)
Adds journal entry to alarm
alarms.addJournal(id: string, text: string, user: string)
Edits alarms
alarms.edit(id: string | string[], summary: string, location: string, source: string, hideUntil: long, forceHide: boolean, `additionalData: object)
alarms.editSummary(id: string | string[], summary: string)
alarms.editLocation(id: string | string[], location: string)
alarms.editSource(id: string | string[], source: string)
alarms.editHideUntil(id: string | string[], hideUntil: long)
alarms.editForceHide(id: string | string[], forceHide: boolean)
alarms.editAdditionalData(id: string | string[], additionalData: object)
Runs an alarms actions (
action.alarms
). The data are the parameters passed to the action when run.alarms.runActionRule(id: string, data: object)
alarms.runActionRule(id: string, data: object, delay: long)
The value of hideUntil
must be a timestamp in milliseconds.
All requests return a result object with
success: boolean
: indicates if the request succeeded or failedmessage: string
: a message containing additional information about the success or the failure of the request
Keycloak users¶
Keycloak users can be fetch in a script with:
users.list()
This returns directly the list of users. Each user object contains:
username
firstname
lastname
email
phoneNumber
dashboard
viewport
groups
Date converter and formatter¶
JS dates can be formatted by specifying the format and a timezone.
timestamp.format(epochMilli: long)
: convert a date passed as milliseconds into a string representation usingdd/MM/yyyy HH:mm
as default format andUTC
as default timezone.timestamp.format(format: string, epochMilli: long)
: convert a date passed as milliseconds into a string representation using a given format andUTC
as default timezone.timestamp.format(format: string, epochMilli: long, timeZone: string)
: convert a date passed as milliseconds into a string representation using a given format and a given timezone.
Generate UUIDv4 ids¶
You can get a randomly generated UUID in a script with:
idGenerator.uuidv4()
This returns directly the generated id as a string.
Scripts execution and result reporting¶
Detached scripts can be run from other scripts with scripts
.
scripts.run(detached_script_id: string)
scripts.run(detached_script_id: string, parameters: any[])
Those requests return a result object with
success: boolean
: indicates if the request succeeded or failedmessage: string
: a message containing additional information about the success or the failure of the requestresult: object
: holds a result generated by the detached script
Detached scripts can also interrupt their execution by reporting a result or an error with execution
.
execution.reportResult(message: string)
execution.reportResult(message: string, result: object)
execution.reportFailure(message: string)
If a detached script does not report any result or failure, it will generate a result depending on the execution of the script (i.e. a syntax error will produce a result with success
set to false
and message
containing a description of the error).
Remote script execution¶
This method execute a command via a SSH connection
on a external system.
Warning
Be aware that this kind of process is heavy. In consequence avoid usage of high frequency request.
Usage :¶
Creates the runner to the given host via the builder
Executes the runner (each request create a new connection to avoid session problems)
Checks the execution status
Build the connection
let runner = ssh
.certCmdBuilder(host, port, username, certificate - file)
.build();
Authenticate with a certificate
ssh.certCmdBuilder(host, port, username, certificate - file);
host
: the server on witch to execute the commandport
: the port to use for SSH connectionusername
: The username of the SSH connectioncertificate-file
: The filename of the certificate
See External resources security for details about the certificate management.
Authenticate with username password
ssh.plainTextCmdBuilder(host, port, username, password);
host
: the server on witch to execute the commandport
: the port to use for SSH connectionusername
: The username of the SSH connectionpassword
: The certificate filename
Additional parameters configuration
Parameters |
Type |
Default value |
Description |
---|---|---|---|
setConnectionTimeout |
number |
10 |
The time allowed to establish the SSH connection |
setMaxCommandTime |
number |
10 |
The time allowed for the command execution, after this time the SSH connection is dropped |
enableStrictHostKeyChecking |
boolean |
false |
If set to true a change of SSH key on server will raise an error. Beware if you are running with this properties set, a change of SSH key will require a restart of the module. |
disableStdIn |
boolean |
true |
Disable the stdIn input collection, this can be used in case of large commands output (to save memory) |
disableStdErr |
boolean |
true |
Disable the stdErr input collection, this can be used in case of large commands output (to save memory) |
Return value¶
Parameters |
Type |
Comment |
---|---|---|
returnCode |
number |
The command return code on the remote host |
stdIn |
string |
The command STDIN output |
stdErr |
string |
The command STDERR output |
exceptionCode |
number |
See below |
Exception code values
NO_EXCEPTION(0)
FAIL_TO_CONNECT(-1)
FAIL_TO_EXECUTE_CMD(-2)
MAX_COMMAND_EXECUTION_TIME_REACHED(-3)
Deploy certificate¶
Warning
OnSphere doesn’t support openssh key, use ssh-keygen -t rsa -m PEM
to generate a compatible key
The certificate must be registered as an osp-script module resource and must be at the root of the folder /certs.
Example
{
"resources": [
{
"source": "root/ssh/",
"destination": "/certs"
}
]
}