美文网首页Ansible
Ansible 开发Action插件之【le_copy】

Ansible 开发Action插件之【le_copy】

作者: lework | 来源:发表于2017-06-01 20:43 被阅读270次

    用于构造action 类


    from ansible.plugins.action import ActionBase
    
    class ActionModule(ActionBase):
        def run(self, tmp=None, task_vars=None):
        
            result = super(ActionModule, self).run(tmp, task_vars)
            
            return result
    

    类名需为ActionModule,需从ActionBase继承。类中必须要有run方法。

    本次实现的action需求


    • 将本地文件拷贝到远程主机

    创建action


    在playbook目录下新建action_plugins目录,在action_plugins目录下新建le_copy.py文件。

    [root@master ansible]# cat action_plugins/le_copy.py
    # coding=utf-8
    
    from __future__ import (absolute_import, division, print_function)
    __metaclass__ = type
    
    import json
    import os
    import stat
    import tempfile
    
    from ansible.constants import mk_boolean as boolean
    from ansible.errors import AnsibleError, AnsibleFileNotFound
    from ansible.module_utils._text import to_bytes, to_native, to_text
    from ansible.plugins.action import ActionBase
    from ansible.utils.hashing import checksum
    
    
    class ActionModule(ActionBase):
    
        def run(self, tmp=None, task_vars=None):
            ''' handler for file transfer operations '''
            if task_vars is None:
                task_vars = dict()
            # 执行父类的run方法
            result = super(ActionModule, self).run(tmp, task_vars)
    
            if result.get('skipped'):
                return result
    
            # 获取参数
            source  = self._task.args.get('src', None)
            dest    = self._task.args.get('dest', None)
            force   = boolean(self._task.args.get('force', 'yes'))
            remote_src = boolean(self._task.args.get('remote_src', False))
    
            # 判定参数
            result['failed'] = True
            if source is None or dest is None:
                result['msg'] = "src and dest are required"
            elif source is not None and source.endswith("/"):
                result['msg'] = "src must be a file"
            else:
                del result['failed']
    
            if result.get('failed'):
                return result
    
            # 如果copy动作在远端执行,直接返回
            if remote_src:
                result.update(self._execute_module(task_vars=task_vars))
                return result
    
            # 找到source的路径地址
            try:
                source = self._find_needle('files', source)
            except AnsibleError as e:
                result['failed'] = True
                result['msg'] = to_text(e)
                return result
    
            # 判断是否是目录,如果是跳出返回
            if os.path.isdir(to_bytes(source, errors='surrogate_or_strict')):
                result['failed'] = True
                result['msg'] = to_text(u'不支持目录')
                return result
    
            changed = False
            module_return = dict(changed=False)
    
            # 创建临时目录
            if tmp is None or "-tmp-" not in tmp:
                tmp = self._make_tmp_path()
    
            #  获取本地文件,不存在抛出异常
            try:
                source_full = self._loader.get_real_file(source)
                source_rel = os.path.basename(source)
            except AnsibleFileNotFound as e:
                result['failed'] = True
                result['msg'] = "could not find src=%s, %s" % (source_full, e)
                self._remove_tmp_path(tmp)
                return result
    
    
            # 获取远程文件信息
            if self._connection._shell.path_has_trailing_slash(dest):
                dest_file = self._connection._shell.join_path(dest, source_rel)
            else:
                dest_file = self._connection._shell.join_path(dest)
    
            dest_status = self._execute_remote_stat(dest_file, all_vars=task_vars, follow=False, tmp=tmp, checksum=force)
    
            # 如果是目录,则返回
            if dest_status['exists'] and dest_status['isdir']:
               self._remove_tmp_path(tmp)
               result['failed'] = True
               result['msg'] = "can not use content with a dir as dest"
               return result
    
            # 如果存在,但force为false。则返回
            if dest_status['exists'] and not force:
              return result
    
            # 定义拷贝到远程的文件路径
            tmp_src = self._connection._shell.join_path(tmp, 'source')
    
            # 传送文件
    
            remote_path = None
            remote_path = self._transfer_file(source_full, tmp_src)
    
            # 确保我们的文件具有执行权限
            if remote_path:
                self._fixup_perms2((tmp, remote_path))
    
            # 运行remote_copy 模块
            new_module_args = self._task.args.copy()
            new_module_args.update(
                dict(
                    src=tmp_src,
                    dest=dest,
                    original_basename=source_rel,
                )
            )
    
            module_return = self._execute_module(module_name='le_copy',
                module_args=new_module_args, task_vars=task_vars,
                tmp=tmp)
    
            # 判断运行结果
            if module_return.get('failed'):
                result.update(module_return)
                return result
            if module_return.get('changed'):
                changed = True
    
            if module_return:
               result.update(module_return)
            else:
               result.update(dict(dest=dest, src=source, changed=changed))
    
            # 清理临时文件
            self._remove_tmp_path(tmp)
            
            # 返回结果
            return result
    

    action只能在本地执行,所以目标服务器上的操作是要交给modules来做。下面modules是用来把action传送的文件移动到目标位置。

    创建module


    在playbook目录下新建library目录,在library目录下新建le_copy.py文件。

    注:module和action plugin的名称要一致。

    [root@master ansible]# cat  library/le_copy.py
    #!/usr/bin/python
    # coding: utf-8
    
    ANSIBLE_METADATA = {'metadata_version': '1.0',
                        'status': ['preview'],
                        'supported_by': 'community'}
    
    DOCUMENTATION = '''
    ---
    module: le_copy
    version_added: "2.3"
    short_description: Copy a C(file) to  remote host
    description:
         - The C(le_copy) module copies a file to remote host from a given source to a provided destination.
    options:
      src:
        description:
          - Path to a file on the source file to remote host
        required: true
      dest:
        description:
          - Path to the destination on the remote host for the copy
        required: true
      force:
        description:
          - the default is C(yes), which will replace the remote file when contents
            are different than the source. If C(no), the file will only be transferred
            if the destination does not exist.
        required: false
        choices: [ "yes", "no" ]
        default: "yes"
      remote_src:
        description:
          - If False, it will search for src at originating/master machine, if True it will go to the remote/target machine for the src. Default is False.
          - Currently remote_src does not support recursive copying.
        choices: [ "True", "False" ]
        required: false
        default: "False"
    
    author:
        - "Lework"
    '''
    
    EXAMPLES = '''
    # Example from Ansible Playbooks
    - name: copy a config C(file)
      le_copy:
        src: /etc/herp/derp.conf
        dest: /root/herp-derp.conf
    '''
    
    RETURN = '''
    src:
        description: source file used for the copy
        returned: success
        type: string
        sample: "/path/to/file.name"
    dest:
        description: destination of the copy
        returned: success
        type: string
        sample: "/path/to/destination.file"
    checksum:
        description: sha1 checksum of the file after running copy
        returned: success
        type: string
        sample: "6e642bb8dd5c2e027bf21dd923337cbb4214f827"
    gid:
        description: group id of the file, after execution
        returned: success
        type: int
        sample: 100
    group:
        description: group of the file, after execution
        returned: success
        type: string
        sample: "httpd"
    owner:
        description: owner of the file, after execution
        returned: success
        type: string
        sample: "httpd"
    uid:
        description: owner id of the file, after execution
        returned: success
        type: int
        sample: 100
    mode:
        description: permissions of the target, after execution
        returned: success
        type: string
        sample: "0644"
    size:
        description: size of the target, after execution
        returned: success
        type: int
        sample: 1220
    state:
        description: C(state) of the target, after execution
        returned: success
        type: string
        sample: "file"
    '''
    
    import os
    import shutil
    
    
    from ansible.module_utils.basic import AnsibleModule
    from ansible.module_utils._text import to_bytes, to_native
    from ansible.module_utils.pycompat24 import get_exception
    
    def main():
        # 定义modules需要的参数
        module = AnsibleModule(
            argument_spec=dict(
                src=dict(required=True, type='path'),
                dest=dict(required=True, type='path'),
                force=dict(default=True, type='bool'),
                original_basename=dict(required=False),
                remote_src=dict(required=False, type='bool')
            ),
            supports_check_mode=True,
        )
    
        # 获取modules的参数
        src = module.params['src']
        dest = module.params['dest']
        b_src = to_bytes(src, errors='surrogate_or_strict')
        b_dest = to_bytes(dest, errors='surrogate_or_strict')
        force = module.params['force']
        remote_src = module.params['remote_src']
        original_basename = module.params.get('original_basename', None)
    
        # 判断参数是否合规
        if not os.path.exists(b_src):
            module.fail_json(msg="Source %s not found" % (src))
        if not os.access(b_src, os.R_OK):
            module.fail_json(msg="Source %s not readable" % (src))
        if os.path.isdir(b_src):
            module.fail_json(msg="Remote copy does not support recursive copy of directory: %s" % (src))
    
        # 获取文件的sha1
        checksum_src = module.sha1(src)
        checksum_dest = None
    
        changed = False
    
        # 确定dest文件路径
        if original_basename and dest.endswith(os.sep):
            dest = os.path.join(dest, original_basename)
            b_dest = to_bytes(dest, errors='surrogate_or_strict')
    
        # 判断目标文件是否存在
        if os.path.exists(b_dest):
            if not force:
                module.exit_json(msg="file already exists", src=src, dest=dest, changed=False)
            if os.access(b_dest, os.R_OK):
                checksum_dest = module.sha1(dest)
        # 目录不存在,退出执行
        elif not os.path.exists(os.path.dirname(b_dest)):
            try:
                os.stat(os.path.dirname(b_dest))
            except OSError:
                e = get_exception()
                if "permission denied" in to_native(e).lower():
                    module.fail_json(msg="Destination directory %s is not accessible" % (os.path.dirname(dest)))
            module.fail_json(msg="Destination directory %s does not exist" % (os.path.dirname(dest)))
    
        # 源文件与目标文件sha1值不一致时覆盖源文件
        if checksum_src != checksum_dest:
          if not module.check_mode:
            try:
                if remote_src:
                    shutil.copy(b_src, b_dest)
                else:
                    module.atomic_move(b_src, b_dest)
            except IOError:
                module.fail_json(msg="failed to copy: %s to %s" % (src, dest))
            changed = True
    
        else:
            changed = False
        
        # 返回值
        res_args = dict(
            dest=dest, src=src, checksum=checksum_src, changed=changed
        )
    
        module.exit_json(**res_args)
    
    
    if __name__ == '__main__':
        main()
    

    playbook


    ---
    - hosts: node1
      gather_facts: false
      
      tasks:
       - name: copy file to remote
         le_copy: src=/tmp/foo dest=/tmp/test
    
       - name: unforce copy file to remote
         le_copy: src=/tmp/foo dest=/tmp/test force=false
    
       - name: copy file on remote
         le_copy: src=/tmp/foo dest=/tmp/123 remote_src=true
    

    执行结果


    image.png

    github

    https://github.com/kuailemy123/Ansible-dev

    相关文章

      网友评论

      • 7db28812e4d1:ansible2.3.1编写的自定义插件,执行时找不到,不知道什么原因,位置都放对了。升级为2.4后马上就可以。为什么2.3不可以呢?
        7db28812e4d1:还有放到python 目录/usr/local/lib/python2.7/site-packages/ansible/plugins/action下也不行,很多书上ansible2.3的自定义插件都可以的啊,不知道为什么
        7db28812e4d1:打开ansible.cfg文件中plugins的存放路径,把插件放进去,这样也不行。两种方法都不行,只能升级ansible才可以。
        lework:@岁月神偷_2573 有没有试过指定插件目录能找到插件么

      本文标题:Ansible 开发Action插件之【le_copy】

      本文链接:https://www.haomeiwen.com/subject/kfzefxtx.html