Close
    logoCloudomation Docs

    Exceptions

    Cloudomation raises exceptions in executions of flows when errors occur. Flows can catch exceptions and implement logic to handle errors.

    Use Cases

    Cloudomation uses standard Python exceptions to signal errors. Exceptions can be raised

    • by Python. See Built-in Exceptions
    • by third-party modules used in a flow. See the documentation of the module you are using.
    • by Cloudomation

    This article describes the exceptions raised by Cloudomation.

    Exception Hierarchy

    • CloudomationException

      Parent of all Cloudomation specific exceptions.

      • InternalError

        An internal error occured. If the error persists, please report a bug to info@cloudomation.com.

      • PermissionDenied

        The execution does not have sufficient permissions to perform the action.

      • LifecycleSignal

        A signal to end an execution. Do not use this class directly, but one of the subclasses.

        It is not possible to catch any of the LifecycleSignal exceptions.
        • ExecutionCancel

          A signal to end an exception with status ENDED_CANCELLED.

        • ExecutionError

          A signal to end an exception with status ENDED_ERROR.

        • ExecutionSuccess

          A signal to end an exception with status ENDED_SUCCESS.

      • ResourceNotFoundError

        A requested resource does not exist.

      • DependencyFailedError

        A dependency failed.

      • TimeoutExceededError

        A timeout occured.

        • LockTimeoutError

          A timeout occured while waiting for a lock.

        • DependencyTimeoutError

          A timeout occured while waiting for a dependency.

        • MessageResponseTimeoutError

          A timeout occured while waiting for a message response.

      • MissingInputError

        An input was required by a task but not provided.

      • InvalidInputError

        An input was not of an accepted type.

      • ExtraInputError

        An input was provided to a task but not needed.

    Unhandled errors

    When an exception is raised in an execution and it is not handled Cloudomation will end the execution with the status ENDED_ERROR and provide a traceback in the status_message field.

    Example

    This example shows how an unhandled exception is presented to the user.

    import flow_api
    def handler(system: flow_api.System, this: flow_api.Execution):
    system.file('this-file-does-not-exist').get_text_content()
    return this.success('all done')

    The execution of this flow will end with the status ENDED_ERROR and an error message:

    Traceback (most recent call last):
    File "my-flow (c379ba18-51ea-4a49-a84b-80c2987b177e)", line 4, in handler
    flow_api.exceptions.ResourceNotFoundError: file name this-file-does-not-exist

    Handling errors

    Flows can catch exceptions and implement logic to deal with errors.

    When developing automation logic we recommend to anticipate potential errors and implement error handling acordingly.
    Example

    This example shows how a flow can handle an exception.

    import flow_api
    def handler(system: flow_api.System, this: flow_api.Execution):
    try:
    content = system.file('report.txt').get_text_content()
    except flow_api.ResourceNotFoundError:
    # the file was not found. Let's create it!
    content = 'initial content'
    system.file('report.txt').save_text_content(content)
    # at this point we can be sure that the file exists.
    return this.success('all done')

    Cleaning up

    Flows might want to clean up resources they created even when an error occurs. The finally block can be used to do just that.

    Consider an execution which launches a cloud-vm instance, then runs some script on the instance and subsequently deletes the cloud-vm instance.

    Example

    Without a finally block. In case of an error, no cleanup is done.

    import flow_api
    def handler(system: flow_api.System, this: flow_api.Execution):
    # execute a child execution which launches a cloud-vm
    this.flow('create-cloud-vm')
    # use the cloud-vm
    this.connect(
    'cloud-vm',
    script='sleep 10; exit 42' # fail after 10 seconds
    )
    # execute a child execution which removes the cloud-vm
    this.flow('remove-cloud-vm')
    return this.success('all done')

    What will happen:

    1. our execution will start a child execution of the flow create-cloud-vm

    2. when the child execution create-cloud-vm ends successfully our execution will continue

    3. our execution will start a child execution of the connection cloud-vm

    4. when the child execution cloud-vm fails a DependencyFailedError will be thrown in our execution

    5. since our execution does not handle the exception, it will end with ENDED_ERROR

    6. the status message of our execution will contain the traceback of the error:

      Traceback (most recent call last):
      File "my-flow (a3d9d997-f355-47dd-8ba4-9b702b6dc1ba)", line 10, in handler
      flow_api.exceptions.DependencyFailedError: dependency SSH (00891f5b-2fb2-4950-a3fa-4a887ea0279c) did not succeed
      caused by:
      return code: 42

    The flow remove-cloud-vm was never started. The cloud-vm will continue running and incur costs.

    A better approach places the cleanup in a finally block.

    Example

    Cleanup in a finally block. Even in case of an error or cancellation of the execution the cleanup will run.

    import flow_api
    def handler(system: flow_api.System, this: flow_api.Execution):
    # execute a child execution which launches a cloud-vm
    this.flow('create-cloud-vm')
    try:
    # use the cloud-vm
    this.connect(
    'cloud-vm',
    script='sleep 10; exit 42' # fail after 10 seconds
    )
    finally:
    # execute a child execution which removes the cloud-vm
    this.flow('remove-cloud-vm')
    return this.success('all done')

    What will happen:

    1. our execution will start a child execution of the flow create-cloud-vm

    2. when the child execution create-cloud-vm ends successfully our execution will continue

    3. our execution will start a child execution of the connection cloud-vm

    4. when the child execution cloud-vm fails a DependencyFailedError will be thrown in our execution

    5. the code of the finally block is executed

    6. our execution will start a child execution of the flow remove-cloud-vm

    7. when the child execution remove-cloud-vm ends successfilly our execution will continue

    8. since the DependencyFailedError was not caught by an except block, it will be re-thrown and our execution will end with ENDED_ERROR

    9. the status message of our execution will contain the traceback of the error:

      Traceback (most recent call last):
      File "my-flow (2f1b49c2-29a7-4b17-a51d-c31862d33c47)", line 9, in handler
      flow_api.exceptions.DependencyFailedError: dependency SSH (d878bbaf-f1a7-439b-b504-dbe78ec0f96b) did not succeed
      caused by:
      return code: 42

    Although the execution ends with the same error message, it started the flow remove-cloud-vm which can clean up the resources which were allocated.

    It is also possible to do both, handle the error and ensure a cleanup.

    Example

    Handle an exception and cleanup in a finally block.

    import flow_api
    def handler(system: flow_api.System, this: flow_api.Execution):
    # execute a child execution which launches a cloud-vm
    this.flow('create-cloud-vm')
    try:
    # use the cloud-vm
    this.connect(
    'cloud-vm',
    script='sleep 10; exit 42' # fail after 10 seconds
    )
    except flow_api.DependencyFailedError as ex:
    # an error occured. let's send a notification
    system.user('operator').send_mail(
    subject='error notification',
    html=(
    f'''
    <h1>cloud-vm script failed</h1>
    <pre>{str(ex)}</pre>
    '''
    ),
    )
    finally:
    # execute a child execution which removes the cloud-vm
    this.flow('remove-cloud-vm')
    return this.success('all done')

    What will happen:

    1. our execution will start a child execution of the flow create-cloud-vm
    2. when the child execution create-cloud-vm ends successfully our execution will continue
    3. our execution will start a child execution of the connection cloud-vm
    4. when the child execution cloud-vm fails a DependencyFailedError will be thrown in our execution
    5. the code in the except flow_api.DependencyFailedError is executed
    6. our execution sends a notification to a Cloudomation user called operator
    7. the code of the finally block is executed
    8. our execution will start a child execution of the flow remove-cloud-vm
    9. when the child execution remove-cloud-vm ends successfilly our execution will continue
    10. the exception was handled, so our execution will continue normnally and execute the code after the try-except-finally block
    11. our execution will end with ENDED_SUCCESS and the status message "all done"

    When a execution catches an exception it sometimes is better to let it end with the status ENDED_ERROR. This is especially important when the execution is used as a dependency of other executions. This way the parent execution knows that there was an error and can react accordingly.

    Learn more

    Flows
    Savepoints
    Knowledge Base — Previous
    Email Integration
    Next — Knowledge Base
    Executions