634 lines
19 KiB
Python
634 lines
19 KiB
Python
#!/usr/bin/python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# (c) 2014, Kevin Carter <kevin.carter@rackspace.com>
|
|
#
|
|
# 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/>.
|
|
|
|
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()
|