{"componentChunkName":"component---src-templates-default-template-tsx","path":"/documentation/api/action-api/","result":{"data":{"asciidoc":{"id":"f3abfbd3-794d-5696-a1cc-c3668d66a756","html":"<div id=\"toc\" class=\"toc\">\n<div id=\"toctitle\">Table of Contents</div>\n<ul class=\"sectlevel1\">\n<li><a href=\"#_terminology\">Terminology</a>\n<ul class=\"sectlevel2\">\n<li><a href=\"#_action_handler\">Action Handler</a></li>\n<li><a href=\"#_capability\">Capability</a></li>\n<li><a href=\"#_applicability\">Applicability</a></li>\n</ul>\n</li>\n<li><a href=\"#_actionhandler_implementation_requirements\">ActionHandler Implementation Requirements</a>\n<ul class=\"sectlevel2\">\n<li><a href=\"#_support_for_transparent_encryption_and_decryption_of_sensitive_information\">Support for transparent encryption and decryption of sensitive Information</a></li>\n</ul>\n</li>\n<li><a href=\"#_interface_api_definitions\">Interface / API Definitions</a>\n<ul class=\"sectlevel2\">\n<li><a href=\"#_get_capabilities\">(GET) / capabilities</a></li>\n<li><a href=\"#_get_applicabilities\">(GET) / applicabilities</a></li>\n<li><a href=\"#_websocket_protocol\">WebSocket Protocol</a></li>\n<li><a href=\"#_hello\">hello</a></li>\n<li><a href=\"#_heartbeat\">heartbeat</a></li>\n<li><a href=\"#_submitaction\">submitAction</a></li>\n<li><a href=\"#_sendactionresult\">sendActionResult</a></li>\n<li><a href=\"#_ack\">ack</a></li>\n<li><a href=\"#_nack\">nack</a></li>\n</ul>\n</li>\n</ul>\n</div>\n<div id=\"preamble\">\n<div class=\"sectionbody\">\n<div class=\"paragraph\">\n<p>The Action API has been introduced to separate the deployment of action handlers from the HIRO&#8482; Engine. The new Action API uses the graph as a \"broker\"\nbetween the Engine and action handlers and all the connections are now outbound (from customer network perspective) to the HIRO SaaS system. This enables customers to deploy\nAction Handlers on premises and make an outbound connection to the Action API without opening an inbound port.</p>\n</div>\n<div class=\"paragraph\">\n<p>Also, the action data is persisted in the SaaS infrastructure, so crashes or interruptions of network connectivity should not result in failed or repeated execution of the same\ncommand.</p>\n</div>\n<div class=\"paragraph\">\n<p><strong>System Architecture</strong></p>\n</div>\n<div class=\"imageblock\">\n<div class=\"content\">\n<img src=\"/7.0/images/documentation/actionhandler_generic_overview.png\" alt=\"actionhandler generic overview\" width=\"Action API System Architecture\">\n</div>\n<div class=\"title\">Figure 1. Action API System Architecture</div>\n</div>\n</div>\n</div>\n<div class=\"sect1\">\n<h2 id=\"_terminology\">Terminology</h2>\n<div class=\"sectionbody\">\n<div class=\"sect2\">\n<h3 id=\"_action_handler\">Action Handler</h3>\n<div class=\"paragraph\">\n<p>An Action Handler is a separate component that has the capability to perform actions on the customer&#8217;s target systems. Depending on the requirements and the type of Action Handler, it can now be\neither deployed on the HIRO&#8482; SaaS platform itself or on the premises of the customer.</p>\n</div>\n</div>\n<div class=\"sect2\">\n<h3 id=\"_capability\">Capability</h3>\n<div class=\"paragraph\">\n<p>A capability describes a specific type of action (e.g. \"ExecuteCommand\") and the\nmandatory and optional parameters (e.g. \"Command\", \"Timeout\" or \"Host\").\nCapabilities are meant to be defined globally and should be considered as kind of a\n\"feature contract\" between Knowledge Item writers and Action Handler implementations. The\nsemantics of a capability and its parameters should be the same across all installations\nso that KIs making use of Action Handlers will continue to work even though underlying\nimplementations of the handlers might be completely different.</p>\n</div>\n</div>\n<div class=\"sect2\">\n<h3 id=\"_applicability\">Applicability</h3>\n<div class=\"paragraph\">\n<p>An applicability binds a global capability to a specific Action Handler instance running\nalongside a single engine. It can optionally supply additional parameters to the actual\nhandler implementation not part of the capability definition but these will not be visible\nor directly accessible to KI writers. The applicability can limit the scope on which\nvertices this specific handler-binding will be available.</p>\n</div>\n<div class=\"paragraph\">\n<p>For example an applicability could specify that an SSH based Action Handler provides the\ncapability ExecuteCommand but only on linux machines (represented in the environment model\nas vertices of the type ogit/MARS/Machine with a machineClass attribute \"Linux\")\nwhich have a \"SSHKeyID\" attribute as well. The SSHKeyID\nwould then be added to the parameter set sent to the action handler implicitly without\nKI writers needing to care about that.</p>\n</div>\n</div>\n</div>\n</div>\n<div class=\"sect1\">\n<h2 id=\"_actionhandler_implementation_requirements\">ActionHandler Implementation Requirements</h2>\n<div class=\"sectionbody\">\n<div class=\"sect2\">\n<h3 id=\"_support_for_transparent_encryption_and_decryption_of_sensitive_information\">Support for transparent encryption and decryption of sensitive Information</h3>\n<div class=\"paragraph\">\n<p>Starting with version 2.3.0 the reference implementation of the HIRO ActionHandler will support asymmetric\ncryptography for secure transportation of sensitive information through the platform. Authors of external\nActionHandlers are strongly encouraged to add support for this functionality as well.</p>\n</div>\n<div class=\"paragraph\">\n<p>In order to support arbitrary lengths of data, S/MIME has been selected as the data format.</p>\n</div>\n<div class=\"paragraph\">\n<p>Encrypting capabilities should support the parameter \"EncryptionKey\" that specifies the logical name of the public\nkey to be used for encryption (translates to a locally stored filename atm, will be moved to Graph Key API when released).</p>\n</div>\n<div class=\"paragraph\">\n<p>The command used to encrypt data in the reference implementation (it is advisable to store VALUE as explicit environment\nvariable before to avoid escaping problems, CERT_FILE should point to the S/MIME Certificate file specified by the\nEncryptionKey parameter):</p>\n</div>\n<div class=\"listingblock\">\n<div class=\"content\">\n<pre class=\"highlight\"><code class=\"language-bash\" data-lang=\"bash\">printf -- \"$VALUE\" | openssl smime -encrypt -aes256 -outform DER $CERT_FILE | base64 -w0</code></pre>\n</div>\n</div>\n<div class=\"paragraph\">\n<p>ALL actionhandlers should automatically detect encrypted values contained within their input parameters and try to decrypt\nthese if the corresponding private key is available. The format of the encrypted data in the input parameters is</p>\n</div>\n<div class=\"listingblock\">\n<div class=\"content\">\n<pre class=\"highlight\"><code class=\"language-bash\" data-lang=\"bash\">{{HIROCRYPT/$KEYNAME:$PAYLOAD}}</code></pre>\n</div>\n</div>\n<div class=\"paragraph\">\n<p>The command used to decrypt the PAYLOAD data in the reference implementation with KEYFILE name derived from KEYNAME:</p>\n</div>\n<div class=\"listingblock\">\n<div class=\"content\">\n<pre class=\"highlight\"><code class=\"language-bash\" data-lang=\"bash\">echo \"$PAYLOAD\" | base64 -d | openssl smime -decrypt -inform DER -inkey $KEYFILE</code></pre>\n</div>\n</div>\n</div>\n</div>\n</div>\n<div class=\"sect1\">\n<h2 id=\"_interface_api_definitions\">Interface / API Definitions</h2>\n<div class=\"sectionbody\">\n<div class=\"sect2\">\n<h3 id=\"_get_capabilities\">(GET) / capabilities</h3>\n<div class=\"paragraph\">\n<p>The Action Handler application can request the list of registered capabilities\nwith their parameter list from the global registry.</p>\n</div>\n<div class=\"paragraph\">\n<p>URL: <a href=\"https://core.engine.datagroup.de/api/action/1/capabilities\" class=\"bare\">https://core.engine.datagroup.de/api/action/1/capabilities</a></p>\n</div>\n<div class=\"paragraph\">\n<p>Response Example</p>\n</div>\n<div class=\"listingblock\">\n<div class=\"content\">\n<pre class=\"highlight\"><code class=\"language-json\" data-lang=\"json\">{\n  \"ExecuteCommand\": {\n    \"description\": \"this one executes commands\",\n    \"mandatoryParameters\": {\n      \"command\": {\n        \"description\": \"command to execute\"\n        },\n    \"host\": {\n      \"description\": \"hostname to execute command on\"\n      }\n    },\n    \"optionalParameters\": {\n      \"timeout\": {\n        \"description\": \"timeout in seconds\",\n        \"default\": \"120\"\n        }\n    }\n  }\n}</code></pre>\n</div>\n</div>\n</div>\n<div class=\"sect2\">\n<h3 id=\"_get_applicabilities\">(GET) / applicabilities</h3>\n<div class=\"paragraph\">\n<p>Application can request the list of registered applicabilities with their parameter list.</p>\n</div>\n<div class=\"paragraph\">\n<p>URL: <a href=\"https://core.engine.datagroup.de/api/action/1.0/applicabilities\" class=\"bare\">https://core.engine.datagroup.de/api/action/1.0/applicabilities</a></p>\n</div>\n<div class=\"listingblock\">\n<div class=\"content\">\n<pre class=\"highlight\"><code class=\"language-json\" data-lang=\"json\">{\n  \"HandlerID1\": {\n    \"ExecuteCommand\": {\n      \"on ogit/machineClass == \\\"linux\\\" ssh_keyfile ssh_options\": {\n        \"ssh_keyfile\": \"${ssh_keyfile}\",\n        \"ssh_options\": \"${ssh_options}\"\n        },\n      \"on ogit/machineClass == \\\"linux\\\" ssh_keyfile not ssh_options\": {\n        \"ssh_keyfile\": \"${ssh_keyfile}\",\n        \"ssh_options\": \"some default options here\"\n        }\n      },\n    \"ParameterlessExecute\": [\n      \"on ogit/machineClass == \\\"windows\\\"\"\n      ]\n  }\n}</code></pre>\n</div>\n</div>\n</div>\n<div class=\"sect2\">\n<h3 id=\"_websocket_protocol\">WebSocket Protocol</h3>\n<div class=\"paragraph\">\n<p>The websocket is a full duplex TCP connection which allows for the bi-directional communication between the Graph and the Action Handler. Below is the sequence of actions that take place from the engine &#8594; graph &#8594; the action handler.</p>\n</div>\n<div class=\"paragraph\">\n<p>URL: <code>wss://core.almato.ai/api/action-ws/1.0/</code><br>\nSubProtocol: action-1.0.0<br></p>\n</div>\n<div class=\"paragraph\">\n<p>Sample websocket connection request:</p>\n</div>\n<div class=\"listingblock\">\n<div class=\"content\">\n<pre class=\"highlight\"><code class=\"language-javascript\" data-lang=\"javascript\">var ws = new WebSocket('wss://core.almato.ai/api/action-ws/1.0/', ['action-1.0.0', 'token-' + '$token'])</code></pre>\n</div>\n</div>\n<div class=\"imageblock\">\n<div class=\"content\">\n<img src=\"/7.0/images/documentation/invocation_sequence.png\" alt=\"invocation sequence\" width=\"Invocation Sequence\">\n</div>\n<div class=\"title\">Figure 2. Invocation Sequence</div>\n</div>\n<div class=\"paragraph\">\n<p>Payload of messages that go from engine via action api to action handler and back are json encoded strings. All messages must have <code>type</code> argument plus additional arguments specific to that message type.\nOn top of that, all messages except <code>hello</code> also contain <code>id</code> argument. That one represents request id and is used for idempotency. Due to the distributed nature of the system, action api\nimplements sending \"at least once\" strategy, meaning that it might send the same message multiple times but action handler should check if that message id has already been processed or not and act accordingly.</p>\n</div>\n<div class=\"paragraph\">\n<p>Message types that are in use are:</p>\n</div>\n<div class=\"ulist\">\n<ul>\n<li>\n<p>hello</p>\n</li>\n<li>\n<p>submitAction</p>\n</li>\n<li>\n<p>sendActionResult</p>\n</li>\n<li>\n<p>acknowledged</p>\n</li>\n<li>\n<p>negativeAcknowledged</p>\n</li>\n</ul>\n</div>\n</div>\n<div class=\"sect2\">\n<h3 id=\"_hello\">hello</h3>\n<div class=\"paragraph\">\n<p>Once connection is successfully established, action handler will receive hello message from action api:</p>\n</div>\n<div class=\"listingblock\">\n<div class=\"content\">\n<pre>[WsConnection] Received hello message: %{\\\"client_id\\\" =&gt; \\\"cloei2yl2h1h4018358ez5g33_cm4ckvw679n5v0193cxogsqwj\\\", \\\"host\\\" =&gt; \\\"hiro-graph-actionapi-774f985cc7-f48bl\\\", \\\"server_version\\\" =&gt; \\\"0.1.0\\\", \\\"type\\\" =&gt; \\\"hello\\\"}\",\"metadata\":{}}</pre>\n</div>\n</div>\n<div class=\"paragraph\">\n<p>The structure of hello message is:</p>\n</div>\n<div class=\"listingblock\">\n<div class=\"content\">\n<pre class=\"highlight\"><code class=\"language-json\" data-lang=\"json\">{\n  \"type\": \"hello\",\n  \"host\": \"action api node that accepted connection\",\n  \"server_version\": \"application version of the action api node\",\n  \"client_id\": \"id of the client in the graph\"\n}</code></pre>\n</div>\n</div>\n<div class=\"paragraph\">\n<p>Arguments in this message are of informational nature only.</p>\n</div>\n</div>\n<div class=\"sect2\">\n<h3 id=\"_heartbeat\">heartbeat</h3>\n<div class=\"paragraph\">\n<p>Action API will send ws ping (not binary) message to connected clients and expects pong message back. If pong message is missing after 3 pings,\naction api will disconnect the client assuming that connection is hanging. Thus, clients should be ready to handle dropped connections and to reconnect.</p>\n</div>\n<div class=\"paragraph\">\n<p>The same is advised for the clients. They should also send ws ping messages to the action api and reconnect if pong is missing couple of times repeatedly.</p>\n</div>\n</div>\n<div class=\"sect2\">\n<h3 id=\"_submitaction\">submitAction</h3>\n<div class=\"paragraph\">\n<p>On the successful execution of a KI which requires an action handler, the engine sends a submitAction message to the graph who in turn invokes the action handler and sends the message to it.</p>\n</div>\n<div class=\"paragraph\">\n<p>Engine &#8594; Graph &#8594; Action Handler</p>\n</div>\n<div class=\"paragraph\">\n<p>The sample structure of the message sent by the graph to the action handler is as follows:</p>\n</div>\n<div class=\"listingblock\">\n<div class=\"content\">\n<pre class=\"highlight\"><code class=\"language-json\" data-lang=\"json\">{\n  \"type\": \"submitAction\",\n  \"id\": \"the $requestId in App-&gt;Graph, the $appId:$requestId in Graph-&gt;AH\",\n  \"handler\": \"handlerId\",\n  \"capability\": \"capabilityName\",\n  \"timeout\": 300000,\n  \"parameters\": {\n    \"parametername1\": \"parametervalue1\",\n    \"parametername2\": \"parametervalue2\"\n  }\n}</code></pre>\n</div>\n</div>\n<div class=\"paragraph\">\n<p>All fields are mandatory but content of the \"parameters\" map is specific to the handler\nand may contain anything. <code>timeout</code> field is in milliseconds and <code>handler</code> field is not sent to the action handler.</p>\n</div>\n<div class=\"paragraph\">\n<p>Action API will send the same request each couple of seconds even if action handler acknowledged that request. In such situation, action handler should not execute action again\nbut it still has to acknowledge message.</p>\n</div>\n<div class=\"paragraph\">\n<p>This behaviour is useful if during action execution action handler is restarted. If it doesn&#8217;t have a way to persist ongoing executions, it can rely that action api will resend request that it didn&#8217;t receive response for.</p>\n</div>\n</div>\n<div class=\"sect2\">\n<h3 id=\"_sendactionresult\">sendActionResult</h3>\n<div class=\"paragraph\">\n<p>Once the action handler is done executing the action it sends back a response to the graph which in turn is sent to the engine by the graph for further processing.</p>\n</div>\n<div class=\"paragraph\">\n<p>Action Handler &#8594; Graph &#8594; Engine</p>\n</div>\n<div class=\"paragraph\">\n<p>The sample structure of the message sent by the action handler to the graph is as follows:</p>\n</div>\n<div class=\"listingblock\">\n<div class=\"content\">\n<pre class=\"highlight\"><code class=\"language-json\" data-lang=\"json\">{\n  \"type\": \"sendActionResult\",\n  \"id\": \"the $appId:$requestId in AH-&gt;Graph, the $requestId in Graph-&gt;App\",\n  \"result\": {\n    \"resultfield1\": \"resultvalue1\",\n    \"resultfield2\": \"resultvalue2\"\n  }\n}</code></pre>\n</div>\n</div>\n<div class=\"paragraph\">\n<p>Again, the content of the result is specific to the handler and may have any content mapped to it.</p>\n</div>\n<div class=\"paragraph\">\n<p>It is highly recommended though that result includes <code>action_status</code> which is non-negative number and <code>action_error</code> as nil or string describing why action execution didn&#8217;t succeed.\nIf action was executed and is returning result of the execution, <code>action_status</code> should be <code>0</code>. This status shouldn&#8217;t be confused with execution status/code though.\nIt is ok to have http request that returns 500 status code for example, but action_status should still be <code>0</code> because response is the result of execution &#8230;&#8203; whatever it is.\nThe same situation is for execution of the scripts or commands on a remote machine. <code>action_status</code> is not representation of <code>exit code</code>!</p>\n</div>\n<div class=\"paragraph\">\n<p><code>action_status</code> can be used in the KI for debugging or adding some additional logic for the execution or retry.</p>\n</div>\n<div class=\"paragraph\">\n<p>Action handler should keep sending response to the action api until it gets acknowledged message back.</p>\n</div>\n<div class=\"sect3\">\n<h4 id=\"_action_status_codes_in_use\">action_status codes in use</h4>\n<div class=\"paragraph\">\n<p>0: \"action executed\"</p>\n</div>\n<div class=\"paragraph\">\n<p>11: \"Action API didn&#8217;t ack request\"\n12: \"Action API didn&#8217;t respond\"\n13: \"ActionHandler didn&#8217;t respond. Last status in Action API was #{inspect(last_status)}\"\n14: \"ActionHandler responded with execution timeout\"</p>\n</div>\n<div class=\"paragraph\">\n<p>51: \"AH configuration problem (proxy misconfigured?)\"\n52: \"Request doesn&#8217;t match AH config (wrong cert requested?)\"\n53: \"Error preparing request in AH (ah can&#8217;t build request with provided args)\"\n54: \"Execution failed/crashed or there was an error with processing response\"</p>\n</div>\n<div class=\"paragraph\">\n<p>666: \"Unexpected error\"</p>\n</div>\n</div>\n</div>\n<div class=\"sect2\">\n<h3 id=\"_ack\">ack</h3>\n<div class=\"paragraph\">\n<p>This is the acknowledgement sent to confirm to the sender that the message has been received in any of the following cases:<br></p>\n</div>\n<div class=\"ulist\">\n<ul>\n<li>\n<p>Sent back from graph once request has been persisted<br></p>\n</li>\n<li>\n<p>Sent back from handler once request has been received<br></p>\n</li>\n<li>\n<p>Sent back from graph once result has been persisted<br></p>\n</li>\n<li>\n<p>Sent back from engine once result has been received<br></p>\n</li>\n</ul>\n</div>\n<div class=\"paragraph\">\n<p>The structure of an ack message is:</p>\n</div>\n<div class=\"listingblock\">\n<div class=\"content\">\n<pre class=\"highlight\"><code class=\"language-json\" data-lang=\"json\">{\n  \"type\": \"acknowledged\",\n  \"id\": \"the $appId:$requestId in AH-&gt;Graph, the $requestId in Graph-&gt;App\"\n}</code></pre>\n</div>\n</div>\n</div>\n<div class=\"sect2\">\n<h3 id=\"_nack\">nack</h3>\n<div class=\"paragraph\">\n<p>This is the message sent to confirm to the sender that the message has <strong>not</strong> been received in any of the following cases:<br></p>\n</div>\n<div class=\"ulist\">\n<ul>\n<li>\n<p>Sent back from graph when request could <strong>not</strong> be persisted<br></p>\n</li>\n<li>\n<p>Sent back from handler when request can <strong>not</strong> be handled<br></p>\n</li>\n<li>\n<p>Sent back from graph when result could <strong>not</strong> be persisted<br></p>\n</li>\n<li>\n<p>Sent back from engine when result can <strong>not</strong> be processed<br></p>\n</li>\n</ul>\n</div>\n<div class=\"paragraph\">\n<p>The structure of an nack message is:</p>\n</div>\n<div class=\"listingblock\">\n<div class=\"content\">\n<pre class=\"highlight\"><code class=\"language-json\" data-lang=\"json\">{\n  \"type\": \"negativeAcknowledged\",\n  \"id\": \"the $appId:$requestId in AH-&gt;Graph, the $requestId in Graph-&gt;App\",\n  \"code\": \" the error_code \",\n  \"message\": \"error description why request was nack'ed\"\n}</code></pre>\n</div>\n</div>\n<div class=\"paragraph\">\n<p><code>code</code> <code>404</code> should be returned from the action handler if it doesn&#8217;t support requested <code>capability</code></p>\n</div>\n</div>\n</div>\n</div>","document":{"main":"HIRO Graph Action API","title":"HIRO Graph Action API","subtitle":""},"fields":{"toc":true,"location":["documentation","api","action-api"]}},"sidebarYaml":{"id":"6d066bdd-c982-5a69-b909-a31e6fc044e0","showIndex":null}},"pageContext":{"id":"f3abfbd3-794d-5696-a1cc-c3668d66a756","parent":"documentation"}},"staticQueryHashes":["1010459453","1010459453","2356112386","2356112386","2603905930","2603905930","3026652197","3026652197","3167850324","3167850324","63159454","63159454"]}