Implementing ManageIQ Provider Operations with Ansible

August 9, 2018 - Words by  Miha Pleško - 5 min read

Card image caption

Traditional way of integrating ManageIQ provider operations is to bake them in code of the provider core, adhering to certain specifics of the provider API. While this is perfectly valid and has served the purposes well for years, ManageIQ team investigated options for superior flexibility.

This post explains how we did it using Ansible on a real example for the Nuage Networks provider. It will serve other adventurous developers getting started with Ansible within ManageIQ.

The New Way - Ansible Way

We were kind of lucky because shortly after we’ve implemented the read-only inventory for the Nuage provider, an announcement came from the ManageIQ core team that CRUD operations on entities can now be done using Ansible. In other words, when a user clicks Create Subnet button on ManageIQ UI, for example, the developer is now given a choice whether she wants to implement it using:

  • Ruby gem to interact with the Nuage endpoint (the old way).
  • Invoke Ansible playbook to do it for her (the new way).

While using Ruby gems is a perfectly valid choice here, we decided to try the Ansible approach and - fell in love.

Why Is Ansible Good for CRUD

Because it’s super simple - and with simplicity comes ease of development. Suppose we wanted to support the Create Subnet operation for the Nuage provider. The subnet must be assigned to a router. Suppose there is an additional requirement when creating a subnet: all subnets must always be created in a dedicated zone named ManageIQ Subnets - which must be silently created on a router if it does not exist.

Think how it would be done in Ruby. A bunch of if statements, perhaps even a begin-rescue to cover the HTTP errors. My point? Besides the fact that you can easily miss a case or two it is very difficult to test.

We can, on the other hand, now do it with an Ansible playbook like this one:

# Playbook Inputs:
# - router_id       (string) router to connect the new subnet to
# - subnet_attributes (hash) desired subnet attributes

- name: Create Subnet
  tasks:
  - include_role:
      name: xlab_si.nuage_create_entity
    vars:
      entity_type: Zone
      attributes:
        name: 'ManageIQ Subnets'
      parent_type: Router
      parent_id: '{{ router_id }}'
  - include_role:
      name: xlab_si.nuage_create_entity
    vars:
      entity_type: Subnet
      attributes: '{{ subnet_attributes }}'
      parent_type: Zone
      parent_id: '{{ result.id }}'

Which basically makes use of xlab_si.nuage_create_entity role twice: first to create the ManageIQ Subnets zone and then to create the subnet itself. Notice how we needn’t worry whether the ManageIQ Subnets zone was already there or not - we just tell Ansible that we want to have one and let it ensure the requested state behind the scenes.

Where to Put Playbooks and Roles

ManageIQ can only install roles from the Ansible Galaxy portal. This means that as a provider developer you need to first implement a custom role (if you need one) and publish it to the Galaxy like we did with xlab_si.nuage_create_entity. The ManageIQ provider codebase only needs to know the name of the role and desired version whereas the actual role is then automatically fetched when the ManageIQ/CloudForms appliance is being prepared.

Ansible playbooks, meanwhile, must be placed in the provider codebase.

Executing Playbook from ManageIQ

OK so we have the playbook and the role, but how can we invoke Ansible from ManageIQ? Super easily, because the ManageIQ core team recently came up with a Ruby class called Ansible::Runner that is eager to run it for you:

Ansible::Runner.run(
  ansible_env_vars,                              # 1st argument
  ansible_extra_vars(                            # 2nd argument
    :router_id         => options[:router_ref],
    :subnet_attributes => {
      :name    => options[:name],
      :address => options[:address],
      :netmask => options[:netmask],
      :gateway => options[:gateway]
    }
  ),
  playbook('create-subnet.yml')                  # 3rd argument
)

Method .run is a thin wrapper around the Ansible Runner tool. But as a provider developer you needn’t really know this, all that matters to you is that the 1st argument is hash of ENV variables, the 2nd is hash of playbook’s extra variables and the 3rd argument is the playbook path. GO!

Can I Access the Playbook Results? - Yes!

Usually, after creating a new entity we need to return some values like the newly created subnet ID. It may be needed for whatever reason, one of them being that State Machines in ManageIQ Automate depend on it. So how do we obtain it?

Answer: by using the set_stats Ansible module.

In your playbook, or better yet in your role, you can expose any variable using the set_stats Ansible module to make it accessible to the caller. For example, the xlab_si.nuage_create_entity role sets it like this:

- local_action:
    module: nuage_vspk
    auth: '{{ nuage_auth }}'
    type: '{{ entity_type }}'
    parent_type: '{{ parent_type }}'
    parent_id: '{{ parent_id }}'
    state: present
    properties: '{{ attributes }}'
  register: result
- set_stats:
    data:
      created_entity: '{{ result }}'

The first task invokes the nuage_vspk module to create subnet and captures its output into result variable. Then it exposes it to the caller by means of set_stats module which can be accessed from Ruby:

response  = Ansible::Runner.run(...)
subnet_id = response.parsed_stdout.dig(
  -1,
  'event_data', 
  'artifact_data', 
  'created_entity',
  'ID'
)

Wait what? It’s hackish but here’s why. Ansible Runner redirects playbook’s output into a JSON file where Ruby is able to grab it and parse it. And the set_stats module makes Ansible always print stat variables as last. You understand now, right? We’re grabbin’ the last entry (hence -1) which under path event_data.artifact_data always contains whatever your role exposed with the set_stats module.

Summary

The option to leverage the power of Ansible in ManageIQ is a great option and we’re happy we took it early for Nuage. As a result, we now have Ansible roles nicely published on the Ansible Galaxy portal which makes them highly reusable and maintainable.

The playbooks we’ve implemented can easily be tested, because one can simply try them without even running ManageIQ - all you need to do is to execute one with:

$ ansible-playbook ./path/to/playbook.yaml -e router_id=123

Another great benefit is that we can easily reuse playbooks and roles in ManageIQ Automate which is able to consume the Ansible Galaxy portal as well.

Contact Us

Need support getting your Ansible playbooks work with ManageIQ/CloudForms? Get in touch with us and we’ll be happy to help you out. Just drop an email to our Steampunk team [email protected] or visit us at steampunk.si. Additionally, you can ping us on Twitter or leave a comment on Reddit.