Extending Sentinel: Modules
Modules allow you to re-use Sentinel code as an import. This allows for the export of any general construct that Sentinel supports, including values, functions, and even rules. As such, it's a great way to package code that is useful across multiple policies, reducing boilerplate and making final policy code simpler.
You may want to use modules for:
- Writing a simple re-usable helper library. If you mostly know Sentinel or have helpers that you have written in Sentinel, moving these helpers to a module will offer a low-friction way of facilitating their re-use.
- Writing re-usable rules. If you have a set of rules you know you want to use across multiple policies, bundling them into a module is a great way of abstracting the logic away.
- Abstracting existing Sentinel imports. Using a module is a great way to extend existing Sentinel imports by abstracting the common functionality that you use within an import to your own workflow.
This page describes how you can use modules within the context of the Sentinel CLI. For instructions for a particular integration, see the documentation for that product.
Not all Sentinel-enabled applications support modules. Please refer to the documentation of the specific Sentinel-enabled application. Some applications disable modules for security reasons.
Configuring a Module
A module is configured within the Sentinel CLI by adding an entry for it in
within the imports
section of the CLI configuration
file. The key for each entry specifies the path that
the module is imported as.
import "module" "foo" { source = "modules/foo.sentinel"} import "module" "bar" { source = "modules/bar.sentinel"}
In this example, we have two modules:
- Import path
foo
, being loaded from the code withinmodules/foo.sentinel
. - Import path
bar
, being loaded from the code withinmodules/bar.sentinel
.
See the Import Modules section within the CLI configuration file syntax page for more details.
Remote Modules
In addition to loading from a local source, modules can be loaded from a remote location. This can increase reusability and allow for sharing modules across multiple configurations.
Testing Using Modules
As with mocks, modules
are loaded relative to the configuration file location when using sentinel test
. As such, you need to account for this in your test configuration files:
import "module" "foo" { source = "../../modules/foo.sentinel"} import "module" "bar" { source = "../../modules/bar.sentinel"}
This could be a test file for a policy (example: policy.sentinel
), residing
under the correct test file directory for this policy (example:
test/policy/pass.json
).
Writing and Using a Module
Writing a module is nearly the same as writing a Sentinel policy, except for the following two exceptions:
- Modules do not require
main
to be present. It can be, but it will not carry any extra significance as it does within a policy. param
declarations cannot be present in a module. If they are encountered, a runtime error is given.
Once you have a complete module ready for use and configured, using the module is as simple as importing it.
Building on the above configuration example, we can
export a simple test function in the module foo
by adding this code to
modules/foo.sentinel
:
// modules/foo.sentinelhello = func() { print("hello world!") return undefined}
We can then import it within our policy and use it:
// policy.sentinelimport "foo" // prints "hello world" in the tracefoo.hello() main = true
Note that modules are considered imports by the Sentinel runtime and as such conform to the specification. This means that you cannot directly assign values to anything behind a module scope. You can, however, use functions to change state.