Configuration Domains with CloudForms/ManageIQ

The CloudForms/ManageIQ automation engine gives us great power to create classes and methods that we can execute in response to various actions (e.g. button presses, events in the environment, REST API calls, etc).

Using Ruby and Ansible we can execute custom code to customise and enhance the CloudForms experience for our customers and users – we can add buttons to interfaces, override provisioning workflows, override approval workflows…etc.

Normally, we’ll have at least two environments – a Quality Assurance environment, and a Production environment. We don’t want those two to mix, and quite often key settings will be different between the two environments. For example,  the FQDNs and credentials used to access remote services (e.g. Single Sign On, corporate DNS, etc) may differ between QA and Production. But the underlying logic in the code remains the same – it’s just environment-specific configuration that changes.

To get to a full Continuous Integration/Continuous Deployment model we need to be able to promote Automate code between environments cleanly. Having to execute find/replace across our automate code to replace QA settings with Production settings is not clean.

So how do we keep our common automation logic from intermixing with ou environment-specific settings and configuration?

The answer is a configuration domain.

 

What’s a configuration domain?

If you’ve read Peter McGowan’s book Mastering Automation in CloudForms and ManageIQ, there’s a few paragraphs at the end of the book about configuration domains. This blog post aims to provide a concrete example.

In short: a configuration domain is a higher priority Automate domain where we have placed class definitions that contain environment-specific configuration – in the example below, it’s our credentials to query the Single Sign On API.

When entering Automate, CFME tries to find the class instance in the highest priority domain first. When our Ruby methods read instance attributes via $evm.object[‘my_attribute’], they will be reading the values from the class definition in our configuration domain, rather than our code domain.

How do we use it?

We separate our code from our configuration attributes by copying our class and instance definitions into our configuration domain, while leaving the code in a generic “code” domain. We then change the attributes of the instances in the configuration domain (e.g. external service credentials) to match the environment.

It is exactly the same concept as overriding the instances found in the /ManageIQ domain to customise one of the out-of-the-box workflows (e.g. provisioning, request approval, etc).

Why is this useful?

Our code domain is now environment independent and executes the same logic no matter which environment (QA, prod, test, etc) it is running in.

We can then make fixes and improvements to the business logic, promoting these fixes and enhancements through our environments, whilst the environment-specific configuration (which is typically static) never changes and doesn’t pollute our business logic.

We can have totally separate configuration for dev, test, QA and production environments, with exactly the same code underneath it all.

In short: we’ve going from tightly coupled code and configuration, to loosely coupled code and configuration. For the sake of maintenance, and CI/CD, this is definitely a good thing!

Let’s see an example!

In the below example, I have some custom automation code that executes in response to a button press. Normally, it would retrieve a list of groups from Single Sign On, but in this case it just prints a message to evm.log with the SSO parameters.

To query the SSO API, the business logic needs:

  1. The hostname of the SSO server
  2. A username and password for the SSO API.

In my example, the hostname and credentials are different between my QA and Production environments. My goal is to define these outside of my Code domain, so that I can promote the Code domain from QA to Production without worrying about the credentials to query SSO.

I’ve created a simple instance at /Buttons/SSO/ListAllGroups:

Here’s my Schema for the SSO class (sso_password should be a password field, not a string field, for the record):

You can see the default values for that schema that all instances inherit. Here’s the ListAllGroups instance:

You can see that the schema defaults have been populated into the instance, and I’ve specified list_all_groups as the method. Let’s look at the code for my list_all_groups method:

$evm.log(:info, "This is my SSO code. Would connect to #{$evm.object['sso_api_url']}, realm #{$evm.object['sso_realm']}, username #{$evm.object['sso_username']}, password #{$evm.object['sso_password']}")

That’s it – it just prints out the attributes the method received. Note you need to use the $evm.object object – you need to access the attributes on the currently executing object. You could also use $evm.current too; it’s just an alias for  $evm.object.

