diff options
Diffstat (limited to 'ansible/library')
| -rw-r--r-- | ansible/library/bslurp.py | 193 | ||||
| -rw-r--r-- | ansible/library/kolla_docker.py | 751 | ||||
| -rw-r--r-- | ansible/library/merge_configs.py | 51 | ||||
| -rw-r--r-- | ansible/library/merge_yaml.py | 51 |
4 files changed, 0 insertions, 1046 deletions
diff --git a/ansible/library/bslurp.py b/ansible/library/bslurp.py deleted file mode 100644 index b117123cd..000000000 --- a/ansible/library/bslurp.py +++ /dev/null @@ -1,193 +0,0 @@ -#!/usr/bin/python - -# Copyright 2015 Sam Yaple -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# This module has been relicensed from the source below: -# https://github.com/SamYaple/yaodu/blob/master/ansible/library/bslurp - -DOCUMENTATION = ''' ---- -module: bslurp -short_description: Slurps a file from a remote node -description: - - Used for fetching a binary blob containing the file, then push that file - to other hosts. -options: - src: - description: - - File to fetch. When dest is used, src is expected to be a str with data - required: True - type: str - compress: - description: - - Compress file with zlib - default: True - type: bool - dest: - description: - - Where to write out binary blob - required: False - type: str - mode: - description: - - Destination file permissions - default: '0644' - type: str - sha1: - description: - - sha1 hash of the underlying data - default: None - type: bool -author: Sam Yaple -''' - -EXAMPLES = ''' -Distribute a file from single to many hosts: - -- hosts: web_servers - tasks: - - name: Pull in web config - bslurp: src="/path/to/file" - register: file_data - run_once: True - - name: Push if changed - bslurp: - src: "{{ file_data.content }}" - dest: "{{ file_data.source }}" - mode: "{{ file_data.mode }}" - sha1: "{{ file_data.sha1 }}" - -Distribute multiple files from single to many hosts: - -- hosts: web_servers - tasks: - - name: Pull in web config - bslurp: src="{{ item }}" - with_items: - - "/path/to/file1" - - "/path/to/file2" - - "/path/to/file3" - register: file_data - run_once: True - - name: Push if changed - bslurp: - src: "{{ item.content }}" - dest: "{{ item.source }}" - mode: "{{ item.mode }}" - sha1: "{{ item.sha1 }}" - with_items: file_data.results - -Distribute a file to many hosts without compression; Change -permissions on dest: - -- hosts: web_servers - tasks: - - name: Pull in web config - bslurp: src="/path/to/file" - register: file_data - run_once: True - - name: Push if changed - bslurp: - src: "{{ file_data.content }}" - dest: "/new/path/to/file" - mode: "0777" - compress: False - sha1: "{{ file_data.sha1 }}" -''' - -import base64 -import hashlib -import os -import traceback -import zlib - - -def copy_from_host(module): - compress = module.params.get('compress') - src = module.params.get('src') - - if not os.path.exists(src): - module.fail_json(msg="file not found: {}".format(src)) - if not os.access(src, os.R_OK): - module.fail_json(msg="file is not readable: {}".format(src)) - - mode = oct(os.stat(src).st_mode & 0o777) - - with open(src, 'rb') as f: - raw_data = f.read() - - sha1 = hashlib.sha1(raw_data).hexdigest() - data = zlib.compress(raw_data) if compress else raw_data - - module.exit_json(content=base64.b64encode(data), sha1=sha1, mode=mode, - source=src) - - -def copy_to_host(module): - compress = module.params.get('compress') - dest = module.params.get('dest') - mode = int(module.params.get('mode'), 0) - sha1 = module.params.get('sha1') - src = module.params.get('src') - - data = base64.b64decode(src) - raw_data = zlib.decompress(data) if compress else data - - if sha1: - if os.path.exists(dest): - if os.access(dest, os.R_OK): - with open(dest, 'rb') as f: - if hashlib.sha1(f.read()).hexdigest() == sha1: - module.exit_json(changed=False) - else: - module.exit_json(failed=True, changed=False, - msg='file is not accessible: {}'.format(dest)) - - if sha1 != hashlib.sha1(raw_data).hexdigest(): - module.exit_json(failed=True, changed=False, - msg='sha1 sum does not match data') - - with os.fdopen(os.open(dest, os.O_WRONLY | os.O_CREAT, mode), 'wb') as f: - f.write(raw_data) - - module.exit_json(changed=True) - - -def main(): - argument_spec = dict( - compress=dict(default=True, type='bool'), - dest=dict(type='str'), - mode=dict(default='0644', type='str'), - sha1=dict(default=None, type='str'), - src=dict(required=True, type='str') - ) - module = AnsibleModule(argument_spec) - - dest = module.params.get('dest') - - try: - if dest: - copy_to_host(module) - else: - copy_from_host(module) - except Exception: - module.exit_json(failed=True, changed=True, - msg=repr(traceback.format_exc())) - - -# import module snippets -from ansible.module_utils.basic import * # noqa -if __name__ == '__main__': - main() diff --git a/ansible/library/kolla_docker.py b/ansible/library/kolla_docker.py deleted file mode 100644 index 28f752a2e..000000000 --- a/ansible/library/kolla_docker.py +++ /dev/null @@ -1,751 +0,0 @@ -#!/usr/bin/python - -# Copyright 2015 Sam Yaple -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -DOCUMENTATION = ''' ---- -module: kolla_docker -short_description: Module for controlling Docker -description: - - A module targeting at controlling Docker as used by Kolla. -options: - common_options: - description: - - A dict containing common params such as login info - required: False - type: dict - default: dict() - action: - description: - - The action the module should take - required: True - type: str - choices: - - compare_image - - create_volume - - get_container_env - - get_container_state - - pull_image - - remove_container - - remove_volume - - restart_container - - start_container - - stop_container - api_version: - description: - - The version of the api for docker-py to use when contacting docker - required: False - type: str - default: auto - auth_email: - description: - - The email address used to authenticate - required: False - type: str - auth_password: - description: - - The password used to authenticate - required: False - type: str - auth_registry: - description: - - The registry to authenticate - required: False - type: str - auth_username: - description: - - The username used to authenticate - required: False - type: str - detach: - description: - - Detach from the container after it is created - required: False - default: True - type: bool - name: - description: - - Name of the container or volume to manage - required: False - type: str - environment: - description: - - The environment to set for the container - required: False - type: dict - image: - description: - - Name of the docker image - required: False - type: str - ipc_mode: - description: - - Set docker ipc namespace - required: False - type: str - default: None - choices: - - host - cap_add: - description: - - Add capabilities to docker container - required: False - type: list - default: list() - security_opt: - description: - - Set container security profile - required: False - type: list - default: list() - labels: - description: - - List of labels to apply to container - required: False - type: dict - default: dict() - pid_mode: - description: - - Set docker pid namespace - required: False - type: str - default: None - choices: - - host - privileged: - description: - - Set the container to privileged - required: False - default: False - type: bool - remove_on_exit: - description: - - When not detaching from container, remove on successful exit - required: False - default: True - type: bool - restart_policy: - description: - - Determine what docker does when the container exits - required: False - type: str - choices: - - never - - on-failure - - always - - unless-stopped - restart_retries: - description: - - How many times to attempt a restart if restart_policy is set - type: int - default: 10 - volumes: - description: - - Set volumes for docker to use - required: False - type: list - volumes_from: - description: - - Name or id of container(s) to use volumes from - required: True - type: list -author: Sam Yaple -''' - -EXAMPLES = ''' -- hosts: kolla_docker - tasks: - - name: Start container - kolla_docker: - image: ubuntu - name: test_container - action: start_container - - name: Remove container - kolla_docker: - name: test_container - action: remove_container - - name: Pull image without starting container - kolla_docker: - action: pull_container - image: private-registry.example.com:5000/ubuntu - - name: Create named volume - action: create_volume - name: name_of_volume - - name: Remove named volume - action: remove_volume - name: name_of_volume -''' - -import json -import os -import traceback - -import docker - - -class DockerWorker(object): - - def __init__(self, module): - self.module = module - self.params = self.module.params - self.changed = False - - # TLS not fully implemented - # tls_config = self.generate_tls() - - options = { - 'version': self.params.get('api_version') - } - - self.dc = docker.Client(**options) - - def generate_tls(self): - tls = {'verify': self.params.get('tls_verify')} - tls_cert = self.params.get('tls_cert'), - tls_key = self.params.get('tls_key'), - tls_cacert = self.params.get('tls_cacert') - - if tls['verify']: - if tls_cert: - self.check_file(tls_cert) - self.check_file(tls_key) - tls['client_cert'] = (tls_cert, tls_key) - if tls_cacert: - self.check_file(tls_cacert) - tls['verify'] = tls_cacert - - return docker.tls.TLSConfig(**tls) - - def check_file(self, path): - if not os.path.isfile(path): - self.module.fail_json( - failed=True, - msg='There is no file at "{}"'.format(path) - ) - if not os.access(path, os.R_OK): - self.module.fail_json( - failed=True, - msg='Permission denied for file at "{}"'.format(path) - ) - - def check_image(self): - find_image = ':'.join(self.parse_image()) - for image in self.dc.images(): - repo_tags = image.get('RepoTags') - if not repo_tags: - continue - for image_name in repo_tags: - if image_name == find_image: - return image - - def check_volume(self): - for vol in self.dc.volumes()['Volumes'] or list(): - if vol['Name'] == self.params.get('name'): - return vol - - def check_container(self): - find_name = '/{}'.format(self.params.get('name')) - for cont in self.dc.containers(all=True): - if find_name in cont['Names']: - return cont - - def get_container_info(self): - container = self.check_container() - if not container: - return None - return self.dc.inspect_container(self.params.get('name')) - - def check_container_differs(self): - container_info = self.get_container_info() - return ( - self.compare_cap_add(container_info) or - self.compare_security_opt(container_info) or - self.compare_image(container_info) or - self.compare_ipc_mode(container_info) or - self.compare_labels(container_info) or - self.compare_privileged(container_info) or - self.compare_pid_mode(container_info) or - self.compare_volumes(container_info) or - self.compare_volumes_from(container_info) or - self.compare_environment(container_info) - ) - - def compare_ipc_mode(self, container_info): - new_ipc_mode = self.params.get('ipc_mode') - current_ipc_mode = container_info['HostConfig'].get('IpcMode') - if not current_ipc_mode: - current_ipc_mode = None - - if new_ipc_mode != current_ipc_mode: - return True - - def compare_cap_add(self, container_info): - new_cap_add = self.params.get('cap_add', list()) - current_cap_add = container_info['HostConfig'].get('CapAdd', - list()) - if not current_cap_add: - current_cap_add = list() - if set(new_cap_add).symmetric_difference(set(current_cap_add)): - return True - - def compare_security_opt(self, container_info): - new_sec_opt = self.params.get('security_opt', list()) - current_sec_opt = container_info['HostConfig'].get('SecurityOpt', - list()) - if not current_sec_opt: - current_sec_opt = list() - if set(new_sec_opt).symmetric_difference(set(current_sec_opt)): - return True - - def compare_pid_mode(self, container_info): - new_pid_mode = self.params.get('pid_mode') - current_pid_mode = container_info['HostConfig'].get('PidMode') - if not current_pid_mode: - current_pid_mode = None - - if new_pid_mode != current_pid_mode: - return True - - def compare_privileged(self, container_info): - new_privileged = self.params.get('privileged') - current_privileged = container_info['HostConfig']['Privileged'] - if new_privileged != current_privileged: - return True - - def compare_image(self, container_info=None): - container_info = container_info or self.get_container_info() - parse_repository_tag = docker.utils.parse_repository_tag - if not container_info: - return True - new_image = self.check_image() - current_image = container_info['Image'] - if not new_image: - return True - if new_image['Id'] != current_image: - return True - # NOTE(Jeffrey4l) when new image and the current image have - # the same id, but the tag name different. - elif (parse_repository_tag(container_info['Config']['Image']) != - parse_repository_tag(self.params.get('image'))): - return True - - def compare_labels(self, container_info): - new_labels = self.params.get('labels') - current_labels = container_info['Config'].get('Labels', dict()) - image_labels = self.check_image().get('Labels', dict()) - for k, v in image_labels.items(): - if k in new_labels: - if v != new_labels[k]: - return True - else: - del current_labels[k] - - if new_labels != current_labels: - return True - - def compare_volumes_from(self, container_info): - new_vols_from = self.params.get('volumes_from') - current_vols_from = container_info['HostConfig'].get('VolumesFrom') - if not new_vols_from: - new_vols_from = list() - if not current_vols_from: - current_vols_from = list() - - if set(current_vols_from).symmetric_difference(set(new_vols_from)): - return True - - def compare_volumes(self, container_info): - volumes, binds = self.generate_volumes() - current_vols = container_info['Config'].get('Volumes') - current_binds = container_info['HostConfig'].get('Binds') - if not volumes: - volumes = list() - if not current_vols: - current_vols = list() - if not current_binds: - current_binds = list() - - if set(volumes).symmetric_difference(set(current_vols)): - return True - - new_binds = list() - if binds: - for k, v in binds.items(): - new_binds.append("{}:{}:{}".format(k, v['bind'], v['mode'])) - - if set(new_binds).symmetric_difference(set(current_binds)): - return True - - def compare_environment(self, container_info): - if self.params.get('environment'): - current_env = dict() - for kv in container_info['Config'].get('Env', list()): - k, v = kv.split('=', 1) - current_env.update({k: v}) - - for k, v in self.params.get('environment').items(): - if k not in current_env: - return True - if current_env[k] != v: - return True - - def parse_image(self): - full_image = self.params.get('image') - - if '/' in full_image: - registry, image = full_image.split('/', 1) - else: - image = full_image - - if ':' in image: - return full_image.rsplit(':', 1) - else: - return full_image, 'latest' - - def pull_image(self): - if self.params.get('auth_username'): - self.dc.login( - username=self.params.get('auth_username'), - password=self.params.get('auth_password'), - registry=self.params.get('auth_registry'), - email=self.params.get('auth_email') - ) - - image, tag = self.parse_image() - - statuses = [ - json.loads(line.strip()) for line in self.dc.pull( - repository=image, tag=tag, stream=True - ) - ] - - for status in reversed(statuses): - if 'error' in status: - if status['error'].endswith('not found'): - self.module.fail_json( - msg="The requested image does not exist: {}:{}".format( - image, tag), - failed=True - ) - else: - self.module.fail_json( - msg="Unknown error message: {}".format( - status['error']), - failed=True - ) - - if status and status.get('status'): - # NOTE(SamYaple): This allows us to use v1 and v2 docker - # registries. Eventually docker will stop supporting v1 - # registries and when that happens we can remove this. - if 'legacy registry' in status['status']: - continue - elif 'Downloaded newer image for' in status['status']: - self.changed = True - return - elif 'Image is up to date for' in status['status']: - return - else: - self.module.fail_json( - msg="Unknown status message: {}".format( - status['status']), - failed=True - ) - - def remove_container(self): - if self.check_container(): - self.changed = True - self.dc.remove_container( - container=self.params.get('name'), - force=True - ) - - def generate_volumes(self): - volumes = self.params.get('volumes') - if not volumes: - return None, None - - vol_list = list() - vol_dict = dict() - - for vol in volumes: - if ':' not in vol: - vol_list.append(vol) - continue - - split_vol = vol.split(':') - - if (len(split_vol) == 2 - and ('/' not in split_vol[0] or '/' in split_vol[1])): - split_vol.append('rw') - - vol_list.append(split_vol[1]) - vol_dict.update({ - split_vol[0]: { - 'bind': split_vol[1], - 'mode': split_vol[2] - } - }) - - return vol_list, vol_dict - - def build_host_config(self, binds): - options = { - 'network_mode': 'host', - 'ipc_mode': self.params.get('ipc_mode'), - 'cap_add': self.params.get('cap_add'), - 'security_opt': self.params.get('security_opt'), - 'pid_mode': self.params.get('pid_mode'), - 'privileged': self.params.get('privileged'), - 'volumes_from': self.params.get('volumes_from') - } - - if self.params.get('restart_policy') in ['on-failure', - 'always', - 'unless-stopped']: - options['restart_policy'] = { - 'Name': self.params.get('restart_policy'), - 'MaximumRetryCount': self.params.get('restart_retries') - } - - if binds: - options['binds'] = binds - - return self.dc.create_host_config(**options) - - def _inject_env_var(self, environment_info): - newenv = { - 'KOLLA_SERVICE_NAME': self.params.get('name').replace('_', '-') - } - environment_info.update(newenv) - return environment_info - - def _format_env_vars(self): - env = self._inject_env_var(self.params.get('environment')) - return {k: "" if env[k] is None else env[k] for k in env} - - def build_container_options(self): - volumes, binds = self.generate_volumes() - return { - 'detach': self.params.get('detach'), - 'environment': self._format_env_vars(), - 'host_config': self.build_host_config(binds), - 'labels': self.params.get('labels'), - 'image': self.params.get('image'), - 'name': self.params.get('name'), - 'volumes': volumes, - 'tty': True - } - - def create_container(self): - self.changed = True - options = self.build_container_options() - self.dc.create_container(**options) - - def start_container(self): - if not self.check_image(): - self.pull_image() - - container = self.check_container() - if container and self.check_container_differs(): - self.remove_container() - container = self.check_container() - - if not container: - self.create_container() - container = self.check_container() - - if not container['Status'].startswith('Up '): - self.changed = True - self.dc.start(container=self.params.get('name')) - - # We do not want to detach so we wait around for container to exit - if not self.params.get('detach'): - rc = self.dc.wait(self.params.get('name')) - if rc != 0: - self.module.fail_json( - failed=True, - changed=True, - msg="Container exited with non-zero return code" - ) - if self.params.get('remove_on_exit'): - self.remove_container() - - def get_container_env(self): - name = self.params.get('name') - info = self.get_container_info() - if not info: - self.module.fail_json(msg="No such container: {}".format(name)) - else: - envs = dict() - for env in info['Config']['Env']: - if '=' in env: - key, value = env.split('=', 1) - else: - key, value = env, '' - envs[key] = value - - self.module.exit_json(**envs) - - def get_container_state(self): - name = self.params.get('name') - info = self.get_container_info() - if not info: - self.module.fail_json(msg="No such container: {}".format(name)) - else: - self.module.exit_json(**info['State']) - - def stop_container(self): - name = self.params.get('name') - container = self.check_container() - if not container: - self.module.fail_json( - msg="No such container: {} to stop".format(name)) - elif not container['Status'].startswith('Exited '): - self.changed = True - self.dc.stop(name) - - def restart_container(self): - name = self.params.get('name') - info = self.get_container_info() - if not info: - self.module.fail_json( - msg="No such container: {}".format(name)) - else: - self.changed = True - self.dc.restart(name) - - def create_volume(self): - if not self.check_volume(): - self.changed = True - self.dc.create_volume(name=self.params.get('name'), driver='local') - - def remove_volume(self): - if self.check_volume(): - self.changed = True - try: - self.dc.remove_volume(name=self.params.get('name')) - except docker.errors.APIError as e: - if e.response.status_code == 409: - self.module.fail_json( - failed=True, - msg="Volume named '{}' is currently in-use".format( - self.params.get('name') - ) - ) - raise - - -def generate_module(): - argument_spec = dict( - common_options=dict(required=False, type='dict', default=dict()), - action=dict(required=True, type='str', choices=['compare_image', - 'create_volume', - 'get_container_env', - 'get_container_state', - 'pull_image', - 'remove_container', - 'remove_volume', - 'restart_container', - 'start_container', - 'stop_container']), - api_version=dict(required=False, type='str', default='auto'), - auth_email=dict(required=False, type='str'), - auth_password=dict(required=False, type='str'), - auth_registry=dict(required=False, type='str'), - auth_username=dict(required=False, type='str'), - detach=dict(required=False, type='bool', default=True), - labels=dict(required=False, type='dict', default=dict()), - name=dict(required=False, type='str'), - environment=dict(required=False, type='dict'), - image=dict(required=False, type='str'), - ipc_mode=dict(required=False, type='str', choices=['host']), - cap_add=dict(required=False, type='list', default=list()), - security_opt=dict(required=False, type='list', default=list()), - pid_mode=dict(required=False, type='str', choices=['host']), - privileged=dict(required=False, type='bool', default=False), - remove_on_exit=dict(required=False, type='bool', default=True), - restart_policy=dict(required=False, type='str', choices=[ - 'no', - 'never', - 'on-failure', - 'always', - 'unless-stopped']), - restart_retries=dict(required=False, type='int', default=10), - tls_verify=dict(required=False, type='bool', default=False), - tls_cert=dict(required=False, type='str'), - tls_key=dict(required=False, type='str'), - tls_cacert=dict(required=False, type='str'), - volumes=dict(required=False, type='list'), - volumes_from=dict(required=False, type='list') - ) - required_together = [ - ['tls_cert', 'tls_key'] - ] - module = AnsibleModule( - argument_spec=argument_spec, - required_together=required_together, - bypass_checks=True - ) - - new_args = module.params.pop('common_options', dict()) - - # NOTE(jeffrey4l): merge the environment - env = module.params.pop('environment', dict()) - if env: - new_args['environment'].update(env) - - for key, value in module.params.items(): - if key in new_args and value is None: - continue - new_args[key] = value - - module.params = new_args - return module - - -def main(): - module = generate_module() - - # TODO(SamYaple): Replace with required_if when Ansible 2.0 lands - if (module.params.get('action') in ['pull_image', 'start_container'] - and not module.params.get('image')): - module.fail_json( - msg="missing required arguments: image", - failed=True - ) - # TODO(SamYaple): Replace with required_if when Ansible 2.0 lands - if (module.params.get('action') != 'pull_image' - and not module.params.get('name')): - module.fail_json( - msg="missing required arguments: name", - failed=True - ) - - try: - dw = DockerWorker(module) - # TODO(inc0): We keep it bool to have ansible deal with consistent - # types. If we ever add method that will have to return some - # meaningful data, we need to refactor all methods to return dicts. - result = bool(getattr(dw, module.params.get('action'))()) - module.exit_json(changed=dw.changed, result=result) - except Exception: - module.exit_json(failed=True, changed=True, - msg=repr(traceback.format_exc())) - -# import module snippets -from ansible.module_utils.basic import * # noqa -if __name__ == '__main__': - main() diff --git a/ansible/library/merge_configs.py b/ansible/library/merge_configs.py deleted file mode 100644 index 5da09eed1..000000000 --- a/ansible/library/merge_configs.py +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/python - -# Copyright 2015 Sam Yaple -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -DOCUMENTATION = ''' ---- -module: merge_configs -short_description: Merge ini-style configs -description: - - ConfigParser is used to merge several ini-style configs into one -options: - dest: - description: - - The destination file name - required: True - type: str - sources: - description: - - A list of files on the destination node to merge together - default: None - required: True - type: str -author: Sam Yaple -''' - -EXAMPLES = ''' -Merge multiple configs: - -- hosts: database - tasks: - - name: Merge configs - merge_configs: - sources: - - "/tmp/config_1.cnf" - - "/tmp/config_2.cnf" - - "/tmp/config_3.cnf" - dest: - - "/etc/mysql/my.cnf" -''' diff --git a/ansible/library/merge_yaml.py b/ansible/library/merge_yaml.py deleted file mode 100644 index 66d316fa5..000000000 --- a/ansible/library/merge_yaml.py +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/python - -# Copyright 2015 Sam Yaple -# Copyright 2016 intel -# -# 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: merge_yaml -short_description: Merge yaml-style configs -description: - - PyYAML is used to merge several yaml files into one -options: - dest: - description: - - The destination file name - required: True - type: str - sources: - description: - - A list of files on the destination node to merge together - default: None - required: True - type: str -author: Sean Mooney -''' - -EXAMPLES = ''' -Merge multiple yaml files: - -- hosts: localhost - tasks: - - name: Merge yaml files - merge_yaml: - sources: - - "/tmp/default.yml" - - "/tmp/override.yml" - dest: - - "/tmp/out.yml" -''' |
