aboutsummaryrefslogtreecommitdiffstats
path: root/ansible/library
diff options
context:
space:
mode:
authorRoss Brattain <ross.b.brattain@intel.com>2017-08-11 17:47:29 +0000
committerGerrit Code Review <gerrit@opnfv.org>2017-08-11 17:47:29 +0000
commit0949884ab431a16dc1841118e13c0b893fa11b85 (patch)
treeba8588f8fbaf0a17179b339f5512d949386a77a7 /ansible/library
parent43bf12d6ab7bcaea16dc75ed4ccbe3895cf51da3 (diff)
parentc7c51d5100e8eba93337c34bd9eb101ec4cf70df (diff)
Merge "yardstick setup ansible, including load_images"
Diffstat (limited to 'ansible/library')
-rw-r--r--ansible/library/fetch_url_and_verify.py80
-rw-r--r--ansible/library/my_make.py138
-rw-r--r--ansible/library/my_os_networks_facts.py144
-rw-r--r--ansible/library/my_os_router_facts.py113
-rw-r--r--ansible/library/os_images_facts.py166
-rw-r--r--ansible/library/os_router_facts.py113
-rw-r--r--ansible/library/os_stack_facts.py83
-rw-r--r--ansible/library/parse_shell_file.py49
-rwxr-xr-xansible/library/parted.py705
-rw-r--r--ansible/library/write_string.py51
10 files changed, 1642 insertions, 0 deletions
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)
+
+
+# <<INCLUDE_ANSIBLE_MODULE_COMMON>>
+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 <linus@folkdatorn.se>
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+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) <linus@folkdatorn.se>
+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 <http://www.gnu.org/licenses/>.
+
+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 <http://www.gnu.org/licenses/>.
+
+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 <http://www.gnu.org/licenses/>.
+
+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 <http://www.gnu.org/licenses/>.
+
+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 <http://www.gnu.org/licenses/>.
+
+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})
+
+
+# <<INCLUDE_ANSIBLE_MODULE_COMMON>>
+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 <colofabrix@tin.it>
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+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:
+ # "<cylinder>,<head>,<sector>" 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: "<number>[<unit>]"
+ 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 <unit> command
+ 'unit': {
+ 'default': 'KiB',
+ 'choices': parted_units,
+ 'type': 'str'
+ },
+
+ # mklabel <label-type> command
+ 'label': {
+ 'choices': [
+ 'aix', 'amiga', 'bsd', 'dvh', 'gpt', 'loop', 'mac', 'msdos',
+ 'pc98', 'sun', ''
+ ],
+ 'type': 'str'
+ },
+
+ # mkpart <part-type> [<fs-type>] <start> <end> command
+ 'part_type': {
+ 'default': 'primary',
+ 'choices': ['primary', 'extended', 'logical'],
+ 'type': 'str'
+ },
+ 'part_start': {'default': '0%', 'type': 'str'},
+ 'part_end': {'default': '100%', 'type': 'str'},
+
+ # name <partition> <name> command
+ 'name': {'type': 'str'},
+
+ # set <partition> <flag> <state> 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)
+
+
+# <<INCLUDE_ANSIBLE_MODULE_COMMON>>
+from ansible.module_utils.basic import * # noqa
+
+if __name__ == '__main__':
+ main()