Linchpin Hooks

Description:

Every resource provisioned by linchpin goes through multiple states. Each state has its own context. Depending upon the state Linchpin provides a feature to trigger single or multiple events. In Linchpin terminology, each event can initiate execution of a script/scripts or Ansible playbooks called hooks. Hooks are used to configure or interact with resources provisioned or about to be provisioned. The trigger to the hooks is determined by the state in which it is defined.

Different states linchpin provisioning undertakes are as follows:

  • preup: State before provisioning the topology resources

  • postup: State after provisioning the topology resources, and generating the optional inventory

  • predestroy: State before teardown of the topology resources

  • postdestroy: State after teardown of the topology resources

Depending upon the state section in which it is defined the hooks are triggered.

In linchpin, there are a set of python interfaces called ActionManagers which are responsible for the execution of a hook. Based on the runtime they use to execute hook there are multiple types of Action managers exists. 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. Refer Action managers documentation for more details.

A hook is bound to a specific target and must be re-stated for each target used.

Based on how they are packaged linchpin hooks are classified into two types:

  • User defined hooks: These hooks are written following specific syntax and folder structure within the workspace. These are triggered based upon the section in which it is declared.

    User-defined hooks are to be declared within a linchpin workspace folder named “hooks” by default. However, this path can be configured by variable hooks_folder in [evars] section of linchpin.conf.

[evars]
...
hooks_folder = /path/to/hooks_folder
  • Built-in hooks (in development): These hooks are pre-packaged with linchpin and they do not need any file structure to be declared in workspaces to work. They can be directly referenced within the Pinfile.

User defined hook example:

Let us consider a user-defined hook for example.

Each hook follows a strict folder structure. If not followed the hooks execution will result in failure. The following is an example workspace which has a user-defined ansible hook named example_hook. The following would be the directory tree structure of the workspace.

.
├── credentials
├── hooks
│   └── ansible
│       ├── example_hook
│       │   ├── test_hook1.yaml
│       │   ├── test_hook2.yaml
│       ├── example_hook2
│       │   ├── test_ex.yaml
├── inventories
├── layouts
│   └── dummy-layout.yml
├── linchpin.conf
├── linchpin.log
├── PinFile
├── resources
└── topologies
  └── dummy-topology.yml

Every hook with respect to their type is declared in their respective folder ie., ansible hooks go inside ansible folder, python hooks are declared in python folder etc., The current example illustrates the folder structure of ansible based hooks. For more examples folder structures of other hooks refer Hooks examples. Further, the name of the folder should be the name of the hook that will be referred to within a PinFile. Since Ansible relies on the playbooks. All the playbooks are to be defined within the folder.

The following is how a user-defined hook looks like when referenced in a Pinfile dummy provider.

---
dummy_target:
  topology:
    topology_name: "dummy"
    resource_groups:
    - resource_group_name: "dummy"
      resource_group_type: "dummy"
      resource_definitions:
      - role: "dummy_node"
        name: "web"
        count: 1
  layout:
    inventory_layout:
      vars:
        hostname: __IP__
      hosts:
        example-node:
          count: 1
          host_groups:
          - example
  hooks:
    postup:
    - name: example_hook    # name of the hook
      type: ansible      # type of the hook ie., the type of action manager being used.
      context: True      # whether to pass the linchpin context variables or not.
      actions:
        - playbook: test_hook1.yaml  # file name of the playbook to be run
        - playbook: test_hook2.yaml
    - name: example_hook2    # name of the hook
      type: ansible      # type of the hook ie., the type of action manager being used.
      context: True      # whether to pass the linchpin context variables or not.
      actions:
        - playbook: test_ex.yaml  # file name of the playbook to be run

As mentioned previously, depending upon the state where the user would like to execute hooks can be triggered at preup, postup, predestroy, postdestroy states. Within Pinfile these states are defined as separate sections. Every hook declared within a section is executed in a top-down approach. Thus, according to the above example, example_hook would be executed first after that execution is successful, example hook2 would be executed.

Parameters of user-defined hooks:

  • name: Name of the hook that is defined. Further, it should match the name of the folder inside the hooks_folder configured

  • type: Type of the action manager that is to be used can be any one of ansible, shell, python, ruby, and nodejs.

  • Context: while declaring hooks provide an option called as context. When the context variable is set to True some of the linchpin context variables are passed as runtime parameters to the playbooks/scripts executed. This is feature is very helpful when end-user would like to run addition configuration playbooks on provisioned instances.

  • actions: Actions are the list of commands, scripts or playbooks which will be run. There can be multiple actions with the same hook file referenced. If it is an ansible type hook, The elements in action should have a playbook, extra_vars(Optional) parameters instead of directly referencing the file path. For more examples refer Linchpin Hooks examples section.

Action manager specific parameters:

