Plugin Overview
Plugins provide an API for Clace Starlark code to call out to external systems. Plugins are implemented in Go. Every plugin API calls needs to be in the approved list for it to be permitted. See security for an overview of the security model.
Each plugin is identified by a unique name, like store.in
or exec.in
. Plugins ending with .in
are internal plugins, built into the Clace binary. Support for external plugins which are loaded dynamically is planned.
Plugin Usage
To use a plugin, load it using
load("http.in", "http")
This adds http
to the namespace for the app. To make a call to the plugin, first add the permissions to the app config.
permissions=[
ace.permission("http.in", "get"),
ace.permission("http.in", "post")
],
Run clace app approve /myapp
to authorize the app to call the get
and post
methods on the http plugin.
In the app handler code, do
ret = http.get(SERVICE_URL + "/api/challenge/" + challenge_id)
if not ret:
return ace.response(ret.error, "invalid_challenge_id", code=404)
At runtime, Clace will check if the get
call is authorized. If so, the call to the plugin will be performed.
Response Handling
All plugin API calls return a plugin_response
structure. The fields in this are
error
The error message string, empty string if no errorerror_code
The error code integer, zero if no errorvalue
The actual return value for the plugin API call. The datatype for this depends on the API, check the API documentation for details.
To check the error status of an API call:
- Check boolean value for the return. If false, that indicates an error which can be returned.
- If no error, get the
value
property and continue with processing
For example,
ret = http.get("https://localhost:9999/test")
if not ret:
# error condition
return ace.response(ret, "error_block")
# success
print(ret.value.json()) # ret.value is the return value. The http plugin response has a json() function
An alternate way to write the error check is
ret = http.get("https://localhost:9999/test")
if ret.error:
# error condition
return ace.response(ret, "error_block")
# Success
print(ret.value.json())
Automatic Error Handling
Clace supports automatic error handling, so that the handler functions do not have to check the error status of every plugin API call. The way this is implemented is such that if no explicit error handling is done, then the automatic error handling kicks in. If explicit error handling is done, then automatic error handling is not done. See bookmarks app for an example of how the automatic error handling can be used.
If the error_handler
function is defined, then that is called with the error. The manual error checking works the same as mentioned above. But if no manual error checking is done, then the Clace platform will automatically call the error_handler
function in case of an error. The error_handler
could be defined as:
def error_handler(req, ret):
if req.IsPartial:
return ace.response(ret, "error", retarget="#error_div", reswap="innerHTML")
else:
return ace.response(ret, "error.go.html")
When no explicit error checks are done, the automatic error handling happens in these three cases:
- Value Access When the response
value
is accessed - Next API call When the next plugin API call happens (to any plugin function)
- Handler Return When the handler function returns
Value Access
If the handler code is
ret = http.get("https://localhost:9999/test")
print(ret.value.json())
If the get
API had succeeded, then the value
property access will work as usual. But if the get
API had failed, then the value
access will fail and the error_handler
will be called with the original request and the error response.
Next API Call
If the value
is not being accessed, then the next plugin call will raise the error. For example, if the handler code is
store.begin()
bookmark = store.select_one(table.bookmark, {"url": url}).value
The response of the begin
API is not checked. When the next select_one
API is called, if the previous begin
had failed, the select_one
API will raise the previous API call’s error, the select_one
will not run.
Handler Return
If the handler code is
def insert(req):
store.begin()
book = doc.bookmark("abc", [])
store.insert(table.bookmark, book)
store.commit()
Assume all the API calls had succeeded and then the commit
fails. Since the value
is not accessed and there is no plugin API call after the commit
call, the Clace platform will raise the error after the handler completes since the commit
had failed.
Overriding Automatic Error Handling
The automatic error handling is great for handling the unusual error scenarios. For the error scenarios which are common, like a database uniqueness check failure, the error handing can be done explicitly in the handler code. If the handler code is
ret = store.insert(table.bookmark, new_bookmark)
if ret.error:
return ace.response(ret, "error.go.html")
The automatic error handling will not be invoked in this case since the ret.error
is being checked. Checking the truth status of ret
also will disable the automatic error handling:
ret = store.insert(table.bookmark, new_bookmark)
if not error:
return ace.response(ret, "error.go.html")
error_handler
and test it for the partial and full page scenarios. All subsequent handler code does not need to handle errors unless specific handling is required. If no error_handler
is defined, a generic error message screen is returned. If is recommended to define a custom error_handler
.Plugin Accounts
Some plugins like exec.in
do not require any account information. Others like store.in
need some account information. The account configuration for a plugin is loaded from the Clace config file clace.toml
. For example, the default configuration for store.in
is here, which contains:
[plugin."store.in"]
db_connection = "sqlite:$CL_HOME/clace_app.db"
Any application using the store.in
plugin will by default use the $CL_HOME/clace_app.db
sqlite database. To change the default account config used by apps, update clace.toml
and restart the Clace server. For example, adding the below will overwrite the default store.in
config for all apps.
[plugin."store.in"]
db_connection = "sqlite:/tmp/clace_app.db"
Account Linking
If specific account config is required for an app, then the app can be linked to a specific account config. First add a new account config by adding in clace.toml
[plugin."store.in#tmpaccount"]
db_connection = "sqlite:/tmp/clace_app.db"
For an app /myapp
using store.in
, run clace account link --promote /myapp store.in tmpaccount
This links the myapp
app to use the tmpaccount
account.
Named Account
In addition to using account linking, the plugin code itself can point to specific accounts. For example, if the app code has
load("http.in#google", "googlehttp")
then the app will use the http.in#google
account config by default. This also can be overridden using account links, by
running clace account link --promote /myapp http.in#google myaccount
This approach is useful if an app has to access multiple accounts for the same plugin. The account linking approach is recommended for normal scenarios.
Clace apps aim to be portable across installations, without requiring code changes. Using account config allows the app code to be independent of the installation specific account config.