[PYTHON] Ansible self-made module rpm operation

Background and notes

```Ansibledoes not have a rpm`` module.

That's right, because there is yum.

Since it is a configuration management tool, rpm that can be called --force is deprecated in `ʻAnsible``.

However, there are some sites that have been developing past systems by splicing them together, and there are times when you really want to put a 32-bit rpm in a 64-bit machine. When I implement this with yum, it says "I will not put it in because there is a 64-bit package".

The ideal form is development with configuration management in mind, This is a deprecated module. </ font>

What you want to achieve

I'm not going to do it so difficult

--The rpm file has already been placed on the node with copy etc. --I want to be able to place files with a built-in equivalent to copy someday. --Receive the option of the rpm command and use it as it is ――I want to manage the flag someday --Confirm that the specified rpm file exists --Confirm that the specified rpm is not installed --Install the specified rpm together with the specified options

Is it about this?

Make a template

See past articles.

-Ansible self-made module creation-Part 1: Life you want to receive arguments-

rpm.py


#!/usr/bin/python
# -*- coding: utf-8 -*-

from ansible.module_utils.basic import AnsibleModule

#Main processing
#-----------------------------------------------------------
def main():
    #AnsibleModule class:Create module
    module = AnsibleModule(

        #Argument reception
        argument_spec=dict(

            #Write the argument receiving process here

        ),
        #Argument check enabled
        supports_check_mode=True
    )

    #Check for the existence of the specified file

    #Judgment of necessity of change
    changed = False

    #rpm installation

    #End processing

if __name__ == '__main__':
    main()

I wonder if this is the place

Receiving arguments

The arguments accepted this time are as follows

Argument name Mandatory Default value Argument meaning
pkgs - List of packages
opts -Uvh Options given to the rpm command
chdir / Package storage directory

By the way, I didn't know how to receive the `` list'', so I posted it to a certain Q & A, but the next day I solved it myself. Reference: To use the array passed in Ansible with_list for a loop in a module

rpm.py


:#(abridgement)

#Argument reception
argument_spec=dict(
    #Package list(Mandatory,list type)
    pkgs = dict(required = True, type = list),
    #option(str type,[-Uvh])
    opts = dict(required = False, type = str, default = '-Uvh'),
    #Package storage directory(str type)
    chdir = dict(required = False, type = str, default = '/'),
),
#Argument check enabled
supports_check_mode=True

:#(abridgement)

In addition, indentation at the beginning of the line is deleted when excerpting partially as described above. Because it will be long sideways.

Check for the existence of the specified file

Last time, when I created a module to mkfifo, I used the stat command and the test command without thinking about anything, but I thought that there would be so many modules.

Reference: Check the existence of the file with python

rpm.py


:#(abridgement)

from ansible.module_utils.basic import AnsibleModule
import os

:#(abridgement)

#Since it becomes redundant, re-enter variables
pkgs = module.params['pkgs']
chdir = module.params['chdir']

#Loop for each package list
for pkg in pkgs:
    #Check for the existence of the specified file
    if (os.path.isfile(chdir + '/' + pkg)):
        #File is not placed
        module.fail_json(msg = chdir + '/' + pkg + ' is not found.')

:#(abridgement)

I'm worried, so let's check the operation here. I haven't placed rpm in / tmp / yet.

test.yml


- name: ntp install
  rpm:
    pkgs: '{{ item }}'
    opts: '-ivh'
    chdir: '/tmp'
  with_list:
    - [ 'ntp-4.2.6p5-15.el6.centos.x86_64.rpm','ntpdate-4.2.6p5-15.el6.centos.x86_64.rpm' ]

Operation check


$ ansible-playbook -i test_grp -l test_srv -u root test.yml -vvv
:#(abridgement)

ok: [192.168.56.104] => (item=[u'ntp-4.2.6p5-15.el6.centos.x86_64.rpm', u'ntpdate-4.2.6p5-15.el6.centos.x86_64.rpm']) => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "ansible_loop_var": "item",
    "changed": false,
    "invocation": {
        "module_args": {
            "chdir": "/tmp",
            "opts": "-ivh",
            "pkgs": [
                "ntp-4.2.6p5-15.el6.centos.x86_64.rpm",
                "ntpdate-4.2.6p5-15.el6.centos.x86_64.rpm"
            ]
        }
    },
    "item": [
        "ntp-4.2.6p5-15.el6.centos.x86_64.rpm",
        "ntpdate-4.2.6p5-15.el6.centos.x86_64.rpm"
    ]
}

That ... a little debug.

rpm.py


#Since it becomes redundant, re-enter variables
pkgs = module.params['pkgs']
chdir = module.params['chdir']
num = 0
hog = 0
#Loop for each package list
for pkg in pkgs:
    num += 1
    #Check for the existence of the specified file
    if (os.path.isfile(chdir + '/' + pkg)):
        hog += 1
        #File is not placed
        module.fail_json(msg = chdir + '/' + pkg + ' is not found.')

