Mixins
To allow resource definitions and fragments to be re-used, Maeztro uses the concept of a resource “mixin”: this is a collection of attributes and blocks to be applied to a Maeztro resource which can be applied inside a resource definition or to all resources matching a condition.
Defining a Mixin
Defining a mixin is straightforward, at the outer level of any *.mz
file in your project,
provide a define mixin "ID" { ... }
block,
and inside the block provide an extend resource { ... }
block containing whatever extensions to a Maeztro resource definition you want.
For example:
define mixin "say_hi" {
extend resource {
config {
greeting = "hi"
}
effector "say_hi" { steps [ "return ${self.greeting}" ] }
}
}
Anywhere this is applied, such as just below, the resource will have a greeting
config set to hi
and an effector say_hi
.
Applying a Mixin in a Resource
Any mixin defined as above can be used inside a resource block by writing apply mixin "ID" {}
.
For example the following code will give the maeztro.mixed_in
resource the greeting
and say_hi
behavior defined above:
resource maeztro "mixed_in" {
apply mixin "say_hi" {}
}
Variable Parameters for Mixins
Mixins become even more powerful with parameters.
These are defined in the define mixin
block using the parameter "PARAM_NAME" {}
syntax,
and set as attributes in the apply mixin
block.
The parameter can then be used anywhere in the extend resource
block, and when the mixin is applied, the expression will be replaced with the supplied value.
For example:
define mixin "say_hi" {
parameter "name" {}
parameter "greeting" {
default = "hi"
}
extend resource {
config {
greeting = title(greeting)
}
effector "say_hi" {
steps = [ "return ${self.greeting} ${name}" ]
}
}
}
resource maeztro "hi1" {
apply mixin "say_hi" {
name = "user"
}
}
resource maeztro "hi2" {
apply mixin "say_hi" {
greeting = "hello"
name = "world"
}
}
The parameter name
is used directly in the effector, and is required whenever the blueprint does as apply mixin "say_hi"
. The parameter greeting
is optional, defaulting to hi
, and is used to set a config key, applying the function title
from Terraform to capitalize the first letter, and that config is used in the effector (as ${self.greeting}
.
So in this example, both hi1
and hi2
will have a say_hi
effector.
The hi1
resource will return Hi user
, and the hi2
resource will return Hello world
.
Note the greeting
is optional, and when omitted in hi1
it takes the default
specified.
It is an error if name
is omitted.
Like an effector parameter
or a Terraform variable
block, the parameter
definition block can optionally take a default
and/or a type
.
If no default is defined, the parameter should be supplied as part of the apply
block.
Applying Mixins Conditionally and to Multiple Resources
The apply mixin
block can also be used outside of a resource
definition,
such as alongside the define mixin
block.
When used here (and only here), the apply
block accepts a search
block
with the same structure as when used for groups.
If omitted, the mixin will be applied to all resources in the module.
All apply mixin
blocks (whether in a resource or global) also accept the following entries:
- a
condition
boolean attribute or block, as described for (workflows)[workflows.md] and (groups)[groups.md] - a
mixin
attribute specifying the name of the mixin to use; this is typically omitted, as above, except in sophisticated cases: whenapply mixin "APPLIER_ID" { ... }
is specified, the block identifierAPPLIER_ID
is normally the defined mixin ID, but these values need to be unique, and need to be static. If the same mixin needs to be applied multiple times (such as with different variable parameters, or different search configurations), or if the mixin name is an expression (which can be extremely powerful, but complicated!), or simply for readability (as below), then theAPPLIER_ID
can be anything unique and informative, andmixin = "DEFINED_ID"
specified in theapply
block to apply thedefine mixin "DEFINED_ID"
block - a
priority
, a real number, allowing mixins to override other mixins; the highest mixin priority will be used in the case of aconfig
,effector
, or other item being defined multiple times; it is an error if two mixins define the same item and tie for the highest priority (assuming 0 as the default priority); items defined in theresource
(ormaeztro extend resource
) block always have a higher priority than any mixins
For example, to add an open_ticket
effector at any resource where the sensor load
exceeds 0.8, you could write:
define mixin "add_open_ticket_effector" {
parameter prefix { default = "" }
extend resource {
effector "open_ticket" {
parameter "message" {}
steps = [
"shell create_ticket ${prefix} ${message}" # assumes you have a CLI tool to create_ticket; adjust as appropriate!
]
}
}
}
apply mixin "add_open_ticket_effector_if_load_high" {
mixin = "add_open_ticket_effector"
condition = try(self.sensor.load > 0.8, false)
search {
ancestor = module_root
}
prefix = "load_high alert:"
}
Note that these conditions are only evaluated as part of an apply cycle, so in a dynamic situation such as this, it might be necessary to trigger a reapply whenever any relevant load exceeds 0.8.