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
conditionboolean attribute or block, as described for (workflows)[workflows.md] and (groups)[groups.md] - a
mixinattribute 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_IDis 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_IDcan be anything unique and informative, andmixin = "DEFINED_ID"specified in theapplyblock 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.