:#(abridgement)

module.exit_json(num = num, hog = hog)

Execution result


ok: [192.168.56.104] => (item=[u'ntp-4.2.6p5-15.el6.centos.x86_64.rpm', u'ntpdate-4.2.6p5-15.el6.centos.x86_64.rpm']) => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "ansible_loop_var": "item",
    "changed": false,
    "hog": 0,  ★
    "invocation": {
        "module_args": {
            "chdir": "/tmp",
            "opts": "-ivh",
            "pkgs": [
                "ntp-4.2.6p5-15.el6.centos.x86_64.rpm",
                "ntpdate-4.2.6p5-15.el6.centos.x86_64.rpm"
            ]
        }
    },
    "item": [
        "ntp-4.2.6p5-15.el6.centos.x86_64.rpm",
        "ntpdate-4.2.6p5-15.el6.centos.x86_64.rpm"
    ],
    "num": 2  ★
}

Hmmm ʻif (os.path.isfile (chdir +'/' + pkg)): I wonder if `` is wrong. As a result of various trials, it worked as ʻif os.path.isfile (chdir +'/' + pkg) == False: ``. Hmm, why?

rpm.py


:#(abridgement)

from ansible.module_utils.basic import AnsibleModule
import os

:#(abridgement)

#Since it becomes redundant, re-enter variables
pkgs = module.params['pkgs']
chdir = module.params['chdir']

#Loop for each package list
for pkg in pkgs:
    #Check for the existence of the specified file
    if os.path.isfile(chdir + '/' + pkg) == False:
        #File is not placed
        module.fail_json(msg = chdir + '/' + pkg + ' is not found.')

:#(abridgement)

Judgment of necessity of change

You don't need to change it, that is, if you already have all the pkg installed. You can tell if the rpm package is installed with the following command.

$ rpm -q ntp-4.2.6p5-15.el6.centos.x86_64
ntp-4.2.6p5-15.el6.centos.x86_64
$ echo $?
0

$ rpm -q hoge
Package hoge is not installed.
$ echo $?
1

However, the rpm file is not always the package name without the .rpm at the end of the file name.

$ ls -1 jdk-8u192-linux-x64.rpm
jdk-8u192-linux-x64.rpm

$ rpm -q jdk-8u192-linux-x64
Package jdk-8u192-linux-x64 is not installed.

$ rpm -qa | grep jdk
jdk1.8-1.8.0_192-fcs.x86_64

In the above example, the file name is jdk-8u192-linux-x64.rpm, but the package name is jdk1.8-1.8.0_192-fcs.x86_64.

To determine this, you need to look up the package name with rpm -qp rpm filename.

$ rpm -qp jdk-8u192-linux-x64.rpm
warning: jdk-8u192-linux-x64.rpm:Header V3 RSA/SHA256 Signature, key ID ec551f03: NOKEY
jdk1.8-1.8.0_192-fcs.x86_64

The package name is obtained from the above standard output.

Incorporate this process into the module

rpm.py


:#(abridgement)

#Since it becomes redundant, re-enter variables
pkgs = module.params['pkgs']
chdir = module.params['chdir']

#Judgment of necessity of change
changed = False

#List of rpm files to be installed
rpms = []

#Loop for each package list
for pkg in pkgs:

    #Create full path of file
    fpath = chdir + '/' + pkg

    #Check for the existence of the specified file
    if os.path.exists(fpath) == False:
        #File is not placed
        module.fail_json(msg = fpath + ' is not found.')

    #Get the package name from the rpm file
    rc, pkg_name, stderr = module.run_command('rpm -qp ' + fpath)

    #Check the installation status of the package
    rc, stdout, stderr = module.run_command('rpm -q ' + pkg_name)

    #Is it installed
    if rc != 0:
        #Not installed, so change required
        changed = True
        #Add to installation target
        rpms.append(fpath)

:#(abridgement)

rpm installation

If the rpm list rpms to be installed is empty, it will end ... It will be left to chagned! = True.

rpm.py


:#(abridgement)

#Judgment of necessity of rpm installation
if changed != True:
    #Not to be installed
    module.exit_json()

#rpm list creation
rpm_line = ' '.join(rpms)

#rpm installation
rc, stdout, stderr = module.run_command('rpm ' + module.params['opts'] + ' ' + rpm_line)

#rpm installation result judgment
if rc != 0:
    #Installation failure
    module.fail_json(stdout = stdout, stderr = stderr)

#End processing
module.exit_json(changed = changed)

:#(abridgement)

Test run

test.yml


- name: ntp install
  rpm:
    pkgs: '{{ item }}'
    opts: '-ivh'
    chdir: '/tmp'
  with_list:
    - [ 'ntp-4.2.6p5-15.el6.centos.x86_64.rpm','ntpdate-4.2.6p5-15.el6.centos.x86_64.rpm' ]
