Plugin Dependencies
Link to code: Adding dependencies to your plugin
Your control plane agent will typically consist of one or more plugins that contain the application logic. In addition there could be a suite of Ligato plugins and components providing various services to your application plugins. Examples include a KV data store, message bus adapters, loggers and health monitors
This tutorial shows how to add dependencies to your plugins.
Requirements:
- Complete the ‘Hello World Agent’ tutorial
The Ligato infrastructure uses the dependency injection design pattern to manage dependencies. In other words, dependencies on other plugins are injected into your plugin when it is initialized. You should use dependency injection to manage all dependencies in your plugin.
Note
You need dependency injection to be able to create mocks in your unit tests. This is especially true for components that interact with the external world, such as KV data store or message bus adapters. Without good mocks (i.e. without dependency injection), it is almost impossible to achieve production-level unit test coverage.
One of the most commonly used dependencies in your plugins will likely be
PluginDeps
defined in the github.com/ligato/cn-infra/infra
package. It is
a struct that aggregates three plugin essentials:
- plugin name
- logging
- plugin configuration.
It is defined as:
type PluginDeps struct {
PluginName
Log logging.PluginLogger
Cfg config.PluginConfig
}
You embed it into your plugin as follows:
type HelloWorld struct {
infra.PluginDeps
}
PluginName
, which is embedded in the PluginDeps
struct, provides the String()
method for obtaining the name of the plugin. The plugin name is set using the
SetName(name string)
method defined for PluginName
:
p.SetName("helloworld")
The two other components in PluginDeps
are Log
and Cfg
. Log
is the plugin’s logger used to log message at different log levels. Cfg
is used to load configuration data from a configuration file in YAML format.
PluginDeps
has the Setup()
method, which initializes Log
and Cfg
with the name from PluginName
. It is typically called in the plugin’s constructor:
func NewHelloWorld() *HelloWorld {
p := new(HelloWorld)
p.SetName("helloworld")
p.Setup()
return p
}
After initializing Log
and Cfg
, they are ready for action.
Let’s log a few messages:
func (p *HelloWorld) Init() error {
p.Log.Info("System ready.")
p.Log.Warn("Problems found!")
p.Log.Error("Errors encountered!")
}
For more details on the Log API see infra/logging/log_api.go.
Now, let’s load configuration from a file. By default, the name of the config file will be derived from the plugin name with extension .conf
. In our example, the configuration file name will be helloworld.conf
.
type Config struct {
MyValue int `json:"my-value"`
}
func (p *HelloWorld) Init() error {
cfg := new(Config)
found, err := p.Cfg.LoadValue(cfg)
// ...
}
If the config file is not found, the LoadValue
will return false. If the configuration cannot be parsed, the function will return an error.
The complete working example can be found at examples/tutorials/02_plugin-deps.