From c7c51d5100e8eba93337c34bd9eb101ec4cf70df Mon Sep 17 00:00:00 2001 From: Malanik Jan Date: Tue, 18 Jul 2017 12:54:10 -0400 Subject: yardstick setup ansible, including load_images also update to cirros 0.3.5 added PROX compilation add create_node_pod_yaml role JIRA: YARDSTICK-639 Change-Id: If5999841287a54c7e5c64a7cc487c6394df90424 Signed-off-by: Malanik Jan Signed-off-by: Ross Brattain --- ansible/library/fetch_url_and_verify.py | 80 ++++ ansible/library/my_make.py | 138 +++++++ ansible/library/my_os_networks_facts.py | 144 +++++++ ansible/library/my_os_router_facts.py | 113 +++++ ansible/library/os_images_facts.py | 166 ++++++++ ansible/library/os_router_facts.py | 113 +++++ ansible/library/os_stack_facts.py | 83 ++++ ansible/library/parse_shell_file.py | 49 +++ ansible/library/parted.py | 705 ++++++++++++++++++++++++++++++++ ansible/library/write_string.py | 51 +++ 10 files changed, 1642 insertions(+) create mode 100644 ansible/library/fetch_url_and_verify.py create mode 100644 ansible/library/my_make.py create mode 100644 ansible/library/my_os_networks_facts.py create mode 100644 ansible/library/my_os_router_facts.py create mode 100644 ansible/library/os_images_facts.py create mode 100644 ansible/library/os_router_facts.py create mode 100644 ansible/library/os_stack_facts.py create mode 100644 ansible/library/parse_shell_file.py create mode 100755 ansible/library/parted.py create mode 100644 ansible/library/write_string.py (limited to 'ansible/library') diff --git a/ansible/library/fetch_url_and_verify.py b/ansible/library/fetch_url_and_verify.py new file mode 100644 index 000000000..6c5c0a8c2 --- /dev/null +++ b/ansible/library/fetch_url_and_verify.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python +# Copyright (c) 2017 Intel Corporation +# +# 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. + +DOCUMENTATION = ''' +--- +module: fetch_url_and_verify +short_description: Fetch image and verify against a SHA256SUM URL +description: + - Download a URL and check it against a remote SHA256SUMS file +options: + url: Image URL + image_dest: Image filename + sha256_url: SHA256SUMS URL + dest: python file mode (w, wb, a, ab) + retries: fetch retries +''' + + +def main(): + module = AnsibleModule( + argument_spec={ + 'url': {'required': True, 'type': 'str'}, + 'sha256url': {'required': True, 'type': 'str'}, + 'dest': {'required': True, 'type': 'path'}, + 'retries': {'required': False, 'type': 'int', 'default': 3}, + } + ) + params = module.params + url = params['url'] + dest = params['dest'] + sha256url = params['sha256url'] + retries = params['retries'] + + image_dir, image_filename = os.path.split(dest) + rc, stdout, stderr = module.run_command(['curl', '-sS', sha256url]) + if rc == 0 and stdout: + sha256line = next( + (l for l in stdout.splitlines() if image_filename in l), "") + if not sha256line: + module.fail_json( + msg="Unable to find SHA256SUMS line for file {}".format( + image_filename)) + rc = \ + module.run_command(['sha256sum', '-c'], data=sha256line, cwd=image_dir)[0] + if rc == 0: + sha256sum = sha256line.split()[0] + module.exit_json(changed=False, dest=dest, url=url, + sha256sum=sha256sum) + + for retry in range(retries): + curl_rc, stdout, stderr = module.run_command( + ['curl', '-sS', '-o', dest, url], cwd=image_dir) + if curl_rc == 0: + sha256_rc, stdout, stderr = module.run_command(['sha256sum', '-c'], + data=sha256line, + cwd=image_dir) + if sha256_rc == 0: + module.exit_json(changed=True) + + module.fail_json(msg="Unable to download {}".format(url), stdout=stdout, + stderr=stderr) + + +# <> +from ansible.module_utils.basic import * # noqa + +if __name__ == '__main__': + main() diff --git a/ansible/library/my_make.py b/ansible/library/my_make.py new file mode 100644 index 000000000..a88053bcc --- /dev/null +++ b/ansible/library/my_make.py @@ -0,0 +1,138 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2015, Linus Unnebäck +# +# This file is part of Ansible +# +# This module is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This software is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this software. If not, see . + +from __future__ import absolute_import +DOCUMENTATION = ''' +--- +module: my_make +short_description: Run targets in a Makefile +requirements: [ make ] +version_added: "2.1" +author: Linus Unnebäck (@LinusU) +description: + - Run targets in a Makefile. +options: + target: + description: + - The target to run + required: false + default: none + params: + description: + - Any extra parameters to pass to make + required: false + default: none + extra_args: + description: + - Any extra options to pass to make + required: false + default: none + chdir: + description: + - cd into this directory before running make + required: true +''' + +EXAMPLES = ''' +# Build the default target +- make: chdir=/home/ubuntu/cool-project + +# Run `install` target as root +- make: chdir=/home/ubuntu/cool-project target=install + become: yes + +# Pass in extra arguments to build +- make: + chdir: /home/ubuntu/cool-project + target: all + params: + NUM_THREADS: 4 + BACKEND: lapack +''' + +# TODO: Disabled the RETURN as it was breaking docs building. Someone needs to +# fix this +RETURN = '''# ''' + + +def format_params(params): + return [k + '=' + str(v) for k, v in params.items()] + + +def push_arguments(cmd, args): + if args['extra_args'] is not None: + cmd.extend(shlex.split(args['extra_args'])) + if args['target'] is not None: + cmd.append(args['target']) + if args['params'] is not None: + cmd.extend(format_params(args['params'])) + return cmd + + +def check_changed(make_path, module, args): + cmd = push_arguments([make_path, '--question'], args) + rc, _, __ = module.run_command(cmd, check_rc=False, cwd=args['chdir']) + return rc != 0 + + +def run_make(make_path, module, args): + cmd = push_arguments([make_path], args) + module.run_command(cmd, check_rc=True, cwd=args['chdir']) + + +def main(): + module = AnsibleModule( + supports_check_mode=True, + argument_spec=dict( + target=dict(required=False, default=None, type='str'), + params=dict(required=False, default=None, type='dict'), + extra_args=dict(required=False, default=None, type='str'), + chdir=dict(required=True, default=None, type='str'), + ), + ) + args = dict( + changed=False, + failed=False, + target=module.params['target'], + params=module.params['params'], + extra_args=module.params['extra_args'], + chdir=module.params['chdir'], + ) + make_path = module.get_bin_path('make', True) + + # Check if target is up to date + args['changed'] = check_changed(make_path, module, args) + + # Check only; don't modify + if module.check_mode: + module.exit_json(changed=args['changed']) + + # Target is already up to date + if not args['changed']: + module.exit_json(**args) + + run_make(make_path, module, args) + module.exit_json(**args) + +from ansible.module_utils.basic import * + +if __name__ == '__main__': + main() + diff --git a/ansible/library/my_os_networks_facts.py b/ansible/library/my_os_networks_facts.py new file mode 100644 index 000000000..1b6ad80f9 --- /dev/null +++ b/ansible/library/my_os_networks_facts.py @@ -0,0 +1,144 @@ +#!/usr/bin/python + +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. +# +# This module is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This software is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this software. If not, see . + +try: + import shade + HAS_SHADE = True +except ImportError: + HAS_SHADE = False + +DOCUMENTATION = ''' +--- +module: my_os_network_facts +short_description: Retrieve facts about one or more OpenStack networks. +version_added: "2.0" +author: "Davide Agnello (@dagnello)" +description: + - Retrieve facts about one or more networks from OpenStack. +requirements: + - "python >= 2.6" + - "shade" +options: + network: + description: + - Name or ID of the Network + required: false + filters: + description: + - A dictionary of meta data to use for further filtering. Elements of + this dictionary may be additional dictionaries. + required: false +extends_documentation_fragment: openstack +''' + +EXAMPLES = ''' +# Gather facts about previously created networks +- my_os_network_facts: + auth: + auth_url: https://your_api_url.com:9000/v2.0 + username: user + password: password + project_name: someproject +- debug: var=openstack_networks + +# Gather facts about a previously created network by name +- my_os_network_facts: + auth: + auth_url: https://your_api_url.com:9000/v2.0 + username: user + password: password + project_name: someproject + name: network1 +- debug: var=openstack_networks + +# Gather facts about a previously created network with filter (note: name and + filters parameters are Not mutually exclusive) +- my_os_network_facts: + auth: + auth_url: https://your_api_url.com:9000/v2.0 + username: user + password: password + project_name: someproject + filters: + tenant_id: 55e2ce24b2a245b09f181bf025724cbe + subnets: + - 057d4bdf-6d4d-4728-bb0f-5ac45a6f7400 + - 443d4dc0-91d4-4998-b21c-357d10433483 +- debug: var=openstack_networks +''' + +RETURN = ''' +openstack_networks: + description: has all the openstack facts about the networks + returned: always, but can be null + type: complex + contains: + id: + description: Unique UUID. + returned: success + type: string + name: + description: Name given to the network. + returned: success + type: string + status: + description: Network status. + returned: success + type: string + subnets: + description: Subnet(s) included in this network. + returned: success + type: list of strings + tenant_id: + description: Tenant id associated with this network. + returned: success + type: string + shared: + description: Network shared flag. + returned: success + type: boolean +''' + +def main(): + + argument_spec = openstack_full_argument_spec( + network={'required': False, 'default': None}, + filters={'required': False, 'default': None} + ) + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec) + + if not HAS_SHADE: + module.fail_json(msg='shade is required for this module') + + network = module.params.pop('network') + filters = module.params.pop('filters') + + try: + cloud = shade.openstack_cloud(**module.params) + networks = cloud.search_networks(network, filters) + module.exit_json(changed=False, ansible_facts={ + 'openstack_networks': networks}) + + except shade.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + +# this is magic, see lib/ansible/module_common.py +from ansible.module_utils.basic import * +from ansible.module_utils.openstack import * +if __name__ == '__main__': + main() diff --git a/ansible/library/my_os_router_facts.py b/ansible/library/my_os_router_facts.py new file mode 100644 index 000000000..ce8d2af25 --- /dev/null +++ b/ansible/library/my_os_router_facts.py @@ -0,0 +1,113 @@ +#!/usr/bin/python + +# Copyright (c) 2016 IBM +# +# This module is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This software is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this software. If not, see . + +try: + import shade + HAS_SHADE = True +except ImportError: + HAS_SHADE = False + +DOCUMENTATION = ''' +module: my_os_router_facts +short_description: Retrieve facts about routers within OpenStack. +version_added: "2.1" +author: "Originally: David Shrewsbury (@Shrews); modified" +description: + - Retrieve facts about routers from OpenStack. +notes: + - Facts are placed in the C(openstack_routers) variable. +requirements: + - "python >= 2.6" + - "shade" +options: + port: + description: + - Unique name or ID of a port. + required: false + default: null + filters: + description: + - A dictionary of meta data to use for further filtering. Elements + of this dictionary will be matched against the returned port + dictionaries. Matching is currently limited to strings within + the port dictionary, or strings within nested dictionaries. + required: false + default: null +extends_documentation_fragment: openstack +''' + +EXAMPLES = ''' +# Gather facts about all routers +- my_os_router_facts: + cloud: mycloud + +# Gather facts about a single port +- my_os_router_facts: + cloud: mycloud + port: 6140317d-e676-31e1-8a4a-b1913814a471 + +# Gather facts about all routers that have device_id set to a specific value +# and with a status of ACTIVE. +- my_os_router_facts: + cloud: mycloud + router: + description: + - Name or ID of the router + required: false + filters: + device_id: 1038a010-3a37-4a9d-82ea-652f1da36597 + status: ACTIVE +''' + +RETURN = ''' +openstack_routers: + description: List of port dictionaries. A subset of the dictionary keys + listed below may be returned, depending on your cloud provider. + returned: always, but can be null + type: complex + contains: +''' + + +def main(): + argument_spec = openstack_full_argument_spec( + router={'required': False, 'default': None}, + filters={'required': False, 'type': 'dict', 'default': None}, + ) + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, **module_kwargs) + + if not HAS_SHADE: + module.fail_json(msg='shade is required for this module') + + name = module.params.pop('name') + filters = module.params.pop('filters') + + try: + cloud = shade.openstack_cloud(**module.params) + routers = cloud.search_routers(name, filters) + module.exit_json(changed=False, ansible_facts=dict( + openstack_routers=routers)) + + except shade.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + +from ansible.module_utils.basic import * +from ansible.module_utils.openstack import * + +if __name__ == '__main__': + main() diff --git a/ansible/library/os_images_facts.py b/ansible/library/os_images_facts.py new file mode 100644 index 000000000..736403893 --- /dev/null +++ b/ansible/library/os_images_facts.py @@ -0,0 +1,166 @@ +#!/usr/bin/python + +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. +# +# This module is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This software is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this software. If not, see . + +try: + import shade + HAS_SHADE = True +except ImportError: + HAS_SHADE = False + +DOCUMENTATION = ''' +module: os_images_facts +short_description: Retrieve facts about an image within OpenStack. +version_added: "2.0" +author: "Originally: Davide Agnello (@dagnello); modified" +description: + - Retrieve facts about a image image from OpenStack. +notes: + - Facts are placed in the C(openstack) variable. +requirements: + - "python >= 2.6" + - "shade" +options: + image: + description: + - Name or ID of the image + required: false + filters: + description: + - A dictionary of meta data to use for further filtering. Elements of + this dictionary may be additional dictionaries. + required: false +extends_documentation_fragment: openstack +''' + +EXAMPLES = ''' +# Gather facts about a previously created image named image1 +- os_images_facts: + auth: + auth_url: https://your_api_url.com:9000/v2.0 + username: user + password: password + project_name: someproject + image: image1 +- debug: var=openstack +''' + +RETURN = ''' +openstack_image: + description: has all the openstack facts about the image + returned: always, but can be null + type: complex + contains: + id: + description: Unique UUID. + returned: success + type: string + name: + description: Name given to the image. + returned: success + type: string + status: + description: Image status. + returned: success + type: string + created_at: + description: Image created at timestamp. + returned: success + type: string + deleted: + description: Image deleted flag. + returned: success + type: boolean + container_format: + description: Container format of the image. + returned: success + type: string + min_ram: + description: Min amount of RAM required for this image. + returned: success + type: int + disk_format: + description: Disk format of the image. + returned: success + type: string + updated_at: + description: Image updated at timestamp. + returned: success + type: string + properties: + description: Additional properties associated with the image. + returned: success + type: dict + min_disk: + description: Min amount of disk space required for this image. + returned: success + type: int + protected: + description: Image protected flag. + returned: success + type: boolean + checksum: + description: Checksum for the image. + returned: success + type: string + owner: + description: Owner for the image. + returned: success + type: string + is_public: + description: Is public flag of the image. + returned: success + type: boolean + deleted_at: + description: Image deleted at timestamp. + returned: success + type: string + size: + description: Size of the image. + returned: success + type: int +''' + + +def main(): + + argument_spec = openstack_full_argument_spec( + image={'required': False, 'default': None}, + filters={'required': False, 'default': None}, + ) + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, **module_kwargs) + + if not HAS_SHADE: + module.fail_json(msg='shade is required for this module') + + image = module.params.pop('image') + filters = module.params.pop('filters') + + try: + cloud = shade.openstack_cloud(**module.params) + images = cloud.search_images(image, filters) + module.exit_json(changed=False, ansible_facts={ + 'openstack_images': images}) + + except shade.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + +# this is magic, see lib/ansible/module_common.py +from ansible.module_utils.basic import * +from ansible.module_utils.openstack import * +if __name__ == '__main__': + main() diff --git a/ansible/library/os_router_facts.py b/ansible/library/os_router_facts.py new file mode 100644 index 000000000..b14a362ef --- /dev/null +++ b/ansible/library/os_router_facts.py @@ -0,0 +1,113 @@ +#!/usr/bin/python + +# Copyright (c) 2016 IBM +# +# This module is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This software is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this software. If not, see . + +try: + import shade + HAS_SHADE = True +except ImportError: + HAS_SHADE = False + +DOCUMENTATION = ''' +module: os_router_facts +short_description: Retrieve facts about routers within OpenStack. +version_added: "2.1" +author: "Originally: David Shrewsbury (@Shrews); modified" +description: + - Retrieve facts about routers from OpenStack. +notes: + - Facts are placed in the C(openstack_routers) variable. +requirements: + - "python >= 2.6" + - "shade" +options: + port: + description: + - Unique name or ID of a port. + required: false + default: null + filters: + description: + - A dictionary of meta data to use for further filtering. Elements + of this dictionary will be matched against the returned port + dictionaries. Matching is currently limited to strings within + the port dictionary, or strings within nested dictionaries. + required: false + default: null +extends_documentation_fragment: openstack +''' + +EXAMPLES = ''' +# Gather facts about all routers +- os_router_facts: + cloud: mycloud + +# Gather facts about a single port +- os_router_facts: + cloud: mycloud + port: 6140317d-e676-31e1-8a4a-b1913814a471 + +# Gather facts about all routers that have device_id set to a specific value +# and with a status of ACTIVE. +- os_router_facts: + cloud: mycloud + router: + description: + - Name or ID of the router + required: false + filters: + device_id: 1038a010-3a37-4a9d-82ea-652f1da36597 + status: ACTIVE +''' + +RETURN = ''' +openstack_routers: + description: List of port dictionaries. A subset of the dictionary keys + listed below may be returned, depending on your cloud provider. + returned: always, but can be null + type: complex + contains: +''' + + +def main(): + argument_spec = openstack_full_argument_spec( + router={'required': False, 'default': None}, + filters={'required': False, 'type': 'dict', 'default': None}, + ) + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, **module_kwargs) + + if not HAS_SHADE: + module.fail_json(msg='shade is required for this module') + + router = module.params.pop('router') + filters = module.params.pop('filters') + + try: + cloud = shade.openstack_cloud(**module.params) + routers = cloud.search_routers(router, filters) + module.exit_json(changed=False, ansible_facts=dict( + openstack_routers=routers)) + + except shade.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + +from ansible.module_utils.basic import * +from ansible.module_utils.openstack import * + +if __name__ == '__main__': + main() diff --git a/ansible/library/os_stack_facts.py b/ansible/library/os_stack_facts.py new file mode 100644 index 000000000..c67947686 --- /dev/null +++ b/ansible/library/os_stack_facts.py @@ -0,0 +1,83 @@ +#!/usr/bin/python + +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. +# +# This module is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This software is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this software. If not, see . + +try: + import shade + HAS_SHADE = True +except ImportError: + HAS_SHADE = False + +DOCUMENTATION = ''' +module: os_stack_facts +short_description: Retrieve facts about an stack within OpenStack. +version_added: "2.0" +author: "Originally: Davide Agnello (@dagnello); modified" +description: + - Retrieve facts about a stack from OpenStack. +notes: + - Facts are placed in the C(openstack) variable. +requirements: + - "python >= 2.6" + - "shade" +options: +extends_documentation_fragment: openstack +''' + +EXAMPLES = ''' +# Gather facts about a previously created stack named stack1 +- os_stack_facts: + auth: + auth_url: https://your_api_url.com:9000/v2.0 + username: user + password: password + project_name: someproject +- debug: var=openstack_stacks +''' + +RETURN = ''' +openstack_stack: + description: has all the openstack facts about the stack + returned: always, but can be null + type: complex +''' + + +def main(): + + argument_spec = openstack_full_argument_spec( + ) + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, **module_kwargs) + + if not HAS_SHADE: + module.fail_json(msg='shade is required for this module') + + + try: + cloud = shade.openstack_cloud(**module.params) + stacks = cloud.list_stacks() + module.exit_json(changed=False, ansible_facts={ + 'openstack_stacks': stacks}) + + except shade.OpenStackCloudException as e: + module.fail_json(msg=str(e)) + +# this is magic, see lib/ansible/module_common.py +from ansible.module_utils.basic import * +from ansible.module_utils.openstack import * +if __name__ == '__main__': + main() diff --git a/ansible/library/parse_shell_file.py b/ansible/library/parse_shell_file.py new file mode 100644 index 000000000..d238d108f --- /dev/null +++ b/ansible/library/parse_shell_file.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python +# Copyright (c) 2017 Intel Corporation +# +# 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. + +DOCUMENTATION = ''' +--- +module: write_string +short_description: write a string to a file +description: + - write a string to a file without using temp files +options: + path: path to write to + val: string to write + mode: python file mode (w, wb, a, ab) +''' + + +def main(): + module = AnsibleModule( + argument_spec={ + 'path': {'required': True, 'type': 'path', 'aliases': ['dest']}, + 'fact_name': {'required': True}, + } + ) + params = module.params + path = params['path'] + fact_name = params['fact_name'] + with open(path) as file_object: + script = file_object.read() + variables = dict(l.split('=') for l in shlex.split(script) if '=' in l) + module.exit_json(changed=True, ansible_facts={fact_name: variables}) + + +# <> +from ansible.module_utils.basic import * # noqa + +if __name__ == '__main__': + main() diff --git a/ansible/library/parted.py b/ansible/library/parted.py new file mode 100755 index 000000000..af9c80f7e --- /dev/null +++ b/ansible/library/parted.py @@ -0,0 +1,705 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2016, Fabrizio Colonna +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +ANSIBLE_METADATA = {'metadata_version': '1.0', + 'status': ['preview'], + 'supported_by': 'curated'} + + +DOCUMENTATION = ''' +--- +author: + - "Fabrizio Colonna (@ColOfAbRiX)" +module: parted +short_description: Configure block device partitions +version_added: "2.3" +description: + - This module allows configuring block device partition using the C(parted) + command line tool. For a full description of the fields and the options + check the GNU parted manual. +notes: + - When fetching information about a new disk and when the version of parted + installed on the system is before version 3.1, the module queries the kernel + through C(/sys/) to obtain disk information. In this case the units CHS and + CYL are not supported. +requirements: + - This module requires parted version 1.8.3 and above. + - If the version of parted is below 3.1, it requires a Linux version running + the sysfs file system C(/sys/). +options: + device: + description: The block device (disk) where to operate. + required: True + align: + description: Set alignment for newly created partitions. + choices: ['none', 'cylinder', 'minimal', 'optimal'] + default: optimal + number: + description: + - The number of the partition to work with or the number of the partition + that will be created. Required when performing any action on the disk, + except fetching information. + unit: + description: + - Selects the current default unit that Parted will use to display + locations and capacities on the disk and to interpret those given by the + user if they are not suffixed by an unit. When fetching information about + a disk, it is always recommended to specify a unit. + choices: [ + 's', 'B', 'KB', 'KiB', 'MB', 'MiB', 'GB', 'GiB', 'TB', 'TiB', '%', 'cyl', + 'chs', 'compact' + ] + default: KiB + label: + description: Creates a new disk label. + choices: [ + 'aix', 'amiga', 'bsd', 'dvh', 'gpt', 'loop', 'mac', 'msdos', 'pc98', + 'sun', '' + ] + default: msdos + part_type: + description: + - Is one of 'primary', 'extended' or 'logical' and may be specified only + with 'msdos' or 'dvh' partition tables. A name must be specified for a + 'gpt' partition table. Neither part-type nor name may be used with a + 'sun' partition table. + choices: ['primary', 'extended', 'logical'] + part_start: + description: + - Where the partition will start as offset from the beginning of the disk, + that is, the "distance" from the start of the disk. The distance can be + specified with all the units supported by parted (except compat) and + it is case sensitive. E.g. C(10GiB), C(15%). + default: 0% + part_end : + description: + - Where the partition will end as offset from the beginning of the disk, + that is, the "distance" from the start of the disk. The distance can be + specified with all the units supported by parted (except compat) and + it is case sensitive. E.g. C(10GiB), C(15%). + default: 100% + name: + description: + - Sets the name for the partition number (GPT, Mac, MIPS and PC98 only). + flags: + description: A list of the flags that has to be set on the partition. + state: + description: + - If to create or delete a partition. If set to C(info) the module will + only return the device information. + choices: ['present', 'absent', 'info'] + default: info +''' + +RETURN = ''' +partition_info: + description: Current partition information + returned: success + type: dict + contains: + device: + description: Generic device information. + type: dict + partitions: + description: List of device partitions. + type: list + sample: > + { + "disk": { + "dev": "/dev/sdb", + "logical_block": 512, + "model": "VMware Virtual disk", + "physical_block": 512, + "size": 5.0, + "table": "msdos", + "unit": "gib" + }, + "partitions": [{ + "begin": 0.0, + "end": 1.0, + "flags": ["boot", "lvm"], + "fstype": null, + "num": 1, + "size": 1.0 + }, { + "begin": 1.0, + "end": 5.0, + "flags": [], + "fstype": null, + "num": 2, + "size": 4.0 + }] + } +''' + +EXAMPLES = """ +# Create a new primary partition +- parted: + device: /dev/sdb + number: 1 + state: present + +# Remove partition number 1 +- parted: + device: /dev/sdb + number: 1 + state: absent + +# Create a new primary partition with a size of 1GiB +- parted: + device: /dev/sdb + number: 1 + state: present + part_end: 1gib + +# Create a new primary partition for LVM +- parted: + device: /dev/sdb + number: 2 + flags: [ lvm ] + state: present + part_start: 1gib + +# Read device information (always use unit when probing) +- parted: device=/dev/sdb unit=MiB + register: sdb_info + +# Remove all partitions from disk +- parted: + device: /dev/sdb + number: "{{ item.num }}" + state: absent + with_items: + - "{{ sdb_info.partitions }}" +""" + + +from ansible.module_utils.basic import AnsibleModule +import locale +import math +import re +import os + + +# Reference prefixes (International System of Units and IEC) +units_si = ['B', 'KB', 'MB', 'GB', 'TB'] +units_iec = ['B', 'KiB', 'MiB', 'GiB', 'TiB'] +parted_units = units_si + units_iec + ['s', '%', 'cyl', 'chs', 'compact'] + + +def parse_unit(size_str, unit=''): + """ + Parses a string containing a size of information + """ + matches = re.search(r'^([\d.]+)([\w%]+)?$', size_str) + if matches is None: + # ",," format + matches = re.search(r'^(\d+),(\d+),(\d+)$', size_str) + if matches is None: + module.fail_json( + msg="Error interpreting parted size output: '%s'" % size_str + ) + + size = { + 'cylinder': int(matches.group(1)), + 'head': int(matches.group(2)), + 'sector': int(matches.group(3)) + } + unit = 'chs' + + else: + # Normal format: "[]" + if matches.group(2) is not None: + unit = matches.group(2) + + size = float(matches.group(1)) + + return size, unit + + +def parse_partition_info(parted_output, unit): + """ + Parses the output of parted and transforms the data into + a dictionary. + + Parted Machine Parseable Output: + See: https://lists.alioth.debian.org/pipermail/parted-devel/2006-December/00 + 0573.html + - All lines end with a semicolon (;) + - The first line indicates the units in which the output is expressed. + CHS, CYL and BYT stands for CHS, Cylinder and Bytes respectively. + - The second line is made of disk information in the following format: + "path":"size":"transport-type":"logical-sector-size":"physical-sector-siz + e":"partition-table-type":"model-name"; + - If the first line was either CYL or CHS, the next line will contain + information on no. of cylinders, heads, sectors and cylinder size. + - Partition information begins from the next line. This is of the format: + (for BYT) + "number":"begin":"end":"size":"filesystem-type":"partition-name":"flags-s + et"; + (for CHS/CYL) + "number":"begin":"end":"filesystem-type":"partition-name":"flags-set"; + """ + lines = [x for x in parted_output.split('\n') if x.strip() != ''] + + # Generic device info + generic_params = lines[1].rstrip(';').split(':') + + # The unit is read once, because parted always returns the same unit + size, unit = parse_unit(generic_params[1], unit) + + generic = { + 'dev': generic_params[0], + 'size': size, + 'unit': unit.lower(), + 'table': generic_params[5], + 'model': generic_params[6], + 'logical_block': int(generic_params[3]), + 'physical_block': int(generic_params[4]) + } + + # CYL and CHS have an additional line in the output + if unit in ['cyl', 'chs']: + chs_info = lines[2].rstrip(';').split(':') + cyl_size, cyl_unit = parse_unit(chs_info[3]) + generic['chs_info'] = { + 'cylinders': int(chs_info[0]), + 'heads': int(chs_info[1]), + 'sectors': int(chs_info[2]), + 'cyl_size': cyl_size, + 'cyl_size_unit': cyl_unit.lower() + } + lines = lines[1:] + + parts = [] + for line in lines[2:]: + part_params = line.rstrip(';').split(':') + + # CHS use a different format than BYT, but contrary to what stated by + # the author, CYL is the same as BYT. I've tested this undocumented + # behaviour down to parted version 1.8.3, which is the first version + # that supports the machine parseable output. + if unit != 'chs': + size = parse_unit(part_params[3])[0] + fstype = part_params[4] + flags = part_params[5] + else: + size = "" + fstype = part_params[3] + flags = part_params[4] + + parts.append({ + 'num': int(part_params[0]), + 'begin': parse_unit(part_params[1])[0], + 'end': parse_unit(part_params[2])[0], + 'size': size, + 'fstype': fstype, + 'flags': [f.strip() for f in flags.split(', ') if f != ''], + 'unit': unit.lower(), + }) + + return {'generic': generic, 'partitions': parts} + + +def format_disk_size(size_bytes, unit): + """ + Formats a size in bytes into a different unit, like parted does. It doesn't + manage CYL and CHS formats, though. + This function has been adapted from https://github.com/Distrotech/parted/blo + b/279d9d869ff472c52b9ec2e180d568f0c99e30b0/libparted/unit.c + """ + global units_si, units_iec + + unit = unit.lower() + + # Shortcut + if size_bytes == 0: + return 0.0 + + # Cases where we default to 'compact' + if unit in ['', 'compact', 'cyl', 'chs']: + index = max(0, int( + (math.log10(size_bytes) - 1.0) / 3.0 + )) + unit = 'b' + if index < len(units_si): + unit = units_si[index] + + # Find the appropriate multiplier + multiplier = 1.0 + if unit in units_si: + multiplier = 1000.0 ** units_si.index(unit) + elif unit in units_iec: + multiplier = 1024.0 ** units_iec.index(unit) + + output = size_bytes / multiplier * (1 + 1E-16) + + # Corrections to round up as per IEEE754 standard + if output < 10: + w = output + 0.005 + elif output < 100: + w = output + 0.05 + else: + w = output + 0.5 + + if w < 10: + precision = 2 + elif w < 100: + precision = 1 + else: + precision = 0 + + # Round and return + return round(output, precision), unit + + +def get_unlabeled_device_info(device, unit): + """ + Fetches device information directly from the kernel and it is used when + parted cannot work because of a missing label. It always returns a 'unknown' + label. + """ + device_name = os.path.basename(device) + base = "/sys/block/%s" % device_name + + vendor = read_record(base + "/device/vendor", "Unknown") + model = read_record(base + "/device/model", "model") + logic_block = int(read_record(base + "/queue/logical_block_size", 0)) + phys_block = int(read_record(base + "/queue/physical_block_size", 0)) + size_bytes = int(read_record(base + "/size", 0)) * logic_block + + size, unit = format_disk_size(size_bytes, unit) + + return { + 'generic': { + 'dev': device, + 'table': "unknown", + 'size': size, + 'unit': unit, + 'logical_block': logic_block, + 'physical_block': phys_block, + 'model': "%s %s" % (vendor, model), + }, + 'partitions': [] + } + + +def get_device_info(device, unit): + """ + Fetches information about a disk and its partitions and it returns a + dictionary. + """ + global module + + # If parted complains about missing labels, it means there are no partitions. + # In this case only, use a custom function to fetch information and emulate + # parted formats for the unit. + label_needed = check_parted_label(device) + if label_needed: + return get_unlabeled_device_info(device, unit) + + command = "parted -s -m %s -- unit '%s' print" % (device, unit) + rc, out, err = module.run_command(command) + if rc != 0 and 'unrecognised disk label' not in err: + module.fail_json(msg=( + "Error while getting device information with parted " + "script: '%s'" % command), + rc=rc, out=out, err=err + ) + + return parse_partition_info(out, unit) + + +def check_parted_label(device): + """ + Determines if parted needs a label to complete its duties. Versions prior + to 3.1 don't return data when there is no label. For more information see: + http://upstream.rosalinux.ru/changelogs/libparted/3.1/changelog.html + """ + # Check the version + parted_major, parted_minor, _ = parted_version() + if (parted_major == 3 and parted_minor >= 1) or parted_major > 3: + return False + + # Older parted versions return a message in the stdout and RC > 0. + rc, out, err = module.run_command("parted -s -m %s print" % device) + if rc != 0 and 'unrecognised disk label' in out.lower(): + return True + + return False + + +def parted_version(): + """ + Returns the major and minor version of parted installed on the system. + """ + global module + + rc, out, err = module.run_command("parted --version") + if rc != 0: + module.fail_json( + msg="Failed to get parted version.", rc=rc, out=out, err=err + ) + + lines = [x for x in out.split('\n') if x.strip() != ''] + if len(lines) == 0: + module.fail_json(msg="Failed to get parted version.", rc=0, out=out) + + matches = re.search(r'^parted.+(\d+)\.(\d+)(?:\.(\d+))?$', lines[0]) + if matches is None: + module.fail_json(msg="Failed to get parted version.", rc=0, out=out) + + # Convert version to numbers + major = int(matches.group(1)) + minor = int(matches.group(2)) + rev = 0 + if matches.group(3) is not None: + rev = int(matches.group(3)) + + return major, minor, rev + + +def parted(script, device, align): + """ + Runs a parted script. + """ + global module + + if script and not module.check_mode: + command = "parted -s -m -a %s %s -- %s" % (align, device, script) + rc, out, err = module.run_command(command) + + if rc != 0: + module.fail_json( + msg="Error while running parted script: %s" % command.strip(), + rc=rc, out=out, err=err + ) + + +def read_record(file_path, default=None): + """ + Reads the first line of a file and returns it. + """ + try: + f = open(file_path, 'r') + try: + return f.readline().strip() + finally: + f.close() + except IOError: + return default + + +def part_exists(partitions, attribute, number): + """ + Looks if a partition that has a specific value for a specific attribute + actually exists. + """ + return any( + part[attribute] and + part[attribute] == number for part in partitions + ) + + +def check_size_format(size_str): + """ + Checks if the input string is an allowed size + """ + size, unit = parse_unit(size_str) + return unit in parted_units + + +def main(): + global module, units_si, units_iec + + changed = False + output_script = "" + script = "" + module = AnsibleModule( + argument_spec={ + 'device': {'required': True, 'type': 'str'}, + 'align': { + 'default': 'optimal', + 'choices': ['none', 'cylinder', 'minimal', 'optimal'], + 'type': 'str' + }, + 'number': {'default': None, 'type': 'int'}, + + # unit command + 'unit': { + 'default': 'KiB', + 'choices': parted_units, + 'type': 'str' + }, + + # mklabel command + 'label': { + 'choices': [ + 'aix', 'amiga', 'bsd', 'dvh', 'gpt', 'loop', 'mac', 'msdos', + 'pc98', 'sun', '' + ], + 'type': 'str' + }, + + # mkpart [] command + 'part_type': { + 'default': 'primary', + 'choices': ['primary', 'extended', 'logical'], + 'type': 'str' + }, + 'part_start': {'default': '0%', 'type': 'str'}, + 'part_end': {'default': '100%', 'type': 'str'}, + + # name command + 'name': {'type': 'str'}, + + # set command + 'flags': {'type': 'list'}, + + # rm/mkpart command + 'state': { + 'choices': ['present', 'absent', 'info'], + 'default': 'info', + 'type': 'str' + } + }, + supports_check_mode=True, + ) + + # Data extraction + device = module.params['device'] + align = module.params['align'] + number = module.params['number'] + unit = module.params['unit'] + label = module.params['label'] + part_type = module.params['part_type'] + part_start = module.params['part_start'] + part_end = module.params['part_end'] + name = module.params['name'] + state = module.params['state'] + flags = module.params['flags'] + + # Conditioning + if number and number < 0: + module.fail_json(msg="The partition number must be non negative.") + if not check_size_format(part_start): + module.fail_json( + msg="The argument 'part_start' doesn't respect required format." + "The size unit is case sensitive.", + err=parse_unit(part_start) + ) + if not check_size_format(part_end): + module.fail_json( + msg="The argument 'part_end' doesn't respect required format." + "The size unit is case sensitive.", + err=parse_unit(part_end) + ) + + # Read the current disk information + current_device = get_device_info(device, unit) + current_parts = current_device['partitions'] + + if state == 'present': + # Default value for the label + if not current_device['generic']['table'] or \ + current_device['generic']['table'] == 'unknown' and \ + not label: + label = 'msdos' + + # Assign label if required + if label: + script += "mklabel %s " % label + + # Create partition if required + if part_type and not part_exists(current_parts, 'num', number): + script += "mkpart %s %s %s " % ( + part_type, + part_start, + part_end + ) + + # Set the unit of the run + if unit and script: + script = "unit %s %s" % (unit, script) + + # Execute the script and update the data structure. + # This will create the partition for the next steps + if script: + output_script += script + parted(script, device, align) + changed = True + script = "" + + current_parts = get_device_info(device, unit)['partitions'] + + if part_exists(current_parts, 'num', number) or module.check_mode: + partition = {'flags': []} # Empty structure for the check-mode + if not module.check_mode: + partition = [p for p in current_parts if p['num'] == number][0] + + # Assign name to the the partition + if name: + script += "name %s %s " % (number, name) + + # Manage flags + if flags: + # Compute only the changes in flags status + flags_off = list(set(partition['flags']) - set(flags)) + flags_on = list(set(flags) - set(partition['flags'])) + + for f in flags_on: + script += "set %s %s on " % (number, f) + + for f in flags_off: + script += "set %s %s off " % (number, f) + + # Set the unit of the run + if unit and script: + script = "unit %s %s" % (unit, script) + + # Execute the script + if script: + output_script += script + changed = True + parted(script, device, align) + + elif state == 'absent': + # Remove the partition + if part_exists(current_parts, 'num', number) or module.check_mode: + script = "rm %s " % number + output_script += script + changed = True + parted(script, device, align) + + elif state == 'info': + output_script = "unit '%s' print " % unit + + # Final status of the device + final_device_status = get_device_info(device, unit) + module.exit_json( + changed=changed, + disk=final_device_status['generic'], + partitions=final_device_status['partitions'], + script=output_script.strip() + ) + + +if __name__ == '__main__': + main() diff --git a/ansible/library/write_string.py b/ansible/library/write_string.py new file mode 100644 index 000000000..9db88fdd0 --- /dev/null +++ b/ansible/library/write_string.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python +# Copyright (c) 2017 Intel Corporation +# +# 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. + +DOCUMENTATION = ''' +--- +module: write_string +short_description: write a string to a file +description: + - write a string to a file without using temp files +options: + path: path to write to + val: string to write + mode: python file mode (w, wb, a, ab) +''' + + +def main(): + module = AnsibleModule( + argument_spec={ + 'path': {'required': True, 'type': 'path', 'aliases': ['dest']}, + 'val': {'required': True, 'type': 'str'}, + 'mode': {'required': False, 'default': "w", 'type': 'str', + 'choices': ['w', 'wb', 'a', 'ab']}} + ) + params = module.params + path = params['path'] + mode = params['mode'] + val = params['val'] + with open(path, mode) as file_object: + file_object.write(val) + + module.exit_json(changed=True) + + +# <> +from ansible.module_utils.basic import * # noqa + +if __name__ == '__main__': + main() -- cgit 1.2.3-korg