# Deployment trigger points

> Orchestration actions run your code at fixed points in an order or job, applying reusable behavior everywhere it's needed

---

Site [llms.txt](/llms.txt)

---

An [action](/docs/cmp/use/orchestration/actions/) attached to a blueprint runs only for that blueprint. The same action attached to a trigger point runs for every order or every server provisioned, which is ideal for policy, naming, validation, and notifications. This topic covers the trigger points a deployment passes through and what each one receives.

## Where the trigger points fall

Deploying a blueprint that contains a single server runs two jobs: a **Deploy Blueprint** job that processes the order and a **Provision Server** job that builds the server, each with its own trigger points. Actions can also be triggered on [order status changes](/docs/cmp/developer-resources/guide/models/orders-and-jobs/#order-lifecycle). See [Orchestration actions](/docs/cmp/use/orchestration/actions/orchestration-actions/) for the full list of trigger points.

## Deploy Blueprint job

This job has two order-related trigger points. The last three columns in the following table contain the keyword arguments passed to your plugin's `run` method; see [Structure of a plugin](/docs/cmp/use/orchestration/plugins/structure/).

| Trigger point | Runs in | Logs to | `job` | `order` | `hook_point` |
| --- | --- | --- | --- | --- | --- |
| Post-Order Approval | Web server | `application.log` | `None` | `Order` | `pre_order_execution` |
| Post-Order Execution | Job engine | The Deploy Blueprint job's log | `None` | `Order` | `post_order_execution` |

Both trigger points receive a `logger` of type `ThreadLogger`.

### Post-Order Approval

This trigger point is named Post-Order Approval, but its `hook_point` value is `pre_order_execution`. It runs immediately after an order is approved and before any jobs are created. It runs on order approval, including automatic approvals.

Because it runs on the web server rather than the job engine, its output goes to `application.log`. In a high availability deployment, this is the log on the web server that handled the request, under `/var/log/cloudbolt/`.

## Provision Server job

These trigger points all run in the job engine, log to the job's log, and are listed below in execution order.

A `server` object exists from early in the job and becomes more complete as it progresses. At **Pre-Create Resource**, the server is still a stub you can modify before the resource handler creates it; after creation, it carries details including its IP address.

![The trigger points a Provision Server job passes through, in order.](/img/cmp/developer-resources/guide/blueprints/deployment-trigger-points/provision-job.png)

| Trigger point | `hook_point` | Also receives |
| --- | --- | --- |
| Pre-Provision | `pre_provision` | No server yet |
| Generated Hostname Overwrite | `generated_hostname_overwrite` | `server`, `servers` |
| Validate Hostname | `validate_hostname` | `server`, `servers` |
| Pre-Create Resource | `pre_create_resource` | `server`, `servers` |
| Pre-Network Configuration | `pre_networkconfig` | `server`, `servers` |
| Post-Network Configuration | `post_networkconfig` | `server`, `servers` |
| Network Verification | `network_verification` | `server`, `servers`, and `nic`, a `ServerNetworkCard` |
| Post-Network Verification | `post_network_verification` | `server`, `servers` |
| Post-Provision | `post_provision` | `server`, `servers` |

They all also receive `logger` and `job`. Where `servers` appears it is a list containing one server.

## A plugin that reports where it ran

Attach this to any trigger point to see what its `kwargs` contain.

```python
from common.methods import set_progress

def run(job, *args, **kwargs):
    hook_point = kwargs.get('hook_point', '')
    set_progress("Running at hook_point={} and job.id={}".format(hook_point, job.id))

    server = kwargs.get('server')
    if server:
        set_progress("This plug-in is running for server {}".format(server))

    set_progress("Dictionary of keyword args passed to this plug-in: {}".format(kwargs.items()))

    return "SUCCESS", "Sample output message", ""
```

## Reusing a blueprint's action everywhere

An action created in a blueprint's build phase is added to the global actions list and referenced from that blueprint. To apply the same logic everywhere, make it a shared plugin and attach it to the relevant trigger point: changes then take effect wherever it is referenced, leaving one copy to maintain rather than one per blueprint.

## Notification trigger points

Two trigger points customize notifications. For both, **returning FAILURE or raising an unhandled exception does not change the order's status**.

### Order Approval Notification

This replaces the notification approvers get when an order is submitted. It does not run if:

- An Order Approval action is enabled, since approval is then handled outside Cloud Management Platform's default process.
- The order is approved automatically, because there is nobody to notify.

You can define more than one, but each will send its own notification. Use execution filters to control which one runs in a given context.

#### Example {#example-order-approval-notification}

This walks the order to build a summary of what was requested, including blueprint parameters, the build item's arguments, and any preconfiguration values.

```python
def order_approval_notification(order, request, *args, **kwargs):
    try:
        # The top-level order item carries the blueprint-level parameters.
        boi = order.orderitem_set.get(parent_order_item=None).cast()
    except Exception as e:
        return "FAILURE", "Could not find the order's top-level item", e

    parts = []

    # Blueprint parameters whose destination includes the resource live on the
    # order item; those targeting build items live on each item's arguments.
    for cfv in boi.custom_field_values.all():
        parts.append(f"{cfv.field.name}: {cfv.value}")

    bia = boi.blueprintitemarguments_set.first()
    if bia:
        try:
            parts.append(f"quantity: {bia.quantity}")
            parts.append(f"environment: {bia.environment.name}")
            parts.append(f"os_build: {bia.os_build.name}")
        except AttributeError:
            # This order may not contain a server tier.
            pass

        for cfv in bia.custom_field_values.all():
            parts.append(f"{cfv.field.name}: {cfv.value}")

        for pcvs in bia.preconfiguration_values.all():
            parts.append(f"preconfiguration: {pcvs.preconfiguration.name}")
            for cfv in pcvs.custom_field_values.all():
                parts.append(f"    {cfv.field.name}: {cfv.value}")

    return "SUCCESS", "; ".join(parts), ""
```

#### What it receives

| Argument | What it is |
| --- | --- |
| `order` | The order, from which you can reach the blueprint as `order.blueprint`, the environment as `order.environment` where one applies, and the order's items. |
| `request` | The HTTP request from the person who placed the order. |

#### What to return

Return a tuple of status, message, and error. They decide what is logged and what the user sees.

| Position | What to put there |
| --- | --- |
| status | One of `SUCCESS`, `INFO`, `WARNING`, or `FAILURE`. It sets both the log level and the color of the message shown to the user. |
| message | The text to log and show. Leave it empty: nothing is logged, and no message appears. |
| error | A string or a Python exception. Pass the exception, and its traceback is logged too. |

`SUCCESS` and `INFO` both log at INFO, shown green and blue; `WARNING` logs a warning, shown orange; `FAILURE` logs an error, shown red.

### Post-Order Execution Notification

This runs after the Post-Order Execution actions have finished, and takes only `order`. Its return tuple works as above, except that there is no user request to show a message to, so the status and message reach only the logs.

As with approval notifications, you can define several, and each will fire. Consider using execution filters.

#### Example {#example-post-order-execution-notification}

This example deliberately calls a function that does not exist, to show how returning an exception affects the logs.

```python
def post_order_execution_notification(order, *args, **kwargs):
    msg = ""
    try:
        # This function does not exist, so this raises an exception.
        return_value = some_function_that_doesnt_exist()
    except Exception as e:
        msg = "Error running some_function_that_doesnt_exist()"
        # Returning the exception logs its traceback as an error.
        return "FAILURE", msg, e

    return "SUCCESS", msg, ""
```

## Related

- [Orchestration actions](/docs/cmp/use/orchestration/actions/orchestration-actions/): Every trigger point and how to attach an action.
- [Orders and jobs](/docs/cmp/developer-resources/guide/models/orders-and-jobs/): Order and job lifecycles where the trigger points reside.
- [Structure of a plugin](/docs/cmp/use/orchestration/plugins/structure/): The `run` method, and what to do with what it receives.