The following are examples for different types of hooks using multiple action_managers

  • Ansible:

    - name: example_hook2    # name of the hook
      type: ansible      # type of the hook ie., the type of action manager being used.
      context: True      # whether to pass the linchpin context variables or not.
        path: /path/to/scripts # optional , by default path would be configured hooks_folder
        actions:
        - playbook: test_ex.yaml  # file name of the playbook to be run
          extra_vars:
            testvar: testval # extravars are optional
    
  • Python:

    - name: example_hook2    # name of the hook
      type: python      # type of the hook ie., the type of action manager being used.
      context: True      # whether to pass the linchpin context variables or not.
      path: /path/to/scripts # optional , by default path would be configured hooks_folder
      actions:
        - script.py  #file name of the playbook to be run
    
  • shell:

    - name: example_hook3    # name of the hook
      type: shell      # type of the hook ie., the type of action manager being used.
      context: True      # whether to pass the linchpin context variables or not.
      path: /path/to/scripts # optional , by default path would be configured hooks_folder
      actions:
      # make sure the script file has execute permissions and shebang header included.
        - script.sh  #file name of the playbook to be run
    
  • Ruby:

    - name: example_ruby   # name of the hook
      type: ruby      # type of the hook ie., the type of action manager being used.
      context: True      # whether to pass the linchpin context variables or not.
      path: /path/to/scripts # optional , by default path would be configured hooks_folder
      actions:
        - script.rb  #file name of the playbook to be run
    
  • Nodejs:

    - name: example_nodejs    # name of the hook
      type: nodejs      # type of the hook ie., the type of action manager being used.
      context: True      # whether to pass the linchpin context variables or not.
      path: /path/to/scripts # optional , by default path would be configured hooks_folder
      actions:
        - script.js  #file name of the playbook to be run
    

Note: For both ruby and nodejs the runtime interpreters should be pre-installed in the host machine.

  • linchpin global hooks or builtins:

Linchpin also provides a prepackaged set of built-in hooks which can be referenced within Pinfile without creating a hooks folder structure. These built-ins are ansible based hooks each having different parameters. Currently, There are three builtin linchpin hooks available to end user. They are:

  • ping: Simple ICMP ping to check the host provisioned in inventory is up or not

  • check_ssh: linchpin tries to check the ssh server is up and running by logging into the machines provisioned using a ssh key

  • port_up: Checks whether the list of network ports are up or down.

All the builtin hooks are context-aware, Thus, every built-in hook is run against the inventory file generated during the linchpin provisioning process.

Builtin hooks Example:

---
os-server-target:
  topology:
    topology_name: os-server-inst
    resource_groups:
      - resource_group_name: os-server-addl-vols
        resource_group_type: openstack
        resource_definitions:
        - name: "database"
          role: os_server
          flavor: m1.small
          image: CentOS-7-x86_64-GenericCloud-1612
          count: 1
          keypair: test_keypairsk2
          fip_pool: 10.8.240.0
          networks:
            - e2e-openstack
        credentials:
          filename: clouds.yaml
          profile: ci-rhos
  layout:
    inventory_layout:
      vars:
        hostname: __IP__
      hosts:
        addl-vols-node:
          count: 1
          host_groups:
          - hello
hooks:
  postup:
    # check_ssh, ping and port_up are builtin hooks
    # note builtin hooks follow different structure when compared to localhooks
    - name: check_ssh
      extra_vars:
        # since checking ssh depends on logging into machine pem file, ssh_user are must
        ansible_ssh_private_key_file:  test_keypairsk2.key
        ansible_ssh_user: centos
        ansible_ssh_common_args: "'-o StrictHostKeyChecking=no'"
        ansible_python_interpreter: "/usr/bin/python"
    - name: ping
    - name: port_up
      ports:
        - 22
        - 8080

Hook Communication:

Hooks can read data from other hooks run in the same target. Hook data is not shared between a provisioning and corresponding teardown task, but is shared between pre- and post- provisioning as well as between action managers.

Experimental With the exception of the Ansible action manager, hook data is passed via the command line. Each hook will receive two arguments on the command line. The first is a json array containing data from previous hook runs. If the hooks are associated with a teardown, this will include hook data for both the hooks in the current run and the hooks in the corresponding provisioning step. Each item in the array is an object with three fields: return_code. data, and state (e.g. preup). The second argument is a path to a temporary file. In order for a hook to share data, it should write any data it wants to share as json to this file. If the data in the file is not valid json, it will be ignored.

The ansible action manager handles data somewhat differently. The results array is passed as a variable called hook_results to Ansible’s extra vars. Data from Ansible will be sent back to LinchPin using the PlaybookCallback class.

Note: For more examples please refer hooks examples section.

See also

Commands (CLI)

Linchpin Command-Line Interface

Common Workflows

Common LinchPin Workflows

Managing Resources

Managing Resources

Examples for all Providers

Providers in Detail