Skip to main content
Version: 11 - 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 4 types of hooks you can define:

  • Before create
  • 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 nullableWhether the attribute can be set to null.
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

Object Template Canvas

The object template canvas provides you with a visual overview of object templates and relations between them, just like a database schema.

It also enables you to create and edit object templates, as if you navigated to an object template in the UI. Editing with the object template canvas has the advantage of showing you how your changes affect other object templates i.e. references.

Each project/bundle has its own object template canvas. A canvas shows all object templates that are in the respective project/bundle, plus any object template that stands in relation with them. To navigate to the object template canvas, click on the button shown below.

The object template canvas button.

If there are no object templates in the project/bundle, an empty canvas is shown:

The empty canvas.

Below you can see an object template canvas of the DevStack bundle. It has two object templates: cde and cde-type. The arrow pointing from cde to cde-type signals that cde has an attribute that references cde-type, just like a foreign key.

The canvas of the DevStack bundle.

The button on the top left lets you create more object templates. The two buttons stacked on each other on the bottom left will let you center or rotate the canvas.

Let's now take a look at the individual parts and what you can use them for. On the top of an object template you can:

  • see (by hovering) and define (by clicking) the flows for the hooks,
  • change the name of the custom object (click on the name),
  • navigate to it (click on button with arrow pointing to top-right),
  • access more options (click on ellipsis),
  • save your changes with the save button.

The top part of an object template in the canvas.

On the bottom you can see the attributes and the button to add more attributes. To examine and edit the properties of an attribute, simply click on it.

The bottom part of an object template in the canvas.

The details (e.g. datatype, uniqueness etc.) of an attribute. To see their values, hover over them with the mouse.

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')