#!/usr/bin/python # -*- coding: utf-8 -*- # (c) 2014, Kevin Carter # # 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 . DOCUMENTATION = """ --- module: swift version_added: "1.6.2" short_description: - Manage objects stored in swift description: - Manage objects stored in swift options: login_user: description: - login username required: true login_password: description: - Password of login user required: true login_tenant_name: description: - The tenant login_user belongs to required: false default: None login_url: description: - Authentication URL required: true region: description: - The password to be assigned to the user required: false container: description: - Name of container required: true src: description: - path to object. Only used for in 'upload' & 'download' command required: false object: description: - Name of object required: false config_file: description: - Path to credential file required: false section: description: - Section within ``config_file`` to load required: false default: default auth_version: description: - Swift authentication version default: 2.0 required: false snet: description: - Enable service Net. This may not be supported by all providers set true or false default: false marker: description: - Set beginning marker. Only used in 'list' command. default: false end_marker: description: - Set ending marker. Only used in 'list' command. default: false limit: description: - Set limit. Only used in 'list' command. default: false prefix: description: - Set prefix filter. Only used in 'list' command. default: false command: description: - Indicate desired state of the resource choices: ['upload', 'download', 'delete', 'create', 'list'] required: true notes: - Environment variables can be set for all auth credentials which allows for seemless access. The available environment variables are, OS_USERNAME, OS_PASSWORD, OS_TENANT_ID, OS_AUTH_URL - A configuration file can be used to load credentials, use ``config_file`` to source the file. If you have multiple sections within the configuration file use the ``section`` argument to define the section, however the default is set to "default". requirements: [ python-swiftclient ] author: Kevin Carter """ EXAMPLES = """ # Create a new container - swift: > login_user="SomeUser" login_password="SomePassword" login_url="https://identity.somedomain.com/v2.0/" command=create container=MyNewContainer # Upload a new object - swift: > login_user="SomeUser" login_password="SomePassword" login_url="https://identity.somedomain.com/v2.0/" command=upload container=MyNewContainer src=/path/to/file object=MyNewObjectName # Download an object - swift: > login_user="SomeUser" login_password="SomePassword" login_url="https://identity.somedomain.com/v2.0/" command=download container=MyNewContainer src=/path/to/file object=MyOldObjectName # list up-to 10K objects - swift: > login_user="SomeUser" login_password="SomePassword" login_url="https://identity.somedomain.com/v2.0/" command=list container=MyNewContainer # Delete an Object - swift: > login_user="SomeUser" login_password="SomePassword" login_url="https://identity.somedomain.com/v2.0/" command=delete container=MyNewContainer object=MyOldObjectName # Delete a container - swift: > login_user="SomeUser" login_password="SomePassword" login_url="https://identity.somedomain.com/v2.0/" command=delete container=MyNewContainer """ COMMAND_MAP = { 'upload': { 'variables': [ 'login_user', 'login_password', 'login_tenant_name', 'login_url', 'region', 'container', 'src', 'object', 'auth_version' ] }, 'download': { 'variables': [ 'login_user', 'login_password', 'login_tenant_name', 'login_url', 'region', 'container', 'src', 'object', 'auth_version' ] }, 'delete': { 'variables': [ 'login_user', 'login_password', 'login_tenant_name', 'login_url', 'region', 'container', 'object', 'auth_version' ] }, 'create': { 'variables': [ 'login_user', 'login_password', 'login_tenant_name', 'login_url', 'region', 'container', 'auth_version' ] }, 'list': { 'variables': [ 'login_user', 'login_password', 'login_tenant_name', 'login_url', 'region', 'container', 'auth_version', 'marker', 'limit', 'prefix', 'end_marker' ] } } import ConfigParser try: from swiftclient import client except ImportError: swiftclient_found = False else: swiftclient_found = True class ManageSwift(object): def __init__(self, module): """Manage Swift via Ansible.""" self.state_change = False self.swift = None # Load AnsibleModule self.module = module def command_router(self): """Run the command as its provided to the module.""" command_name = self.module.params['command'] if command_name not in COMMAND_MAP: self.failure( error='No Command Found', rc=2, msg='Command [ %s ] was not found.' % command_name ) action_command = COMMAND_MAP[command_name] if hasattr(self, '_%s' % command_name): action = getattr(self, '_%s' % command_name) self._authenticate() facts = action(variables=action_command['variables']) if facts is None: self.module.exit_json(changed=self.state_change) else: self.module.exit_json( changed=self.state_change, ansible_facts=facts ) else: self.failure( error='Command not in ManageSwift class', rc=2, msg='Method [ %s ] was not found.' % command_name ) @staticmethod def _facts(facts): """Return a dict for our Ansible facts. :param facts: ``dict`` Dict with data to return """ return {'swift_facts': facts} def _get_vars(self, variables, required=None): """Return a dict of all variables as found within the module. :param variables: ``list`` List of all variables that are available to use within the Swift Command. :param required: ``list`` Name of variables that are required. """ return_dict = {} for variable in variables: return_dict[variable] = self.module.params.get(variable) else: if isinstance(required, list): for var_name in required: check = return_dict.get(var_name) if check is None: self.failure( error='Missing [ %s ] from Task or found a None' ' value' % var_name, rc=000, msg='variables %s - available params [ %s ]' % (variables, self.module.params) ) return return_dict def failure(self, error, rc, msg): """Return a Failure when running an Ansible command. :param error: ``str`` Error that occurred. :param rc: ``int`` Return code while executing an Ansible command. :param msg: ``str`` Message to report. """ self.module.fail_json(msg=msg, rc=rc, err=error) def _env_vars(self, cred_file=None, section='default'): """Load environment or sourced credentials. If the credentials are specified in either environment variables or in a credential file the sourced variables will be loaded IF the not set within the ``module.params``. :param cred_file: ``str`` Path to credentials file. :param section: ``str`` Section within creds file to load. """ if cred_file: parser = ConfigParser.SafeConfigParser() parser.optionxform = str parser.read(os.path.expanduser(cred_file)) for name, value in parser.items(section): if name == 'OS_AUTH_URL': if not self.module.params.get('login_url'): self.module.params['login_url'] = value if name == 'OS_USERNAME': if not self.module.params.get('login_user'): self.module.params['login_user'] = value if name == 'OS_PASSWORD': if not self.module.params.get('login_password'): self.module.params['login_password'] = value if name == 'OS_TENANT_ID': if not self.module.params.get('login_tenant_name'): self.module.params['login_tenant_name'] = value else: if not self.module.params.get('login_url'): authurl = os.getenv('OS_AUTH_URL') self.module.params['login_url'] = authurl if not self.module.params.get('login_user'): username = os.getenv('OS_USERNAME') self.module.params['login_user'] = username if not self.module.params.get('login_password'): password = os.getenv('OS_PASSWORD') self.module.params['login_password'] = password if not self.module.params.get('login_tenant_name'): tenant = os.getenv('OS_TENANT_ID') self.module.params['login_tenant_name'] = tenant def _authenticate(self): """Return a swift client object.""" cred_file = self.module.params.pop('config_file', None) section = self.module.params.pop('section') self._env_vars(cred_file=cred_file, section=section) required_vars = ['login_url', 'login_user', 'login_password'] variables = [ 'login_url', 'login_user', 'login_password', 'login_tenant_name', 'region', 'auth_version', 'snet' ] variables_dict = self._get_vars(variables, required=required_vars) login_url = variables_dict.pop('login_url') login_user = variables_dict.pop( 'login_user', os.getenv('OS_AUTH_URL') ) login_password = variables_dict.pop( 'login_password', os.getenv('OS_AUTH_URL') ) login_tenant_name = variables_dict.pop( 'login_tenant_name', os.getenv('OS_TENANT_ID') ) region = variables_dict.pop('region', None) auth_version = variables_dict.pop('auth_version') snet = variables_dict.pop('snet', None) if snet in BOOLEANS_TRUE: snet = True else: snet = None if login_password is None: self.failure( error='Missing Password', rc=2, msg='A Password is required for authentication. Try adding' ' [ login_password ] to the task' ) if login_tenant_name is None: login_tenant_name = ' ' creds_dict = { 'user': login_user, 'key': login_password, 'authurl': login_url, 'tenant_name': login_tenant_name, 'os_options': { 'region': region }, 'snet': snet, 'auth_version': auth_version } self.swift = client.Connection(**creds_dict) def _upload(self, variables): """Upload an object to a swift object store. :param variables: ``list`` List of all variables that are available to use within the Keystone Command. """ required_vars = ['container', 'src', 'object'] variables_dict = self._get_vars(variables, required=required_vars) container_name = variables_dict.pop('container') object_name = variables_dict.pop('object') src_path = variables_dict.pop('src') self._create_container(container_name=container_name) with open(src_path, 'rb') as f: self.swift.put_object(container_name, object_name, contents=f) object_data = self.swift.head_object(container_name, object_name) self.state_change = True return self._facts(facts=[object_data]) def _download(self, variables): """Upload an object to a swift object store. :param variables: ``list`` List of all variables that are available to use within the Keystone Command. """ required_vars = ['container', 'src', 'object'] variables_dict = self._get_vars(variables, required=required_vars) container_name = variables_dict.pop('container') object_name = variables_dict.pop('object') src_path = variables_dict.pop('src') with open(src_path, 'wb') as f: f.write( self.swift.get_object( container_name, object_name, resp_chunk_size=204800 ) ) self.state_change = True def _delete(self, variables): """Upload an object to a swift object store. If the ``object`` variable is not used the container will be deleted. This assumes that the container is empty. :param variables: ``list`` List of all variables that are available to use within the Keystone Command. """ required_vars = ['container'] variables_dict = self._get_vars(variables, required=required_vars) container_name = variables_dict.pop('container') object_name = variables_dict.pop('object', None) if object_name: self.swift.delete_object(container_name, object_name) else: self.swift.delete_container(container_name) self.state_change = True def _create_container(self, container_name): """Ensure a container exists. If it does not, it will be created. :param container_name: ``str`` Name of the container. """ try: container = self.swift.head_container(container_name) except client.ClientException: self.swift.put_container(container_name) else: return container def _create(self, variables): """Create a new container in swift. :param variables: ``list`` List of all variables that are available to use within the Keystone Command. """ required_vars = ['container'] variables_dict = self._get_vars(variables, required=required_vars) container_name = variables_dict.pop('container') container_data = self._create_container(container_name=container_name) if not container_data: container_data = self.swift.head_container(container_name) return self._facts(facts=[container_data]) def _list(self, variables): """Return a list of objects or containers. If the ``container`` variable is not used this will return a list of containers in the region. :param variables: ``list`` List of all variables that are available to use within the Keystone Command. """ variables_dict = self._get_vars(variables) container_name = variables_dict.pop('container', None) filters = { 'marker': variables_dict.pop('marker', None), 'limit': variables_dict.pop('limit', None), 'prefix': variables_dict.pop('prefix', None), 'end_marker': variables_dict.pop('end_marker', None) } if container_name: list_data = self.swift.get_container(container_name, **filters)[1] else: list_data = self.swift.get_account(**filters)[1] return self._facts(facts=list_data) def main(): module = AnsibleModule( argument_spec=dict( login_user=dict( required=False ), login_password=dict( required=False ), login_tenant_name=dict( required=False ), login_url=dict( required=False ), config_file=dict( required=False ), section=dict( required=False, default='default' ), command=dict( required=True, choices=COMMAND_MAP.keys() ), region=dict( required=False ), container=dict( required=False ), src=dict( required=False ), object=dict( required=False ), marker=dict( required=False ), limit=dict( required=False ), prefix=dict( required=False ), end_marker=dict( required=False ), auth_version=dict( required=False, default='2.0' ), snet=dict( required=False, default='false', choices=BOOLEANS ) ), supports_check_mode=False, ) sm = ManageSwift(module=module) if not swiftclient_found: sm.failure( error='python-swiftclient is missing', rc=2, msg='Swift client was not importable, is it installed?' ) sm.command_router() # import module snippets from ansible.module_utils.basic import * if __name__ == '__main__': main()