Skip to main content
Version: 10 - TBD

Object Templates and Custom Objects

In Cloudomation Engine you can store data in a similar way to a relational database, with the help of custom objects. Moreover, you can define hooks, that automatically handle lifecycle events like creating or updating a custom object.

There are other simpler ways to store data in Cloudomation Engine like Settings or Files. They are easier to use initially because they don't require the definition of an object template. However, you cannot define hooks for them or cross-reference them to each other.

The best way of storing data depends on the use-case. Generally, complex the data is suited better for custom objects while something more simple (e.g. storing a return code) can be done with settings or files.

Use Cases

You can use custom objects whenever you need to store structured information. Here are some examples:

  • Mailing lists

    If your workflows send out emails (e.g. notifications on errors), you can store a mailing list of the users that should receive the email.

    One way would be to store this list in a setting, and add/remove items when the mailing list changes.

    A more sophisticated approach would be to use custom objects. This way you can validate entries, and define hooks (e.g. when a new user is added to the list, they receive a notification that they were signed up for the mailing list).

  • Discount codes

    If you use Cloudomation Engine for processing orders from a webshop, you can store discount codes as custom objects.

    You can define attributes for the discount codes (e.g. code, percentage, validity) and set up a hook that notifies your customers about new discount codes.

  • Cloud development environment (CDE) management

    Cloudomation DevStack relies heavily on custom objects to keep track of virtual machines (VMs) for CDEs, and automate their lifecycle events.

    This is a good example of the scope of automation you can achieve with custom objects. Simply by creating a new custom object (a new CDE) you trigger a series of events:

    • deploying and starting a VM from a snapshot,
    • setting up the Known Hosts File and Authorized Keys File on the VM,
    • providing a command to the user for connecting to the VM via SSH,
    • scheduling a configurable shutdown of the VM to save costs,
    • etc.

Concept

Custom objects go hand in hand with object templates.

Object Templates

Object templates are the blueprint for creating custom objects. You can define the structure that every custom object that is based on a specific object template will share.

You can create object templates just like any other Cloudomation Engine resource in the UI ("Create +" -> "More" -> "Object template").

If we stick to the comparison with a relational database, you can think of object templates as a table in a database. The attributes are the columns, with a data-type and other properties (e.g. uniqueness, nullability).

An empty object template

Hooks

Hooks help you automate lifecycle events. A hook can be something very simple, like sending an email. It can also be complex, like executing multiple stored procedures and parsing their return values or deleting a resource group at a cloud provider.

There are 3 types of hooks you can define:

  • On create
  • On update
  • On delete

Hooks are implemented as flows. When a hook is triggered by a lifecycle event (e.g. updating a custom object), the specified flow gets executed. The following information about the custom object gets passed as input_value to the flow execution:

NameDescription
valueA key-value pair with the current values of the custom object.

Gets passed on create and on update.
old_valueA key-value pair with the values before the lifecycle event.

Gets passed on update and on delete.
custom_objectA reference to the custom object.
custom_object_idThe id of the custom object.
provisioning_typeThe type of the lifecycle event.

Here, two hooks are defined that will be triggered if a custom object is created or deleted

Attributes

Attributes define how and what kind of information is stored in the custom objects that are based on the object template. Each attribute has the same characteristics:

NameDescription
DatatypeWhat kind of data to store e.g. boolean or string.
ReferenceReference to another object (applicable only if the data-type is a reference).
Is requiredWhether the attribute is required (nullability).
Is uniqueWhether multiple custom objects based on the same object template can have an attribute with the same value.
Is hiddenWhether the attribute is shown when you open the custom object.
Silent updateWhether an update of the attribute triggers the on update hook.

This object template has two attributes

Custom Objects

Custom Objects can be created based on object templates.

note

Only users who have at least read access to the object template, that the custom object is based on, can see the custom object in the UI. For more on read access, refer to RBAC.

You can create custom objects just like any other Cloudomation Engine resource in the UI ("Create +" -> "Custom object" -> select the object template you want to use).

