Linchpin Custom Action Managers

Linchpin custom action managers:

In linchpin, ActionManagers are set of python interfaces responsible for execution of linchpin hook based on their type. There are two types of ActionManagers builtins and custom.

Here’s a list of built-in Action Managers:

  • shell: Allows either inline shell commands or an executable shell script

  • python: Executes a Python script

  • ansible: Executes an Ansible playbook, allowing passing of a vars_file and extra_vars represented as a Python dict

  • nodejs: Executes a Node.js script

  • ruby: Executes a Ruby script

In addition to the above action managers, User can define their custom action manager. custom/userdefined action managers are helpful when there is a specific runtime end user would like to make use of for executing a hook.

For example, if linchpin end user would like to use a “xyz” language based runtime or a custom command to be run when certain paramters are passed to a hook. They can do it with help of hook based on custom_action_manager.

Consider the following dummy workspace example for custom_action_manager:

├── credentials
├── hooks
│   ├── custom
│   │   └── somecustomhook
│   │       ├──
│   │       ├── custom_action_manager.pyc
│   │       └──
├── inventories
├── layouts
├── linchpin.conf
├── linchpin.log
├── localhost
├── PinFile
├── resources
└── topologies
  └── dummy-topology.yml
    topology_name: "dummy"
    - resource_group_name: "dummy"
      resource_group_type: "dummy"
      - role: "dummy_node"
        name: "web"
        count: 1
        hostname: __IP__
          count: 1
            - example
      - name: somecustomhook
        type: custom
        # action_manager: /path/to/manager
        # if not absolute path
        # linchpin searches in hooks folder configured
        context: True
          - script: some_script.go

As you can see in the above structure the custom hook follows the same structure of a userdefined hook. However, we also need to add python interface (which can be named any) within thehooks folder or the absolute path to the python file is to be mentioned in the Pinfile

In order to write a custom_action_manager one has to implement builtin linchpin ActionManager class overriding the following functions:

  • validate: (optional): validate schema for hook designed

  • load: How to load the context parameters

  • execute: Responsible for executing the files based on the parameters

Once the above functions are implemented the class file can be included in Pinfile.

Following is an example for the python interface implemented:

import os
import yaml
import json

from cerberus import Validator

from linchpin.exceptions import HookError
from linchpin.hooks.action_managers.action_manager import ActionManager

class CustomActionManager(ActionManager):

    def __init__(self, name, action_data, target_data, **kwargs):

        The following is an example for CustomActionManager
        AnsibleActionManager constructor
        :param name: Name of Action Manager , ( ie., ansible)
        :param action_data: dictionary of action_block
        consists of set of actions
        - name: nameofthehook
          type: custom
            - script: test_playbook.yaml
        :param target_data: Target specific data defined in PinFile
        :param kwargs: anyother keyword args passed as metadata
        """ = name
        self.action_data = action_data
        self.target_data = target_data
        self.context = kwargs.get("context", True)
        self.kwargs = kwargs

    def validate(self):

        Validates the action_block based on the cerberus schema
        example:: ansible_action_block::::
        - name: nameofthehook
          type: customhook
            - script: test_playbook.yaml
        schema = {
            'name': {'type': 'string', 'required': True},
            'type': {'type': 'string', 'allowed': ['custom']},
            'path': {'type': 'string', 'required': False},
            'context': {'type': 'boolean', 'required': False},
            'actions': {
                'type': 'list',
                'schema': {
                    'type': 'dict',
                    'schema': {
                        'script': {'type': 'string', 'required': True}
                'required': True

        v = Validator(schema)
        status = v.validate(self.action_data)

        if not status:
            raise HookError("Invalid syntax: {0}".format((v.errors)))
            return status

    def load(self):

        Loads the ansible specific managers and loaders
        return True

    def get_ctx_params(self):

        Reformats the ansible specific context variables

        ctx_params = {}
        ctx_params["resource_file"] = (
            self.target_data.get("resource_file", None))
        ctx_params["layout_file"] = self.target_data.get("layout_file", None)
        ctx_params["inventory_file"] = (
            self.target_data.get("inventory_file", None))

        return ctx_params

    def execute(self):

        Executes the action_block in the PinFile
        The following logic just prints out path of the script being used

        extra_vars = {}
        runners = []

        print("This is the custom hook that runs custom logic")

        for action in self.action_data["actions"]:
            path = self.action_data["path"]
            script = action.get("script")