[Solved] Documentation on using Ruby-Modules in Automate


#1

Hello,
I am fairly new to ManageIQ and I am looking for a way to build ‘library’-code to abstract the interaction with external systems to my Automate Code. As far as I can tell, there are 3 ways to go about that:

  1. Use $evm.instantiate()
  2. Build a Ruby gem and deploy it on the ManageIQ Appliances
  3. (This is what I am asking about) Namespace the Ruby Scripts with Modules and use them natively. (e.g. /ManageIQ/Cloud/Orchestration/Provisioning/StateMachines/check_provisioned)

I guess (from the few clues I was able to find on google), that this is how reuseable/library-style code is meant to be done in the future?
Is there any Documentation on how this stuff works?
Are there any caveats a ruby novice should be aware of?


#2

Have you read this as in intro?


#3

Yes, I have read the whole book

The problem I am trying to solve is:

  • Getting a list of documents from CouchDB
  • Search for a particular document
  • Update the document in some particular way

I want to put the code that performs the REST-Calls to CouchDB in one file and the business logic (how to update the document) in another file. Something like this:

class CouchdbWrapper
    def setup_connection(host, user, password, database)
        ...
    end
    def get_all_docs()
        return RestClient.get("#{host}/#{database}/_all_docs", self.headers)
    end
    def put_document(new_document)
        RestClient.post(url, new_document, self.headers)
    end
    ...
end

Then the business logic does not have to worry about how to do the API calls and I can just write

require 'CouchdbWrapper'
couchdb = CouchdbWrapper.setup_connection(my_host, my_user, my_passwd, database_name)
documents_i_care_about = couchdb.get_all_docs().select { |doc| ... }
documents_i_care_about.each { |doc|
    ...
    couchdb.put_document(doc)
}

Until now we defined methods for this kind of stuff, which leads to the problem, that we have to copy&paste the relevant code, if we have to interact with the same CouchDB in a different part of Automate.

I don’t want to use $evm.instantiate(), because it is a pretty costly operation compared to loading an additional class and executing a method on it.

I have read a bunch of people suggesting to build a Ruby-gem and installing it on the Cloudforms Appliance (which we did). This approach works great, except for the drawback of having to update the gem every time something changes, the lost visibility of which code gets executed and debugging if something blows up.

I was hoping this “new” way of writing would give me the best of both worlds. Being as easy to use as the gem method and and have no code “hidden” in the gem repository


#4

I found the Sprint-Review that sparked that whole idea: https://youtu.be/zaAT4SRz3L8?t=21m29s
Basically I am trying to do that. I want to include one Automate Method in another Automate Method


#5

This looks to be more of a macro-type of implementation and not gem-ish.


#6

You now have the ability to add “Embedded Methods” in each of your Ruby method. Above the code area, you have a button named “Add Method”. It allows you to insert the code of a method just before yours, in a “require” approach. I have started using it lately to factor code for better reusability.

You import a method, not a class, so it might feel strange in the object oriented approach. In my case, I use methods called “Utils” for each area I need to factor. I also use the new method syntax based on modules and classes: https://github.com/ManageIQ/manageiq-content/issues/8.

Here is an example. I need to do some actions on a RHV/oVirt VM, but it is not (yet) available in the provider. I have created a utility class (which simply calls a method) with a method called Utils. The code is in /FabienDupont/Providers/RedHat/Utils with the following code:

module FabienDupont
  module Providers
    module RedHat
      class Utils
        def initialize(handle = $evm)
          @debug = true
          @handle = handle
        end
        
        def main
        end

        def rhv_rest(ems, method, request, payload="")
          require 'restclient'
          require 'json'
          
          # Set default content type to JSON
          accept = 'application/json'
          content_type   = 'application/json'
          
          # Override content type if payload contains XML
          if [:post, :put].include?(method) and /^</.match(payload)
              accept   = 'application/xml'
              content_type = 'application/xml'
          end
          
          @handle.log(:info, "Connecting to RHV Manager: #{ems.hostname} as #{ems.authentication_userid}.") if @debug
          response = RestClient::Request.new(
            :method => method,
            :url => "https://#{ems.hostname}/#{request.gsub(/^\//, '')}",
            :user => ems.authentication_userid,
            :password => ems.authentication_password,
            :verify_ssl => false,
            :payload => payload,
            :headers => {
              :accept => accept,
              :content_type => content_type
            }
          ).execute
          
          result = method.to_s.match('get') ? JSON.parse(response) : response
          return result
        end
        
        def vm_update_description(vm, description)
          payload = "<vm><description>#{description}</description></vm>"
          self.rhv_rest(vm.ext_management_system, :put, vm.ems_ref_string, payload)
        end
      end
    end
  end
end

if __FILE__ == $PROGRAM_NAME
  FabienDupont::Providers::RedHat::VM_UpdateDescription.new.main
end

The code itself is quite simple: it calls the RHV/oVirt API in the rhv_rest method which is used by the vm_update_description that basically updates the VM description in RHV/oVirt (captain obvious’s not far :smile: ). With that, I can have a very simple method later on, to update the description of my VM, for example as a post provision task in the provisioning state machine. To do so, I have created a method named VM_UpdateDescription in my utility class, and this method leverages the Utils method:

module FabienDupont
  module Providers
    module RedHat
      class VM_UpdateDescription
        def initialize(handle = $evm)
          @debug = true
          @handle = handle
        end
        
        def main
          vm = @handle.root['target_vm']
          description = "Provisioned by Cloudforms on #{Time.now}."
          FabienDupont::Providers::RedHat::Utils.new.vm_update_description(vm, description)
        end
      end
    end
  end
end

if __FILE__ == $PROGRAM_NAME
  FabienDupont::Providers::RedHat::VM_UpdateDescription.new.main
end

In the VM_UpdateDescription, I have added the /FabienDupont/Providers/RedHat/Utils method as an Embedded Method, so I can call it using FabienDupont::Providers::RedHat::Utils.new.vm_update_description(vm, description). If I used it more than one time, I could instantiate a reusable object by simply doing the following:

        def initialize(handle = $evm)
          @debug = true
          @handle = handle
          @rhv_utils= FabienDupont::Providers::RedHat::Utils.new
        end
        
        def main
          vm = @handle.root['target_vm']
          description = "Provisioned by Cloudforms on #{Time.now}."
          @rhv_utils.vm_update_description(vm, description)
        end

This is not exactly gem-ish, but it allows creating a library of reusable code. Another advantage upon Gem is that the code is stored in the Automate tree, thus in the database, so it is available to all appliances of the region without installing Gem.


#7

Having the name of the feature “Embedded Methods” as a starting point for my searches helped a lot and clarified some misconceptions I had.
I thought the class-style methods would enable some black-class-loading-magic, when the real reason was to be able to load them in automated tests, without executing the code right away (https://github.com/ManageIQ/manageiq-content/issues/8)

Unfortunately I am working with Cloudforms 4.5/CFME 5.8, which does not have embedded Methods yet. According to the PR (https://github.com/ManageIQ/manageiq-automation_engine/pull/16), it should be part of the Gaprindashvili-Release (although I can’t find it in the changelog)?
I guess there is no way to make something like this possible in the current Cloudforms Release, and I have to wait until Embedded Methods find their way into Cloudforms?

This is the reason I opened the topic in the first place. While the gem method works great, it is a second code repository, which needs to be kept in sync with the main Automate code and I would like to get rid of it as soon as there is an alternative

Thank you


#8

Gapri RC1 has this feature (just checked), so should land in CF 4.6 (question is when it’ll be released).


#9

It’s already part of CloudForms 4.6 beta, that you can get on Red Hat site: https://access.redhat.com/downloads/content/167/. It would at least let you validate that it works as you expect.


#10

I will try it out later.