If we stick to the comparison with a relational database, you can think of custom objects as rows or entries in a table.

This custom object was created using the object template from before. You can see the attributes that were defined in the object template.

note

The attribute My name is currently "Unset". To enter a value, first you need to set it from the dropdown.

note

An attribute can only be "Unset" if it is not required, meaning that the attribute My date always needs to have a value.

Provisioning State

Provisioning states are unique to custom objects and are not found in other Cloudomation resources.

Custom objects change during their lifecycle. They get created, updated, and deleted. The provisioning state shows, if a custom object is currently ready (stable), is going through a change, or if a previous change could not be concluded successfully.

Whenever a hook for a lifecycle event is triggered, the provisioning state changes to represent that event e.g. UPDATING. Whether a change was concluded successfully, depends on the the status of the execution that gets run by the hook.

note

If there is no hook defined for a lifecycle event, no flow gets executed and the provisioning state changes to READY.

When we created the custom object above, the On create hook was triggered (just like it was defined in the object template).

The provisioning state changes to CREATING and we can also see the flow execution triggered by the hook.

The provisioning state changes to READY once the flow execution triggered by the hook successfully finishes.

note

Depending on the complexity of the flow executed by the hook, a change in provisioning states can take a while, especially if the flow interacts with third-party systems.

note

If a custom object receives changes while it's still transitioning (i.e. the provisioning state is not READY), the changes will be queued and the provisioning executions will run one at a time, in the order they were created.

Dedicated flow_api Methods

For easier access and manipulation of object templates and custom objects, the flow_api provides dedicated methods. The entry point for these methods is system.object_data, or its alias system.od.

Here are some examples how you can:

  • list object_templates:

    for object_template in system.object_data:
    ...
  • access an object_template by name:

    system.object_data.object_template_name
    # or
    system.object_data['object-template-name']
  • list custom_objects of a specific object template:

    for custom_object in system.object_data.object_template_name:
    ...
    # or
    for custom_object in system.object_data['object-template-name']:
    ...
  • access custom object by name:

    system.object_data.object_template_name.custom_object_name
    # or
    system.object_data.object_template_name['custom-object-name']
  • create custom object or write multiple custom object attributes:

    system.object_data.object_template_name.custom_object_name = {'attribute1': 'value1', 'attr2': 42, ...}
    # or
    system.object_data.object_template_name['custom-object-name'] = {...}
  • delete custom object

    del system.object_data.object_template_name.custom_object_name
    # or
    del system.object_data.object_template_name['custom_object_name']
  • access custom object attribute

    system.object_data.object_template_name.custom_object_name.attribute_name
    # or
    system.object_data.object_template_name.custom_object_name['attribute-name']
  • write custom object attribute

    system.object_data.object_template_name.custom_object_name.attribute_name = 'new value'
    # or
    system.object_data.object_template_name.custom_object_name['attribute-name'] = 42

Limitations of Object Templates

Attribute Name Uniqueness

Attribute names have to be unique across a workspace. If you want to use the same attribute name in multiple objects templates, you can do so by prefixing them, e.g. my_template_1_my_date, my_template_2_my_date, etc.

Data-Type json

If an attribute has the data-type json, it is automatically set to be required. This is due to JSON allowing for null being a value - which cannot be distinguished from being "Unset".

If an attribute with the data-type json is not needed in a custom object, you can simply enter the value "null".

Changing Object Templates

When changing an object template that already has custom objects depending on it, there are some limitations, similarly to when changing a table in a database.

Changes that would result in an invalid custom object, cannot be done. E.g. you cannot add a required property without a default value to an object template.

Default Values of Attributes

Currently the user can only decide whether an attribute has a default value or not. The default values are non-customizable. Here are the default values for each data-type:

Data-typeDefault value
STRING or TEXT'' (empty string)
NUMBER0
BOOLEANFalse
DATEthe current date
TIMEthe current time
DATETIMEthe current datetime
JSONnull
BYTES'' (empty string)
RECORD_REFERENCENone
OBJECT_TEMPLATE_REFERENCENone

