Lulo

A plugin engine for AWS CloudFormation Custom Resources

View project on GitHub

Writing Plugins

Lulo is plugin based so without plugins Lulo doesn't do anything. Writing plugins is fairly straight forward depending on your use case. A simple Custom Resource takes a matter of minutes to create while more complex cases can easily become, well, complex. It comes down to what you want your plugin to do.

Migrating Plugins to V4

If you have an existing plugin and need to update it to be v4 compatible, please refer to the migration documentation.

Plugin API

Each plugin module must export the following properties:

schema; // Optional
async validateEvent(event): void; // Optional (recommended)
async createResource(event, context): object; // Required
async deleteResource(event, context): object; // Required
async updateResource(event, context): object; // Required

Schema (object)

Schema is an optional object that can be exported by your plugin. If provided, the schema property should indicate type specific information about event.ResourceProperties.

CloudFormation casts all event input values to strings regardless of how it was initially provided in the template. In some cases this can be a problem as the aws-sdk will complain that "true" !== true. Schema allows you to focus more on what your plugin should be doing and less on type casting.

The schema property can define if event input should be cast to another primitive type including boolean, integer and numeric (numeric allows both integer and float). Structurally the schema supports primitive types, primitive arrays, complex arrays and objects. Properties that are not represented in the schema are passed on as is, i.e. as string values. Properties defined in the schema but that are not represented in the event are ignored.

If schema is provided, the resulting type casted event is provided to all other methods in your plugin.

Note: Schema is not for validation!

Example

This example shows how to flag the different supported types and what the schema is expected to look like.

schema = {
    BooleanParameter: { type: 'boolean' },
    IntegerParameter: { type: 'integer' },
    NumericParameter: { type: 'numeric' },
    Nested: {
        type: 'object',
        schema: {
            NumericParameter: { type: 'integer' }
        }
    },
    PrimitiveArray: { type: 'array', schema: 'integer' },
    ComplexArray: {
        type: 'array',
        schema: {
            BooleanParameter: { type: 'boolean' },
            IntegerParameter: { type: 'integer' },
            PrimitiveArray: {
                type: 'array',
                schema: 'boolean'
            }
        }
    }
};

validateEvent(event)

Optionally validate the incoming event. Validate is not expected to return anything but it is expected to throw Error(message) on validation error.

Validate is not required but highly recommended to catch obvious input errors.

Validate is not invoked on Delete requests.

createResource(event, context): object

Invoked when the resource is created.

updateResource(event, context): object

Invoked when the custom resource is updated.

deleteResource(event, context): object

Invoked when the custom resource is deleted.

Return values

Errors

Errors should be thrown by the respective function.

Response Data

The response parameter is optional. If not provided, an empty response will be returned.

If provided, the response parameter should be a flat object, keys cannot be nested. If you need to namespace return values from a plugin, use dot.notation in the object keys. These values are referenced using
!GetAtt 'ResourceName.ReturnValueKey or !GetAtt 'ResourceName.NestedReturnValue.ReturnValueKey'

Physical Resource Id

If you want to set the PhysicalResourceId of the CustomResource, set response.physicalResourceId to the value you want.
If you set the physicalResourceId, this value can be referenced directly via { "Ref": "ResourceName" }
If not set it will default to the value of the Lambda invocation logStream, a nonsensical value that cannot be used as a reference.

Lambda Runtimes

Your plugins are expected to run on the supported nodejs Lambda runtimes >= 18.x.

IAM permissions

Document any additional IAM permissions that your plugin requires.

Dependencies

Do not bundle the aws-sdk, add it to your dev dependencies.

Keeping the plugins small will decrease the cold start time.

List your plugin on the project page

Update plugins.html and submit a pull request to get your plugin listed on the project page.