After completing this lesson, you will be able to:
You should have done the following before starting this lesson:
Modules make KRL programming easier. KRL has a powerful parameterized module facility.
In this lesson, we're going to explore how modules can be used to wrap an external API and make it available inside a pico. Along the way we'll explore using HTTP actions, creating our own actions, and managing API keys.
For this lesson, we're going to be using the Twilio API to send SMS messages. Twilio provides a RESTful API for both voice and SMS interactions. Before you can complete this lesson, you'll need to register for a Twilio account.
Our first attempt will ignore some aspects of creating a useful module in order to just get something that works. Later we'll come back and clean those up.
We'll start by defining an action called
send_sms() that takes all of the information we need to use the Twilio API to send a message, including the Twilio API keys.
A user-defined action is similar to a KRL function in that its body consists of a set of declarations, but instead of the last line being an expression representing the return value, the last line is an action. We used the defaction() keyword to define the function. In our case, the action will be the
http:post() action that POSTs to the Twilio API. Here's our action definition:
The action takes the following arguments:
The action creates a base URL using the instructions in the Twilio API docs and then POSTs to that URL.
We can test this action, by creating a rule that uses it as follows.
This simple rule selects on the
test:new_message event and uses the action we just defined. All the needed arguments must be included as event attributes.
After we install a ruleset containing this action definition and rule in our pico, we can use the Sky Event Console to publish an event to the pico. If you're using valid values for the account SID, authentication token, and from phone number, the to phone number should receive an SMS with the message you give.
The user-defined action hides some of the details of the Twilio API and presents a simple action that any rule can use to send an SMS.
Unlike many programming languages, KRL rules make a syntactic distinction between operations that merely read state and those that change it. The KRL prelude is expected to not have an effect on state, either internal or external to the ruleset. This is more than stylistic; there are security and programming advantages to this segregation. Specifically, the action (and postlude) are conditional while the prelude declarations are not. Consequently, state changes made in the prelude that affect state are difficult to guard.
KRL itself takes pains to not permit state changing language features in the prelude, but a module developer could create functions that make state changes in the external API. In general, you should avoid this so that your module is easy for developers to use correctly and easily. In KRL rules, actions should generally be used for API requests done with a POST, PUT, or DEL since those requests change state in the API while GET requests should be used in functions. This ensures that operations in your API that affect external state can be guarded by the rule's conditional and any calls made in the prelude or event expression will not change state. Of course, how your module does this will depend a lot on how the API is built.
The previously defined action is not as useful as it could be because it requires that the account SID and authorization token be passed in each time. This is where KRL's parameterized modules come in handy.
Modules are parameterized using a
configure pragma in the meta block. The variables declared in the configure pragma take on the values that are set when the module is used. The configuration allows for default values.
The following is a complete module for the
send_sms() action using configuration.
configure pragma's defaults are the empty string since there's no workable default for the Twilio keys. You can see that those variables are referenced in the
send_sms() action to create the based URL for the Twilio call.
We use a parameterized module by declaring its use in the meta block of the ruleset that wants to use the module. The use pragma allows parameters to be supplied for configuration variables. The following shows a ruleset that uses the Twilio module.
In this example, we've used the
key pragma to declare the Twilio keys. Those keys are included in the
use pragma that uses the Twilio module. By putting the keys in a
key pragma, we tell the KRL parser and interpreter that they are keys so that it can take greater care. For example, they could be automatically redacted when shared.
Don't put keys in rulesets that are publicly hosted, on GitHub for example. Any ruleset containing keys must be carefully stored so that only authorized parties can see them. See the Security Considerations section of the KRL documentation on
keys for more information.
But, putting the keys in the ruleset means that it can't be publicly hosted and we have to take special care to ensure the keys aren't exposed. Consequently, best practice is to store keys in a module specifically designed for keys. That way, you only have to be careful with modules you are aware have keys.
We can do this by defining a keys module that only contains keys. The
provides keys pragma limits which keys are shared with which module for security.
We modify the preceding ruleset using the keys to load this ruleset as a module like so:
For this to work, the module containing the keys must be registered as
io.picolabs.twilio_keys because that's the name of the module we load. The ruleset using the keys must be registered as
io.piolabs.use_twilio_v2 since that's the name of the ruleset the keys are provided to in the
provide keys pragma. You can store the ruleset containing the keys on AWS S3 behind a private URL, on a Web server with Basic Auth, or using some other scheme that will protect it from viewing by people who would steal the keys. The ruleset using the keys can be stored at a public URL.
We now have a Twilio module that wraps the Twilio API in functions and actions that are more convenient for KRL rulesets to use. We've also taken steps to protect sensitive keys while making their use convenient.