Example

Here's how a simple framework for storing and utilizing a mailing list could look like.

The goal is to have a mailing list that stores users. Each user has a category that defines which mails they are subscribed to. When a user is added or removed, or if their category is changed, they should get an email notification.

We will need flows for the lifecycle event hooks. We will also need a template for items on the mailing list.

Flows for the Hooks

On Create

This is the flow that gets executed on creation of a new item (i.e. a new user) on the list. It receives a reference to the user and the category in its input value and notifies the user about being added to the list.

We will call this flow "Mailing List Item on create".

import flow_api

def handler(system: flow_api.System, this: flow_api.Execution, inputs: dict):
category = inputs['value']['Category']
user = inputs['value']['User_user']

user.send_mail(
subject='Added to mailing list',
text=f'You are now subscribed to all mails with category {category} and below',
)

return this.success('all done')

On Delete

This is the flow that gets executed on deletion of a new item on the list. It is almost identical to the on create flow. It receives a reference to the user and the category in its input value and notifies the user about being deleted from the list.

We will call this flow "Mailing List Item on delete".

import flow_api

def handler(system: flow_api.System, this: flow_api.Execution, inputs: dict):
user = inputs['old_value']['User_user']

user.send_mail(
subject='Removed from mailing list',
text='You are not subscribed anymore to mails.',
)

return this.success('all done')

On Update

The on update flow is similar to the other flows. However, it also needs to account for the case, that the user gets changed, which is equivalent to adding the new user and deleting the old user.

We will call this flow "Mailing List Item on update".

import flow_api

def handler(system: flow_api.System, this: flow_api.Execution, inputs: dict):
new_category = inputs['value']['Category']
new_user = inputs['value']['User_user']
old_category = inputs['old_value']['Category']
old_user = inputs['old_value']['User_user']

if old_user != new_user:
# the on create flow
this.flow(
'Mailing List Item on create',
value={'User_user': new_user,'Category': new_category}
)

# the on delete flow
this.flow(
'Mailing List Item on delete',
old_value={'User_user': old_user}
)

else:
new_user.send_mail(
subject='Changed category on mailing list',
text=f'You are now subscribed to all mails with category {new_category} and below (old category: {old_category}).',
)

return this.success('all done')
note

When calling the on create flow, we pass the argument value. However, when calling the on delete flow, we pass the argument old_value.

Object Template for Mailing List

We can now create the object template and define the hook, using the flows from before.

The template with the hooks defined.

Now add two attributes: User and Category.

The User attribute.

note

User is unique. This means that a user can only be added once to the list.

The Category attribute.

And that's about it. Now we have a template that defines items on the mailing list. When an item is added, changed, or removed, the user will be notified.

Adding an Item to the Mailing List

Let's see how this works in action. Let's add an item to the mailing list i.e. create a custom object using the template above.

Here is the first item that we added. We selected a user and specified the category

Once you save the item (i.e. the custom object), the on create hook is triggered, and the User is notified via email.

You can also see the provisioning state changing from CREATING to READY as the on create flow is executed by the hook.

Accessing the Mailing List

You can integrate the mailing list (i.e. the object template) into a workflow by accessing it's items (i.e. the custom objects). The custom_object_list method of an object template returns all its custom objects.

Below is a flow script that takes category, subject, and text as inputs. For each input there is a default set. The script fetches all users (i.e. the custom objects) from the mailing list and then sends an email if the user's category allows it.

import flow_api

def handler(system: flow_api.System, this: flow_api.Execution, inputs: dict):
category = inputs.get('category', 0)
subject = inputs.get('subject', 'This is a test')
text = inputs.get('subject', 'You friendly neighbourhood test email')

# get the mailing list
mailing_list_items = system.object_template('Mailing List').custom_object_list()

for item in mailing_list_items:
user = system.user(item.get('value')['User'], by='id')
user_category = item.get('value')['Category']

if user_category >= category:
user.send_mail(
subject=subject,
text=text,
)


return this.success('all done')