#Make sure the package file is in place
$ ssh [email protected] "ls -l /tmp/*rpm"
-rw-rw-r--.1 root root 614364 May 11 17:01 2020 /tmp/ntp-4.2.6p5-15.el6.centos.x86_64.rpm
-rw-rw-r--.1 root root 80836 May 11 17:00 2020 /tmp/ntpdate-4.2.6p5-15.el6.centos.x86_64.rpm

#Make sure the package file is not installed
$ ssh [email protected] "rpm -qa | grep ntp"
$

#Run!
$ ansible-playbook -i test_grp -l test_srv -u root test.yml -vvv
:#(abridgement)
changed: [192.168.56.104] => (item=[u'ntp-4.2.6p5-15.el6.centos.x86_64.rpm', u'ntpdate-4.2.6p5-15.el6.centos.x86_64.rpm']) => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "ansible_loop_var": "item",
    "changed": true,
    "invocation": {
        "module_args": {
            "chdir": "/tmp",
            "opts": "-ivh",
            "pkgs": [
                "ntp-4.2.6p5-15.el6.centos.x86_64.rpm",
                "ntpdate-4.2.6p5-15.el6.centos.x86_64.rpm"
            ]
        }
    },
    "item": [
        "ntp-4.2.6p5-15.el6.centos.x86_64.rpm",
        "ntpdate-4.2.6p5-15.el6.centos.x86_64.rpm"
    ],
    "msg": "/tmp/ntp-4.2.6p5-15.el6.centos.x86_64.rpm /tmp/ntpdate-4.2.6p5-15.el6.centos.x86_64.rpm"
}

#Run again to check idempotence!
$ ansible-playbook -i test_grp -l test_srv -u root test.yml -vvv
ok: [192.168.56.104] => (item=[u'ntp-4.2.6p5-15.el6.centos.x86_64.rpm', u'ntpdate-4.2.6p5-15.el6.centos.x86_64.rpm']) => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "ansible_loop_var": "item",
    "changed": false,
    "invocation": {
        "module_args": {
            "chdir": "/tmp",
            "opts": "-ivh",
            "pkgs": [
                "ntp-4.2.6p5-15.el6.centos.x86_64.rpm",
                "ntpdate-4.2.6p5-15.el6.centos.x86_64.rpm"
            ]
        }
    },
    "item": [
        "ntp-4.2.6p5-15.el6.centos.x86_64.rpm",
        "ntpdate-4.2.6p5-15.el6.centos.x86_64.rpm"
    ]
}

Isn't it okay?

Created module

rpm.py


#!/usr/bin/python
# -*- coding: utf-8 -*-

from ansible.module_utils.basic import AnsibleModule
import os

#Main processing
#-----------------------------------------------------------
def main():
    #AnsibleModule class:Create module
    module = AnsibleModule(

        #Argument reception
        argument_spec=dict(
            #Package list(Mandatory,list type)
            pkgs = dict(required = True, type = list),
            #option(str type,[-Uvh])
            opts = dict(required = False, type = str, default = '-Uvh'),
            #Package storage directory(str type)
            chdir = dict(required = False, type = str, default = '/'),
        ),
        #Argument check enabled
        supports_check_mode=True
    )

    #Since it becomes redundant, re-enter variables
    pkgs = module.params['pkgs']
    chdir = module.params['chdir']

    #Judgment of necessity of change
    changed = False

    #List of rpm files to be installed
    rpms = []

    #Loop for each package list
    for pkg in pkgs:

        #Create full path of file
        fpath = chdir + '/' + pkg

        #Check for the existence of the specified file
        if os.path.exists(fpath) == False:
            #File is not placed
            module.fail_json(msg = fpath + ' is not found.')

        #Get the package name from the rpm file
        rc, pkg_name, stderr = module.run_command('rpm -qp ' + fpath)

        #Check the installation status of the package
        rc, stdout, stderr = module.run_command('rpm -q ' + pkg_name)

        #Is it installed
        if rc != 0:
            #Not installed, so change required
            changed = True
            #Add to installation target
            rpms.append(fpath)

    #Judgment of necessity of rpm installation
    if changed != True:
        #Not to be installed
        module.exit_json()

    #rpm list creation
    rpm_line = ' '.join(rpms)

    #rpm installation
    rc, stdout, stderr = module.run_command('rpm ' + module.params['opts'] + ' ' + rpm_line)

    #rpm installation result judgment
    if rc != 0:
        #Installation failure
        module.fail_json(stdout = stdout, stderr = stderr)

    #End processing
    module.exit_json(changed = changed)

if __name__ == '__main__':
    main()

Recommended Posts

Ansible self-made module rpm operation
Ansible self-made module creation-Part 2: Life just wanting to execute commands-
Ansible self-made module creation-Part 1: Life you want to receive arguments-