cloudsoft.io

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: when apply mixin "APPLIER_ID" { ... } is specified, the block identifier APPLIER_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 the APPLIER_ID can be anything unique and informative, and mixin = "DEFINED_ID" specified in the apply block to apply the define 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 a config, 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 the resource (or maeztro 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.