Skip to content
Snippets Groups Projects
bslurp.py 5.97 KiB
Newer Older
  • Learn to ignore specific revisions
  • Abel Lopez's avatar
    Abel Lopez committed
    #!/usr/bin/env python
    
    Sam Yaple's avatar
    Sam Yaple committed
    
    # Copyright 2015 Sam Yaple
    #
    # Licensed under the Apache License, Version 2.0 (the "License");
    # you may not use this file except in compliance with the License.
    # You may obtain a copy of the License at
    #
    #     http://www.apache.org/licenses/LICENSE-2.0
    #
    # Unless required by applicable law or agreed to in writing, software
    # distributed under the License is distributed on an "AS IS" BASIS,
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    # See the License for the specific language governing permissions and
    # limitations under the License.
    
    # This module has been relicensed from the source below:
    # https://github.com/SamYaple/yaodu/blob/master/ansible/library/bslurp
    
    
    confi-surya's avatar
    confi-surya committed
    import base64
    import hashlib
    import os
    import traceback
    import zlib
    
    from ansible.module_utils.basic import AnsibleModule
    
    
    Sam Yaple's avatar
    Sam Yaple committed
    DOCUMENTATION = '''
    ---
    module: bslurp
    short_description: Slurps a file from a remote node
    description:
         - Used for fetching a binary blob containing the file, then push that file
           to other hosts.
    options:
      src:
        description:
          - File to fetch. When dest is used, src is expected to be a str with data
        required: True
        type: str
      compress:
        description:
          - Compress file with zlib
        default: True
        type: bool
      dest:
        description:
          - Where to write out binary blob
        required: False
        type: str
      mode:
        description:
          - Destination file permissions
        default: '0644'
        type: str
      sha1:
        description:
          - sha1 hash of the underlying data
        default: None
        type: bool
    
      sha256:
        description:
          - sha256 hash of the underlying data
        default: None
        type: bool
    
    Sam Yaple's avatar
    Sam Yaple committed
    author: Sam Yaple
    '''
    
    EXAMPLES = '''
    
    Lu lei's avatar
    Lu lei committed
    Distribute a file from single to many hosts:
    
    Sam Yaple's avatar
    Sam Yaple committed
    
    - hosts: web_servers
      tasks:
        - name: Pull in web config
          bslurp: src="/path/to/file"
          register: file_data
          run_once: True
        - name: Push if changed
          bslurp:
            src: "{{ file_data.content }}"
            dest: "{{ file_data.source }}"
            mode: "{{ file_data.mode }}"
            sha1: "{{ file_data.sha1 }}"
    
    
    Lu lei's avatar
    Lu lei committed
    Distribute multiple files from single to many hosts:
    
    Sam Yaple's avatar
    Sam Yaple committed
    
    - hosts: web_servers
      tasks:
        - name: Pull in web config
          bslurp: src="{{ item }}"
          with_items:
            - "/path/to/file1"
            - "/path/to/file2"
            - "/path/to/file3"
          register: file_data
          run_once: True
        - name: Push if changed
          bslurp:
            src: "{{ item.content }}"
            dest: "{{ item.source }}"
            mode: "{{ item.mode }}"
            sha1: "{{ item.sha1 }}"
          with_items: file_data.results
    
    
    Lu lei's avatar
    Lu lei committed
    Distribute a file to many hosts without compression; Change
    permissions on dest:
    
    Sam Yaple's avatar
    Sam Yaple committed
    
    - hosts: web_servers
      tasks:
        - name: Pull in web config
          bslurp: src="/path/to/file"
          register: file_data
          run_once: True
        - name: Push if changed
          bslurp:
            src: "{{ file_data.content }}"
            dest: "/new/path/to/file"
            mode: "0777"
            compress: False
            sha1: "{{ file_data.sha1 }}"
    '''
    
    
    Sam Yaple's avatar
    Sam Yaple committed
    def copy_from_host(module):
        compress = module.params.get('compress')
        src = module.params.get('src')
    
        if not os.path.exists(src):
            module.fail_json(msg="file not found: {}".format(src))
        if not os.access(src, os.R_OK):
            module.fail_json(msg="file is not readable: {}".format(src))
    
    
    SamYaple's avatar
    SamYaple committed
        mode = oct(os.stat(src).st_mode & 0o777)
    
    Sam Yaple's avatar
    Sam Yaple committed
    
        with open(src, 'rb') as f:
            raw_data = f.read()
    
        sha1 = hashlib.sha1(raw_data).hexdigest()
    
        sha256 = hashlib.sha256(raw_data).hexdigest()
    
    
    Sam Yaple's avatar
    Sam Yaple committed
        data = zlib.compress(raw_data) if compress else raw_data
    
    
        module.exit_json(content=base64.b64encode(data), sha1=sha1, sha256=sha256,
                         mode=mode, source=src)
    
    Sam Yaple's avatar
    Sam Yaple committed
    
    
    def copy_to_host(module):
        compress = module.params.get('compress')
        dest = module.params.get('dest')
        mode = int(module.params.get('mode'), 0)
        sha1 = module.params.get('sha1')
    
        sha256 = module.params.get('sha256')
    
    Sam Yaple's avatar
    Sam Yaple committed
        src = module.params.get('src')
    
        data = base64.b64decode(src)
        raw_data = zlib.decompress(data) if compress else data
    
    
        if sha256:
            if os.path.exists(dest):
                if os.access(dest, os.R_OK):
                    with open(dest, 'rb') as f:
                        if hashlib.sha256(f.read()).hexdigest() == sha256:
                            module.exit_json(changed=False)
                else:
                    module.exit_json(failed=True, changed=False,
                                     msg='file is not accessible: {}'.format(dest))
    
            if sha256 != hashlib.sha256(raw_data).hexdigest():
                module.exit_json(failed=True, changed=False,
                                 msg='sha256 sum does not match data')
        elif sha1:
    
    Sam Yaple's avatar
    Sam Yaple committed
            if os.path.exists(dest):
                if os.access(dest, os.R_OK):
                    with open(dest, 'rb') as f:
                        if hashlib.sha1(f.read()).hexdigest() == sha1:
                            module.exit_json(changed=False)
                else:
                    module.exit_json(failed=True, changed=False,
                                     msg='file is not accessible: {}'.format(dest))
    
            if sha1 != hashlib.sha1(raw_data).hexdigest():
                module.exit_json(failed=True, changed=False,
                                 msg='sha1 sum does not match data')
    
        with os.fdopen(os.open(dest, os.O_WRONLY | os.O_CREAT, mode), 'wb') as f:
            f.write(raw_data)
    
        module.exit_json(changed=True)
    
    
    Sam Yaple's avatar
    Sam Yaple committed
    def main():
    
    SamYaple's avatar
    SamYaple committed
        argument_spec = dict(
            compress=dict(default=True, type='bool'),
            dest=dict(type='str'),
            mode=dict(default='0644', type='str'),
            sha1=dict(default=None, type='str'),
    
            sha256=dict(default=None, type='str'),
    
    SamYaple's avatar
    SamYaple committed
            src=dict(required=True, type='str')
    
    Sam Yaple's avatar
    Sam Yaple committed
        )
    
    SamYaple's avatar
    SamYaple committed
        module = AnsibleModule(argument_spec)
    
    Sam Yaple's avatar
    Sam Yaple committed
    
        dest = module.params.get('dest')
    
        try:
            if dest:
                copy_to_host(module)
            else:
                copy_from_host(module)
    
        except Exception:
            module.exit_json(failed=True, changed=True,
                             msg=repr(traceback.format_exc()))
    
    Sam Yaple's avatar
    Sam Yaple committed
    
    
    Sam Yaple's avatar
    Sam Yaple committed
    if __name__ == '__main__':
        main()