I’ve created a custom button on Cloud Tenants that calls this automation code, let’s give it a try:

Here’s the output in evm.log:

[----] I, [2018-05-05T14:23:24.745936 #2398:689110]  INFO -- : Invoking [inline] method [/Code/Buttons/SSO/list_all_groups] with inputs [{}]
[----] I, [2018-05-05T14:23:24.746792 #2398:689110]  INFO -- : <AEMethod [/Code/Buttons/SSO/list_all_groups]> Starting 
[----] I, [2018-05-05T14:23:25.098581 #2398:69b3c4]  INFO -- : <AEMethod list_all_groups> This is my SSO code. Would connect to https://sso.example.com/auth/api, realm QARealm, username admin, password 1800redhat
[----] I, [2018-05-05T14:23:25.111340 #2398:689110]  INFO -- : <AEMethod [/Code/Buttons/SSO/list_all_groups]> Ending

Great. Now let’s separate the QA attributes from the code…

Move instance into a configuration domain

I’ve created a new domain, Config-Prod, and made it the highest priority domain. I’m going to copy my ListAllGroups instance and place the copy in the Config-Prod domain, at exactly the same path. I’ve also changed the schema for the /Config-Prod/SSO class by updating the default values of my attributes:

Now let’s run the button again:

[----] I, [2018-05-05T14:27:25.065131 #2390:689110]  INFO -- : Updated namespace [miqaedb:/Buttons/SSO/ListAllGroups#create  Config-Prod/Buttons]
[----] I, [2018-05-05T14:27:25.086315 #2390:689110]  INFO -- : Updated namespace [Buttons/SSO/list_all_groups  Code/Buttons]
[----] I, [2018-05-05T14:27:25.097583 #2390:689110]  INFO -- : Invoking [inline] method [/Code/Buttons/SSO/list_all_groups] with inputs [{}]
[----] I, [2018-05-05T14:27:25.098357 #2390:689110]  INFO -- : <AEMethod [/Code/Buttons/SSO/list_all_groups]> Starting 
[----] I, [2018-05-05T14:27:25.477178 #2390:692490]  INFO -- : <AEMethod list_all_groups> This is my SSO code. Would connect to https://sso.production.com/auth/api, realm ProdRealm, username admin_production, password 1800redhat
[----] I, [2018-05-05T14:27:25.490125 #2390:689110]  INFO -- : <AEMethod [/Code/Buttons/SSO/list_all_groups]> Ending

Now it’s using the updated attributes from the configuration domain! Nice.

See the first line? When we attempted to run /Buttons/SSO/ListAllGroups, Automate has used the Config-Prod/Buttons namespace, rather than Code/Buttons, since the Config-Prod domain is a higher priority.

Automate has still executed the list_all_groups method at /Code/Buttons/SSO/list_all_groups, because an overriding method isn’t found in Config-Prod.

So what have we done?

We:

  1. Started with configuration parameters mixed into our Code domain.
  2. Created a higher priority domain called Config-Prod.
  3. Copied the /Code/Buttons/SSO/ListAllGroups class instance into the Config-Prod domain, at exactly the same path, so as to override the instance in the Code domain.
  4. Changed the schema attributes in the Config-Prod/Buttons/SSO class, and saw that these values overrode the values in Code/Buttons/SSO.

Nothing comes for free, however. There’s a couple of gotchas with this approach:

  1. You now introduce the complexity of having to manage two domains – configuration and code – instead of just one. If you have multiple environments (perhaps dev, test, QA, prod) the problem is compounded further.
  2. Once you place an instance override in the configuration domain, all subsequent changes to the schema or attributes of that instance have to go into the configuration domain. You can’t split an instance’s attribute values between the config domain instance and the code domain instance; once you override a class instance in the configuration domain – you’re all in.

In the end we can’t completely sever the link between configuration and code – we might still have occasions where a code update requires a schema change in the configuration domain – but we are making the two much more loosely coupled, and that’s definitely a good thing.

Give it a try!

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *