About Configi modules


Configi modules are regular Lua modules which return a table. It was decided to call them modules too since the term module is common and easily understood in the software industry.

Here is a contrived module to demonstrate the parts:

local ENV, M, example = {}, {}, {}
local cfg = require"cfg-core.lib"
local lib = require"lib"
local cmd = lib.cmd
_ENV = ENV

M.required = { "what" }
M.alias = {}
M.alias.task = { "work" }

local done = function()
end

function example.present(S)
    M.parameters = { "task" }
    M.report = {
        repaired = "example.present: Ok",
            kept = "example.present: Skipped",
          failed = "example.present: Error"
    }
    return function(P)
        P.what = S
        local F, R = cfg.init(P, M)
        if done() then
            return F.kept(P.what)
        end
        return F.result(P.task, F.run(cmd.something{ action = P.task }))
    end
end
example.done = example.present
return example

Anatomy of a Configi module


Local variable declaration and loaded Lua modules

local ENV, M, example = {}, {}, {}
local cfg = require"configi"
local lib = require"lib"
local cmd = lib.cmd
_ENV = ENV

It is good practice to localize Lua modules and variables in the first few lines of the function. For example, here we have the ENV table, M table (used later) and the table to be returned as the module.

The rest of the local declaration specifies the Lua modules that are required. If you look at the example above or at the existing Configi modules you will notice that the following Lua modules are usually required.

Module Description
cfg The core Lua module of Configi. It is the only required Lua module.
lib The official standard library / utility belt / batteries of Configi. Includes auxillary Lua functions and extensions to the luaposix module.

Feel free to submit patches to extend or fix something in these modules.


Required parameters and aliases

M.required = { "what" }
M.alias = {}
M.alias.task = { "work" }

The middle part of the header is where the required parameters to the Configi module and aliases are declared. The M table assigned to a local table earlier is what we're going to use to the store these.

M.required is the required parameter by all functions of the module. Here the parameters what is required and if not present in the policy, will produce an error. Before assigning aliases to a parameter, the M.alias table should be declared first.


Local functions

local done = function()
end

Functions local to the module should be declared at the end of the header. You can see it in practice in the above module when done() was assigned a function.


Module functions

function example.present(S)

Functions performs the actual execution of commands or call operations that change the state of a host. The structure of a function follows the skip or execute or fail pattern.

When naming the function, keep in mind that you are going to describe the state of a promise. The parameter S is a string that will be the subject of a promise.


Function header

M.parameters = { "task" }
M.report = {
    repaired = "example.present: Ok",
        kept = "example.present: Skipped",
      failed = "example.present: Error"
}

Each function in the Configi module has their own headers. This is where the declaration of the M.parameters and M.report tables is done. The M.parameters table is a sequence of parameters valid to the function. If a parameter not in this sequence is used in a policy, it produces a warning.

The M.report table is a dictionary of strings used for reporting and logging. The repaired key corresponds to a string value that is used for reporting if changes were applied; kept if no changes were made; failed if an error was encountered.


Function proper

return function(P)
    P.what = S
    local F, R = cfg.init(P, M)
    if done() then
        return F.kept(P.what)
    end
    return F.result(P.task, F.run(cmd.something{ action = P.task }))
end

A function is returned and it takes a single table as the argument. The subject string S is assigned to one of the parameters (usually a required parameter). In this case, the key what is assigned S.

You might be wondering why this is returning a closure. A closure allows for calling the promise/function like this:

example.present"subject"{
  task = "something"
}

Here subject is the string assigned to S and then assigned to P.what. The string something is assigned to P.task

cfg.init takes two arguments, the P (parameters) table and M (used for module attributes) table. It is called by each module function to initialize P and returns the tables for module functions F and R a table that is used to store results.

P argument to the function is the dictionary of parameter-arguments. They are written as parameter = "argument" in policies.

Configi has support for handlers. The value of the notify_kept parameter is called when a function is skipped. The value of notify when the function succeeds with the prescribed operation. The value of to notify_failed when the operation fails.

The following example promise if applied triggers the handler tagged with repaired:

example.present"subject"{
  notify = "repaired",
    task = "something"
}

Some modules require you to micromanage operations, messages and results. Here's an equivalent of the function above:

function example.present(S)
    M.parameters = { "task" }
    M.report = {
        repaired = "example.present: Ok",
            kept = "example.present: Skipped",
          failed = "example.present: Error"
    }
    return function(P)
        P.what = S
        local F, R = cfg.init(P, M)
        if done() then
            F.msg(P.task, M.report.kept, nil)
            R.notify_kept = P.notify_kept
            return R
        end
        if F.run(cmd.something, { action = P.task }) then
            F.msg(P.task, M.report.repaired, true)
            R.notify = P.notify
            R.repaired = true
        else
            R.notify_failed = P.notify_failed
            R.failed = true
        end
        return R
    end
end
example.done = example.present
return example

Footer

example.done = example.present
return example

This is where function aliases are declared and the table return statement of a Lua module.

Documentation

Module and function documentation is generated using LDoc. See the existing modules for examples.

Notes

example.present"subject"{
  task = true
}