From 4723ad0a71885117d884022c73c0672bbd59d512 Mon Sep 17 00:00:00 2001 From: Luke Sneeringer Date: Wed, 6 Aug 2014 09:02:14 -0500 Subject: [PATCH] VMware support within Tower. --- awx/lib/site-packages/psphere/__init__.py | 359 + awx/lib/site-packages/psphere/client.py | 632 + awx/lib/site-packages/psphere/config.py | 26 + awx/lib/site-packages/psphere/errors.py | 37 + .../site-packages/psphere/managedobjects.py | 1500 ++ awx/lib/site-packages/psphere/scripting.py | 106 + awx/lib/site-packages/psphere/soap.py | 95 + awx/lib/site-packages/psphere/template.py | 61 + .../site-packages/psphere/wsdl/core-types.xsd | 267 + .../psphere/wsdl/query-messagetypes.xsd | 85 + .../psphere/wsdl/query-types.xsd | 254 + .../psphere/wsdl/vim-messagetypes.xsd | 3082 +++ .../site-packages/psphere/wsdl/vim-types.xsd | 22161 ++++++++++++++++ awx/lib/site-packages/psphere/wsdl/vim.wsdl | 19528 ++++++++++++++ .../psphere/wsdl/vimService.wsdl | 16 + awx/lib/site-packages/suds/__init__.py | 154 + .../site-packages/suds/bindings/__init__.py | 20 + .../site-packages/suds/bindings/binding.py | 538 + .../site-packages/suds/bindings/document.py | 160 + .../site-packages/suds/bindings/multiref.py | 126 + awx/lib/site-packages/suds/bindings/rpc.py | 98 + awx/lib/site-packages/suds/builder.py | 121 + awx/lib/site-packages/suds/cache.py | 337 + awx/lib/site-packages/suds/client.py | 785 + awx/lib/site-packages/suds/metrics.py | 62 + awx/lib/site-packages/suds/mx/__init__.py | 59 + awx/lib/site-packages/suds/mx/appender.py | 316 + awx/lib/site-packages/suds/mx/basic.py | 48 + awx/lib/site-packages/suds/mx/core.py | 158 + awx/lib/site-packages/suds/mx/encoded.py | 133 + awx/lib/site-packages/suds/mx/literal.py | 291 + awx/lib/site-packages/suds/mx/typer.py | 123 + awx/lib/site-packages/suds/options.py | 123 + awx/lib/site-packages/suds/plugin.py | 257 + awx/lib/site-packages/suds/properties.py | 543 + awx/lib/site-packages/suds/reader.py | 169 + awx/lib/site-packages/suds/resolver.py | 496 + awx/lib/site-packages/suds/sax/__init__.py | 109 + awx/lib/site-packages/suds/sax/attribute.py | 181 + awx/lib/site-packages/suds/sax/date.py | 378 + awx/lib/site-packages/suds/sax/document.py | 61 + awx/lib/site-packages/suds/sax/element.py | 1147 + awx/lib/site-packages/suds/sax/enc.py | 79 + awx/lib/site-packages/suds/sax/parser.py | 139 + awx/lib/site-packages/suds/sax/text.py | 116 + .../site-packages/suds/servicedefinition.py | 248 + awx/lib/site-packages/suds/serviceproxy.py | 86 + awx/lib/site-packages/suds/soaparray.py | 72 + awx/lib/site-packages/suds/store.py | 594 + awx/lib/site-packages/suds/sudsobject.py | 390 + .../site-packages/suds/transport/__init__.py | 130 + awx/lib/site-packages/suds/transport/http.py | 187 + awx/lib/site-packages/suds/transport/https.py | 98 + .../site-packages/suds/transport/options.py | 57 + awx/lib/site-packages/suds/umx/__init__.py | 56 + awx/lib/site-packages/suds/umx/attrlist.py | 88 + awx/lib/site-packages/suds/umx/basic.py | 41 + awx/lib/site-packages/suds/umx/core.py | 216 + awx/lib/site-packages/suds/umx/encoded.py | 128 + awx/lib/site-packages/suds/umx/typed.py | 141 + awx/lib/site-packages/suds/wsdl.py | 922 + awx/lib/site-packages/suds/wsse.py | 212 + awx/lib/site-packages/suds/xsd/__init__.py | 86 + awx/lib/site-packages/suds/xsd/deplist.py | 140 + awx/lib/site-packages/suds/xsd/doctor.py | 226 + awx/lib/site-packages/suds/xsd/query.py | 208 + awx/lib/site-packages/suds/xsd/schema.py | 422 + awx/lib/site-packages/suds/xsd/sxbase.py | 669 + awx/lib/site-packages/suds/xsd/sxbasic.py | 825 + awx/lib/site-packages/suds/xsd/sxbuiltin.py | 274 + awx/main/constants.py | 2 +- awx/main/models/inventory.py | 18 +- awx/main/tasks.py | 4 + awx/main/tests/tasks.py | 6 + awx/plugins/inventory/vmware.py | 18 +- awx/ui/static/js/forms/Credentials.js | 18 +- awx/ui/static/js/helpers/Credentials.js | 8 + awx/ui/static/js/helpers/Groups.js | 4 +- requirements/psphere-0.5.2.tar.gz | Bin 0 -> 161091 bytes requirements/suds-0.4.tar.gz | Bin 0 -> 104013 bytes 80 files changed, 62116 insertions(+), 14 deletions(-) create mode 100644 awx/lib/site-packages/psphere/__init__.py create mode 100644 awx/lib/site-packages/psphere/client.py create mode 100644 awx/lib/site-packages/psphere/config.py create mode 100644 awx/lib/site-packages/psphere/errors.py create mode 100644 awx/lib/site-packages/psphere/managedobjects.py create mode 100644 awx/lib/site-packages/psphere/scripting.py create mode 100644 awx/lib/site-packages/psphere/soap.py create mode 100644 awx/lib/site-packages/psphere/template.py create mode 100644 awx/lib/site-packages/psphere/wsdl/core-types.xsd create mode 100644 awx/lib/site-packages/psphere/wsdl/query-messagetypes.xsd create mode 100644 awx/lib/site-packages/psphere/wsdl/query-types.xsd create mode 100644 awx/lib/site-packages/psphere/wsdl/vim-messagetypes.xsd create mode 100644 awx/lib/site-packages/psphere/wsdl/vim-types.xsd create mode 100644 awx/lib/site-packages/psphere/wsdl/vim.wsdl create mode 100644 awx/lib/site-packages/psphere/wsdl/vimService.wsdl create mode 100644 awx/lib/site-packages/suds/__init__.py create mode 100644 awx/lib/site-packages/suds/bindings/__init__.py create mode 100644 awx/lib/site-packages/suds/bindings/binding.py create mode 100644 awx/lib/site-packages/suds/bindings/document.py create mode 100644 awx/lib/site-packages/suds/bindings/multiref.py create mode 100644 awx/lib/site-packages/suds/bindings/rpc.py create mode 100644 awx/lib/site-packages/suds/builder.py create mode 100644 awx/lib/site-packages/suds/cache.py create mode 100644 awx/lib/site-packages/suds/client.py create mode 100644 awx/lib/site-packages/suds/metrics.py create mode 100644 awx/lib/site-packages/suds/mx/__init__.py create mode 100644 awx/lib/site-packages/suds/mx/appender.py create mode 100644 awx/lib/site-packages/suds/mx/basic.py create mode 100644 awx/lib/site-packages/suds/mx/core.py create mode 100644 awx/lib/site-packages/suds/mx/encoded.py create mode 100644 awx/lib/site-packages/suds/mx/literal.py create mode 100644 awx/lib/site-packages/suds/mx/typer.py create mode 100644 awx/lib/site-packages/suds/options.py create mode 100644 awx/lib/site-packages/suds/plugin.py create mode 100644 awx/lib/site-packages/suds/properties.py create mode 100644 awx/lib/site-packages/suds/reader.py create mode 100644 awx/lib/site-packages/suds/resolver.py create mode 100644 awx/lib/site-packages/suds/sax/__init__.py create mode 100644 awx/lib/site-packages/suds/sax/attribute.py create mode 100644 awx/lib/site-packages/suds/sax/date.py create mode 100644 awx/lib/site-packages/suds/sax/document.py create mode 100644 awx/lib/site-packages/suds/sax/element.py create mode 100644 awx/lib/site-packages/suds/sax/enc.py create mode 100644 awx/lib/site-packages/suds/sax/parser.py create mode 100644 awx/lib/site-packages/suds/sax/text.py create mode 100644 awx/lib/site-packages/suds/servicedefinition.py create mode 100644 awx/lib/site-packages/suds/serviceproxy.py create mode 100644 awx/lib/site-packages/suds/soaparray.py create mode 100644 awx/lib/site-packages/suds/store.py create mode 100644 awx/lib/site-packages/suds/sudsobject.py create mode 100644 awx/lib/site-packages/suds/transport/__init__.py create mode 100644 awx/lib/site-packages/suds/transport/http.py create mode 100644 awx/lib/site-packages/suds/transport/https.py create mode 100644 awx/lib/site-packages/suds/transport/options.py create mode 100644 awx/lib/site-packages/suds/umx/__init__.py create mode 100644 awx/lib/site-packages/suds/umx/attrlist.py create mode 100644 awx/lib/site-packages/suds/umx/basic.py create mode 100644 awx/lib/site-packages/suds/umx/core.py create mode 100644 awx/lib/site-packages/suds/umx/encoded.py create mode 100644 awx/lib/site-packages/suds/umx/typed.py create mode 100644 awx/lib/site-packages/suds/wsdl.py create mode 100644 awx/lib/site-packages/suds/wsse.py create mode 100644 awx/lib/site-packages/suds/xsd/__init__.py create mode 100644 awx/lib/site-packages/suds/xsd/deplist.py create mode 100644 awx/lib/site-packages/suds/xsd/doctor.py create mode 100644 awx/lib/site-packages/suds/xsd/query.py create mode 100644 awx/lib/site-packages/suds/xsd/schema.py create mode 100644 awx/lib/site-packages/suds/xsd/sxbase.py create mode 100644 awx/lib/site-packages/suds/xsd/sxbasic.py create mode 100644 awx/lib/site-packages/suds/xsd/sxbuiltin.py create mode 100644 requirements/psphere-0.5.2.tar.gz create mode 100644 requirements/suds-0.4.tar.gz diff --git a/awx/lib/site-packages/psphere/__init__.py b/awx/lib/site-packages/psphere/__init__.py new file mode 100644 index 0000000000..0db745ffc2 --- /dev/null +++ b/awx/lib/site-packages/psphere/__init__.py @@ -0,0 +1,359 @@ +# Copyright 2010 Jonathan Kinred +# +# 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. + +import logging +import time + +from suds import MethodNotFound + +logger = logging.getLogger(__name__) + +__version__ = '0.5.2' +__released__ = '0.5.2' + +class cached_property(object): + """Decorator for read-only properties evaluated only once within TTL period. + + It can be used to created a cached property like this:: + + import random + + # the class containing the property must be a new-style class + class MyClass(object): + # create property whose value is cached for ten minutes + @cached_property(ttl=600) + def randint(self): + # will only be evaluated every 10 min. at maximum. + return random.randint(0, 100) + + The value is cached in the '_cache' attribute of the object instance that + has the property getter method wrapped by this decorator. The '_cache' + attribute value is a dictionary which has a key for every property of the + object which is wrapped by this decorator. Each entry in the cache is + created only when the property is accessed for the first time and is a + two-element tuple with the last computed property value and the last time + it was updated in seconds since the epoch. + + The default time-to-live (TTL) is 300 seconds (5 minutes). Set the TTL to + zero for the cached value to never expire. + + To expire a cached property value manually just do:: + + del instance._cache[] + + """ + def __init__(self, fget, doc=None): + self.ttl = 300 + self.fget = fget + self.__doc__ = doc or fget.__doc__ + self.__name__ = fget.__name__ + self.__module__ = fget.__module__ + + def __get__(self, inst, owner): + now = time.time() + try: + # Get the value from the cache + value, last_update = inst._cache[self.__name__] + logger.info("Found cached value for %s", self.__name__) + # If the value in the cache exceeds the TTL then raise + # AttributeError so that we retrieve the value again below + if self.ttl > 0 and now - last_update > self.ttl: + logger.info("Cached value has exceeded TTL") + raise AttributeError + except (KeyError, AttributeError): + # We end up here if the value hasn't been cached + # or the value exceeds the TTL. We call the decorated + # function to get the value. + logger.info("%s is not cached.", self.__name__) + value = self.fget(inst) + try: + # See if the instance has a cache attribute + cache = inst._cache + except AttributeError: + # If it doesn't, initialise the attribute and use it + cache = inst._cache = {} + # Set the value in the cache dict to our values + cache[self.__name__] = (value, now) + # Finally, return either the value from the cache or the + # newly retrieved value + return value + + +class ManagedObject(object): + """The base class which all managed object's derive from. + + :param mo_ref: The managed object reference used to create this instance + :type mo_ref: ManagedObjectReference + :param client: A reference back to the psphere client object, which \ + we use to make calls. + :type client: Client + + """ + _valid_attrs = set([]) + def __init__(self, mo_ref, client): + self._cache = {} + logger.debug("===== Have been passed %s as mo_ref: ", mo_ref) + self._mo_ref = mo_ref + self._client = client + + def _get_dataobject(self, name, multivalued): + """This function only gets called if the decorated property + doesn't have a value in the cache.""" + logger.debug("Querying server for uncached data object %s", name) + # This will retrieve the value and inject it into the cache + self.update_view_data(properties=[name]) + return self._cache[name][0] + + def _get_mor(self, name, multivalued): + """This function only gets called if the decorated property + doesn't have a value in the cache.""" + logger.debug("Querying server for uncached MOR %s", name) + # This will retrieve the value and inject it into the cache + logger.debug("Getting view for MOR") + self.update(properties=[name]) + return self._cache[name][0] + +# return self._cache[name][0] +# if multivalued is True: +# logger.debug("Getting views for MOR") +# self.update(properties=[name]) +# views = self._client.get_views(self._cache[name][0]) +# return views +# else: +# logger.debug("Getting view for MOR") +# self.update(properties=[name]) +# return self._cache[name][0] + + def flush_cache(self, properties=None): + """Flushes the cache being held for this instance. + + :param properties: The list of properties to flush from the cache. + :type properties: list or None (default). If None, flush entire cache. + + """ + if hasattr(self, '_cache'): + if properties is None: + del(self._cache) + else: + for prop in properties: + if prop in self._cache: + del(self._cache[prop]) + + def update(self, properties=None): + """Updates the properties being held for this instance. + + :param properties: The list of properties to update. + :type properties: list or None (default). If None, update all + currently cached properties. + + """ + if properties is None: + try: + self.update_view_data(properties=self._cache.keys()) + except AttributeError: + # We end up here and ignore it self._cache doesn't exist + pass + else: + self.update_view_data(properties=properties) + + def _get_properties(self, properties=None): + """Retrieve the requested properties from the server. + + :param properties: The list of properties to update. + :type properties: list or None (default). + + """ + pass + + def update_view_data(self, properties=None): + """Update the local object from the server-side object. + + >>> vm = VirtualMachine.find_one(client, filter={"name": "genesis"}) + >>> # Update all properties + >>> vm.update_view_data() + >>> # Update the config and summary properties + >>> vm.update_view_data(properties=["config", "summary"] + + :param properties: A list of properties to update. + :type properties: list + + """ + if properties is None: + properties = [] + logger.info("Updating view data for object of type %s", + self._mo_ref._type) + property_spec = self._client.create('PropertySpec') + property_spec.type = str(self._mo_ref._type) + # Determine which properties to retrieve from the server + if properties is None: + properties = [] + else: + if properties == "all": + logger.debug("Retrieving all properties") + property_spec.all = True + else: + logger.debug("Retrieving %s properties", len(properties)) + property_spec.all = False + property_spec.pathSet = properties + + object_spec = self._client.create('ObjectSpec') + object_spec.obj = self._mo_ref + + pfs = self._client.create('PropertyFilterSpec') + pfs.propSet = [property_spec] + pfs.objectSet = [object_spec] + + # Create a copy of the property collector and call the method + pc = self._client.sc.propertyCollector + object_content = pc.RetrieveProperties(specSet=pfs)[0] + if not object_content: + # TODO: Improve error checking and reporting + logger.error("Nothing returned from RetrieveProperties!") + + self._set_view_data(object_content) + + def preload(self, name, properties=None): + """Pre-loads the requested properties for each object in the "name" + attribute. + + :param name: The name of the attribute containing the list to + preload. + :type name: str + :param properties: The properties to preload on the objects or the + string all to preload all properties. + :type properties: list or the string "all" + + """ + if properties is None: + raise ValueError("You must specify some properties to preload. To" + " preload all properties use the string \"all\".") + # Don't do anything if the attribute contains an empty list + if not getattr(self, name): + return + + mo_refs = [] + # Iterate over each item and collect the mo_ref + for item in getattr(self, name): + # Make sure the items are ManagedObjectReference's + if isinstance(item, ManagedObject) is False: + raise ValueError("Only ManagedObject's can be pre-loaded.") + + mo_refs.append(item._mo_ref) + + # Send a single query to the server which gets views + views = self._client.get_views(mo_refs, properties) + + # Populate the inst.attr item with the retrieved object/properties + self._cache[name] = (views, time.time()) + + def _set_view_data(self, object_content): + """Update the local object from the passed in object_content.""" + # A debugging convenience, allows inspection of the object_content + # that was used to create the object + logger.info("Setting view data for a %s", self.__class__) + self._object_content = object_content + + for dynprop in object_content.propSet: + # If the class hasn't defined the property, don't use it + if dynprop.name not in self._valid_attrs: + logger.error("Server returned a property '%s' but the object" + " hasn't defined it so it is being ignored." % + dynprop.name) + continue + + try: + if not len(dynprop.val): + logger.info("Server returned empty value for %s", + dynprop.name) + except TypeError: + # This except allows us to pass over: + # TypeError: object of type 'datetime.datetime' has no len() + # It will be processed in the next code block + logger.info("%s of type %s has no len!", + dynprop.name, type(dynprop.val)) + pass + + try: + # See if we have a cache attribute + cache = self._cache + except AttributeError: + # If we don't create one and use it + cache = self._cache = {} + + # Values which contain classes starting with Array need + # to be converted into a nicer Python list + if dynprop.val.__class__.__name__.startswith('Array'): + # suds returns a list containing a single item, which + # is another list. Use the first item which is the real list + logger.info("Setting value of an Array* property") + logger.debug("%s being set to %s", + dynprop.name, dynprop.val[0]) + now = time.time() + cache[dynprop.name] = (dynprop.val[0], now) + else: + logger.info("Setting value of a single-valued property") + logger.debug("DynamicProperty value is a %s: ", + dynprop.val.__class__.__name__) + logger.debug("%s being set to %s", dynprop.name, dynprop.val) + now = time.time() + cache[dynprop.name] = (dynprop.val, now) + + def __getattr__(self, name): + """Overridden so that SOAP methods can be proxied. + + The magic contained here allows us to automatically access vSphere + SOAP methods through the Python object, like: + >>> client.si.content.rootFolder.CreateFolder(name="foo") + + This is achieved by asking the underlying SOAP service if the + requested name is a valid method. If the method name is not valid + then we pass the attribute retrieval back to __getattribute__ + which will use the default behaviour (i.e. just get the attribute). + + TODO: There's no checking if the SOAP method is valid for the type + of object being called. e.g. You could do folder.Login() which would + be totally bogus. + + :param name: The name of the method to call. + :param type: str + + """ + logger.debug("Entering overridden built-in __getattr__" + " with %s" % name) + # Built-ins always use the default behaviour +# if name.startswith("__"): +# logger.debug("Returning built-in attribute %s", name) +# return object.__getattribute__(self, name) + + # Here we must access _client through __getattribute__, if we were + # to use "self._client" we'd call recursively through __getattr__ + client = object.__getattribute__(self, "_client") + + try: + getattr(client.service, name) + except MethodNotFound: + # It doesn't, so we let the object check if it's a standard + # attribute. This is cool because it + return object.__getattribute__(self, name) + + # Caller has requested a valid SOAP reference + logger.debug("Constructing proxy method %s for a %s", + name, self._mo_ref._type) + def func(**kwargs): + result = self._client.invoke(name, _this=self._mo_ref, + **kwargs) + logger.debug("Invoke returned %s", result) + return result + + return func diff --git a/awx/lib/site-packages/psphere/client.py b/awx/lib/site-packages/psphere/client.py new file mode 100644 index 0000000000..28be5ef44e --- /dev/null +++ b/awx/lib/site-packages/psphere/client.py @@ -0,0 +1,632 @@ +""" +:mod:`psphere.client` - A client for communicating with a vSphere server +======================================================================== + +.. module:: client + +The main module for accessing a vSphere server. + +.. moduleauthor:: Jonathan Kinred + +""" + +# Copyright 2010 Jonathan Kinred +# +# 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. + + +import logging +import os +import suds +import time + +from urllib2 import URLError +from suds.plugin import MessagePlugin +from suds.transport import TransportError + +from psphere import soap, ManagedObject +from psphere.config import _config_value +from psphere.errors import (ConfigError, ObjectNotFoundError, TaskFailedError, + NotLoggedInError) +from psphere.managedobjects import ServiceInstance, Task, classmapper + +logger = logging.getLogger(__name__) + +class Client(suds.client.Client): + """A client for communicating with a VirtualCenter/ESX/ESXi server + + >>> from psphere.client import Client + >>> Client = Client(server="esx.foo.com", username="me", password="pass") + + :param server: The server of the server. e.g. https://esx.foo.com/sdk + :type server: str + :param username: The username to connect with + :type username: str + :param password: The password to connect with + :type password: str + :param wsdl_location: Whether to use the provided WSDL or load the server WSDL + :type wsdl_location: The string "local" (default) or "remote" + :param timeout: The timeout to use when connecting to the server + :type timeout: int (default=30) + :param plugins: The plugins classes that will be used to process messages + before send them to the web service + :type plugins: list of classes + """ + def __init__(self, server=None, username=None, password=None, + wsdl_location="local", timeout=30, plugins=[]): + self._logged_in = False + if server is None: + server = _config_value("general", "server") + if username is None: + username = _config_value("general", "username") + if password is None: + password = _config_value("general", "password") + if server is None: + raise ConfigError("server must be set in config file or Client()") + if username is None: + raise ConfigError("username must be set in config file or Client()") + if password is None: + raise ConfigError("password must be set in config file or Client()") + self.server = server + self.username = username + self.password = password + url = "https://%s/sdk" % self.server + if wsdl_location == "local": + current_path = os.path.abspath(os.path.dirname(__file__)) + current_path = current_path.replace('\\', '/') + if not current_path.startswith('/') : + current_path = '/' + current_path + if current_path.endswith('/') : + current_path = current_path[:-1] + wsdl_uri = ("file://%s/wsdl/vimService.wsdl" % current_path) + elif wsdl_location == "remote": + wsdl_uri = url + "/vimService.wsdl" + else: + raise ValueError("wsdl_location must be \"local\" or \"remote\"") + # Init the base class + try: + # Add ExtraConfigPlugin to the plugins + plugins.append(ExtraConfigPlugin()) + suds.client.Client.__init__(self, wsdl_uri, plugins=plugins) + except URLError: + logger.critical("Failed to connect to %s", self.server) + raise + except IOError: + logger.critical("Failed to load the local WSDL from %s", wsdl_uri) + raise + except TransportError: + logger.critical("Failed to load the remote WSDL from %s", wsdl_uri) + raise + self.options.transport.options.timeout = timeout + self.set_options(location=url) + mo_ref = soap.ManagedObjectReference("ServiceInstance", + "ServiceInstance") + self.si = ServiceInstance(mo_ref, self) + try: + self.sc = self.si.RetrieveServiceContent() + except URLError, e: + logger.critical("Failed to connect to %s" % self.server) + logger.critical("urllib2 said: %s" % e.reason) + raise + + if self._logged_in is False: + self.login(self.username, self.password) + + def login(self, username=None, password=None): + """Login to a vSphere server. + + >>> client.login(username='Administrator', password='strongpass') + + :param username: The username to authenticate as. + :type username: str + :param password: The password to authenticate with. + :type password: str + """ + if username is None: + username = self.username + if password is None: + password = self.password + logger.debug("Logging into server") + self.sc.sessionManager.Login(userName=username, password=password) + self._logged_in = True + + def logout(self): + """Logout of a vSphere server.""" + if self._logged_in is True: + self.si.flush_cache() + self.sc.sessionManager.Logout() + self._logged_in = False + + def invoke(self, method, _this, **kwargs): + """Invoke a method on the server. + + >>> client.invoke('CurrentTime', client.si) + + :param method: The method to invoke, as found in the SDK. + :type method: str + :param _this: The managed object reference against which to invoke \ + the method. + :type _this: ManagedObject + :param kwargs: The arguments to pass to the method, as \ + found in the SDK. + :type kwargs: TODO + + """ + if (self._logged_in is False and + method not in ["Login", "RetrieveServiceContent"]): + logger.critical("Cannot exec %s unless logged in", method) + raise NotLoggedInError("Cannot exec %s unless logged in" % method) + + for kwarg in kwargs: + kwargs[kwarg] = self._marshal(kwargs[kwarg]) + + result = getattr(self.service, method)(_this=_this, **kwargs) + if hasattr(result, '__iter__') is False: + logger.debug("Returning non-iterable result") + return result + + # We must traverse the result and convert any ManagedObjectReference + # to a psphere class, this will then be lazy initialised on use + logger.debug(result.__class__) + logger.debug("Result: %s", result) + logger.debug("Length: %s", len(result)) + if type(result) == list: + new_result = [] + for item in result: + new_result.append(self._unmarshal(item)) + else: + new_result = self._unmarshal(result) + + logger.debug("Finished in invoke.") + #property = self.find_and_destroy(property) + #print result + # Return the modified result to the caller + return new_result + + def _mor_to_pobject(self, mo_ref): + """Converts a MOR to a psphere object.""" + kls = classmapper(mo_ref._type) + new_object = kls(mo_ref, self) + return new_object + + def _marshal(self, obj): + """Walks an object and marshals any psphere object into MORs.""" + logger.debug("Checking if %s needs to be marshalled", obj) + if isinstance(obj, ManagedObject): + logger.debug("obj is a psphere object, converting to MOR") + return obj._mo_ref + + if isinstance(obj, list): + logger.debug("obj is a list, recursing it") + new_list = [] + for item in obj: + new_list.append(self._marshal(item)) + return new_list + + if not isinstance(obj, suds.sudsobject.Object): + logger.debug("%s is not a sudsobject subclass, skipping", obj) + return obj + + if hasattr(obj, '__iter__'): + logger.debug("obj is iterable, recursing it") + for (name, value) in obj: + setattr(obj, name, self._marshal(value)) + return obj + + # The obj has nothing that we want to marshal or traverse, return it + logger.debug("obj doesn't need to be marshalled") + return obj + + def _unmarshal(self, obj): + """Walks an object and unmarshals any MORs into psphere objects.""" + if isinstance(obj, suds.sudsobject.Object) is False: + logger.debug("%s is not a suds instance, skipping", obj) + return obj + + logger.debug("Processing:") + logger.debug(obj) + logger.debug("...with keylist:") + logger.debug(obj.__keylist__) + # If the obj that we're looking at has a _type key + # then create a class of that type and return it immediately + if "_type" in obj.__keylist__: + logger.debug("obj is a MOR, converting to psphere class") + return self._mor_to_pobject(obj) + + new_object = obj.__class__() + for sub_obj in obj: + logger.debug("Looking at %s of type %s", sub_obj, type(sub_obj)) + + if isinstance(sub_obj[1], list): + new_embedded_objs = [] + for emb_obj in sub_obj[1]: + new_emb_obj = self._unmarshal(emb_obj) + new_embedded_objs.append(new_emb_obj) + setattr(new_object, sub_obj[0], new_embedded_objs) + continue + + if not issubclass(sub_obj[1].__class__, suds.sudsobject.Object): + logger.debug("%s is not a sudsobject subclass, skipping", + sub_obj[1].__class__) + setattr(new_object, sub_obj[0], sub_obj[1]) + continue + + logger.debug("Obj keylist: %s", sub_obj[1].__keylist__) + if "_type" in sub_obj[1].__keylist__: + logger.debug("Converting nested MOR to psphere class:") + logger.debug(sub_obj[1]) + kls = classmapper(sub_obj[1]._type) + logger.debug("Setting %s.%s to %s", + new_object.__class__.__name__, + sub_obj[0], + sub_obj[1]) + setattr(new_object, sub_obj[0], kls(sub_obj[1], self)) + else: + logger.debug("Didn't find _type in:") + logger.debug(sub_obj[1]) + setattr(new_object, sub_obj[0], self._unmarshal(sub_obj[1])) + + return new_object + + def create(self, type_, **kwargs): + """Create a SOAP object of the requested type. + + >>> client.create('VirtualE1000') + + :param type_: The type of SOAP object to create. + :type type_: str + :param kwargs: TODO + :type kwargs: TODO + + """ + obj = self.factory.create("ns0:%s" % type_) + for key, value in kwargs.items(): + setattr(obj, key, value) + return obj + +# Notes +# ----- +# A view is a local, static representation of a managed object in +# the inventory. The view is not automatically synchronised with +# the server-side object and can therefore be out of date a moment +# after it is retrieved. +# +# Retrieval of only the properties you intend to use -- through +# the use of the properties parameter -- is considered best +# practise as the properties of some managed objects can be +# costly to retrieve. + + def get_view(self, mo_ref, properties=None): + """Get a view of a vSphere managed object. + + :param mo_ref: The MOR to get a view of + :type mo_ref: ManagedObjectReference + :param properties: A list of properties to retrieve from the \ + server + :type properties: list + :returns: A view representing the ManagedObjectReference. + :rtype: ManagedObject + + """ + # This maps the mo_ref into a psphere class and then instantiates it + kls = classmapper(mo_ref._type) + view = kls(mo_ref, self) + # Update the requested properties of the instance + #view.update_view_data(properties=properties) + + return view + + def get_views(self, mo_refs, properties=None): + """Get a list of local view's for multiple managed objects. + + :param mo_refs: The list of ManagedObjectReference's that views are \ + to be created for. + :type mo_refs: ManagedObjectReference + :param properties: The properties to retrieve in the views. + :type properties: list + :returns: A list of local instances representing the server-side \ + managed objects. + :rtype: list of ManagedObject's + + """ + property_specs = [] + for mo_ref in mo_refs: + property_spec = self.create('PropertySpec') + property_spec.type = str(mo_ref._type) + if properties is None: + properties = [] + else: + # Only retrieve the requested properties + if properties == "all": + property_spec.all = True + else: + property_spec.all = False + property_spec.pathSet = properties + property_specs.append(property_spec) + + object_specs = [] + for mo_ref in mo_refs: + object_spec = self.create('ObjectSpec') + object_spec.obj = mo_ref + object_specs.append(object_spec) + + pfs = self.create('PropertyFilterSpec') + pfs.propSet = property_specs + pfs.objectSet = object_specs + + object_contents = self.sc.propertyCollector.RetrieveProperties( + specSet=pfs) + views = [] + for object_content in object_contents: + # Update the instance with the data in object_content + object_content.obj._set_view_data(object_content=object_content) + views.append(object_content.obj) + + return views + + def get_search_filter_spec(self, begin_entity, property_spec): + """Build a PropertyFilterSpec capable of full inventory traversal. + + By specifying all valid traversal specs we are creating a PFS that + can recursively select any object under the given entity. + + :param begin_entity: The place in the MOB to start the search. + :type begin_entity: ManagedEntity + :param property_spec: TODO + :type property_spec: TODO + :returns: A PropertyFilterSpec, suitable for recursively searching \ + under the given ManagedEntity. + :rtype: PropertyFilterSpec + + """ + # The selection spec for additional objects we want to filter + ss_strings = ['resource_pool_traversal_spec', + 'resource_pool_vm_traversal_spec', + 'folder_traversal_spec', + 'datacenter_host_traversal_spec', + 'datacenter_vm_traversal_spec', + 'compute_resource_rp_traversal_spec', + 'compute_resource_host_traversal_spec', + 'host_vm_traversal_spec'] + + # Create a selection spec for each of the strings specified above + selection_specs = [ + self.create('SelectionSpec', name=ss_string) + for ss_string in ss_strings + ] + + # A traversal spec for deriving ResourcePool's from found VMs + rpts = self.create('TraversalSpec') + rpts.name = 'resource_pool_traversal_spec' + rpts.type = 'ResourcePool' + rpts.path = 'resourcePool' + rpts.selectSet = [selection_specs[0], selection_specs[1]] + + # A traversal spec for deriving ResourcePool's from found VMs + rpvts = self.create('TraversalSpec') + rpvts.name = 'resource_pool_vm_traversal_spec' + rpvts.type = 'ResourcePool' + rpvts.path = 'vm' + + crrts = self.create('TraversalSpec') + crrts.name = 'compute_resource_rp_traversal_spec' + crrts.type = 'ComputeResource' + crrts.path = 'resourcePool' + crrts.selectSet = [selection_specs[0], selection_specs[1]] + + crhts = self.create('TraversalSpec') + crhts.name = 'compute_resource_host_traversal_spec' + crhts.type = 'ComputeResource' + crhts.path = 'host' + + dhts = self.create('TraversalSpec') + dhts.name = 'datacenter_host_traversal_spec' + dhts.type = 'Datacenter' + dhts.path = 'hostFolder' + dhts.selectSet = [selection_specs[2]] + + dvts = self.create('TraversalSpec') + dvts.name = 'datacenter_vm_traversal_spec' + dvts.type = 'Datacenter' + dvts.path = 'vmFolder' + dvts.selectSet = [selection_specs[2]] + + hvts = self.create('TraversalSpec') + hvts.name = 'host_vm_traversal_spec' + hvts.type = 'HostSystem' + hvts.path = 'vm' + hvts.selectSet = [selection_specs[2]] + + fts = self.create('TraversalSpec') + fts.name = 'folder_traversal_spec' + fts.type = 'Folder' + fts.path = 'childEntity' + fts.selectSet = [selection_specs[2], selection_specs[3], + selection_specs[4], selection_specs[5], + selection_specs[6], selection_specs[7], + selection_specs[1]] + + obj_spec = self.create('ObjectSpec') + obj_spec.obj = begin_entity + obj_spec.selectSet = [fts, dvts, dhts, crhts, crrts, + rpts, hvts, rpvts] + + pfs = self.create('PropertyFilterSpec') + pfs.propSet = [property_spec] + pfs.objectSet = [obj_spec] + return pfs + + def invoke_task(self, method, **kwargs): + """Execute a \*_Task method and wait for it to complete. + + :param method: The \*_Task method to invoke. + :type method: str + :param kwargs: The arguments to pass to the method. + :type kwargs: TODO + + """ + # Don't execute methods which don't return a Task object + if not method.endswith('_Task'): + logger.error('invoke_task can only be used for methods which ' + 'return a ManagedObjectReference to a Task.') + return None + + task_mo_ref = self.invoke(method=method, **kwargs) + task = Task(task_mo_ref, self) + task.update_view_data(properties=['info']) + # TODO: This returns true when there is an error + while True: + if task.info.state == 'success': + return task + elif task.info.state == 'error': + # TODO: Handle error checking properly + raise TaskFailedError(task.info.error.localizedMessage) + + # TODO: Implement progresscallbackfunc + # Sleep two seconds and then refresh the data from the server + time.sleep(2) + task.update_view_data(properties=['info']) + + def find_entity_views(self, view_type, begin_entity=None, properties=None): + """Find all ManagedEntity's of the requested type. + + :param view_type: The type of ManagedEntity's to find. + :type view_type: str + :param begin_entity: The MOR to start searching for the entity. \ + The default is to start the search at the root folder. + :type begin_entity: ManagedObjectReference or None + :returns: A list of ManagedEntity's + :rtype: list + + """ + if properties is None: + properties = [] + + # Start the search at the root folder if no begin_entity was given + if not begin_entity: + begin_entity = self.sc.rootFolder._mo_ref + + property_spec = self.create('PropertySpec') + property_spec.type = view_type + property_spec.all = False + property_spec.pathSet = properties + + pfs = self.get_search_filter_spec(begin_entity, property_spec) + + # Retrieve properties from server and update entity + obj_contents = self.sc.propertyCollector.RetrieveProperties(specSet=pfs) + + views = [] + for obj_content in obj_contents: + logger.debug("In find_entity_view with object of type %s", + obj_content.obj.__class__.__name__) + obj_content.obj.update_view_data(properties=properties) + views.append(obj_content.obj) + + return views + + def find_entity_view(self, view_type, begin_entity=None, filter={}, + properties=None): + """Find a ManagedEntity of the requested type. + + Traverses the MOB looking for an entity matching the filter. + + :param view_type: The type of ManagedEntity to find. + :type view_type: str + :param begin_entity: The MOR to start searching for the entity. \ + The default is to start the search at the root folder. + :type begin_entity: ManagedObjectReference or None + :param filter: Key/value pairs to filter the results. The key is \ + a valid parameter of the ManagedEntity type. The value is what \ + that parameter should match. + :type filter: dict + :returns: If an entity is found, a ManagedEntity matching the search. + :rtype: ManagedEntity + + """ + if properties is None: + properties = [] + + kls = classmapper(view_type) + # Start the search at the root folder if no begin_entity was given + if not begin_entity: + begin_entity = self.sc.rootFolder._mo_ref + logger.debug("Using %s", self.sc.rootFolder._mo_ref) + + property_spec = self.create('PropertySpec') + property_spec.type = view_type + property_spec.all = False + property_spec.pathSet = filter.keys() + + pfs = self.get_search_filter_spec(begin_entity, property_spec) + + # Retrieve properties from server and update entity + #obj_contents = self.propertyCollector.RetrieveProperties(specSet=pfs) + obj_contents = self.sc.propertyCollector.RetrieveProperties(specSet=pfs) + + # TODO: Implement filtering + if not filter: + logger.warning('No filter specified, returning first match.') + # If no filter is specified we just return the first item + # in the list of returned objects + logger.debug("Creating class in find_entity_view (filter)") + view = kls(obj_contents[0].obj, self) + logger.debug("Completed creating class in find_entity_view (filter)") + #view.update_view_data(properties) + return view + + matched = False + # Iterate through obj_contents retrieved + for obj_content in obj_contents: + # If there are is no propSet, skip this one + if not obj_content.propSet: + continue + + matches = 0 + # Iterate through each property in the set + for prop in obj_content.propSet: + for key in filter.keys(): + # If the property name is in the defined filter + if prop.name == key: + # ...and it matches the value specified + # TODO: Regex this? + if prop.val == filter[prop.name]: + # We've found a match + matches += 1 + else: + break + else: + continue + if matches == len(filter): + filtered_obj_content = obj_content + matched = True + break + else: + continue + + if matched is not True: + # There were no matches + raise ObjectNotFoundError("No matching objects for filter") + + logger.debug("Creating class in find_entity_view") + view = kls(filtered_obj_content.obj._mo_ref, self) + logger.debug("Completed creating class in find_entity_view") + #view.update_view_data(properties=properties) + return view + +class ExtraConfigPlugin(MessagePlugin): + def addAttributeForValue(self, node): + if node.parent.name == 'extraConfig' and node.name == 'value': + node.set('xsi:type', 'xsd:string') + def marshalled(self, context): + context.envelope.walk(self.addAttributeForValue) diff --git a/awx/lib/site-packages/psphere/config.py b/awx/lib/site-packages/psphere/config.py new file mode 100644 index 0000000000..df81a9198a --- /dev/null +++ b/awx/lib/site-packages/psphere/config.py @@ -0,0 +1,26 @@ +import os +import yaml +import logging + +logger = logging.getLogger(__name__) +config_path = os.path.expanduser('~/.psphere/config.yaml') +try: + config_file = open(config_path, "r") + PSPHERE_CONFIG = yaml.load(config_file) + config_file.close() +except IOError: + logger.warning("Configuration file %s could not be opened, perhaps you" + " haven't created one?" % config_path) + PSPHERE_CONFIG = {"general": {}, "logging": {}} + pass + + +def _config_value(section, name, default=None): + file_value = None + if name in PSPHERE_CONFIG[section]: + file_value = PSPHERE_CONFIG[section][name] + + if file_value: + return file_value + else: + return default diff --git a/awx/lib/site-packages/psphere/errors.py b/awx/lib/site-packages/psphere/errors.py new file mode 100644 index 0000000000..6a79ecfb24 --- /dev/null +++ b/awx/lib/site-packages/psphere/errors.py @@ -0,0 +1,37 @@ +# Copyright 2010 Jonathan Kinred +# +# 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. + + +class ConfigError(Exception): + pass + + +class NotLoggedInError(Exception): + pass + + +class ObjectNotFoundError(Exception): + pass + + +class TaskFailedError(Exception): + pass + + +class TemplateNotFoundError(Exception): + pass + + +class NotImplementedError(Exception): + pass diff --git a/awx/lib/site-packages/psphere/managedobjects.py b/awx/lib/site-packages/psphere/managedobjects.py new file mode 100644 index 0000000000..0adc054bf4 --- /dev/null +++ b/awx/lib/site-packages/psphere/managedobjects.py @@ -0,0 +1,1500 @@ +from psphere import ManagedObject, cached_property + +class ExtensibleManagedObject(ManagedObject): + _valid_attrs = set(['availableField', 'value']) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + @cached_property + def availableField(self): + return self._get_dataobject("availableField", True) + @cached_property + def value(self): + return self._get_dataobject("value", True) + + +class Alarm(ExtensibleManagedObject): + _valid_attrs = set(['info']) + def __init__(self, mo_ref, client): + ExtensibleManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ExtensibleManagedObject._valid_attrs) + @cached_property + def info(self): + return self._get_dataobject("info", False) + + +class AlarmManager(ManagedObject): + _valid_attrs = set(['defaultExpression', 'description']) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + @cached_property + def defaultExpression(self): + return self._get_dataobject("defaultExpression", True) + @cached_property + def description(self): + return self._get_dataobject("description", False) + + +class AuthorizationManager(ManagedObject): + _valid_attrs = set(['description', 'privilegeList', 'roleList']) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + @cached_property + def description(self): + return self._get_dataobject("description", False) + @cached_property + def privilegeList(self): + return self._get_dataobject("privilegeList", True) + @cached_property + def roleList(self): + return self._get_dataobject("roleList", True) + + +class ManagedEntity(ExtensibleManagedObject): + _valid_attrs = set(['alarmActionsEnabled', 'configIssue', 'configStatus', 'customValue', 'declaredAlarmState', 'disabledMethod', 'effectiveRole', 'name', 'overallStatus', 'parent', 'permission', 'recentTask', 'tag', 'triggeredAlarmState']) + def __init__(self, mo_ref, client): + ExtensibleManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ExtensibleManagedObject._valid_attrs) + @cached_property + def alarmActionsEnabled(self): + return self._get_dataobject("alarmActionsEnabled", False) + @cached_property + def configIssue(self): + return self._get_dataobject("configIssue", True) + @cached_property + def configStatus(self): + return self._get_dataobject("configStatus", False) + @cached_property + def customValue(self): + return self._get_dataobject("customValue", True) + @cached_property + def declaredAlarmState(self): + return self._get_dataobject("declaredAlarmState", True) + @cached_property + def disabledMethod(self): + return self._get_dataobject("disabledMethod", True) + @cached_property + def effectiveRole(self): + return self._get_dataobject("effectiveRole", True) + @cached_property + def name(self): + return self._get_dataobject("name", False) + @cached_property + def overallStatus(self): + return self._get_dataobject("overallStatus", False) + @cached_property + def parent(self): + return self._get_mor("parent", False) + @cached_property + def permission(self): + return self._get_dataobject("permission", True) + @cached_property + def recentTask(self): + return self._get_mor("recentTask", True) + @cached_property + def tag(self): + return self._get_dataobject("tag", True) + @cached_property + def triggeredAlarmState(self): + return self._get_dataobject("triggeredAlarmState", True) + + @classmethod + def all(cls, client, properties=None): + if properties is None: + properties = [] + + if "name" not in properties: + properties.append("name") + + return client.find_entity_views(cls.__name__, properties=properties) + + @classmethod + def get(cls, client, **kwargs): + if "properties" in kwargs.keys(): + properties = kwargs["properties"] + # Delete properties key so it doesn't get filtered + del kwargs["properties"] + else: + properties = None + + if properties is None: + properties = [] + + # Automatically get the name property for every ManagedEntity + if "name" not in properties: + properties.append("name") + + filter = {} + for key in kwargs.keys(): + filter[key] = kwargs[key] + + return client.find_entity_view(cls.__name__, + filter=filter, + properties=properties) + + def __cmp__(self, other): + if self.name == other.name: + return 0 + if self.name < other.name: + return -1 + if self.name > other.name: + return 1 + +# def __str__(self): +# return self.name + + +class ComputeResource(ManagedEntity): + _valid_attrs = set(['configurationEx', 'datastore', 'environmentBrowser', 'host', 'network', 'resourcePool', 'summary']) + def __init__(self, mo_ref, client): + ManagedEntity.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedEntity._valid_attrs) + @cached_property + def configurationEx(self): + return self._get_dataobject("configurationEx", False) + @cached_property + def datastore(self): + return self._get_mor("datastore", True) + @cached_property + def environmentBrowser(self): + return self._get_mor("environmentBrowser", False) + @cached_property + def host(self): + return self._get_mor("host", True) + @cached_property + def network(self): + return self._get_mor("network", True) + @cached_property + def resourcePool(self): + return self._get_mor("resourcePool", False) + @cached_property + def summary(self): + return self._get_dataobject("summary", False) + + +class ClusterComputeResource(ComputeResource): + _valid_attrs = set(['actionHistory', 'configuration', 'drsFault', 'drsRecommendation', 'migrationHistory', 'recommendation']) + def __init__(self, mo_ref, client): + ComputeResource.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ComputeResource._valid_attrs) + @cached_property + def actionHistory(self): + return self._get_dataobject("actionHistory", True) + @cached_property + def configuration(self): + return self._get_dataobject("configuration", False) + @cached_property + def drsFault(self): + return self._get_dataobject("drsFault", True) + @cached_property + def drsRecommendation(self): + return self._get_dataobject("drsRecommendation", True) + @cached_property + def migrationHistory(self): + return self._get_dataobject("migrationHistory", True) + @cached_property + def recommendation(self): + return self._get_dataobject("recommendation", True) + + +class Profile(ManagedObject): + _valid_attrs = set(['complianceStatus', 'config', 'createdTime', 'description', 'entity', 'modifiedTime', 'name']) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + @cached_property + def complianceStatus(self): + return self._get_dataobject("complianceStatus", False) + @cached_property + def config(self): + return self._get_dataobject("config", False) + @cached_property + def createdTime(self): + return self._get_dataobject("createdTime", False) + @cached_property + def description(self): + return self._get_dataobject("description", False) + @cached_property + def entity(self): + return self._get_mor("entity", True) + @cached_property + def modifiedTime(self): + return self._get_dataobject("modifiedTime", False) + @cached_property + def name(self): + return self._get_dataobject("name", False) + + +class ClusterProfile(Profile): + _valid_attrs = set([]) + def __init__(self, mo_ref, client): + Profile.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, Profile._valid_attrs) + + +class ProfileManager(ManagedObject): + _valid_attrs = set(['profile']) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + @cached_property + def profile(self): + return self._get_mor("profile", True) + + +class ClusterProfileManager(ProfileManager): + _valid_attrs = set([]) + def __init__(self, mo_ref, client): + ProfileManager.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ProfileManager._valid_attrs) + + +class View(ManagedObject): + _valid_attrs = set([]) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + + +class ManagedObjectView(View): + _valid_attrs = set(['view']) + def __init__(self, mo_ref, client): + View.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, View._valid_attrs) + @cached_property + def view(self): + return self._get_mor("view", True) + + +class ContainerView(ManagedObjectView): + _valid_attrs = set(['container', 'recursive', 'type']) + def __init__(self, mo_ref, client): + ManagedObjectView.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObjectView._valid_attrs) + @cached_property + def container(self): + return self._get_mor("container", False) + @cached_property + def recursive(self): + return self._get_dataobject("recursive", False) + @cached_property + def type(self): + return self._get_dataobject("type", True) + + +class CustomFieldsManager(ManagedObject): + _valid_attrs = set(['field']) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + @cached_property + def field(self): + return self._get_dataobject("field", True) + + +class CustomizationSpecManager(ManagedObject): + _valid_attrs = set(['encryptionKey', 'info']) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + @cached_property + def encryptionKey(self): + return self._get_dataobject("encryptionKey", True) + @cached_property + def info(self): + return self._get_dataobject("info", True) + + +class Datacenter(ManagedEntity): + _valid_attrs = set(['datastore', 'datastoreFolder', 'hostFolder', 'network', 'networkFolder', 'vmFolder']) + def __init__(self, mo_ref, client): + ManagedEntity.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedEntity._valid_attrs) + @cached_property + def datastore(self): + return self._get_mor("datastore", True) + @cached_property + def datastoreFolder(self): + return self._get_mor("datastoreFolder", False) + @cached_property + def hostFolder(self): + return self._get_mor("hostFolder", False) + @cached_property + def network(self): + return self._get_mor("network", True) + @cached_property + def networkFolder(self): + return self._get_mor("networkFolder", False) + @cached_property + def vmFolder(self): + return self._get_mor("vmFolder", False) + + +class Datastore(ManagedEntity): + _valid_attrs = set(['browser', 'capability', 'host', 'info', 'iormConfiguration', 'summary', 'vm']) + def __init__(self, mo_ref, client): + ManagedEntity.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedEntity._valid_attrs) + @cached_property + def browser(self): + return self._get_mor("browser", False) + @cached_property + def capability(self): + return self._get_dataobject("capability", False) + @cached_property + def host(self): + return self._get_dataobject("host", True) + @cached_property + def info(self): + return self._get_dataobject("info", False) + @cached_property + def iormConfiguration(self): + return self._get_dataobject("iormConfiguration", False) + @cached_property + def summary(self): + return self._get_dataobject("summary", False) + @cached_property + def vm(self): + return self._get_mor("vm", True) + + +class DiagnosticManager(ManagedObject): + _valid_attrs = set([]) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + + +class Network(ManagedEntity): + _valid_attrs = set(['host', 'name', 'summary', 'vm']) + def __init__(self, mo_ref, client): + ManagedEntity.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedEntity._valid_attrs) + @cached_property + def host(self): + return self._get_mor("host", True) + @cached_property + def name(self): + return self._get_dataobject("name", False) + @cached_property + def summary(self): + return self._get_dataobject("summary", False) + @cached_property + def vm(self): + return self._get_mor("vm", True) + + +class DistributedVirtualPortgroup(Network): + _valid_attrs = set(['config', 'key', 'portKeys']) + def __init__(self, mo_ref, client): + Network.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, Network._valid_attrs) + @cached_property + def config(self): + return self._get_dataobject("config", False) + @cached_property + def key(self): + return self._get_dataobject("key", False) + @cached_property + def portKeys(self): + return self._get_dataobject("portKeys", True) + + +class DistributedVirtualSwitch(ManagedEntity): + _valid_attrs = set(['capability', 'config', 'networkResourcePool', 'portgroup', 'summary', 'uuid']) + def __init__(self, mo_ref, client): + ManagedEntity.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedEntity._valid_attrs) + @cached_property + def capability(self): + return self._get_dataobject("capability", False) + @cached_property + def config(self): + return self._get_dataobject("config", False) + @cached_property + def networkResourcePool(self): + return self._get_dataobject("networkResourcePool", True) + @cached_property + def portgroup(self): + return self._get_mor("portgroup", True) + @cached_property + def summary(self): + return self._get_dataobject("summary", False) + @cached_property + def uuid(self): + return self._get_dataobject("uuid", False) + + +class DistributedVirtualSwitchManager(ManagedObject): + _valid_attrs = set([]) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + + +class EnvironmentBrowser(ManagedObject): + _valid_attrs = set(['datastoreBrowser']) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + @cached_property + def datastoreBrowser(self): + return self._get_mor("datastoreBrowser", False) + + +class HistoryCollector(ManagedObject): + _valid_attrs = set(['filter']) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + @cached_property + def filter(self): + return self._get_dataobject("filter", False) + + +class EventHistoryCollector(HistoryCollector): + _valid_attrs = set(['latestPage']) + def __init__(self, mo_ref, client): + HistoryCollector.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, HistoryCollector._valid_attrs) + @cached_property + def latestPage(self): + return self._get_dataobject("latestPage", True) + + +class EventManager(ManagedObject): + _valid_attrs = set(['description', 'latestEvent', 'maxCollector']) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + @cached_property + def description(self): + return self._get_dataobject("description", False) + @cached_property + def latestEvent(self): + return self._get_dataobject("latestEvent", False) + @cached_property + def maxCollector(self): + return self._get_dataobject("maxCollector", False) + + +class ExtensionManager(ManagedObject): + _valid_attrs = set(['extensionList']) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + @cached_property + def extensionList(self): + return self._get_dataobject("extensionList", True) + + +class FileManager(ManagedObject): + _valid_attrs = set([]) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + + +class Folder(ManagedEntity): + _valid_attrs = set(['childEntity', 'childType']) + def __init__(self, mo_ref, client): + ManagedEntity.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedEntity._valid_attrs) + @cached_property + def childEntity(self): + return self._get_mor("childEntity", True) + @cached_property + def childType(self): + return self._get_dataobject("childType", True) + + +class GuestAuthManager(ManagedObject): + _valid_attrs = set([]) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + + +class GuestFileManager(ManagedObject): + _valid_attrs = set([]) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + + +class GuestOperationsManager(ManagedObject): + _valid_attrs = set(['authManager', 'fileManager', 'processManager']) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + @cached_property + def authManager(self): + return self._get_mor("authManager", False) + @cached_property + def fileManager(self): + return self._get_mor("fileManager", False) + @cached_property + def processManager(self): + return self._get_mor("processManager", False) + + +class GuestProcessManager(ManagedObject): + _valid_attrs = set([]) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + +class HostAuthenticationStore(ManagedObject): + _valid_attrs = set(['info']) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + @cached_property + def info(self): + return self._get_dataobject("info", False) + + +class HostDirectoryStore(HostAuthenticationStore): + _valid_attrs = set([]) + def __init__(self, mo_ref, client): + HostAuthenticationStore.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, HostAuthenticationStore._valid_attrs) + + +class HostActiveDirectoryAuthentication(HostDirectoryStore): + _valid_attrs = set([]) + def __init__(self, mo_ref, client): + HostDirectoryStore.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, HostDirectoryStore._valid_attrs) + + +class HostAuthenticationManager(ManagedObject): + _valid_attrs = set(['info', 'supportedStore']) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + @cached_property + def info(self): + return self._get_dataobject("info", False) + @cached_property + def supportedStore(self): + return self._get_mor("supportedStore", True) + + +class HostAutoStartManager(ManagedObject): + _valid_attrs = set(['config']) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + @cached_property + def config(self): + return self._get_dataobject("config", False) + + +class HostBootDeviceSystem(ManagedObject): + _valid_attrs = set([]) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + +class HostCacheConfigurationManager(ManagedObject): + _valid_attrs = set(['cacheConfigurationInfo']) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + @cached_property + def cacheConfigurationInfo(self): + return self._get_dataobject("cacheConfigurationInfo", True) + +class HostCpuSchedulerSystem(ExtensibleManagedObject): + _valid_attrs = set(['hyperthreadInfo']) + def __init__(self, mo_ref, client): + ExtensibleManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ExtensibleManagedObject._valid_attrs) + @cached_property + def hyperthreadInfo(self): + return self._get_dataobject("hyperthreadInfo", False) + + +class HostDatastoreBrowser(ManagedObject): + _valid_attrs = set(['datastore', 'supportedType']) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + @cached_property + def datastore(self): + return self._get_mor("datastore", True) + @cached_property + def supportedType(self): + return self._get_dataobject("supportedType", True) + + +class HostDatastoreSystem(ManagedObject): + _valid_attrs = set(['capabilities', 'datastore']) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + @cached_property + def capabilities(self): + return self._get_dataobject("capabilities", False) + @cached_property + def datastore(self): + return self._get_mor("datastore", True) + + +class HostDateTimeSystem(ManagedObject): + _valid_attrs = set(['dateTimeInfo']) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + @cached_property + def dateTimeInfo(self): + return self._get_dataobject("dateTimeInfo", False) + + +class HostDiagnosticSystem(ManagedObject): + _valid_attrs = set(['activePartition']) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + @cached_property + def activePartition(self): + return self._get_dataobject("activePartition", False) + +class HostEsxAgentHostManager(ManagedObject): + _valid_attrs = set(['configInfo']) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + @cached_property + def configInfo(self): + return self._get_dataobject("configInfo", False) + +class HostFirewallSystem(ExtensibleManagedObject): + _valid_attrs = set(['firewallInfo']) + def __init__(self, mo_ref, client): + ExtensibleManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ExtensibleManagedObject._valid_attrs) + @cached_property + def firewallInfo(self): + return self._get_dataobject("firewallInfo", False) + + +class HostFirmwareSystem(ManagedObject): + _valid_attrs = set([]) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + + +class HostHealthStatusSystem(ManagedObject): + _valid_attrs = set(['runtime']) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + @cached_property + def runtime(self): + return self._get_dataobject("runtime", False) + +class HostImageConfigManager(ManagedObject): + _valid_attrs = set([]) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + +class HostKernelModuleSystem(ManagedObject): + _valid_attrs = set([]) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + + +class HostLocalAccountManager(ManagedObject): + _valid_attrs = set([]) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + + +class HostLocalAuthentication(HostAuthenticationStore): + _valid_attrs = set([]) + def __init__(self, mo_ref, client): + HostAuthenticationStore.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, HostAuthenticationStore._valid_attrs) + + +class HostMemorySystem(ExtensibleManagedObject): + _valid_attrs = set(['consoleReservationInfo', 'virtualMachineReservationInfo']) + def __init__(self, mo_ref, client): + ExtensibleManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ExtensibleManagedObject._valid_attrs) + @cached_property + def consoleReservationInfo(self): + return self._get_dataobject("consoleReservationInfo", False) + @cached_property + def virtualMachineReservationInfo(self): + return self._get_dataobject("virtualMachineReservationInfo", False) + + +class HostNetworkSystem(ExtensibleManagedObject): + _valid_attrs = set(['capabilities', 'consoleIpRouteConfig', 'dnsConfig', 'ipRouteConfig', 'networkConfig', 'networkInfo', 'offloadCapabilities']) + def __init__(self, mo_ref, client): + ExtensibleManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ExtensibleManagedObject._valid_attrs) + @cached_property + def capabilities(self): + return self._get_dataobject("capabilities", False) + @cached_property + def consoleIpRouteConfig(self): + return self._get_dataobject("consoleIpRouteConfig", False) + @cached_property + def dnsConfig(self): + return self._get_dataobject("dnsConfig", False) + @cached_property + def ipRouteConfig(self): + return self._get_dataobject("ipRouteConfig", False) + @cached_property + def networkConfig(self): + return self._get_dataobject("networkConfig", False) + @cached_property + def networkInfo(self): + return self._get_dataobject("networkInfo", False) + @cached_property + def offloadCapabilities(self): + return self._get_dataobject("offloadCapabilities", False) + + +class HostPatchManager(ManagedObject): + _valid_attrs = set([]) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + + +class HostPciPassthruSystem(ExtensibleManagedObject): + _valid_attrs = set(['pciPassthruInfo']) + def __init__(self, mo_ref, client): + ExtensibleManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ExtensibleManagedObject._valid_attrs) + @cached_property + def pciPassthruInfo(self): + return self._get_dataobject("pciPassthruInfo", True) + + +class HostPowerSystem(ManagedObject): + _valid_attrs = set(['capability', 'info']) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + @cached_property + def capability(self): + return self._get_dataobject("capability", False) + @cached_property + def info(self): + return self._get_dataobject("info", False) + + +class HostProfile(Profile): + _valid_attrs = set(['referenceHost']) + def __init__(self, mo_ref, client): + Profile.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, Profile._valid_attrs) + @cached_property + def referenceHost(self): + return self._get_mor("referenceHost", False) + + +class HostProfileManager(ProfileManager): + _valid_attrs = set([]) + def __init__(self, mo_ref, client): + ProfileManager.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ProfileManager._valid_attrs) + + +class HostServiceSystem(ExtensibleManagedObject): + _valid_attrs = set(['serviceInfo']) + def __init__(self, mo_ref, client): + ExtensibleManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ExtensibleManagedObject._valid_attrs) + @cached_property + def serviceInfo(self): + return self._get_dataobject("serviceInfo", False) + + +class HostSnmpSystem(ManagedObject): + _valid_attrs = set(['configuration', 'limits']) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + @cached_property + def configuration(self): + return self._get_dataobject("configuration", False) + @cached_property + def limits(self): + return self._get_dataobject("limits", False) + + +class HostStorageSystem(ExtensibleManagedObject): + _valid_attrs = set(['fileSystemVolumeInfo', 'multipathStateInfo', 'storageDeviceInfo', 'systemFile']) + def __init__(self, mo_ref, client): + ExtensibleManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ExtensibleManagedObject._valid_attrs) + @cached_property + def fileSystemVolumeInfo(self): + return self._get_dataobject("fileSystemVolumeInfo", False) + @cached_property + def multipathStateInfo(self): + return self._get_dataobject("multipathStateInfo", False) + @cached_property + def storageDeviceInfo(self): + return self._get_dataobject("storageDeviceInfo", False) + @cached_property + def systemFile(self): + return self._get_dataobject("systemFile", True) + + +class HostSystem(ManagedEntity): + _valid_attrs = set(['capability', 'config', 'configManager', 'datastore', 'datastoreBrowser', 'hardware', 'licensableResource', 'network', 'runtime', 'summary', 'systemResources', 'vm']) + def __init__(self, mo_ref, client): + ManagedEntity.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedEntity._valid_attrs) + @cached_property + def capability(self): + return self._get_dataobject("capability", False) + @cached_property + def config(self): + return self._get_dataobject("config", False) + @cached_property + def configManager(self): + return self._get_dataobject("configManager", False) + @cached_property + def datastore(self): + return self._get_mor("datastore", True) + @cached_property + def datastoreBrowser(self): + return self._get_mor("datastoreBrowser", False) + @cached_property + def licensableResource(self): + return self._get_dataobject("licensableResource", False) + @cached_property + def hardware(self): + return self._get_dataobject("hardware", False) + @cached_property + def network(self): + return self._get_mor("network", True) + @cached_property + def runtime(self): + return self._get_dataobject("runtime", False) + @cached_property + def summary(self): + return self._get_dataobject("summary", False) + @cached_property + def systemResources(self): + return self._get_dataobject("systemResources", False) + @cached_property + def vm(self): + return self._get_mor("vm", True) + + +class HostVirtualNicManager(ExtensibleManagedObject): + _valid_attrs = set(['info']) + def __init__(self, mo_ref, client): + ExtensibleManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ExtensibleManagedObject._valid_attrs) + @cached_property + def info(self): + return self._get_dataobject("info", False) + + +class HostVMotionSystem(ExtensibleManagedObject): + _valid_attrs = set(['ipConfig', 'netConfig']) + def __init__(self, mo_ref, client): + ExtensibleManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ExtensibleManagedObject._valid_attrs) + @cached_property + def ipConfig(self): + return self._get_dataobject("ipConfig", False) + @cached_property + def netConfig(self): + return self._get_dataobject("netConfig", False) + + +class HttpNfcLease(ManagedObject): + _valid_attrs = set(['error', 'info', 'initializeProgress', 'state']) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + @cached_property + def error(self): + return self._get_dataobject("error", False) + @cached_property + def info(self): + return self._get_dataobject("info", False) + @cached_property + def initializeProgress(self): + return self._get_dataobject("initializeProgress", False) + @cached_property + def state(self): + return self._get_dataobject("state", False) + + +class InventoryView(ManagedObjectView): + _valid_attrs = set([]) + def __init__(self, mo_ref, client): + ManagedObjectView.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObjectView._valid_attrs) + + +class IpPoolManager(ManagedObject): + _valid_attrs = set([]) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + +class IscsiManager(ManagedObject): + _valid_attrs = set([]) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + +class LicenseAssignmentManager(ManagedObject): + _valid_attrs = set([]) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + + +class LicenseManager(ManagedObject): + _valid_attrs = set(['diagnostics', 'evaluation', 'featureInfo', 'licenseAssignmentManager', 'licensedEdition', 'licenses', 'source', 'sourceAvailable']) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + @cached_property + def diagnostics(self): + return self._get_dataobject("diagnostics", False) + @cached_property + def evaluation(self): + return self._get_dataobject("evaluation", False) + @cached_property + def featureInfo(self): + return self._get_dataobject("featureInfo", True) + @cached_property + def licenseAssignmentManager(self): + return self._get_mor("licenseAssignmentManager", False) + @cached_property + def licensedEdition(self): + return self._get_dataobject("licensedEdition", False) + @cached_property + def licenses(self): + return self._get_dataobject("licenses", True) + @cached_property + def source(self): + return self._get_dataobject("source", False) + @cached_property + def sourceAvailable(self): + return self._get_dataobject("sourceAvailable", False) + + +class ListView(ManagedObjectView): + _valid_attrs = set([]) + def __init__(self, mo_ref, client): + ManagedObjectView.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObjectView._valid_attrs) + + +class LocalizationManager(ManagedObject): + _valid_attrs = set(['catalog']) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + @cached_property + def catalog(self): + return self._get_dataobject("catalog", True) + + +class OptionManager(ManagedObject): + _valid_attrs = set(['setting', 'supportedOption']) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + @cached_property + def setting(self): + return self._get_dataobject("setting", True) + @cached_property + def supportedOption(self): + return self._get_dataobject("supportedOption", True) + + +class OvfManager(ManagedObject): + _valid_attrs = set([]) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + + +class PerformanceManager(ManagedObject): + _valid_attrs = set(['description', 'historicalInterval', 'perfCounter']) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + @cached_property + def description(self): + return self._get_dataobject("description", False) + @cached_property + def historicalInterval(self): + return self._get_dataobject("historicalInterval", True) + @cached_property + def perfCounter(self): + return self._get_dataobject("perfCounter", True) + + +class ProfileComplianceManager(ManagedObject): + _valid_attrs = set([]) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + + +class PropertyCollector(ManagedObject): + _valid_attrs = set(['filter']) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + @cached_property + def filter(self): + return self._get_mor("filter", True) + + +class PropertyFilter(ManagedObject): + _valid_attrs = set(['partialUpdates', 'spec']) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + @cached_property + def partialUpdates(self): + return self._get_dataobject("partialUpdates", False) + @cached_property + def spec(self): + return self._get_dataobject("spec", False) + + +class ResourcePlanningManager(ManagedObject): + _valid_attrs = set([]) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + + +class ResourcePool(ManagedEntity): + _valid_attrs = set(['childConfiguration', 'config', 'owner', 'resourcePool', 'runtime', 'summary', 'vm']) + def __init__(self, mo_ref, client): + ManagedEntity.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedEntity._valid_attrs) + @cached_property + def childConfiguration(self): + return self._get_dataobject("childConfiguration", True) + @cached_property + def config(self): + return self._get_dataobject("config", False) + @cached_property + def owner(self): + return self._get_mor("owner", False) + @cached_property + def resourcePool(self): + return self._get_mor("resourcePool", True) + @cached_property + def runtime(self): + return self._get_dataobject("runtime", False) + @cached_property + def summary(self): + return self._get_dataobject("summary", False) + @cached_property + def vm(self): + return self._get_mor("vm", True) + + +class ScheduledTask(ExtensibleManagedObject): + _valid_attrs = set(['info']) + def __init__(self, mo_ref, client): + ExtensibleManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ExtensibleManagedObject._valid_attrs) + @cached_property + def info(self): + return self._get_dataobject("info", False) + + +class ScheduledTaskManager(ManagedObject): + _valid_attrs = set(['description', 'scheduledTask']) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + @cached_property + def description(self): + return self._get_dataobject("description", False) + @cached_property + def scheduledTask(self): + return self._get_mor("scheduledTask", True) + + +class SearchIndex(ManagedObject): + _valid_attrs = set([]) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + + +class ServiceInstance(ManagedObject): + _valid_attrs = set(['capability', 'content', 'serverClock']) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + @cached_property + def capability(self): + return self._get_dataobject("capability", False) + @cached_property + def content(self): + return self._get_dataobject("content", False) + @cached_property + def serverClock(self): + return self._get_dataobject("serverClock", False) + + +class SessionManager(ManagedObject): + _valid_attrs = set(['currentSession', 'defaultLocale', 'message', 'messageLocaleList', 'sessionList', 'supportedLocaleList']) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + @cached_property + def currentSession(self): + return self._get_dataobject("currentSession", False) + @cached_property + def defaultLocale(self): + return self._get_dataobject("defaultLocale", False) + @cached_property + def message(self): + return self._get_dataobject("message", False) + @cached_property + def messageLocaleList(self): + return self._get_dataobject("messageLocaleList", True) + @cached_property + def sessionList(self): + return self._get_dataobject("sessionList", True) + @cached_property + def supportedLocaleList(self): + return self._get_dataobject("supportedLocaleList", True) + +class StoragePod(Folder): + _valid_attrs = set(['podStorageDrsEntry', 'summary']) + def __init__(self, mo_ref, client): + Folder.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, Folder._valid_attrs) + @cached_property + def podStorageDrsEntry(self): + return self._get_dataobject("podStorageDrsEntry", False) + @cached_property + def summary(self): + return self._get_dataobject("summary", False) + +class StorageResourceManager(ManagedObject): + _valid_attrs = set([]) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + + +class Task(ExtensibleManagedObject): + _valid_attrs = set(['info']) + def __init__(self, mo_ref, client): + ExtensibleManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ExtensibleManagedObject._valid_attrs) + @cached_property + def info(self): + return self._get_dataobject("info", False) + + +class TaskHistoryCollector(HistoryCollector): + _valid_attrs = set(['latestPage']) + def __init__(self, mo_ref, client): + HistoryCollector.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, HistoryCollector._valid_attrs) + @cached_property + def latestPage(self): + return self._get_dataobject("latestPage", True) + + +class TaskManager(ManagedObject): + _valid_attrs = set(['description', 'maxCollector', 'recentTask']) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + @cached_property + def description(self): + return self._get_dataobject("description", False) + @cached_property + def maxCollector(self): + return self._get_dataobject("maxCollector", False) + @cached_property + def recentTask(self): + return self._get_mor("recentTask", True) + + +class UserDirectory(ManagedObject): + _valid_attrs = set(['domainList']) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + @cached_property + def domainList(self): + return self._get_dataobject("domainList", True) + + +class ViewManager(ManagedObject): + _valid_attrs = set(['viewList']) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + @cached_property + def viewList(self): + return self._get_mor("viewList", True) + + +class VirtualApp(ResourcePool): + _valid_attrs = set(['childLink', 'datastore', 'network', 'parentFolder', 'parentVApp', 'vAppConfig']) + def __init__(self, mo_ref, client): + ResourcePool.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ResourcePool._valid_attrs) + @cached_property + def childLink(self): + return self._get_dataobject("childLink", True) + @cached_property + def datastore(self): + return self._get_mor("datastore", True) + @cached_property + def network(self): + return self._get_mor("network", True) + @cached_property + def parentFolder(self): + return self._get_mor("parentFolder", False) + @cached_property + def parentVApp(self): + return self._get_mor("parentVApp", False) + @cached_property + def vAppConfig(self): + return self._get_dataobject("vAppConfig", False) + + +class VirtualDiskManager(ManagedObject): + _valid_attrs = set([]) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + + +class VirtualizationManager(ManagedObject): + _valid_attrs = set([]) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + + +class VirtualMachine(ManagedEntity): + _valid_attrs = set(['capability', 'config', 'datastore', 'environmentBrowser', 'guest', 'guestHeartbeatStatus', 'layout', 'layoutEx', 'network', 'parentVApp', 'resourceConfig', 'resourcePool', 'rootSnapshot', 'runtime', 'snapshot', 'storage', 'summary']) + def __init__(self, mo_ref, client): + ManagedEntity.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedEntity._valid_attrs) + @cached_property + def capability(self): + return self._get_dataobject("capability", False) + @cached_property + def config(self): + return self._get_dataobject("config", False) + @cached_property + def datastore(self): + return self._get_mor("datastore", True) + @cached_property + def environmentBrowser(self): + return self._get_mor("environmentBrowser", False) + @cached_property + def guest(self): + return self._get_dataobject("guest", False) + @cached_property + def guestHeartbeatStatus(self): + return self._get_dataobject("guestHeartbeatStatus", False) + @cached_property + def layout(self): + return self._get_dataobject("layout", False) + @cached_property + def layoutEx(self): + return self._get_dataobject("layoutEx", False) + @cached_property + def network(self): + return self._get_mor("network", True) + @cached_property + def parentVApp(self): + return self._get_mor("parentVApp", False) + @cached_property + def resourceConfig(self): + return self._get_dataobject("resourceConfig", False) + @cached_property + def resourcePool(self): + return self._get_mor("resourcePool", False) + @cached_property + def rootSnapshot(self): + return self._get_mor("rootSnapshot", True) + @cached_property + def runtime(self): + return self._get_dataobject("runtime", False) + @cached_property + def snapshot(self): + return self._get_dataobject("snapshot", False) + @cached_property + def storage(self): + return self._get_dataobject("storage", False) + @cached_property + def summary(self): + return self._get_dataobject("summary", False) + + +class VirtualMachineCompatibilityChecker(ManagedObject): + _valid_attrs = set([]) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + + +class VirtualMachineProvisioningChecker(ManagedObject): + _valid_attrs = set([]) + def __init__(self, mo_ref, client): + ManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ManagedObject._valid_attrs) + + +class VirtualMachineSnapshot(ExtensibleManagedObject): + _valid_attrs = set(['childSnapshot', 'config']) + def __init__(self, mo_ref, client): + ExtensibleManagedObject.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, ExtensibleManagedObject._valid_attrs) + @cached_property + def childSnapshot(self): + return self._get_mor("childSnapshot", True) + @cached_property + def config(self): + return self._get_dataobject("config", False) + + +class VmwareDistributedVirtualSwitch(DistributedVirtualSwitch): + _valid_attrs = set([]) + def __init__(self, mo_ref, client): + DistributedVirtualSwitch.__init__(self, mo_ref, client) + self._valid_attrs = set.union(self._valid_attrs, DistributedVirtualSwitch._valid_attrs) + + +classmap = dict((x.__name__, x) for x in ( + ExtensibleManagedObject, + Alarm, + AlarmManager, + AuthorizationManager, + ManagedEntity, + ComputeResource, + ClusterComputeResource, + Profile, + ClusterProfile, + ProfileManager, + ClusterProfileManager, + View, + ManagedObjectView, + ContainerView, + CustomFieldsManager, + CustomizationSpecManager, + Datacenter, + Datastore, + DiagnosticManager, + Network, + DistributedVirtualPortgroup, + DistributedVirtualSwitch, + DistributedVirtualSwitchManager, + EnvironmentBrowser, + HistoryCollector, + EventHistoryCollector, + EventManager, + ExtensionManager, + FileManager, + Folder, + GuestAuthManager, + GuestFileManager, + GuestOperationsManager, + GuestProcessManager, + HostAuthenticationStore, + HostDirectoryStore, + HostActiveDirectoryAuthentication, + HostAuthenticationManager, + HostAutoStartManager, + HostBootDeviceSystem, + HostCacheConfigurationManager, + HostCpuSchedulerSystem, + HostDatastoreBrowser, + HostDatastoreSystem, + HostDateTimeSystem, + HostDiagnosticSystem, + HostEsxAgentHostManager, + HostFirewallSystem, + HostFirmwareSystem, + HostHealthStatusSystem, + HostImageConfigManager, + HostKernelModuleSystem, + HostLocalAccountManager, + HostLocalAuthentication, + HostMemorySystem, + HostNetworkSystem, + HostPatchManager, + HostPciPassthruSystem, + HostPowerSystem, + HostProfile, + HostProfileManager, + HostServiceSystem, + HostSnmpSystem, + HostStorageSystem, + HostSystem, + HostVirtualNicManager, + HostVMotionSystem, + HttpNfcLease, + InventoryView, + IpPoolManager, + IscsiManager, + LicenseAssignmentManager, + LicenseManager, + ListView, + LocalizationManager, + OptionManager, + OvfManager, + PerformanceManager, + ProfileComplianceManager, + PropertyCollector, + PropertyFilter, + ResourcePlanningManager, + ResourcePool, + ScheduledTask, + ScheduledTaskManager, + SearchIndex, + ServiceInstance, + SessionManager, + StoragePod, + StorageResourceManager, + Task, + TaskHistoryCollector, + TaskManager, + UserDirectory, + ViewManager, + VirtualApp, + VirtualDiskManager, + VirtualizationManager, + VirtualMachine, + VirtualMachineCompatibilityChecker, + VirtualMachineProvisioningChecker, + VirtualMachineSnapshot, + VmwareDistributedVirtualSwitch +)) +def classmapper(name): + return classmap[name] diff --git a/awx/lib/site-packages/psphere/scripting.py b/awx/lib/site-packages/psphere/scripting.py new file mode 100644 index 0000000000..c52609e155 --- /dev/null +++ b/awx/lib/site-packages/psphere/scripting.py @@ -0,0 +1,106 @@ + +""" +Parse command line options, allow users to append their own options and +read predefined configuration from the users .visdkrc file. +""" + +# Copyright 2010 Jonathan Kinred +# +# 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. + + +import optparse + +class BaseScript(object): + def __init__(self, client): + self.client = client + self.required_opts = [] + + usage = ('usage: %prog --url https:///sdk --username ' + '--password ') + self.parser = optparse.OptionParser(usage) + self.parser.add_option('--url', dest='url', + help='the url of the vSphere server') + self.parser.add_option('--username', dest='username', + help='the username to connnect with') + self.parser.add_option('--password', dest='password', + help='the password to connect with') + + def add_option(self, opt, dest, help, required): + self.parser.add_option(opt, dest=dest, help=help) + # TODO: Append to usage + # Add to the list of required options which we'll use later + if required: + self.required_opts.append(dest) + + def get_options(self): + """Get the options that have been set. + + Called after the user has added all their own options + and is ready to use the variables. + + """ + (options, args) = self.parser.parse_args() + + # Set values from .visdkrc, but only if they haven't already been set + visdkrc_opts = self.read_visdkrc() + for opt in self.config_vars: + if not getattr(options, opt): + # Try and use value from visdkrc + if visdkrc_opts: + if opt in visdkrc_opts: + setattr(options, opt, visdkrc_opts[opt]) + + # Ensure all the required options are set + for opt in self.required_opts: + if opt not in dir(options) or getattr(options, opt) == None: + self.parser.error('%s must be set!' % opt) + + return options + + def read_visdkrc(self): + try: + config = open(self.visdkrc) + except IOError, e: + if e.errno == 2: + # Doesn't exist, ignore it + return None + elif e.errno == 13: + print('ERROR: Permission denied opening %s' % self.visdkrc) + return None + else: + print('ERROR: Could not open %s: %s' % (self.visdkrc, e.strerror)) + return None + + lines = config.readlines() + config.close() + + parsed_opts = {} + for line in lines: + (key, value) = line.split('=') + parsed_opts[key] = value.rstrip('\n') + + visdkrc_opts = {} + if('VI_PROTOCOL' in parsed_opts and 'VI_SERVER' in parsed_opts and + 'VI_SERVICEPATH' in parsed_opts): + visdkrc_opts['url'] = '%s://%s%s' % (parsed_opts['VI_PROTOCOL'], + parsed_opts['VI_SERVER'], + parsed_opts['VI_SERVICEPATH']) + if 'VI_USERNAME' in parsed_opts: + visdkrc_opts['username'] = parsed_opts['VI_USERNAME'] + + if 'VI_PASSWORD' in parsed_opts: + visdkrc_opts['password'] = parsed_opts['VI_PASSWORD'] + + return visdkrc_opts + diff --git a/awx/lib/site-packages/psphere/soap.py b/awx/lib/site-packages/psphere/soap.py new file mode 100644 index 0000000000..2e86c6e8a1 --- /dev/null +++ b/awx/lib/site-packages/psphere/soap.py @@ -0,0 +1,95 @@ +""" +A leaky wrapper for the underlying suds library. +""" + +# Copyright 2010 Jonathan Kinred +# +# 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. + + +import logging +import urllib2 +import suds + +from pprint import pprint + +logger = logging.getLogger(__name__) + +class VimFault(Exception): + def __init__(self, fault): + self.fault = fault + self.fault_type = fault.__class__.__name__ + self._fault_dict = {} + for attr in fault: + self._fault_dict[attr[0]] = attr[1] + + Exception.__init__(self, "%s: %s" % (self.fault_type, self._fault_dict)) + + +def get_client(url): + client = suds.client.Client(url + "/vimService.wsdl") + client.set_options(location=url) + return client + + +def create(client, _type, **kwargs): + """Create a suds object of the requested _type.""" + obj = client.factory.create("ns0:%s" % _type) + for key, value in kwargs.items(): + setattr(obj, key, value) + return obj + + +def invoke(client, method, **kwargs): + """Invoke a method on the underlying soap service.""" + try: + # Proxy the method to the suds service + result = getattr(client.service, method)(**kwargs) + except AttributeError, e: + logger.critical("Unknown method: %s", method) + raise + except urllib2.URLError, e: + logger.debug(pprint(e)) + logger.debug("A URL related error occurred while invoking the '%s' " + "method on the VIM server, this can be caused by " + "name resolution or connection problems.", method) + logger.debug("The underlying error is: %s", e.reason[1]) + raise + except suds.client.TransportError, e: + logger.debug(pprint(e)) + logger.debug("TransportError: %s", e) + except suds.WebFault, e: + # Get the type of fault + logger.critical("SUDS Fault: %s" % e.fault.faultstring) + if len(e.fault.faultstring) > 0: + raise + + detail = e.document.childAtPath("/Envelope/Body/Fault/detail") + fault_type = detail.getChildren()[0].name + fault = create(fault_type) + if isinstance(e.fault.detail[0], list): + for attr in e.fault.detail[0]: + setattr(fault, attr[0], attr[1]) + else: + fault["text"] = e.fault.detail[0] + + raise VimFault(fault) + + return result + + +class ManagedObjectReference(suds.sudsobject.Property): + """Custom class to replace the suds generated class, which lacks _type.""" + def __init__(self, _type, value): + suds.sudsobject.Property.__init__(self, value) + self._type = _type diff --git a/awx/lib/site-packages/psphere/template.py b/awx/lib/site-packages/psphere/template.py new file mode 100644 index 0000000000..e0ddc03a19 --- /dev/null +++ b/awx/lib/site-packages/psphere/template.py @@ -0,0 +1,61 @@ +import glob +import os +import yaml +import logging + +from psphere import config +from psphere.errors import TemplateNotFoundError + +logger = logging.getLogger(__name__) + +template_path = os.path.expanduser(config._config_value("general", + "template_dir")) + +def _merge(first, second): + """Merge a list of templates. + + The templates will be merged with values in higher templates + taking precedence. + + :param templates: The templates to merge. + :type templates: list + + """ + return dict(first.items() + second.items()) + + +def load_template(name=None): + """Loads a template of the specified name. + + Templates are placed in the directory in YAML format with + a .yaml extension. + + If no name is specified then the function will return the default + template (/default.yaml) if it exists. + + :param name: The name of the template to load. + :type name: str or None (default) + + """ + if name is None: + name = "default" + + logger.info("Loading template with name %s", name) + try: + template_file = open("%s/%s.yaml" % (template_path, name)) + except IOError: + raise TemplateNotFoundError + + template = yaml.safe_load(template_file) + template_file.close() + if "extends" in template: + logger.debug("Merging %s with %s", name, template["extends"]) + template = _merge(load_template(template["extends"]), template) + + return template + + +def list_templates(): + """Returns a list of all templates.""" + templates = [f for f in glob.glob(os.path.join(template_path, '*.yaml'))] + return templates diff --git a/awx/lib/site-packages/psphere/wsdl/core-types.xsd b/awx/lib/site-packages/psphere/wsdl/core-types.xsd new file mode 100644 index 0000000000..af03160d11 --- /dev/null +++ b/awx/lib/site-packages/psphere/wsdl/core-types.xsd @@ -0,0 +1,267 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/awx/lib/site-packages/psphere/wsdl/query-messagetypes.xsd b/awx/lib/site-packages/psphere/wsdl/query-messagetypes.xsd new file mode 100644 index 0000000000..cffb6fe2a4 --- /dev/null +++ b/awx/lib/site-packages/psphere/wsdl/query-messagetypes.xsd @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/awx/lib/site-packages/psphere/wsdl/query-types.xsd b/awx/lib/site-packages/psphere/wsdl/query-types.xsd new file mode 100644 index 0000000000..abac085bf7 --- /dev/null +++ b/awx/lib/site-packages/psphere/wsdl/query-types.xsd @@ -0,0 +1,254 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/awx/lib/site-packages/psphere/wsdl/vim-messagetypes.xsd b/awx/lib/site-packages/psphere/wsdl/vim-messagetypes.xsd new file mode 100644 index 0000000000..c87fd1a0e6 --- /dev/null +++ b/awx/lib/site-packages/psphere/wsdl/vim-messagetypes.xsd @@ -0,0 +1,3082 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/awx/lib/site-packages/psphere/wsdl/vim-types.xsd b/awx/lib/site-packages/psphere/wsdl/vim-types.xsd new file mode 100644 index 0000000000..8175a25d70 --- /dev/null +++ b/awx/lib/site-packages/psphere/wsdl/vim-types.xsd @@ -0,0 +1,22161 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/awx/lib/site-packages/psphere/wsdl/vim.wsdl b/awx/lib/site-packages/psphere/wsdl/vim.wsdl new file mode 100644 index 0000000000..05b998bee9 --- /dev/null +++ b/awx/lib/site-packages/psphere/wsdl/vim.wsdl @@ -0,0 +1,19528 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/awx/lib/site-packages/psphere/wsdl/vimService.wsdl b/awx/lib/site-packages/psphere/wsdl/vimService.wsdl new file mode 100644 index 0000000000..3513ad0201 --- /dev/null +++ b/awx/lib/site-packages/psphere/wsdl/vimService.wsdl @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/awx/lib/site-packages/suds/__init__.py b/awx/lib/site-packages/suds/__init__.py new file mode 100644 index 0000000000..166a2065fb --- /dev/null +++ b/awx/lib/site-packages/suds/__init__.py @@ -0,0 +1,154 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Suds is a lightweight SOAP python client that provides a +service proxy for Web Services. +""" + +import os +import sys + +# +# Project properties +# + +__version__ = '0.4' +__build__="GA R699-20100913" + +# +# Exceptions +# + +class MethodNotFound(Exception): + def __init__(self, name): + Exception.__init__(self, "Method not found: '%s'" % name) + +class PortNotFound(Exception): + def __init__(self, name): + Exception.__init__(self, "Port not found: '%s'" % name) + +class ServiceNotFound(Exception): + def __init__(self, name): + Exception.__init__(self, "Service not found: '%s'" % name) + +class TypeNotFound(Exception): + def __init__(self, name): + Exception.__init__(self, "Type not found: '%s'" % tostr(name)) + +class BuildError(Exception): + msg = \ + """ + An error occured while building a instance of (%s). As a result + the object you requested could not be constructed. It is recommended + that you construct the type manually using a Suds object. + Please open a ticket with a description of this error. + Reason: %s + """ + def __init__(self, name, exception): + Exception.__init__(self, BuildError.msg % (name, exception)) + +class SoapHeadersNotPermitted(Exception): + msg = \ + """ + Method (%s) was invoked with SOAP headers. The WSDL does not + define SOAP headers for this method. Retry without the soapheaders + keyword argument. + """ + def __init__(self, name): + Exception.__init__(self, self.msg % name) + +class WebFault(Exception): + def __init__(self, fault, document): + if hasattr(fault, 'faultstring'): + Exception.__init__(self, "Server raised fault: '%s'" % fault.faultstring) + self.fault = fault + self.document = document + +# +# Logging +# + +class Repr: + def __init__(self, x): + self.x = x + def __str__(self): + return repr(self.x) + +# +# Utility +# + +def tostr(object, encoding=None): + """ get a unicode safe string representation of an object """ + if isinstance(object, basestring): + if encoding is None: + return object + else: + return object.encode(encoding) + if isinstance(object, tuple): + s = ['('] + for item in object: + if isinstance(item, basestring): + s.append(item) + else: + s.append(tostr(item)) + s.append(', ') + s.append(')') + return ''.join(s) + if isinstance(object, list): + s = ['['] + for item in object: + if isinstance(item, basestring): + s.append(item) + else: + s.append(tostr(item)) + s.append(', ') + s.append(']') + return ''.join(s) + if isinstance(object, dict): + s = ['{'] + for item in object.items(): + if isinstance(item[0], basestring): + s.append(item[0]) + else: + s.append(tostr(item[0])) + s.append(' = ') + if isinstance(item[1], basestring): + s.append(item[1]) + else: + s.append(tostr(item[1])) + s.append(', ') + s.append('}') + return ''.join(s) + try: + return unicode(object) + except: + return str(object) + +class null: + """ + The I{null} object. + Used to pass NULL for optional XML nodes. + """ + pass + +def objid(obj): + return obj.__class__.__name__\ + +':'+hex(id(obj)) + + +import client diff --git a/awx/lib/site-packages/suds/bindings/__init__.py b/awx/lib/site-packages/suds/bindings/__init__.py new file mode 100644 index 0000000000..5471ebadf1 --- /dev/null +++ b/awx/lib/site-packages/suds/bindings/__init__.py @@ -0,0 +1,20 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Provides modules containing classes to support Web Services (SOAP) +bindings. +""" \ No newline at end of file diff --git a/awx/lib/site-packages/suds/bindings/binding.py b/awx/lib/site-packages/suds/bindings/binding.py new file mode 100644 index 0000000000..4a7a996acd --- /dev/null +++ b/awx/lib/site-packages/suds/bindings/binding.py @@ -0,0 +1,538 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Provides classes for (WS) SOAP bindings. +""" + +from logging import getLogger +from suds import * +from suds.sax import Namespace +from suds.sax.parser import Parser +from suds.sax.document import Document +from suds.sax.element import Element +from suds.sudsobject import Factory, Object +from suds.mx import Content +from suds.mx.literal import Literal as MxLiteral +from suds.umx.basic import Basic as UmxBasic +from suds.umx.typed import Typed as UmxTyped +from suds.bindings.multiref import MultiRef +from suds.xsd.query import TypeQuery, ElementQuery +from suds.xsd.sxbasic import Element as SchemaElement +from suds.options import Options +from suds.plugin import PluginContainer +from copy import deepcopy + +log = getLogger(__name__) + +envns = ('SOAP-ENV', 'http://schemas.xmlsoap.org/soap/envelope/') + + +class Binding: + """ + The soap binding class used to process outgoing and imcoming + soap messages per the WSDL port binding. + @cvar replyfilter: The reply filter function. + @type replyfilter: (lambda s,r: r) + @ivar wsdl: The wsdl. + @type wsdl: L{suds.wsdl.Definitions} + @ivar schema: The collective schema contained within the wsdl. + @type schema: L{xsd.schema.Schema} + @ivar options: A dictionary options. + @type options: L{Options} + """ + + replyfilter = (lambda s,r: r) + + def __init__(self, wsdl): + """ + @param wsdl: A wsdl. + @type wsdl: L{wsdl.Definitions} + """ + self.wsdl = wsdl + self.multiref = MultiRef() + + def schema(self): + return self.wsdl.schema + + def options(self): + return self.wsdl.options + + def unmarshaller(self, typed=True): + """ + Get the appropriate XML decoder. + @return: Either the (basic|typed) unmarshaller. + @rtype: L{UmxTyped} + """ + if typed: + return UmxTyped(self.schema()) + else: + return UmxBasic() + + def marshaller(self): + """ + Get the appropriate XML encoder. + @return: An L{MxLiteral} marshaller. + @rtype: L{MxLiteral} + """ + return MxLiteral(self.schema(), self.options().xstq) + + def param_defs(self, method): + """ + Get parameter definitions. + Each I{pdef} is a tuple (I{name}, L{xsd.sxbase.SchemaObject}) + @param method: A servic emethod. + @type method: I{service.Method} + @return: A collection of parameter definitions + @rtype: [I{pdef},..] + """ + raise Exception, 'not implemented' + + def get_message(self, method, args, kwargs): + """ + Get the soap message for the specified method, args and soapheaders. + This is the entry point for creating the outbound soap message. + @param method: The method being invoked. + @type method: I{service.Method} + @param args: A list of args for the method invoked. + @type args: list + @param kwargs: Named (keyword) args for the method invoked. + @type kwargs: dict + @return: The soap envelope. + @rtype: L{Document} + """ + + content = self.headercontent(method) + header = self.header(content) + content = self.bodycontent(method, args, kwargs) + body = self.body(content) + env = self.envelope(header, body) + if self.options().prefixes: + body.normalizePrefixes() + env.promotePrefixes() + else: + env.refitPrefixes() + return Document(env) + + def get_reply(self, method, reply): + """ + Process the I{reply} for the specified I{method} by sax parsing the I{reply} + and then unmarshalling into python object(s). + @param method: The name of the invoked method. + @type method: str + @param reply: The reply XML received after invoking the specified method. + @type reply: str + @return: The unmarshalled reply. The returned value is an L{Object} for a + I{list} depending on whether the service returns a single object or a + collection. + @rtype: tuple ( L{Element}, L{Object} ) + """ + reply = self.replyfilter(reply) + sax = Parser() + replyroot = sax.parse(string=reply) + plugins = PluginContainer(self.options().plugins) + plugins.message.parsed(reply=replyroot) + soapenv = replyroot.getChild('Envelope') + soapenv.promotePrefixes() + soapbody = soapenv.getChild('Body') + self.detect_fault(soapbody) + soapbody = self.multiref.process(soapbody) + nodes = self.replycontent(method, soapbody) + rtypes = self.returned_types(method) + if len(rtypes) > 1: + result = self.replycomposite(rtypes, nodes) + return (replyroot, result) + if len(rtypes) == 1: + if rtypes[0].unbounded(): + result = self.replylist(rtypes[0], nodes) + return (replyroot, result) + if len(nodes): + unmarshaller = self.unmarshaller() + resolved = rtypes[0].resolve(nobuiltin=True) + result = unmarshaller.process(nodes[0], resolved) + return (replyroot, result) + return (replyroot, None) + + def detect_fault(self, body): + """ + Detect I{hidden} soapenv:Fault element in the soap body. + @param body: The soap envelope body. + @type body: L{Element} + @raise WebFault: When found. + """ + fault = body.getChild('Fault', envns) + if fault is None: + return + unmarshaller = self.unmarshaller(False) + p = unmarshaller.process(fault) + if self.options().faults: + raise WebFault(p, fault) + return self + + + def replylist(self, rt, nodes): + """ + Construct a I{list} reply. This mehod is called when it has been detected + that the reply is a list. + @param rt: The return I{type}. + @type rt: L{suds.xsd.sxbase.SchemaObject} + @param nodes: A collection of XML nodes. + @type nodes: [L{Element},...] + @return: A list of I{unmarshalled} objects. + @rtype: [L{Object},...] + """ + result = [] + resolved = rt.resolve(nobuiltin=True) + unmarshaller = self.unmarshaller() + for node in nodes: + sobject = unmarshaller.process(node, resolved) + result.append(sobject) + return result + + def replycomposite(self, rtypes, nodes): + """ + Construct a I{composite} reply. This method is called when it has been + detected that the reply has multiple root nodes. + @param rtypes: A list of known return I{types}. + @type rtypes: [L{suds.xsd.sxbase.SchemaObject},...] + @param nodes: A collection of XML nodes. + @type nodes: [L{Element},...] + @return: The I{unmarshalled} composite object. + @rtype: L{Object},... + """ + dictionary = {} + for rt in rtypes: + dictionary[rt.name] = rt + unmarshaller = self.unmarshaller() + composite = Factory.object('reply') + for node in nodes: + tag = node.name + rt = dictionary.get(tag, None) + if rt is None: + if node.get('id') is None: + raise Exception('<%s/> not mapped to message part' % tag) + else: + continue + resolved = rt.resolve(nobuiltin=True) + sobject = unmarshaller.process(node, resolved) + value = getattr(composite, tag, None) + if value is None: + if rt.unbounded(): + value = [] + setattr(composite, tag, value) + value.append(sobject) + else: + setattr(composite, tag, sobject) + else: + if not isinstance(value, list): + value = [value,] + setattr(composite, tag, value) + value.append(sobject) + return composite + + def get_fault(self, reply): + """ + Extract the fault from the specified soap reply. If I{faults} is True, an + exception is raised. Otherwise, the I{unmarshalled} fault L{Object} is + returned. This method is called when the server raises a I{web fault}. + @param reply: A soap reply message. + @type reply: str + @return: A fault object. + @rtype: tuple ( L{Element}, L{Object} ) + """ + reply = self.replyfilter(reply) + sax = Parser() + faultroot = sax.parse(string=reply) + soapenv = faultroot.getChild('Envelope') + soapbody = soapenv.getChild('Body') + fault = soapbody.getChild('Fault') + unmarshaller = self.unmarshaller(False) + p = unmarshaller.process(fault) + if self.options().faults: + raise WebFault(p, faultroot) + return (faultroot, p.detail) + + def mkparam(self, method, pdef, object): + """ + Builds a parameter for the specified I{method} using the parameter + definition (pdef) and the specified value (object). + @param method: A method name. + @type method: str + @param pdef: A parameter definition. + @type pdef: tuple: (I{name}, L{xsd.sxbase.SchemaObject}) + @param object: The parameter value. + @type object: any + @return: The parameter fragment. + @rtype: L{Element} + """ + marshaller = self.marshaller() + content = \ + Content(tag=pdef[0], + value=object, + type=pdef[1], + real=pdef[1].resolve()) + return marshaller.process(content) + + def mkheader(self, method, hdef, object): + """ + Builds a soapheader for the specified I{method} using the header + definition (hdef) and the specified value (object). + @param method: A method name. + @type method: str + @param hdef: A header definition. + @type hdef: tuple: (I{name}, L{xsd.sxbase.SchemaObject}) + @param object: The header value. + @type object: any + @return: The parameter fragment. + @rtype: L{Element} + """ + marshaller = self.marshaller() + if isinstance(object, (list, tuple)): + tags = [] + for item in object: + tags.append(self.mkheader(method, hdef, item)) + return tags + content = Content(tag=hdef[0], value=object, type=hdef[1]) + return marshaller.process(content) + + def envelope(self, header, body): + """ + Build the B{} for an soap outbound message. + @param header: The soap message B{header}. + @type header: L{Element} + @param body: The soap message B{body}. + @type body: L{Element} + @return: The soap envelope containing the body and header. + @rtype: L{Element} + """ + env = Element('Envelope', ns=envns) + env.addPrefix(Namespace.xsins[0], Namespace.xsins[1]) + env.append(header) + env.append(body) + return env + + def header(self, content): + """ + Build the B{} for an soap outbound message. + @param content: The header content. + @type content: L{Element} + @return: the soap body fragment. + @rtype: L{Element} + """ + header = Element('Header', ns=envns) + header.append(content) + return header + + def bodycontent(self, method, args, kwargs): + """ + Get the content for the soap I{body} node. + @param method: A service method. + @type method: I{service.Method} + @param args: method parameter values + @type args: list + @param kwargs: Named (keyword) args for the method invoked. + @type kwargs: dict + @return: The xml content for the + @rtype: [L{Element},..] + """ + raise Exception, 'not implemented' + + def headercontent(self, method): + """ + Get the content for the soap I{Header} node. + @param method: A service method. + @type method: I{service.Method} + @return: The xml content for the + @rtype: [L{Element},..] + """ + n = 0 + content = [] + wsse = self.options().wsse + if wsse is not None: + content.append(wsse.xml()) + headers = self.options().soapheaders + if not isinstance(headers, (tuple,list,dict)): + headers = (headers,) + if len(headers) == 0: + return content + pts = self.headpart_types(method) + if isinstance(headers, (tuple,list)): + for header in headers: + if isinstance(header, Element): + content.append(deepcopy(header)) + continue + if len(pts) == n: break + h = self.mkheader(method, pts[n], header) + ns = pts[n][1].namespace('ns0') + h.setPrefix(ns[0], ns[1]) + content.append(h) + n += 1 + else: + for pt in pts: + header = headers.get(pt[0]) + if header is None: + continue + h = self.mkheader(method, pt, header) + ns = pt[1].namespace('ns0') + h.setPrefix(ns[0], ns[1]) + content.append(h) + return content + + def replycontent(self, method, body): + """ + Get the reply body content. + @param method: A service method. + @type method: I{service.Method} + @param body: The soap body + @type body: L{Element} + @return: the body content + @rtype: [L{Element},...] + """ + raise Exception, 'not implemented' + + def body(self, content): + """ + Build the B{} for an soap outbound message. + @param content: The body content. + @type content: L{Element} + @return: the soap body fragment. + @rtype: L{Element} + """ + body = Element('Body', ns=envns) + body.append(content) + return body + + def bodypart_types(self, method, input=True): + """ + Get a list of I{parameter definitions} (pdef) defined for the specified method. + Each I{pdef} is a tuple (I{name}, L{xsd.sxbase.SchemaObject}) + @param method: A service method. + @type method: I{service.Method} + @param input: Defines input/output message. + @type input: boolean + @return: A list of parameter definitions + @rtype: [I{pdef},] + """ + result = [] + if input: + parts = method.soap.input.body.parts + else: + parts = method.soap.output.body.parts + for p in parts: + if p.element is not None: + query = ElementQuery(p.element) + else: + query = TypeQuery(p.type) + pt = query.execute(self.schema()) + if pt is None: + raise TypeNotFound(query.ref) + if p.type is not None: + pt = PartElement(p.name, pt) + if input: + if pt.name is None: + result.append((p.name, pt)) + else: + result.append((pt.name, pt)) + else: + result.append(pt) + return result + + def headpart_types(self, method, input=True): + """ + Get a list of I{parameter definitions} (pdef) defined for the specified method. + Each I{pdef} is a tuple (I{name}, L{xsd.sxbase.SchemaObject}) + @param method: A service method. + @type method: I{service.Method} + @param input: Defines input/output message. + @type input: boolean + @return: A list of parameter definitions + @rtype: [I{pdef},] + """ + result = [] + if input: + headers = method.soap.input.headers + else: + headers = method.soap.output.headers + for header in headers: + part = header.part + if part.element is not None: + query = ElementQuery(part.element) + else: + query = TypeQuery(part.type) + pt = query.execute(self.schema()) + if pt is None: + raise TypeNotFound(query.ref) + if part.type is not None: + pt = PartElement(part.name, pt) + if input: + if pt.name is None: + result.append((part.name, pt)) + else: + result.append((pt.name, pt)) + else: + result.append(pt) + return result + + def returned_types(self, method): + """ + Get the L{xsd.sxbase.SchemaObject} returned by the I{method}. + @param method: A service method. + @type method: I{service.Method} + @return: The name of the type return by the method. + @rtype: [I{rtype},..] + """ + result = [] + for rt in self.bodypart_types(method, input=False): + result.append(rt) + return result + + +class PartElement(SchemaElement): + """ + A part used to represent a message part when the part + references a schema type and thus assumes to be an element. + @ivar resolved: The part type. + @type resolved: L{suds.xsd.sxbase.SchemaObject} + """ + + def __init__(self, name, resolved): + """ + @param name: The part name. + @type name: str + @param resolved: The part type. + @type resolved: L{suds.xsd.sxbase.SchemaObject} + """ + root = Element('element', ns=Namespace.xsdns) + SchemaElement.__init__(self, resolved.schema, root) + self.__resolved = resolved + self.name = name + self.form_qualified = False + + def implany(self): + return self + + def optional(self): + return True + + def namespace(self, prefix=None): + return Namespace.default + + def resolve(self, nobuiltin=False): + if nobuiltin and self.__resolved.builtin(): + return self + else: + return self.__resolved + \ No newline at end of file diff --git a/awx/lib/site-packages/suds/bindings/document.py b/awx/lib/site-packages/suds/bindings/document.py new file mode 100644 index 0000000000..cace0d5cac --- /dev/null +++ b/awx/lib/site-packages/suds/bindings/document.py @@ -0,0 +1,160 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Provides classes for the (WS) SOAP I{document/literal}. +""" + +from logging import getLogger +from suds import * +from suds.bindings.binding import Binding +from suds.sax.element import Element + +log = getLogger(__name__) + + +class Document(Binding): + """ + The document/literal style. Literal is the only (@use) supported + since document/encoded is pretty much dead. + Although the soap specification supports multiple documents within the soap + , it is very uncommon. As such, suds presents an I{RPC} view of + service methods defined with a single document parameter. This is done so + that the user can pass individual parameters instead of one, single document. + To support the complete specification, service methods defined with multiple documents + (multiple message parts), must present a I{document} view for that method. + """ + + def bodycontent(self, method, args, kwargs): + # + # The I{wrapped} vs I{bare} style is detected in 2 ways. + # If there is 2+ parts in the message then it is I{bare}. + # If there is only (1) part and that part resolves to a builtin then + # it is I{bare}. Otherwise, it is I{wrapped}. + # + if not len(method.soap.input.body.parts): + return () + wrapped = method.soap.input.body.wrapped + if wrapped: + pts = self.bodypart_types(method) + root = self.document(pts[0]) + else: + root = [] + n = 0 + for pd in self.param_defs(method): + if n < len(args): + value = args[n] + else: + value = kwargs.get(pd[0]) + n += 1 + p = self.mkparam(method, pd, value) + if p is None: + continue + if not wrapped: + ns = pd[1].namespace('ns0') + p.setPrefix(ns[0], ns[1]) + root.append(p) + return root + + def replycontent(self, method, body): + wrapped = method.soap.output.body.wrapped + if wrapped: + return body[0].children + else: + return body.children + + def document(self, wrapper): + """ + Get the document root. For I{document/literal}, this is the + name of the wrapper element qualifed by the schema tns. + @param wrapper: The method name. + @type wrapper: L{xsd.sxbase.SchemaObject} + @return: A root element. + @rtype: L{Element} + """ + tag = wrapper[1].name + ns = wrapper[1].namespace('ns0') + d = Element(tag, ns=ns) + return d + + def mkparam(self, method, pdef, object): + # + # Expand list parameters into individual parameters + # each with the type information. This is because in document + # arrays are simply unbounded elements. + # + if isinstance(object, (list, tuple)): + tags = [] + for item in object: + tags.append(self.mkparam(method, pdef, item)) + return tags + else: + return Binding.mkparam(self, method, pdef, object) + + def param_defs(self, method): + # + # Get parameter definitions for document literal. + # The I{wrapped} vs I{bare} style is detected in 2 ways. + # If there is 2+ parts in the message then it is I{bare}. + # If there is only (1) part and that part resolves to a builtin then + # it is I{bare}. Otherwise, it is I{wrapped}. + # + pts = self.bodypart_types(method) + wrapped = method.soap.input.body.wrapped + if not wrapped: + return pts + result = [] + # wrapped + for p in pts: + resolved = p[1].resolve() + for child, ancestry in resolved: + if child.isattr(): + continue + if self.bychoice(ancestry): + log.debug( + '%s\ncontained by , excluded as param for %s()', + child, + method.name) + continue + result.append((child.name, child)) + return result + + def returned_types(self, method): + result = [] + wrapped = method.soap.output.body.wrapped + rts = self.bodypart_types(method, input=False) + if wrapped: + for pt in rts: + resolved = pt.resolve(nobuiltin=True) + for child, ancestry in resolved: + result.append(child) + break + else: + result += rts + return result + + def bychoice(self, ancestry): + """ + The ancestry contains a + @param ancestry: A list of ancestors. + @type ancestry: list + @return: True if contains + @rtype: boolean + """ + for x in ancestry: + if x.choice(): + return True + return False \ No newline at end of file diff --git a/awx/lib/site-packages/suds/bindings/multiref.py b/awx/lib/site-packages/suds/bindings/multiref.py new file mode 100644 index 0000000000..e539592b63 --- /dev/null +++ b/awx/lib/site-packages/suds/bindings/multiref.py @@ -0,0 +1,126 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Provides classes for handling soap multirefs. +""" + +from logging import getLogger +from suds import * +from suds.sax.element import Element + +log = getLogger(__name__) + +soapenc = (None, 'http://schemas.xmlsoap.org/soap/encoding/') + +class MultiRef: + """ + Resolves and replaces multirefs. + @ivar nodes: A list of non-multiref nodes. + @type nodes: list + @ivar catalog: A dictionary of multiref nodes by id. + @type catalog: dict + """ + + def __init__(self): + self.nodes = [] + self.catalog = {} + + def process(self, body): + """ + Process the specified soap envelope body and replace I{multiref} node + references with the contents of the referenced node. + @param body: A soap envelope body node. + @type body: L{Element} + @return: The processed I{body} + @rtype: L{Element} + """ + self.nodes = [] + self.catalog = {} + self.build_catalog(body) + self.update(body) + body.children = self.nodes + return body + + def update(self, node): + """ + Update the specified I{node} by replacing the I{multiref} references with + the contents of the referenced nodes and remove the I{href} attribute. + @param node: A node to update. + @type node: L{Element} + @return: The updated node + @rtype: L{Element} + """ + self.replace_references(node) + for c in node.children: + self.update(c) + return node + + def replace_references(self, node): + """ + Replacing the I{multiref} references with the contents of the + referenced nodes and remove the I{href} attribute. Warning: since + the I{ref} is not cloned, + @param node: A node to update. + @type node: L{Element} + """ + href = node.getAttribute('href') + if href is None: + return + id = href.getValue() + ref = self.catalog.get(id) + if ref is None: + log.error('soap multiref: %s, not-resolved', id) + return + node.append(ref.children) + node.setText(ref.getText()) + for a in ref.attributes: + if a.name != 'id': + node.append(a) + node.remove(href) + + def build_catalog(self, body): + """ + Create the I{catalog} of multiref nodes by id and the list of + non-multiref nodes. + @param body: A soap envelope body node. + @type body: L{Element} + """ + for child in body.children: + if self.soaproot(child): + self.nodes.append(child) + id = child.get('id') + if id is None: continue + key = '#%s' % id + self.catalog[key] = child + + def soaproot(self, node): + """ + Get whether the specified I{node} is a soap encoded root. + This is determined by examining @soapenc:root='1'. + The node is considered to be a root when the attribute + is not specified. + @param node: A node to evaluate. + @type node: L{Element} + @return: True if a soap encoded root. + @rtype: bool + """ + root = node.getAttribute('root', ns=soapenc) + if root is None: + return True + else: + return ( root.value == '1' ) + \ No newline at end of file diff --git a/awx/lib/site-packages/suds/bindings/rpc.py b/awx/lib/site-packages/suds/bindings/rpc.py new file mode 100644 index 0000000000..f780aa4898 --- /dev/null +++ b/awx/lib/site-packages/suds/bindings/rpc.py @@ -0,0 +1,98 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Provides classes for the (WS) SOAP I{rpc/literal} and I{rpc/encoded} bindings. +""" + +from logging import getLogger +from suds import * +from suds.mx.encoded import Encoded as MxEncoded +from suds.umx.encoded import Encoded as UmxEncoded +from suds.bindings.binding import Binding, envns +from suds.sax.element import Element + +log = getLogger(__name__) + + +encns = ('SOAP-ENC', 'http://schemas.xmlsoap.org/soap/encoding/') + +class RPC(Binding): + """ + RPC/Literal binding style. + """ + + def param_defs(self, method): + return self.bodypart_types(method) + + def envelope(self, header, body): + env = Binding.envelope(self, header, body) + env.addPrefix(encns[0], encns[1]) + env.set('%s:encodingStyle' % envns[0], + 'http://schemas.xmlsoap.org/soap/encoding/') + return env + + def bodycontent(self, method, args, kwargs): + n = 0 + root = self.method(method) + for pd in self.param_defs(method): + if n < len(args): + value = args[n] + else: + value = kwargs.get(pd[0]) + p = self.mkparam(method, pd, value) + if p is not None: + root.append(p) + n += 1 + return root + + def replycontent(self, method, body): + return body[0].children + + def method(self, method): + """ + Get the document root. For I{rpc/(literal|encoded)}, this is the + name of the method qualifed by the schema tns. + @param method: A service method. + @type method: I{service.Method} + @return: A root element. + @rtype: L{Element} + """ + ns = method.soap.input.body.namespace + if ns[0] is None: + ns = ('ns0', ns[1]) + method = Element(method.name, ns=ns) + return method + + +class Encoded(RPC): + """ + RPC/Encoded (section 5) binding style. + """ + + def marshaller(self): + return MxEncoded(self.schema()) + + def unmarshaller(self, typed=True): + """ + Get the appropriate XML decoder. + @return: Either the (basic|typed) unmarshaller. + @rtype: L{UmxTyped} + """ + if typed: + return UmxEncoded(self.schema()) + else: + return RPC.unmarshaller(self, typed) diff --git a/awx/lib/site-packages/suds/builder.py b/awx/lib/site-packages/suds/builder.py new file mode 100644 index 0000000000..c2aad9851f --- /dev/null +++ b/awx/lib/site-packages/suds/builder.py @@ -0,0 +1,121 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +The I{builder} module provides an wsdl/xsd defined types factory +""" + +from logging import getLogger +from suds import * +from suds.sudsobject import Factory + +log = getLogger(__name__) + + +class Builder: + """ Builder used to construct an object for types defined in the schema """ + + def __init__(self, resolver): + """ + @param resolver: A schema object name resolver. + @type resolver: L{resolver.Resolver} + """ + self.resolver = resolver + + def build(self, name): + """ build a an object for the specified typename as defined in the schema """ + if isinstance(name, basestring): + type = self.resolver.find(name) + if type is None: + raise TypeNotFound(name) + else: + type = name + cls = type.name + if type.mixed(): + data = Factory.property(cls) + else: + data = Factory.object(cls) + resolved = type.resolve() + md = data.__metadata__ + md.sxtype = resolved + md.ordering = self.ordering(resolved) + history = [] + self.add_attributes(data, resolved) + for child, ancestry in type.children(): + if self.skip_child(child, ancestry): + continue + self.process(data, child, history[:]) + return data + + def process(self, data, type, history): + """ process the specified type then process its children """ + if type in history: + return + if type.enum(): + return + history.append(type) + resolved = type.resolve() + value = None + if type.unbounded(): + value = [] + else: + if len(resolved) > 0: + if resolved.mixed(): + value = Factory.property(resolved.name) + md = value.__metadata__ + md.sxtype = resolved + else: + value = Factory.object(resolved.name) + md = value.__metadata__ + md.sxtype = resolved + md.ordering = self.ordering(resolved) + setattr(data, type.name, value) + if value is not None: + data = value + if not isinstance(data, list): + self.add_attributes(data, resolved) + for child, ancestry in resolved.children(): + if self.skip_child(child, ancestry): + continue + self.process(data, child, history[:]) + + def add_attributes(self, data, type): + """ add required attributes """ + for attr, ancestry in type.attributes(): + name = '_%s' % attr.name + value = attr.get_default() + setattr(data, name, value) + + def skip_child(self, child, ancestry): + """ get whether or not to skip the specified child """ + if child.any(): return True + for x in ancestry: + if x.choice(): + return True + return False + + def ordering(self, type): + """ get the ordering """ + result = [] + for child, ancestry in type.resolve(): + name = child.name + if child.name is None: + continue + if child.isattr(): + name = '_%s' % child.name + result.append(name) + return result + \ No newline at end of file diff --git a/awx/lib/site-packages/suds/cache.py b/awx/lib/site-packages/suds/cache.py new file mode 100644 index 0000000000..801c23cfd9 --- /dev/null +++ b/awx/lib/site-packages/suds/cache.py @@ -0,0 +1,337 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Contains basic caching classes. +""" + +import os +import suds +from tempfile import gettempdir as tmp +from suds.transport import * +from suds.sax.parser import Parser +from suds.sax.element import Element +from datetime import datetime as dt +from datetime import timedelta +from cStringIO import StringIO +from logging import getLogger +try: + import cPickle as pickle +except: + import pickle + +log = getLogger(__name__) + + +class Cache: + """ + An object object cache. + """ + + def get(self, id): + """ + Get a object from the cache by ID. + @param id: The object ID. + @type id: str + @return: The object, else None + @rtype: any + """ + raise Exception('not-implemented') + + def getf(self, id): + """ + Get a object from the cache by ID. + @param id: The object ID. + @type id: str + @return: The object, else None + @rtype: any + """ + raise Exception('not-implemented') + + def put(self, id, object): + """ + Put a object into the cache. + @param id: The object ID. + @type id: str + @param object: The object to add. + @type object: any + """ + raise Exception('not-implemented') + + def putf(self, id, fp): + """ + Write a fp into the cache. + @param id: The object ID. + @type id: str + @param fp: File pointer. + @type fp: file-like object. + """ + raise Exception('not-implemented') + + def purge(self, id): + """ + Purge a object from the cache by id. + @param id: A object ID. + @type id: str + """ + raise Exception('not-implemented') + + def clear(self): + """ + Clear all objects from the cache. + """ + raise Exception('not-implemented') + + +class NoCache(Cache): + """ + The passthru object cache. + """ + + def get(self, id): + return None + + def getf(self, id): + return None + + def put(self, id, object): + pass + + def putf(self, id, fp): + pass + + +class FileCache(Cache): + """ + A file-based URL cache. + @cvar fnprefix: The file name prefix. + @type fnsuffix: str + @ivar duration: The cached file duration which defines how + long the file will be cached. + @type duration: (unit, value) + @ivar location: The directory for the cached files. + @type location: str + """ + fnprefix = 'suds' + units = ('months', 'weeks', 'days', 'hours', 'minutes', 'seconds') + + def __init__(self, location=None, **duration): + """ + @param location: The directory for the cached files. + @type location: str + @param duration: The cached file duration which defines how + long the file will be cached. A duration=0 means forever. + The duration may be: (months|weeks|days|hours|minutes|seconds). + @type duration: {unit:value} + """ + if location is None: + location = os.path.join(tmp(), 'suds') + self.location = location + self.duration = (None, 0) + self.setduration(**duration) + self.checkversion() + + def fnsuffix(self): + """ + Get the file name suffix + @return: The suffix + @rtype: str + """ + return 'gcf' + + def setduration(self, **duration): + """ + Set the caching duration which defines how long the + file will be cached. + @param duration: The cached file duration which defines how + long the file will be cached. A duration=0 means forever. + The duration may be: (months|weeks|days|hours|minutes|seconds). + @type duration: {unit:value} + """ + if len(duration) == 1: + arg = duration.items()[0] + if not arg[0] in self.units: + raise Exception('must be: %s' % str(self.units)) + self.duration = arg + return self + + def setlocation(self, location): + """ + Set the location (directory) for the cached files. + @param location: The directory for the cached files. + @type location: str + """ + self.location = location + + def mktmp(self): + """ + Make the I{location} directory if it doesn't already exits. + """ + try: + if not os.path.isdir(self.location): + os.makedirs(self.location) + except: + log.debug(self.location, exc_info=1) + return self + + def put(self, id, bfr): + try: + fn = self.__fn(id) + f = self.open(fn, 'w') + f.write(bfr) + f.close() + return bfr + except: + log.debug(id, exc_info=1) + return bfr + + def putf(self, id, fp): + try: + fn = self.__fn(id) + f = self.open(fn, 'w') + f.write(fp.read()) + fp.close() + f.close() + return open(fn) + except: + log.debug(id, exc_info=1) + return fp + + def get(self, id): + try: + f = self.getf(id) + bfr = f.read() + f.close() + return bfr + except: + pass + + def getf(self, id): + try: + fn = self.__fn(id) + self.validate(fn) + return self.open(fn) + except: + pass + + def validate(self, fn): + """ + Validate that the file has not expired based on the I{duration}. + @param fn: The file name. + @type fn: str + """ + if self.duration[1] < 1: + return + created = dt.fromtimestamp(os.path.getctime(fn)) + d = { self.duration[0]:self.duration[1] } + expired = created+timedelta(**d) + if expired < dt.now(): + log.debug('%s expired, deleted', fn) + os.remove(fn) + + def clear(self): + for fn in os.listdir(self.location): + if os.path.isdir(fn): + continue + if fn.startswith(self.fnprefix): + log.debug('deleted: %s', fn) + os.remove(os.path.join(self.location, fn)) + + def purge(self, id): + fn = self.__fn(id) + try: + os.remove(fn) + except: + pass + + def open(self, fn, *args): + """ + Open the cache file making sure the directory is created. + """ + self.mktmp() + return open(fn, *args) + + def checkversion(self): + path = os.path.join(self.location, 'version') + try: + + f = self.open(path) + version = f.read() + f.close() + if version != suds.__version__: + raise Exception() + except: + self.clear() + f = self.open(path, 'w') + f.write(suds.__version__) + f.close() + + def __fn(self, id): + name = id + suffix = self.fnsuffix() + fn = '%s-%s.%s' % (self.fnprefix, name, suffix) + return os.path.join(self.location, fn) + + +class DocumentCache(FileCache): + """ + Provides xml document caching. + """ + + def fnsuffix(self): + return 'xml' + + def get(self, id): + try: + fp = FileCache.getf(self, id) + if fp is None: + return None + p = Parser() + return p.parse(fp) + except: + FileCache.purge(self, id) + + def put(self, id, object): + if isinstance(object, Element): + FileCache.put(self, id, str(object)) + return object + + +class ObjectCache(FileCache): + """ + Provides pickled object caching. + @cvar protocol: The pickling protocol. + @type protocol: int + """ + protocol = 2 + + def fnsuffix(self): + return 'px' + + def get(self, id): + try: + fp = FileCache.getf(self, id) + if fp is None: + return None + else: + return pickle.load(fp) + except: + FileCache.purge(self, id) + + def put(self, id, object): + bfr = pickle.dumps(object, self.protocol) + FileCache.put(self, id, bfr) + return object diff --git a/awx/lib/site-packages/suds/client.py b/awx/lib/site-packages/suds/client.py new file mode 100644 index 0000000000..5a74097508 --- /dev/null +++ b/awx/lib/site-packages/suds/client.py @@ -0,0 +1,785 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +The I{2nd generation} service proxy provides access to web services. +See I{README.txt} +""" + +import suds +import suds.metrics as metrics +from cookielib import CookieJar +from suds import * +from suds.reader import DefinitionsReader +from suds.transport import TransportError, Request +from suds.transport.https import HttpAuthenticated +from suds.servicedefinition import ServiceDefinition +from suds import sudsobject +from sudsobject import Factory as InstFactory +from sudsobject import Object +from suds.resolver import PathResolver +from suds.builder import Builder +from suds.wsdl import Definitions +from suds.cache import ObjectCache +from suds.sax.document import Document +from suds.sax.parser import Parser +from suds.options import Options +from suds.properties import Unskin +from urlparse import urlparse +from copy import deepcopy +from suds.plugin import PluginContainer +from logging import getLogger + +log = getLogger(__name__) + + +class Client(object): + """ + A lightweight web services client. + I{(2nd generation)} API. + @ivar wsdl: The WSDL object. + @type wsdl:L{Definitions} + @ivar service: The service proxy used to invoke operations. + @type service: L{Service} + @ivar factory: The factory used to create objects. + @type factory: L{Factory} + @ivar sd: The service definition + @type sd: L{ServiceDefinition} + @ivar messages: The last sent/received messages. + @type messages: str[2] + """ + @classmethod + def items(cls, sobject): + """ + Extract the I{items} from a suds object much like the + items() method works on I{dict}. + @param sobject: A suds object + @type sobject: L{Object} + @return: A list of items contained in I{sobject}. + @rtype: [(key, value),...] + """ + return sudsobject.items(sobject) + + @classmethod + def dict(cls, sobject): + """ + Convert a sudsobject into a dictionary. + @param sobject: A suds object + @type sobject: L{Object} + @return: A python dictionary containing the + items contained in I{sobject}. + @rtype: dict + """ + return sudsobject.asdict(sobject) + + @classmethod + def metadata(cls, sobject): + """ + Extract the metadata from a suds object. + @param sobject: A suds object + @type sobject: L{Object} + @return: The object's metadata + @rtype: L{sudsobject.Metadata} + """ + return sobject.__metadata__ + + def __init__(self, url, **kwargs): + """ + @param url: The URL for the WSDL. + @type url: str + @param kwargs: keyword arguments. + @see: L{Options} + """ + options = Options() + options.transport = HttpAuthenticated() + self.options = options + options.cache = ObjectCache(days=1) + self.set_options(**kwargs) + reader = DefinitionsReader(options, Definitions) + self.wsdl = reader.open(url) + plugins = PluginContainer(options.plugins) + plugins.init.initialized(wsdl=self.wsdl) + self.factory = Factory(self.wsdl) + self.service = ServiceSelector(self, self.wsdl.services) + self.sd = [] + for s in self.wsdl.services: + sd = ServiceDefinition(self.wsdl, s) + self.sd.append(sd) + self.messages = dict(tx=None, rx=None) + + def set_options(self, **kwargs): + """ + Set options. + @param kwargs: keyword arguments. + @see: L{Options} + """ + p = Unskin(self.options) + p.update(kwargs) + + def add_prefix(self, prefix, uri): + """ + Add I{static} mapping of an XML namespace prefix to a namespace. + This is useful for cases when a wsdl and referenced schemas make heavy + use of namespaces and those namespaces are subject to changed. + @param prefix: An XML namespace prefix. + @type prefix: str + @param uri: An XML namespace URI. + @type uri: str + @raise Exception: when prefix is already mapped. + """ + root = self.wsdl.root + mapped = root.resolvePrefix(prefix, None) + if mapped is None: + root.addPrefix(prefix, uri) + return + if mapped[1] != uri: + raise Exception('"%s" already mapped as "%s"' % (prefix, mapped)) + + def last_sent(self): + """ + Get last sent I{soap} message. + @return: The last sent I{soap} message. + @rtype: L{Document} + """ + return self.messages.get('tx') + + def last_received(self): + """ + Get last received I{soap} message. + @return: The last received I{soap} message. + @rtype: L{Document} + """ + return self.messages.get('rx') + + def clone(self): + """ + Get a shallow clone of this object. + The clone only shares the WSDL. All other attributes are + unique to the cloned object including options. + @return: A shallow clone. + @rtype: L{Client} + """ + class Uninitialized(Client): + def __init__(self): + pass + clone = Uninitialized() + clone.options = Options() + cp = Unskin(clone.options) + mp = Unskin(self.options) + cp.update(deepcopy(mp)) + clone.wsdl = self.wsdl + clone.factory = self.factory + clone.service = ServiceSelector(clone, self.wsdl.services) + clone.sd = self.sd + clone.messages = dict(tx=None, rx=None) + return clone + + def __str__(self): + return unicode(self) + + def __unicode__(self): + s = ['\n'] + build = suds.__build__.split() + s.append('Suds ( https://fedorahosted.org/suds/ )') + s.append(' version: %s' % suds.__version__) + s.append(' %s build: %s' % (build[0], build[1])) + for sd in self.sd: + s.append('\n\n%s' % unicode(sd)) + return ''.join(s) + + +class Factory: + """ + A factory for instantiating types defined in the wsdl + @ivar resolver: A schema type resolver. + @type resolver: L{PathResolver} + @ivar builder: A schema object builder. + @type builder: L{Builder} + """ + + def __init__(self, wsdl): + """ + @param wsdl: A schema object. + @type wsdl: L{wsdl.Definitions} + """ + self.wsdl = wsdl + self.resolver = PathResolver(wsdl) + self.builder = Builder(self.resolver) + + def create(self, name): + """ + create a WSDL type by name + @param name: The name of a type defined in the WSDL. + @type name: str + @return: The requested object. + @rtype: L{Object} + """ + timer = metrics.Timer() + timer.start() + type = self.resolver.find(name) + if type is None: + raise TypeNotFound(name) + if type.enum(): + result = InstFactory.object(name) + for e, a in type.children(): + setattr(result, e.name, e.name) + else: + try: + result = self.builder.build(type) + except Exception, e: + log.error("create '%s' failed", name, exc_info=True) + raise BuildError(name, e) + timer.stop() + metrics.log.debug('%s created: %s', name, timer) + return result + + def separator(self, ps): + """ + Set the path separator. + @param ps: The new path separator. + @type ps: char + """ + self.resolver = PathResolver(self.wsdl, ps) + + +class ServiceSelector: + """ + The B{service} selector is used to select a web service. + In most cases, the wsdl only defines (1) service in which access + by subscript is passed through to a L{PortSelector}. This is also the + behavior when a I{default} service has been specified. In cases + where multiple services have been defined and no default has been + specified, the service is found by name (or index) and a L{PortSelector} + for the service is returned. In all cases, attribute access is + forwarded to the L{PortSelector} for either the I{first} service or the + I{default} service (when specified). + @ivar __client: A suds client. + @type __client: L{Client} + @ivar __services: A list of I{wsdl} services. + @type __services: list + """ + def __init__(self, client, services): + """ + @param client: A suds client. + @type client: L{Client} + @param services: A list of I{wsdl} services. + @type services: list + """ + self.__client = client + self.__services = services + + def __getattr__(self, name): + """ + Request to access an attribute is forwarded to the + L{PortSelector} for either the I{first} service or the + I{default} service (when specified). + @param name: The name of a method. + @type name: str + @return: A L{PortSelector}. + @rtype: L{PortSelector}. + """ + default = self.__ds() + if default is None: + port = self.__find(0) + else: + port = default + return getattr(port, name) + + def __getitem__(self, name): + """ + Provides selection of the I{service} by name (string) or + index (integer). In cases where only (1) service is defined + or a I{default} has been specified, the request is forwarded + to the L{PortSelector}. + @param name: The name (or index) of a service. + @type name: (int|str) + @return: A L{PortSelector} for the specified service. + @rtype: L{PortSelector}. + """ + if len(self.__services) == 1: + port = self.__find(0) + return port[name] + default = self.__ds() + if default is not None: + port = default + return port[name] + return self.__find(name) + + def __find(self, name): + """ + Find a I{service} by name (string) or index (integer). + @param name: The name (or index) of a service. + @type name: (int|str) + @return: A L{PortSelector} for the found service. + @rtype: L{PortSelector}. + """ + service = None + if not len(self.__services): + raise Exception, 'No services defined' + if isinstance(name, int): + try: + service = self.__services[name] + name = service.name + except IndexError: + raise ServiceNotFound, 'at [%d]' % name + else: + for s in self.__services: + if name == s.name: + service = s + break + if service is None: + raise ServiceNotFound, name + return PortSelector(self.__client, service.ports, name) + + def __ds(self): + """ + Get the I{default} service if defined in the I{options}. + @return: A L{PortSelector} for the I{default} service. + @rtype: L{PortSelector}. + """ + ds = self.__client.options.service + if ds is None: + return None + else: + return self.__find(ds) + + +class PortSelector: + """ + The B{port} selector is used to select a I{web service} B{port}. + In cases where multiple ports have been defined and no default has been + specified, the port is found by name (or index) and a L{MethodSelector} + for the port is returned. In all cases, attribute access is + forwarded to the L{MethodSelector} for either the I{first} port or the + I{default} port (when specified). + @ivar __client: A suds client. + @type __client: L{Client} + @ivar __ports: A list of I{service} ports. + @type __ports: list + @ivar __qn: The I{qualified} name of the port (used for logging). + @type __qn: str + """ + def __init__(self, client, ports, qn): + """ + @param client: A suds client. + @type client: L{Client} + @param ports: A list of I{service} ports. + @type ports: list + @param qn: The name of the service. + @type qn: str + """ + self.__client = client + self.__ports = ports + self.__qn = qn + + def __getattr__(self, name): + """ + Request to access an attribute is forwarded to the + L{MethodSelector} for either the I{first} port or the + I{default} port (when specified). + @param name: The name of a method. + @type name: str + @return: A L{MethodSelector}. + @rtype: L{MethodSelector}. + """ + default = self.__dp() + if default is None: + m = self.__find(0) + else: + m = default + return getattr(m, name) + + def __getitem__(self, name): + """ + Provides selection of the I{port} by name (string) or + index (integer). In cases where only (1) port is defined + or a I{default} has been specified, the request is forwarded + to the L{MethodSelector}. + @param name: The name (or index) of a port. + @type name: (int|str) + @return: A L{MethodSelector} for the specified port. + @rtype: L{MethodSelector}. + """ + default = self.__dp() + if default is None: + return self.__find(name) + else: + return default + + def __find(self, name): + """ + Find a I{port} by name (string) or index (integer). + @param name: The name (or index) of a port. + @type name: (int|str) + @return: A L{MethodSelector} for the found port. + @rtype: L{MethodSelector}. + """ + port = None + if not len(self.__ports): + raise Exception, 'No ports defined: %s' % self.__qn + if isinstance(name, int): + qn = '%s[%d]' % (self.__qn, name) + try: + port = self.__ports[name] + except IndexError: + raise PortNotFound, qn + else: + qn = '.'.join((self.__qn, name)) + for p in self.__ports: + if name == p.name: + port = p + break + if port is None: + raise PortNotFound, qn + qn = '.'.join((self.__qn, port.name)) + return MethodSelector(self.__client, port.methods, qn) + + def __dp(self): + """ + Get the I{default} port if defined in the I{options}. + @return: A L{MethodSelector} for the I{default} port. + @rtype: L{MethodSelector}. + """ + dp = self.__client.options.port + if dp is None: + return None + else: + return self.__find(dp) + + +class MethodSelector: + """ + The B{method} selector is used to select a B{method} by name. + @ivar __client: A suds client. + @type __client: L{Client} + @ivar __methods: A dictionary of methods. + @type __methods: dict + @ivar __qn: The I{qualified} name of the method (used for logging). + @type __qn: str + """ + def __init__(self, client, methods, qn): + """ + @param client: A suds client. + @type client: L{Client} + @param methods: A dictionary of methods. + @type methods: dict + @param qn: The I{qualified} name of the port. + @type qn: str + """ + self.__client = client + self.__methods = methods + self.__qn = qn + + def __getattr__(self, name): + """ + Get a method by name and return it in an I{execution wrapper}. + @param name: The name of a method. + @type name: str + @return: An I{execution wrapper} for the specified method name. + @rtype: L{Method} + """ + return self[name] + + def __getitem__(self, name): + """ + Get a method by name and return it in an I{execution wrapper}. + @param name: The name of a method. + @type name: str + @return: An I{execution wrapper} for the specified method name. + @rtype: L{Method} + """ + m = self.__methods.get(name) + if m is None: + qn = '.'.join((self.__qn, name)) + raise MethodNotFound, qn + return Method(self.__client, m) + + +class Method: + """ + The I{method} (namespace) object. + @ivar client: A client object. + @type client: L{Client} + @ivar method: A I{wsdl} method. + @type I{wsdl} Method. + """ + + def __init__(self, client, method): + """ + @param client: A client object. + @type client: L{Client} + @param method: A I{raw} method. + @type I{raw} Method. + """ + self.client = client + self.method = method + + def __call__(self, *args, **kwargs): + """ + Invoke the method. + """ + clientclass = self.clientclass(kwargs) + client = clientclass(self.client, self.method) + if not self.faults(): + try: + return client.invoke(args, kwargs) + except WebFault, e: + return (500, e) + else: + return client.invoke(args, kwargs) + + def faults(self): + """ get faults option """ + return self.client.options.faults + + def clientclass(self, kwargs): + """ get soap client class """ + if SimClient.simulation(kwargs): + return SimClient + else: + return SoapClient + + +class SoapClient: + """ + A lightweight soap based web client B{**not intended for external use} + @ivar service: The target method. + @type service: L{Service} + @ivar method: A target method. + @type method: L{Method} + @ivar options: A dictonary of options. + @type options: dict + @ivar cookiejar: A cookie jar. + @type cookiejar: libcookie.CookieJar + """ + + def __init__(self, client, method): + """ + @param client: A suds client. + @type client: L{Client} + @param method: A target method. + @type method: L{Method} + """ + self.client = client + self.method = method + self.options = client.options + self.cookiejar = CookieJar() + + def invoke(self, args, kwargs): + """ + Send the required soap message to invoke the specified method + @param args: A list of args for the method invoked. + @type args: list + @param kwargs: Named (keyword) args for the method invoked. + @type kwargs: dict + @return: The result of the method invocation. + @rtype: I{builtin}|I{subclass of} L{Object} + """ + timer = metrics.Timer() + timer.start() + result = None + binding = self.method.binding.input + soapenv = binding.get_message(self.method, args, kwargs) + timer.stop() + metrics.log.debug( + "message for '%s' created: %s", + self.method.name, + timer) + timer.start() + result = self.send(soapenv) + timer.stop() + metrics.log.debug( + "method '%s' invoked: %s", + self.method.name, + timer) + return result + + def send(self, soapenv): + """ + Send soap message. + @param soapenv: A soap envelope to send. + @type soapenv: L{Document} + @return: The reply to the sent message. + @rtype: I{builtin} or I{subclass of} L{Object} + """ + result = None + location = self.location() + binding = self.method.binding.input + transport = self.options.transport + retxml = self.options.retxml + prettyxml = self.options.prettyxml + log.debug('sending to (%s)\nmessage:\n%s', location, soapenv) + try: + self.last_sent(soapenv) + plugins = PluginContainer(self.options.plugins) + plugins.message.marshalled(envelope=soapenv.root()) + if prettyxml: + soapenv = soapenv.str() + else: + soapenv = soapenv.plain() + soapenv = soapenv.encode('utf-8') + plugins.message.sending(envelope=soapenv) + request = Request(location, soapenv) + request.headers = self.headers() + reply = transport.send(request) + ctx = plugins.message.received(reply=reply.message) + reply.message = ctx.reply + if retxml: + result = reply.message + else: + result = self.succeeded(binding, reply.message) + except TransportError, e: + if e.httpcode in (202,204): + result = None + else: + log.error(self.last_sent()) + result = self.failed(binding, e) + return result + + def headers(self): + """ + Get http headers or the http/https request. + @return: A dictionary of header/values. + @rtype: dict + """ + action = self.method.soap.action + stock = { 'Content-Type' : 'text/xml; charset=utf-8', 'SOAPAction': action } + result = dict(stock, **self.options.headers) + log.debug('headers = %s', result) + return result + + def succeeded(self, binding, reply): + """ + Request succeeded, process the reply + @param binding: The binding to be used to process the reply. + @type binding: L{bindings.binding.Binding} + @param reply: The raw reply text. + @type reply: str + @return: The method result. + @rtype: I{builtin}, L{Object} + @raise WebFault: On server. + """ + log.debug('http succeeded:\n%s', reply) + plugins = PluginContainer(self.options.plugins) + if len(reply) > 0: + reply, result = binding.get_reply(self.method, reply) + self.last_received(reply) + else: + result = None + ctx = plugins.message.unmarshalled(reply=result) + result = ctx.reply + if self.options.faults: + return result + else: + return (200, result) + + def failed(self, binding, error): + """ + Request failed, process reply based on reason + @param binding: The binding to be used to process the reply. + @type binding: L{suds.bindings.binding.Binding} + @param error: The http error message + @type error: L{transport.TransportError} + """ + status, reason = (error.httpcode, tostr(error)) + reply = error.fp.read() + log.debug('http failed:\n%s', reply) + if status == 500: + if len(reply) > 0: + r, p = binding.get_fault(reply) + self.last_received(r) + return (status, p) + else: + return (status, None) + if self.options.faults: + raise Exception((status, reason)) + else: + return (status, None) + + def location(self): + p = Unskin(self.options) + return p.get('location', self.method.location) + + def last_sent(self, d=None): + key = 'tx' + messages = self.client.messages + if d is None: + return messages.get(key) + else: + messages[key] = d + + def last_received(self, d=None): + key = 'rx' + messages = self.client.messages + if d is None: + return messages.get(key) + else: + messages[key] = d + + +class SimClient(SoapClient): + """ + Loopback client used for message/reply simulation. + """ + + injkey = '__inject' + + @classmethod + def simulation(cls, kwargs): + """ get whether loopback has been specified in the I{kwargs}. """ + return kwargs.has_key(SimClient.injkey) + + def invoke(self, args, kwargs): + """ + Send the required soap message to invoke the specified method + @param args: A list of args for the method invoked. + @type args: list + @param kwargs: Named (keyword) args for the method invoked. + @type kwargs: dict + @return: The result of the method invocation. + @rtype: I{builtin} or I{subclass of} L{Object} + """ + simulation = kwargs[self.injkey] + msg = simulation.get('msg') + reply = simulation.get('reply') + fault = simulation.get('fault') + if msg is None: + if reply is not None: + return self.__reply(reply, args, kwargs) + if fault is not None: + return self.__fault(fault) + raise Exception('(reply|fault) expected when msg=None') + sax = Parser() + msg = sax.parse(string=msg) + return self.send(msg) + + def __reply(self, reply, args, kwargs): + """ simulate the reply """ + binding = self.method.binding.input + msg = binding.get_message(self.method, args, kwargs) + log.debug('inject (simulated) send message:\n%s', msg) + binding = self.method.binding.output + return self.succeeded(binding, reply) + + def __fault(self, reply): + """ simulate the (fault) reply """ + binding = self.method.binding.output + if self.options.faults: + r, p = binding.get_fault(reply) + self.last_received(r) + return (500, p) + else: + return (500, None) diff --git a/awx/lib/site-packages/suds/metrics.py b/awx/lib/site-packages/suds/metrics.py new file mode 100644 index 0000000000..403224ae09 --- /dev/null +++ b/awx/lib/site-packages/suds/metrics.py @@ -0,0 +1,62 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +The I{metrics} module defines classes and other resources +designed for collecting and reporting performance metrics. +""" + +import time +from logging import getLogger +from suds import * +from math import modf + +log = getLogger(__name__) + +class Timer: + + def __init__(self): + self.started = 0 + self.stopped = 0 + + def start(self): + self.started = time.time() + self.stopped = 0 + return self + + def stop(self): + if self.started > 0: + self.stopped = time.time() + return self + + def duration(self): + return ( self.stopped - self.started ) + + def __str__(self): + if self.started == 0: + return 'not-running' + if self.started > 0 and self.stopped == 0: + return 'started: %d (running)' % self.started + duration = self.duration() + jmod = ( lambda m : (m[1], m[0]*1000) ) + if duration < 1: + ms = (duration*1000) + return '%d (ms)' % ms + if duration < 60: + m = modf(duration) + return '%d.%.3d (seconds)' % jmod(m) + m = modf(duration/60) + return '%d.%.3d (minutes)' % jmod(m) diff --git a/awx/lib/site-packages/suds/mx/__init__.py b/awx/lib/site-packages/suds/mx/__init__.py new file mode 100644 index 0000000000..77e6ca1763 --- /dev/null +++ b/awx/lib/site-packages/suds/mx/__init__.py @@ -0,0 +1,59 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Provides modules containing classes to support +marshalling (XML). +""" + +from suds.sudsobject import Object + + +class Content(Object): + """ + Marshaller Content. + @ivar tag: The content tag. + @type tag: str + @ivar value: The content's value. + @type value: I{any} + """ + + extensions = [] + + def __init__(self, tag=None, value=None, **kwargs): + """ + @param tag: The content tag. + @type tag: str + @param value: The content's value. + @type value: I{any} + """ + Object.__init__(self) + self.tag = tag + self.value = value + for k,v in kwargs.items(): + setattr(self, k, v) + + def __getattr__(self, name): + if name not in self.__dict__: + if name in self.extensions: + v = None + setattr(self, name, v) + else: + raise AttributeError, \ + 'Content has no attribute %s' % name + else: + v = self.__dict__[name] + return v \ No newline at end of file diff --git a/awx/lib/site-packages/suds/mx/appender.py b/awx/lib/site-packages/suds/mx/appender.py new file mode 100644 index 0000000000..206abc04e8 --- /dev/null +++ b/awx/lib/site-packages/suds/mx/appender.py @@ -0,0 +1,316 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Provides appender classes for I{marshalling}. +""" + +from logging import getLogger +from suds import * +from suds.mx import * +from suds.sudsobject import footprint +from suds.sudsobject import Object, Property +from suds.sax.element import Element +from suds.sax.text import Text +from copy import deepcopy + +log = getLogger(__name__) + +class Matcher: + """ + Appender matcher. + @ivar cls: A class object. + @type cls: I{classobj} + """ + + def __init__(self, cls): + """ + @param cls: A class object. + @type cls: I{classobj} + """ + self.cls = cls + + def __eq__(self, x): + if self.cls is None: + return ( x is None ) + else: + return isinstance(x, self.cls) + + +class ContentAppender: + """ + Appender used to add content to marshalled objects. + @ivar default: The default appender. + @type default: L{Appender} + @ivar appenders: A I{table} of appenders mapped by class. + @type appenders: I{table} + """ + + def __init__(self, marshaller): + """ + @param marshaller: A marshaller. + @type marshaller: L{suds.mx.core.Core} + """ + self.default = PrimativeAppender(marshaller) + self.appenders = ( + (Matcher(None), + NoneAppender(marshaller)), + (Matcher(null), + NoneAppender(marshaller)), + (Matcher(Property), + PropertyAppender(marshaller)), + (Matcher(Object), + ObjectAppender(marshaller)), + (Matcher(Element), + ElementAppender(marshaller)), + (Matcher(Text), + TextAppender(marshaller)), + (Matcher(list), + ListAppender(marshaller)), + (Matcher(tuple), + ListAppender(marshaller)), + (Matcher(dict), + DictAppender(marshaller)), + ) + + def append(self, parent, content): + """ + Select an appender and append the content to parent. + @param parent: A parent node. + @type parent: L{Element} + @param content: The content to append. + @type content: L{Content} + """ + appender = self.default + for a in self.appenders: + if a[0] == content.value: + appender = a[1] + break + appender.append(parent, content) + + +class Appender: + """ + An appender used by the marshaller to append content. + @ivar marshaller: A marshaller. + @type marshaller: L{suds.mx.core.Core} + """ + + def __init__(self, marshaller): + """ + @param marshaller: A marshaller. + @type marshaller: L{suds.mx.core.Core} + """ + self.marshaller = marshaller + + def node(self, content): + """ + Create and return an XML node that is qualified + using the I{type}. Also, make sure all referenced namespace + prefixes are declared. + @param content: The content for which proccessing has ended. + @type content: L{Object} + @return: A new node. + @rtype: L{Element} + """ + return self.marshaller.node(content) + + def setnil(self, node, content): + """ + Set the value of the I{node} to nill. + @param node: A I{nil} node. + @type node: L{Element} + @param content: The content for which proccessing has ended. + @type content: L{Object} + """ + self.marshaller.setnil(node, content) + + def setdefault(self, node, content): + """ + Set the value of the I{node} to a default value. + @param node: A I{nil} node. + @type node: L{Element} + @param content: The content for which proccessing has ended. + @type content: L{Object} + @return: The default. + """ + return self.marshaller.setdefault(node, content) + + def optional(self, content): + """ + Get whether the specified content is optional. + @param content: The content which to check. + @type content: L{Content} + """ + return self.marshaller.optional(content) + + def suspend(self, content): + """ + Notify I{marshaller} that appending this content has suspended. + @param content: The content for which proccessing has been suspended. + @type content: L{Object} + """ + self.marshaller.suspend(content) + + def resume(self, content): + """ + Notify I{marshaller} that appending this content has resumed. + @param content: The content for which proccessing has been resumed. + @type content: L{Object} + """ + self.marshaller.resume(content) + + def append(self, parent, content): + """ + Append the specified L{content} to the I{parent}. + @param content: The content to append. + @type content: L{Object} + """ + self.marshaller.append(parent, content) + + +class PrimativeAppender(Appender): + """ + An appender for python I{primative} types. + """ + + def append(self, parent, content): + if content.tag.startswith('_'): + attr = content.tag[1:] + value = tostr(content.value) + if value: + parent.set(attr, value) + else: + child = self.node(content) + child.setText(tostr(content.value)) + parent.append(child) + + +class NoneAppender(Appender): + """ + An appender for I{None} values. + """ + + def append(self, parent, content): + child = self.node(content) + default = self.setdefault(child, content) + if default is None: + self.setnil(child, content) + parent.append(child) + + +class PropertyAppender(Appender): + """ + A L{Property} appender. + """ + + def append(self, parent, content): + p = content.value + child = self.node(content) + child.setText(p.get()) + parent.append(child) + for item in p.items(): + cont = Content(tag=item[0], value=item[1]) + Appender.append(self, child, cont) + + +class ObjectAppender(Appender): + """ + An L{Object} appender. + """ + + def append(self, parent, content): + object = content.value + if self.optional(content) and footprint(object) == 0: + return + child = self.node(content) + parent.append(child) + for item in object: + cont = Content(tag=item[0], value=item[1]) + Appender.append(self, child, cont) + + +class DictAppender(Appender): + """ + An python I{dict} appender. + """ + + def append(self, parent, content): + d = content.value + if self.optional(content) and len(d) == 0: + return + child = self.node(content) + parent.append(child) + for item in d.items(): + cont = Content(tag=item[0], value=item[1]) + Appender.append(self, child, cont) + + +class ElementWrapper(Element): + """ + Element wrapper. + """ + + def __init__(self, content): + Element.__init__(self, content.name, content.parent) + self.__content = content + + def str(self, indent=0): + return self.__content.str(indent) + + +class ElementAppender(Appender): + """ + An appender for I{Element} types. + """ + + def append(self, parent, content): + if content.tag.startswith('_'): + raise Exception('raw XML not valid as attribute value') + child = ElementWrapper(content.value) + parent.append(child) + + +class ListAppender(Appender): + """ + A list/tuple appender. + """ + + def append(self, parent, content): + collection = content.value + if len(collection): + self.suspend(content) + for item in collection: + cont = Content(tag=content.tag, value=item) + Appender.append(self, parent, cont) + self.resume(content) + + +class TextAppender(Appender): + """ + An appender for I{Text} values. + """ + + def append(self, parent, content): + if content.tag.startswith('_'): + attr = content.tag[1:] + value = tostr(content.value) + if value: + parent.set(attr, value) + else: + child = self.node(content) + child.setText(content.value) + parent.append(child) diff --git a/awx/lib/site-packages/suds/mx/basic.py b/awx/lib/site-packages/suds/mx/basic.py new file mode 100644 index 0000000000..336f68438e --- /dev/null +++ b/awx/lib/site-packages/suds/mx/basic.py @@ -0,0 +1,48 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Provides basic I{marshaller} classes. +""" + +from logging import getLogger +from suds import * +from suds.mx import * +from suds.mx.core import Core + +log = getLogger(__name__) + + +class Basic(Core): + """ + A I{basic} (untyped) marshaller. + """ + + def process(self, value, tag=None): + """ + Process (marshal) the tag with the specified value using the + optional type information. + @param value: The value (content) of the XML node. + @type value: (L{Object}|any) + @param tag: The (optional) tag name for the value. The default is + value.__class__.__name__ + @type tag: str + @return: An xml node. + @rtype: L{Element} + """ + content = Content(tag=tag, value=value) + result = Core.process(self, content) + return result \ No newline at end of file diff --git a/awx/lib/site-packages/suds/mx/core.py b/awx/lib/site-packages/suds/mx/core.py new file mode 100644 index 0000000000..3c9ef597b5 --- /dev/null +++ b/awx/lib/site-packages/suds/mx/core.py @@ -0,0 +1,158 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Provides I{marshaller} core classes. +""" + +from logging import getLogger +from suds import * +from suds.mx import * +from suds.mx.appender import ContentAppender +from suds.sax.element import Element +from suds.sax.document import Document +from suds.sudsobject import Property + + +log = getLogger(__name__) + + +class Core: + """ + An I{abstract} marshaller. This class implement the core + functionality of the marshaller. + @ivar appender: A content appender. + @type appender: L{ContentAppender} + """ + + def __init__(self): + """ + """ + self.appender = ContentAppender(self) + + def process(self, content): + """ + Process (marshal) the tag with the specified value using the + optional type information. + @param content: The content to process. + @type content: L{Object} + """ + log.debug('processing:\n%s', content) + self.reset() + if content.tag is None: + content.tag = content.value.__class__.__name__ + document = Document() + if isinstance(content.value, Property): + root = self.node(content) + self.append(document, content) + else: + self.append(document, content) + return document.root() + + def append(self, parent, content): + """ + Append the specified L{content} to the I{parent}. + @param parent: The parent node to append to. + @type parent: L{Element} + @param content: The content to append. + @type content: L{Object} + """ + log.debug('appending parent:\n%s\ncontent:\n%s', parent, content) + if self.start(content): + self.appender.append(parent, content) + self.end(parent, content) + + def reset(self): + """ + Reset the marshaller. + """ + pass + + def node(self, content): + """ + Create and return an XML node. + @param content: The content for which proccessing has been suspended. + @type content: L{Object} + @return: An element. + @rtype: L{Element} + """ + return Element(content.tag) + + def start(self, content): + """ + Appending this content has started. + @param content: The content for which proccessing has started. + @type content: L{Content} + @return: True to continue appending + @rtype: boolean + """ + return True + + def suspend(self, content): + """ + Appending this content has suspended. + @param content: The content for which proccessing has been suspended. + @type content: L{Content} + """ + pass + + def resume(self, content): + """ + Appending this content has resumed. + @param content: The content for which proccessing has been resumed. + @type content: L{Content} + """ + pass + + def end(self, parent, content): + """ + Appending this content has ended. + @param parent: The parent node ending. + @type parent: L{Element} + @param content: The content for which proccessing has ended. + @type content: L{Content} + """ + pass + + def setnil(self, node, content): + """ + Set the value of the I{node} to nill. + @param node: A I{nil} node. + @type node: L{Element} + @param content: The content to set nil. + @type content: L{Content} + """ + pass + + def setdefault(self, node, content): + """ + Set the value of the I{node} to a default value. + @param node: A I{nil} node. + @type node: L{Element} + @param content: The content to set the default value. + @type content: L{Content} + @return: The default. + """ + pass + + def optional(self, content): + """ + Get whether the specified content is optional. + @param content: The content which to check. + @type content: L{Content} + """ + return False + diff --git a/awx/lib/site-packages/suds/mx/encoded.py b/awx/lib/site-packages/suds/mx/encoded.py new file mode 100644 index 0000000000..9cbc8c5fba --- /dev/null +++ b/awx/lib/site-packages/suds/mx/encoded.py @@ -0,0 +1,133 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Provides encoded I{marshaller} classes. +""" + +from logging import getLogger +from suds import * +from suds.mx import * +from suds.mx.literal import Literal +from suds.mx.typer import Typer +from suds.sudsobject import Factory, Object +from suds.xsd.query import TypeQuery + +log = getLogger(__name__) + +# +# Add encoded extensions +# aty = The soap (section 5) encoded array type. +# +Content.extensions.append('aty') + + +class Encoded(Literal): + """ + A SOAP section (5) encoding marshaller. + This marshaller supports rpc/encoded soap styles. + """ + + def start(self, content): + # + # For soap encoded arrays, the 'aty' (array type) information + # is extracted and added to the 'content'. Then, the content.value + # is replaced with an object containing an 'item=[]' attribute + # containing values that are 'typed' suds objects. + # + start = Literal.start(self, content) + if start and isinstance(content.value, (list,tuple)): + resolved = content.type.resolve() + for c in resolved: + if hasattr(c[0], 'aty'): + content.aty = (content.tag, c[0].aty) + self.cast(content) + break + return start + + def end(self, parent, content): + # + # For soap encoded arrays, the soapenc:arrayType attribute is + # added with proper type and size information. + # Eg: soapenc:arrayType="xs:int[3]" + # + Literal.end(self, parent, content) + if content.aty is None: + return + tag, aty = content.aty + ns0 = ('at0', aty[1]) + ns1 = ('at1', 'http://schemas.xmlsoap.org/soap/encoding/') + array = content.value.item + child = parent.getChild(tag) + child.addPrefix(ns0[0], ns0[1]) + child.addPrefix(ns1[0], ns1[1]) + name = '%s:arrayType' % ns1[0] + value = '%s:%s[%d]' % (ns0[0], aty[0], len(array)) + child.set(name, value) + + def encode(self, node, content): + if content.type.any(): + Typer.auto(node, content.value) + return + if content.real.any(): + Typer.auto(node, content.value) + return + ns = None + name = content.real.name + if self.xstq: + ns = content.real.namespace() + Typer.manual(node, name, ns) + + def cast(self, content): + """ + Cast the I{untyped} list items found in content I{value}. + Each items contained in the list is checked for XSD type information. + Items (values) that are I{untyped}, are replaced with suds objects and + type I{metadata} is added. + @param content: The content holding the collection. + @type content: L{Content} + @return: self + @rtype: L{Encoded} + """ + aty = content.aty[1] + resolved = content.type.resolve() + array = Factory.object(resolved.name) + array.item = [] + query = TypeQuery(aty) + ref = query.execute(self.schema) + if ref is None: + raise TypeNotFound(qref) + for x in content.value: + if isinstance(x, (list, tuple)): + array.item.append(x) + continue + if isinstance(x, Object): + md = x.__metadata__ + md.sxtype = ref + array.item.append(x) + continue + if isinstance(x, dict): + x = Factory.object(ref.name, x) + md = x.__metadata__ + md.sxtype = ref + array.item.append(x) + continue + x = Factory.property(ref.name, x) + md = x.__metadata__ + md.sxtype = ref + array.item.append(x) + content.value = array + return self diff --git a/awx/lib/site-packages/suds/mx/literal.py b/awx/lib/site-packages/suds/mx/literal.py new file mode 100644 index 0000000000..937ad8ecc7 --- /dev/null +++ b/awx/lib/site-packages/suds/mx/literal.py @@ -0,0 +1,291 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Provides literal I{marshaller} classes. +""" + +from logging import getLogger +from suds import * +from suds.mx import * +from suds.mx.core import Core +from suds.mx.typer import Typer +from suds.resolver import GraphResolver, Frame +from suds.sax.element import Element +from suds.sudsobject import Factory + +log = getLogger(__name__) + + +# +# Add typed extensions +# type = The expected xsd type +# real = The 'true' XSD type +# ancestry = The 'type' ancestry +# +Content.extensions.append('type') +Content.extensions.append('real') +Content.extensions.append('ancestry') + + + +class Typed(Core): + """ + A I{typed} marshaller. + This marshaller is semi-typed as needed to support both + I{document/literal} and I{rpc/literal} soap message styles. + @ivar schema: An xsd schema. + @type schema: L{xsd.schema.Schema} + @ivar resolver: A schema type resolver. + @type resolver: L{GraphResolver} + """ + + def __init__(self, schema, xstq=True): + """ + @param schema: A schema object + @type schema: L{xsd.schema.Schema} + @param xstq: The B{x}ml B{s}chema B{t}ype B{q}ualified flag indicates + that the I{xsi:type} attribute values should be qualified by namespace. + @type xstq: bool + """ + Core.__init__(self) + self.schema = schema + self.xstq = xstq + self.resolver = GraphResolver(self.schema) + + def reset(self): + self.resolver.reset() + + def start(self, content): + # + # Start marshalling the 'content' by ensuring that both the + # 'content' _and_ the resolver are primed with the XSD type + # information. The 'content' value is both translated and + # sorted based on the XSD type. Only values that are objects + # have their attributes sorted. + # + log.debug('starting content:\n%s', content) + if content.type is None: + name = content.tag + if name.startswith('_'): + name = '@'+name[1:] + content.type = self.resolver.find(name, content.value) + if content.type is None: + raise TypeNotFound(content.tag) + else: + known = None + if isinstance(content.value, Object): + known = self.resolver.known(content.value) + if known is None: + log.debug('object has no type information', content.value) + known = content.type + frame = Frame(content.type, resolved=known) + self.resolver.push(frame) + frame = self.resolver.top() + content.real = frame.resolved + content.ancestry = frame.ancestry + self.translate(content) + self.sort(content) + if self.skip(content): + log.debug('skipping (optional) content:\n%s', content) + self.resolver.pop() + return False + else: + return True + + def suspend(self, content): + # + # Suspend to process a list content. Primarily, this + # involves popping the 'list' content off the resolver's + # stack so the list items can be marshalled. + # + self.resolver.pop() + + def resume(self, content): + # + # Resume processing a list content. To do this, we + # really need to simply push the 'list' content + # back onto the resolver stack. + # + self.resolver.push(Frame(content.type)) + + def end(self, parent, content): + # + # End processing the content. Make sure the content + # ending matches the top of the resolver stack since for + # list processing we play games with the resolver stack. + # + log.debug('ending content:\n%s', content) + current = self.resolver.top().type + if current == content.type: + self.resolver.pop() + else: + raise Exception, \ + 'content (end) mismatch: top=(%s) cont=(%s)' % \ + (current, content) + + def node(self, content): + # + # Create an XML node and namespace qualify as defined + # by the schema (elementFormDefault). + # + ns = content.type.namespace() + if content.type.form_qualified: + node = Element(content.tag, ns=ns) + node.addPrefix(ns[0], ns[1]) + else: + node = Element(content.tag) + self.encode(node, content) + log.debug('created - node:\n%s', node) + return node + + def setnil(self, node, content): + # + # Set the 'node' nil only if the XSD type + # specifies that it is permitted. + # + if content.type.nillable: + node.setnil() + + def setdefault(self, node, content): + # + # Set the node to the default value specified + # by the XSD type. + # + default = content.type.default + if default is None: + pass + else: + node.setText(default) + return default + + def optional(self, content): + if content.type.optional(): + return True + for a in content.ancestry: + if a.optional(): + return True + return False + + def encode(self, node, content): + # Add (soap) encoding information only if the resolved + # type is derived by extension. Further, the xsi:type values + # is qualified by namespace only if the content (tag) and + # referenced type are in different namespaces. + if content.type.any(): + return + if not content.real.extension(): + return + if content.type.resolve() == content.real: + return + ns = None + name = content.real.name + if self.xstq: + ns = content.real.namespace('ns1') + Typer.manual(node, name, ns) + + def skip(self, content): + """ + Get whether to skip this I{content}. + Should be skipped when the content is optional + and either the value=None or the value is an empty list. + @param content: The content to skip. + @type content: L{Object} + @return: True if content is to be skipped. + @rtype: bool + """ + if self.optional(content): + v = content.value + if v is None: + return True + if isinstance(v, (list,tuple)) and len(v) == 0: + return True + return False + + def optional(self, content): + if content.type.optional(): + return True + for a in content.ancestry: + if a.optional(): + return True + return False + + def translate(self, content): + """ + Translate using the XSD type information. + Python I{dict} is translated to a suds object. Most + importantly, primative values are translated from python + types to XML types using the XSD type. + @param content: The content to translate. + @type content: L{Object} + @return: self + @rtype: L{Typed} + """ + v = content.value + if v is None: + return + if isinstance(v, dict): + cls = content.real.name + content.value = Factory.object(cls, v) + md = content.value.__metadata__ + md.sxtype = content.type + return + v = content.real.translate(v, False) + content.value = v + return self + + def sort(self, content): + """ + Sort suds object attributes based on ordering defined + in the XSD type information. + @param content: The content to sort. + @type content: L{Object} + @return: self + @rtype: L{Typed} + """ + v = content.value + if isinstance(v, Object): + md = v.__metadata__ + md.ordering = self.ordering(content.real) + return self + + def ordering(self, type): + """ + Get the attribute ordering defined in the specified + XSD type information. + @param type: An XSD type object. + @type type: SchemaObject + @return: An ordered list of attribute names. + @rtype: list + """ + result = [] + for child, ancestry in type.resolve(): + name = child.name + if child.name is None: + continue + if child.isattr(): + name = '_%s' % child.name + result.append(name) + return result + + +class Literal(Typed): + """ + A I{literal} marshaller. + This marshaller is semi-typed as needed to support both + I{document/literal} and I{rpc/literal} soap message styles. + """ + pass \ No newline at end of file diff --git a/awx/lib/site-packages/suds/mx/typer.py b/awx/lib/site-packages/suds/mx/typer.py new file mode 100644 index 0000000000..ea88df7b1d --- /dev/null +++ b/awx/lib/site-packages/suds/mx/typer.py @@ -0,0 +1,123 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Provides sx typing classes. +""" + +from logging import getLogger +from suds import * +from suds.mx import * +from suds.sax import Namespace as NS +from suds.sax.text import Text + +log = getLogger(__name__) + + +class Typer: + """ + Provides XML node typing as either automatic or manual. + @cvar types: A dict of class to xs type mapping. + @type types: dict + """ + + types = { + int : ('int', NS.xsdns), + long : ('long', NS.xsdns), + float : ('float', NS.xsdns), + str : ('string', NS.xsdns), + unicode : ('string', NS.xsdns), + Text : ('string', NS.xsdns), + bool : ('boolean', NS.xsdns), + } + + @classmethod + def auto(cls, node, value=None): + """ + Automatically set the node's xsi:type attribute based on either I{value}'s + class or the class of the node's text. When I{value} is an unmapped class, + the default type (xs:any) is set. + @param node: An XML node + @type node: L{sax.element.Element} + @param value: An object that is or would be the node's text. + @type value: I{any} + @return: The specified node. + @rtype: L{sax.element.Element} + """ + if value is None: + value = node.getText() + if isinstance(value, Object): + known = cls.known(value) + if known.name is None: + return node + tm = (known.name, known.namespace()) + else: + tm = cls.types.get(value.__class__, cls.types.get(str)) + cls.manual(node, *tm) + return node + + @classmethod + def manual(cls, node, tval, ns=None): + """ + Set the node's xsi:type attribute based on either I{value}'s + class or the class of the node's text. Then adds the referenced + prefix(s) to the node's prefix mapping. + @param node: An XML node + @type node: L{sax.element.Element} + @param tval: The name of the schema type. + @type tval: str + @param ns: The XML namespace of I{tval}. + @type ns: (prefix, uri) + @return: The specified node. + @rtype: L{sax.element.Element} + """ + xta = ':'.join((NS.xsins[0], 'type')) + node.addPrefix(NS.xsins[0], NS.xsins[1]) + if ns is None: + node.set(xta, tval) + else: + ns = cls.genprefix(node, ns) + qname = ':'.join((ns[0], tval)) + node.set(xta, qname) + node.addPrefix(ns[0], ns[1]) + return node + + @classmethod + def genprefix(cls, node, ns): + """ + Generate a prefix. + @param node: An XML node on which the prefix will be used. + @type node: L{sax.element.Element} + @param ns: A namespace needing an unique prefix. + @type ns: (prefix, uri) + @return: The I{ns} with a new prefix. + """ + for n in range(1, 1024): + p = 'ns%d' % n + u = node.resolvePrefix(p, default=None) + if u is None or u == ns[1]: + return (p, ns[1]) + raise Exception('auto prefix, exhausted') + + @classmethod + def known(cls, object): + try: + md = object.__metadata__ + known = md.sxtype + return known + except: + pass + diff --git a/awx/lib/site-packages/suds/options.py b/awx/lib/site-packages/suds/options.py new file mode 100644 index 0000000000..86ea245808 --- /dev/null +++ b/awx/lib/site-packages/suds/options.py @@ -0,0 +1,123 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Suds basic options classes. +""" + +from suds.properties import * +from suds.wsse import Security +from suds.xsd.doctor import Doctor +from suds.transport import Transport +from suds.cache import Cache, NoCache + + +class TpLinker(AutoLinker): + """ + Transport (auto) linker used to manage linkage between + transport objects Properties and those Properties that contain them. + """ + + def updated(self, properties, prev, next): + if isinstance(prev, Transport): + tp = Unskin(prev.options) + properties.unlink(tp) + if isinstance(next, Transport): + tp = Unskin(next.options) + properties.link(tp) + + +class Options(Skin): + """ + Options: + - B{cache} - The XML document cache. May be set (None) for no caching. + - type: L{Cache} + - default: L{NoCache} + - B{faults} - Raise faults raised by server, + else return tuple from service method invocation as (httpcode, object). + - type: I{bool} + - default: True + - B{service} - The default service name. + - type: I{str} + - default: None + - B{port} - The default service port name, not tcp port. + - type: I{str} + - default: None + - B{location} - This overrides the service port address I{URL} defined + in the WSDL. + - type: I{str} + - default: None + - B{transport} - The message transport. + - type: L{Transport} + - default: None + - B{soapheaders} - The soap headers to be included in the soap message. + - type: I{any} + - default: None + - B{wsse} - The web services I{security} provider object. + - type: L{Security} + - default: None + - B{doctor} - A schema I{doctor} object. + - type: L{Doctor} + - default: None + - B{xstq} - The B{x}ml B{s}chema B{t}ype B{q}ualified flag indicates + that the I{xsi:type} attribute values should be qualified by namespace. + - type: I{bool} + - default: True + - B{prefixes} - Elements of the soap message should be qualified (when needed) + using XML prefixes as opposed to xmlns="" syntax. + - type: I{bool} + - default: True + - B{retxml} - Flag that causes the I{raw} soap envelope to be returned instead + of the python object graph. + - type: I{bool} + - default: False + - B{prettyxml} - Flag that causes I{pretty} xml to be rendered when generating + the outbound soap envelope. + - type: I{bool} + - default: False + - B{autoblend} - Flag that ensures that the schema(s) defined within the + WSDL import each other. + - type: I{bool} + - default: False + - B{cachingpolicy} - The caching policy. + - type: I{int} + - 0 = Cache XML documents. + - 1 = Cache WSDL (pickled) object. + - default: 0 + - B{plugins} - A plugin container. + - type: I{list} + """ + def __init__(self, **kwargs): + domain = __name__ + definitions = [ + Definition('cache', Cache, NoCache()), + Definition('faults', bool, True), + Definition('transport', Transport, None, TpLinker()), + Definition('service', (int, basestring), None), + Definition('port', (int, basestring), None), + Definition('location', basestring, None), + Definition('soapheaders', (), ()), + Definition('wsse', Security, None), + Definition('doctor', Doctor, None), + Definition('xstq', bool, True), + Definition('prefixes', bool, True), + Definition('retxml', bool, False), + Definition('prettyxml', bool, False), + Definition('autoblend', bool, False), + Definition('cachingpolicy', int, 0), + Definition('plugins', (list, tuple), []), + ] + Skin.__init__(self, domain, definitions, kwargs) diff --git a/awx/lib/site-packages/suds/plugin.py b/awx/lib/site-packages/suds/plugin.py new file mode 100644 index 0000000000..061c564fb8 --- /dev/null +++ b/awx/lib/site-packages/suds/plugin.py @@ -0,0 +1,257 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +The plugin module provides classes for implementation +of suds plugins. +""" + +from suds import * +from logging import getLogger + +log = getLogger(__name__) + + +class Context(object): + """ + Plugin context. + """ + pass + + +class InitContext(Context): + """ + Init Context. + @ivar wsdl: The wsdl. + @type wsdl: L{wsdl.Definitions} + """ + pass + + +class DocumentContext(Context): + """ + The XML document load context. + @ivar url: The URL. + @type url: str + @ivar document: Either the XML text or the B{parsed} document root. + @type document: (str|L{sax.element.Element}) + """ + pass + + +class MessageContext(Context): + """ + The context for sending the soap envelope. + @ivar envelope: The soap envelope to be sent. + @type envelope: (str|L{sax.element.Element}) + @ivar reply: The reply. + @type reply: (str|L{sax.element.Element}|object) + """ + pass + + +class Plugin: + """ + Plugin base. + """ + pass + + +class InitPlugin(Plugin): + """ + The base class for suds I{init} plugins. + """ + + def initialized(self, context): + """ + Suds client initialization. + Called after wsdl the has been loaded. Provides the plugin + with the opportunity to inspect/modify the WSDL. + @param context: The init context. + @type context: L{InitContext} + """ + pass + + +class DocumentPlugin(Plugin): + """ + The base class for suds I{document} plugins. + """ + + def loaded(self, context): + """ + Suds has loaded a WSDL/XSD document. Provides the plugin + with an opportunity to inspect/modify the unparsed document. + Called after each WSDL/XSD document is loaded. + @param context: The document context. + @type context: L{DocumentContext} + """ + pass + + def parsed(self, context): + """ + Suds has parsed a WSDL/XSD document. Provides the plugin + with an opportunity to inspect/modify the parsed document. + Called after each WSDL/XSD document is parsed. + @param context: The document context. + @type context: L{DocumentContext} + """ + pass + + +class MessagePlugin(Plugin): + """ + The base class for suds I{soap message} plugins. + """ + + def marshalled(self, context): + """ + Suds will send the specified soap envelope. + Provides the plugin with the opportunity to inspect/modify + the envelope Document before it is sent. + @param context: The send context. + The I{envelope} is the envelope docuemnt. + @type context: L{MessageContext} + """ + pass + + def sending(self, context): + """ + Suds will send the specified soap envelope. + Provides the plugin with the opportunity to inspect/modify + the message text it is sent. + @param context: The send context. + The I{envelope} is the envelope text. + @type context: L{MessageContext} + """ + pass + + def received(self, context): + """ + Suds has received the specified reply. + Provides the plugin with the opportunity to inspect/modify + the received XML text before it is SAX parsed. + @param context: The reply context. + The I{reply} is the raw text. + @type context: L{MessageContext} + """ + pass + + def parsed(self, context): + """ + Suds has sax parsed the received reply. + Provides the plugin with the opportunity to inspect/modify + the sax parsed DOM tree for the reply before it is unmarshalled. + @param context: The reply context. + The I{reply} is DOM tree. + @type context: L{MessageContext} + """ + pass + + def unmarshalled(self, context): + """ + Suds has unmarshalled the received reply. + Provides the plugin with the opportunity to inspect/modify + the unmarshalled reply object before it is returned. + @param context: The reply context. + The I{reply} is unmarshalled suds object. + @type context: L{MessageContext} + """ + pass + + +class PluginContainer: + """ + Plugin container provides easy method invocation. + @ivar plugins: A list of plugin objects. + @type plugins: [L{Plugin},] + @cvar ctxclass: A dict of plugin method / context classes. + @type ctxclass: dict + """ + + domains = {\ + 'init': (InitContext, InitPlugin), + 'document': (DocumentContext, DocumentPlugin), + 'message': (MessageContext, MessagePlugin ), + } + + def __init__(self, plugins): + """ + @param plugins: A list of plugin objects. + @type plugins: [L{Plugin},] + """ + self.plugins = plugins + + def __getattr__(self, name): + domain = self.domains.get(name) + if domain: + plugins = [] + ctx, pclass = domain + for p in self.plugins: + if isinstance(p, pclass): + plugins.append(p) + return PluginDomain(ctx, plugins) + else: + raise Exception, 'plugin domain (%s), invalid' % name + + +class PluginDomain: + """ + The plugin domain. + @ivar ctx: A context. + @type ctx: L{Context} + @ivar plugins: A list of plugins (targets). + @type plugins: list + """ + + def __init__(self, ctx, plugins): + self.ctx = ctx + self.plugins = plugins + + def __getattr__(self, name): + return Method(name, self) + + +class Method: + """ + Plugin method. + @ivar name: The method name. + @type name: str + @ivar domain: The plugin domain. + @type domain: L{PluginDomain} + """ + + def __init__(self, name, domain): + """ + @param name: The method name. + @type name: str + @param domain: A plugin domain. + @type domain: L{PluginDomain} + """ + self.name = name + self.domain = domain + + def __call__(self, **kwargs): + ctx = self.domain.ctx() + ctx.__dict__.update(kwargs) + for plugin in self.domain.plugins: + try: + method = getattr(plugin, self.name, None) + if method and callable(method): + method(ctx) + except Exception, pe: + log.exception(pe) + return ctx diff --git a/awx/lib/site-packages/suds/properties.py b/awx/lib/site-packages/suds/properties.py new file mode 100644 index 0000000000..50b2593959 --- /dev/null +++ b/awx/lib/site-packages/suds/properties.py @@ -0,0 +1,543 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Properties classes. +""" + +from logging import getLogger + +log = getLogger(__name__) + + +class AutoLinker(object): + """ + Base class, provides interface for I{automatic} link + management between a L{Properties} object and the L{Properties} + contained within I{values}. + """ + def updated(self, properties, prev, next): + """ + Notification that a values was updated and the linkage + between the I{properties} contained with I{prev} need to + be relinked to the L{Properties} contained within the + I{next} value. + """ + pass + + +class Link(object): + """ + Property link object. + @ivar endpoints: A tuple of the (2) endpoints of the link. + @type endpoints: tuple(2) + """ + def __init__(self, a, b): + """ + @param a: Property (A) to link. + @type a: L{Property} + @param b: Property (B) to link. + @type b: L{Property} + """ + pA = Endpoint(self, a) + pB = Endpoint(self, b) + self.endpoints = (pA, pB) + self.validate(a, b) + a.links.append(pB) + b.links.append(pA) + + def validate(self, pA, pB): + """ + Validate that the two properties may be linked. + @param pA: Endpoint (A) to link. + @type pA: L{Endpoint} + @param pB: Endpoint (B) to link. + @type pB: L{Endpoint} + @return: self + @rtype: L{Link} + """ + if pA in pB.links or \ + pB in pA.links: + raise Exception, 'Already linked' + dA = pA.domains() + dB = pB.domains() + for d in dA: + if d in dB: + raise Exception, 'Duplicate domain "%s" found' % d + for d in dB: + if d in dA: + raise Exception, 'Duplicate domain "%s" found' % d + kA = pA.keys() + kB = pB.keys() + for k in kA: + if k in kB: + raise Exception, 'Duplicate key %s found' % k + for k in kB: + if k in kA: + raise Exception, 'Duplicate key %s found' % k + return self + + def teardown(self): + """ + Teardown the link. + Removes endpoints from properties I{links} collection. + @return: self + @rtype: L{Link} + """ + pA, pB = self.endpoints + if pA in pB.links: + pB.links.remove(pA) + if pB in pA.links: + pA.links.remove(pB) + return self + + +class Endpoint(object): + """ + Link endpoint (wrapper). + @ivar link: The associated link. + @type link: L{Link} + @ivar target: The properties object. + @type target: L{Property} + """ + def __init__(self, link, target): + self.link = link + self.target = target + + def teardown(self): + return self.link.teardown() + + def __eq__(self, rhs): + return ( self.target == rhs ) + + def __hash__(self): + return hash(self.target) + + def __getattr__(self, name): + return getattr(self.target, name) + + +class Definition: + """ + Property definition. + @ivar name: The property name. + @type name: str + @ivar classes: The (class) list of permitted values + @type classes: tuple + @ivar default: The default value. + @ivar type: any + """ + def __init__(self, name, classes, default, linker=AutoLinker()): + """ + @param name: The property name. + @type name: str + @param classes: The (class) list of permitted values + @type classes: tuple + @param default: The default value. + @type default: any + """ + if not isinstance(classes, (list, tuple)): + classes = (classes,) + self.name = name + self.classes = classes + self.default = default + self.linker = linker + + def nvl(self, value=None): + """ + Convert the I{value} into the default when I{None}. + @param value: The proposed value. + @type value: any + @return: The I{default} when I{value} is I{None}, else I{value}. + @rtype: any + """ + if value is None: + return self.default + else: + return value + + def validate(self, value): + """ + Validate the I{value} is of the correct class. + @param value: The value to validate. + @type value: any + @raise AttributeError: When I{value} is invalid. + """ + if value is None: + return + if len(self.classes) and \ + not isinstance(value, self.classes): + msg = '"%s" must be: %s' % (self.name, self.classes) + raise AttributeError,msg + + + def __repr__(self): + return '%s: %s' % (self.name, str(self)) + + def __str__(self): + s = [] + if len(self.classes): + s.append('classes=%s' % str(self.classes)) + else: + s.append('classes=*') + s.append("default=%s" % str(self.default)) + return ', '.join(s) + + +class Properties: + """ + Represents basic application properties. + Provides basic type validation, default values and + link/synchronization behavior. + @ivar domain: The domain name. + @type domain: str + @ivar definitions: A table of property definitions. + @type definitions: {name: L{Definition}} + @ivar links: A list of linked property objects used to create + a network of properties. + @type links: [L{Property},..] + @ivar defined: A dict of property values. + @type defined: dict + """ + def __init__(self, domain, definitions, kwargs): + """ + @param domain: The property domain name. + @type domain: str + @param definitions: A table of property definitions. + @type definitions: {name: L{Definition}} + @param kwargs: A list of property name/values to set. + @type kwargs: dict + """ + self.definitions = {} + for d in definitions: + self.definitions[d.name] = d + self.domain = domain + self.links = [] + self.defined = {} + self.modified = set() + self.prime() + self.update(kwargs) + + def definition(self, name): + """ + Get the definition for the property I{name}. + @param name: The property I{name} to find the definition for. + @type name: str + @return: The property definition + @rtype: L{Definition} + @raise AttributeError: On not found. + """ + d = self.definitions.get(name) + if d is None: + raise AttributeError(name) + return d + + def update(self, other): + """ + Update the property values as specified by keyword/value. + @param other: An object to update from. + @type other: (dict|L{Properties}) + @return: self + @rtype: L{Properties} + """ + if isinstance(other, Properties): + other = other.defined + for n,v in other.items(): + self.set(n, v) + return self + + def notset(self, name): + """ + Get whether a property has never been set by I{name}. + @param name: A property name. + @type name: str + @return: True if never been set. + @rtype: bool + """ + self.provider(name).__notset(name) + + def set(self, name, value): + """ + Set the I{value} of a property by I{name}. + The value is validated against the definition and set + to the default when I{value} is None. + @param name: The property name. + @type name: str + @param value: The new property value. + @type value: any + @return: self + @rtype: L{Properties} + """ + self.provider(name).__set(name, value) + return self + + def unset(self, name): + """ + Unset a property by I{name}. + @param name: A property name. + @type name: str + @return: self + @rtype: L{Properties} + """ + self.provider(name).__set(name, None) + return self + + def get(self, name, *df): + """ + Get the value of a property by I{name}. + @param name: The property name. + @type name: str + @param df: An optional value to be returned when the value + is not set + @type df: [1]. + @return: The stored value, or I{df[0]} if not set. + @rtype: any + """ + return self.provider(name).__get(name, *df) + + def link(self, other): + """ + Link (associate) this object with anI{other} properties object + to create a network of properties. Links are bidirectional. + @param other: The object to link. + @type other: L{Properties} + @return: self + @rtype: L{Properties} + """ + Link(self, other) + return self + + def unlink(self, *others): + """ + Unlink (disassociate) the specified properties object. + @param others: The list object to unlink. Unspecified means unlink all. + @type others: [L{Properties},..] + @return: self + @rtype: L{Properties} + """ + if not len(others): + others = self.links[:] + for p in self.links[:]: + if p in others: + p.teardown() + return self + + def provider(self, name, history=None): + """ + Find the provider of the property by I{name}. + @param name: The property name. + @type name: str + @param history: A history of nodes checked to prevent + circular hunting. + @type history: [L{Properties},..] + @return: The provider when found. Otherwise, None (when nested) + and I{self} when not nested. + @rtype: L{Properties} + """ + if history is None: + history = [] + history.append(self) + if name in self.definitions: + return self + for x in self.links: + if x in history: + continue + provider = x.provider(name, history) + if provider is not None: + return provider + history.remove(self) + if len(history): + return None + return self + + def keys(self, history=None): + """ + Get the set of I{all} property names. + @param history: A history of nodes checked to prevent + circular hunting. + @type history: [L{Properties},..] + @return: A set of property names. + @rtype: list + """ + if history is None: + history = [] + history.append(self) + keys = set() + keys.update(self.definitions.keys()) + for x in self.links: + if x in history: + continue + keys.update(x.keys(history)) + history.remove(self) + return keys + + def domains(self, history=None): + """ + Get the set of I{all} domain names. + @param history: A history of nodes checked to prevent + circular hunting. + @type history: [L{Properties},..] + @return: A set of domain names. + @rtype: list + """ + if history is None: + history = [] + history.append(self) + domains = set() + domains.add(self.domain) + for x in self.links: + if x in history: + continue + domains.update(x.domains(history)) + history.remove(self) + return domains + + def prime(self): + """ + Prime the stored values based on default values + found in property definitions. + @return: self + @rtype: L{Properties} + """ + for d in self.definitions.values(): + self.defined[d.name] = d.default + return self + + def __notset(self, name): + return not (name in self.modified) + + def __set(self, name, value): + d = self.definition(name) + d.validate(value) + value = d.nvl(value) + prev = self.defined[name] + self.defined[name] = value + self.modified.add(name) + d.linker.updated(self, prev, value) + + def __get(self, name, *df): + d = self.definition(name) + value = self.defined.get(name) + if value == d.default and len(df): + value = df[0] + return value + + def str(self, history): + s = [] + s.append('Definitions:') + for d in self.definitions.values(): + s.append('\t%s' % repr(d)) + s.append('Content:') + for d in self.defined.items(): + s.append('\t%s' % str(d)) + if self not in history: + history.append(self) + s.append('Linked:') + for x in self.links: + s.append(x.str(history)) + history.remove(self) + return '\n'.join(s) + + def __repr__(self): + return str(self) + + def __str__(self): + return self.str([]) + + +class Skin(object): + """ + The meta-programming I{skin} around the L{Properties} object. + @ivar __pts__: The wrapped object. + @type __pts__: L{Properties}. + """ + def __init__(self, domain, definitions, kwargs): + self.__pts__ = Properties(domain, definitions, kwargs) + + def __setattr__(self, name, value): + builtin = name.startswith('__') and name.endswith('__') + if builtin: + self.__dict__[name] = value + return + self.__pts__.set(name, value) + + def __getattr__(self, name): + return self.__pts__.get(name) + + def __repr__(self): + return str(self) + + def __str__(self): + return str(self.__pts__) + + +class Unskin(object): + def __new__(self, *args, **kwargs): + return args[0].__pts__ + + +class Inspector: + """ + Wrapper inspector. + """ + def __init__(self, options): + self.properties = options.__pts__ + + def get(self, name, *df): + """ + Get the value of a property by I{name}. + @param name: The property name. + @type name: str + @param df: An optional value to be returned when the value + is not set + @type df: [1]. + @return: The stored value, or I{df[0]} if not set. + @rtype: any + """ + return self.properties.get(name, *df) + + def update(self, **kwargs): + """ + Update the property values as specified by keyword/value. + @param kwargs: A list of property name/values to set. + @type kwargs: dict + @return: self + @rtype: L{Properties} + """ + return self.properties.update(**kwargs) + + def link(self, other): + """ + Link (associate) this object with anI{other} properties object + to create a network of properties. Links are bidirectional. + @param other: The object to link. + @type other: L{Properties} + @return: self + @rtype: L{Properties} + """ + p = other.__pts__ + return self.properties.link(p) + + def unlink(self, other): + """ + Unlink (disassociate) the specified properties object. + @param other: The object to unlink. + @type other: L{Properties} + @return: self + @rtype: L{Properties} + """ + p = other.__pts__ + return self.properties.unlink(p) diff --git a/awx/lib/site-packages/suds/reader.py b/awx/lib/site-packages/suds/reader.py new file mode 100644 index 0000000000..1184f1274e --- /dev/null +++ b/awx/lib/site-packages/suds/reader.py @@ -0,0 +1,169 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Contains xml document reader classes. +""" + + +from suds.sax.parser import Parser +from suds.transport import Request +from suds.cache import Cache, NoCache +from suds.store import DocumentStore +from suds.plugin import PluginContainer +from logging import getLogger + + +log = getLogger(__name__) + + +class Reader: + """ + The reader provides integration with cache. + @ivar options: An options object. + @type options: I{Options} + """ + + def __init__(self, options): + """ + @param options: An options object. + @type options: I{Options} + """ + self.options = options + self.plugins = PluginContainer(options.plugins) + + def mangle(self, name, x): + """ + Mangle the name by hashing the I{name} and appending I{x}. + @return: the mangled name. + """ + h = abs(hash(name)) + return '%s-%s' % (h, x) + + +class DocumentReader(Reader): + """ + The XML document reader provides an integration + between the SAX L{Parser} and the document cache. + """ + + def open(self, url): + """ + Open an XML document at the specified I{url}. + First, the document attempted to be retrieved from + the I{object cache}. If not found, it is downloaded and + parsed using the SAX parser. The result is added to the + cache for the next open(). + @param url: A document url. + @type url: str. + @return: The specified XML document. + @rtype: I{Document} + """ + cache = self.cache() + id = self.mangle(url, 'document') + d = cache.get(id) + if d is None: + d = self.download(url) + cache.put(id, d) + self.plugins.document.parsed(url=url, document=d.root()) + return d + + def download(self, url): + """ + Download the docuemnt. + @param url: A document url. + @type url: str. + @return: A file pointer to the docuemnt. + @rtype: file-like + """ + store = DocumentStore() + fp = store.open(url) + if fp is None: + fp = self.options.transport.open(Request(url)) + content = fp.read() + fp.close() + ctx = self.plugins.document.loaded(url=url, document=content) + content = ctx.document + sax = Parser() + return sax.parse(string=content) + + def cache(self): + """ + Get the cache. + @return: The I{options} when I{cachingpolicy} = B{0}. + @rtype: L{Cache} + """ + if self.options.cachingpolicy == 0: + return self.options.cache + else: + return NoCache() + + +class DefinitionsReader(Reader): + """ + The WSDL definitions reader provides an integration + between the Definitions and the object cache. + @ivar fn: A factory function (constructor) used to + create the object not found in the cache. + @type fn: I{Constructor} + """ + + def __init__(self, options, fn): + """ + @param options: An options object. + @type options: I{Options} + @param fn: A factory function (constructor) used to + create the object not found in the cache. + @type fn: I{Constructor} + """ + Reader.__init__(self, options) + self.fn = fn + + def open(self, url): + """ + Open a WSDL at the specified I{url}. + First, the WSDL attempted to be retrieved from + the I{object cache}. After unpickled from the cache, the + I{options} attribute is restored. + If not found, it is downloaded and instantiated using the + I{fn} constructor and added to the cache for the next open(). + @param url: A WSDL url. + @type url: str. + @return: The WSDL object. + @rtype: I{Definitions} + """ + cache = self.cache() + id = self.mangle(url, 'wsdl') + d = cache.get(id) + if d is None: + d = self.fn(url, self.options) + cache.put(id, d) + else: + d.options = self.options + for imp in d.imports: + imp.imported.options = self.options + return d + + def cache(self): + """ + Get the cache. + @return: The I{options} when I{cachingpolicy} = B{1}. + @rtype: L{Cache} + """ + if self.options.cachingpolicy == 1: + return self.options.cache + else: + return NoCache() \ No newline at end of file diff --git a/awx/lib/site-packages/suds/resolver.py b/awx/lib/site-packages/suds/resolver.py new file mode 100644 index 0000000000..278b5da649 --- /dev/null +++ b/awx/lib/site-packages/suds/resolver.py @@ -0,0 +1,496 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +The I{resolver} module provides a collection of classes that +provide wsdl/xsd named type resolution. +""" + +import re +from logging import getLogger +from suds import * +from suds.sax import splitPrefix, Namespace +from suds.sudsobject import Object +from suds.xsd.query import BlindQuery, TypeQuery, qualify + +log = getLogger(__name__) + + +class Resolver: + """ + An I{abstract} schema-type resolver. + @ivar schema: A schema object. + @type schema: L{xsd.schema.Schema} + """ + + def __init__(self, schema): + """ + @param schema: A schema object. + @type schema: L{xsd.schema.Schema} + """ + self.schema = schema + + def find(self, name, resolved=True): + """ + Get the definition object for the schema object by name. + @param name: The name of a schema object. + @type name: basestring + @param resolved: A flag indicating that the fully resolved type + should be returned. + @type resolved: boolean + @return: The found schema I{type} + @rtype: L{xsd.sxbase.SchemaObject} + """ + log.debug('searching schema for (%s)', name) + qref = qualify(name, self.schema.root, self.schema.tns) + query = BlindQuery(qref) + result = query.execute(self.schema) + if result is None: + log.error('(%s) not-found', name) + return None + log.debug('found (%s) as (%s)', name, Repr(result)) + if resolved: + result = result.resolve() + return result + + +class PathResolver(Resolver): + """ + Resolveds the definition object for the schema type located at the specified path. + The path may contain (.) dot notation to specify nested types. + @ivar wsdl: A wsdl object. + @type wsdl: L{wsdl.Definitions} + """ + + def __init__(self, wsdl, ps='.'): + """ + @param wsdl: A schema object. + @type wsdl: L{wsdl.Definitions} + @param ps: The path separator character + @type ps: char + """ + Resolver.__init__(self, wsdl.schema) + self.wsdl = wsdl + self.altp = re.compile('({)(.+)(})(.+)') + self.splitp = re.compile('({.+})*[^\%s]+' % ps[0]) + + def find(self, path, resolved=True): + """ + Get the definition object for the schema type located at the specified path. + The path may contain (.) dot notation to specify nested types. + Actually, the path separator is usually a (.) but can be redefined + during contruction. + @param path: A (.) separated path to a schema type. + @type path: basestring + @param resolved: A flag indicating that the fully resolved type + should be returned. + @type resolved: boolean + @return: The found schema I{type} + @rtype: L{xsd.sxbase.SchemaObject} + """ + result = None + parts = self.split(path) + try: + result = self.root(parts) + if len(parts) > 1: + result = result.resolve(nobuiltin=True) + result = self.branch(result, parts) + result = self.leaf(result, parts) + if resolved: + result = result.resolve(nobuiltin=True) + except PathResolver.BadPath: + log.error('path: "%s", not-found' % path) + return result + + def root(self, parts): + """ + Find the path root. + @param parts: A list of path parts. + @type parts: [str,..] + @return: The root. + @rtype: L{xsd.sxbase.SchemaObject} + """ + result = None + name = parts[0] + log.debug('searching schema for (%s)', name) + qref = self.qualify(parts[0]) + query = BlindQuery(qref) + result = query.execute(self.schema) + if result is None: + log.error('(%s) not-found', name) + raise PathResolver.BadPath(name) + else: + log.debug('found (%s) as (%s)', name, Repr(result)) + return result + + def branch(self, root, parts): + """ + Traverse the path until the leaf is reached. + @param parts: A list of path parts. + @type parts: [str,..] + @param root: The root. + @type root: L{xsd.sxbase.SchemaObject} + @return: The end of the branch. + @rtype: L{xsd.sxbase.SchemaObject} + """ + result = root + for part in parts[1:-1]: + name = splitPrefix(part)[1] + log.debug('searching parent (%s) for (%s)', Repr(result), name) + result, ancestry = result.get_child(name) + if result is None: + log.error('(%s) not-found', name) + raise PathResolver.BadPath(name) + else: + result = result.resolve(nobuiltin=True) + log.debug('found (%s) as (%s)', name, Repr(result)) + return result + + def leaf(self, parent, parts): + """ + Find the leaf. + @param parts: A list of path parts. + @type parts: [str,..] + @param parent: The leaf's parent. + @type parent: L{xsd.sxbase.SchemaObject} + @return: The leaf. + @rtype: L{xsd.sxbase.SchemaObject} + """ + name = splitPrefix(parts[-1])[1] + if name.startswith('@'): + result, path = parent.get_attribute(name[1:]) + else: + result, ancestry = parent.get_child(name) + if result is None: + raise PathResolver.BadPath(name) + return result + + def qualify(self, name): + """ + Qualify the name as either: + - plain name + - ns prefixed name (eg: ns0:Person) + - fully ns qualified name (eg: {http://myns-uri}Person) + @param name: The name of an object in the schema. + @type name: str + @return: A qualifed name. + @rtype: qname + """ + m = self.altp.match(name) + if m is None: + return qualify(name, self.wsdl.root, self.wsdl.tns) + else: + return (m.group(4), m.group(2)) + + def split(self, s): + """ + Split the string on (.) while preserving any (.) inside the + '{}' alternalte syntax for full ns qualification. + @param s: A plain or qualifed name. + @type s: str + @return: A list of the name's parts. + @rtype: [str,..] + """ + parts = [] + b = 0 + while 1: + m = self.splitp.match(s, b) + if m is None: + break + b,e = m.span() + parts.append(s[b:e]) + b = e+1 + return parts + + class BadPath(Exception): pass + + +class TreeResolver(Resolver): + """ + The tree resolver is a I{stateful} tree resolver + used to resolve each node in a tree. As such, it mirrors + the tree structure to ensure that nodes are resolved in + context. + @ivar stack: The context stack. + @type stack: list + """ + + def __init__(self, schema): + """ + @param schema: A schema object. + @type schema: L{xsd.schema.Schema} + """ + Resolver.__init__(self, schema) + self.stack = Stack() + + def reset(self): + """ + Reset the resolver's state. + """ + self.stack = Stack() + + def push(self, x): + """ + Push an I{object} onto the stack. + @param x: An object to push. + @type x: L{Frame} + @return: The pushed frame. + @rtype: L{Frame} + """ + if isinstance(x, Frame): + frame = x + else: + frame = Frame(x) + self.stack.append(frame) + log.debug('push: (%s)\n%s', Repr(frame), Repr(self.stack)) + return frame + + def top(self): + """ + Get the I{frame} at the top of the stack. + @return: The top I{frame}, else None. + @rtype: L{Frame} + """ + if len(self.stack): + return self.stack[-1] + else: + return Frame.Empty() + + def pop(self): + """ + Pop the frame at the top of the stack. + @return: The popped frame, else None. + @rtype: L{Frame} + """ + if len(self.stack): + popped = self.stack.pop() + log.debug('pop: (%s)\n%s', Repr(popped), Repr(self.stack)) + return popped + else: + log.debug('stack empty, not-popped') + return None + + def depth(self): + """ + Get the current stack depth. + @return: The current stack depth. + @rtype: int + """ + return len(self.stack) + + def getchild(self, name, parent): + """ get a child by name """ + log.debug('searching parent (%s) for (%s)', Repr(parent), name) + if name.startswith('@'): + return parent.get_attribute(name[1:]) + else: + return parent.get_child(name) + + +class NodeResolver(TreeResolver): + """ + The node resolver is a I{stateful} XML document resolver + used to resolve each node in a tree. As such, it mirrors + the tree structure to ensure that nodes are resolved in + context. + """ + + def __init__(self, schema): + """ + @param schema: A schema object. + @type schema: L{xsd.schema.Schema} + """ + TreeResolver.__init__(self, schema) + + def find(self, node, resolved=False, push=True): + """ + @param node: An xml node to be resolved. + @type node: L{sax.element.Element} + @param resolved: A flag indicating that the fully resolved type should be + returned. + @type resolved: boolean + @param push: Indicates that the resolved type should be + pushed onto the stack. + @type push: boolean + @return: The found schema I{type} + @rtype: L{xsd.sxbase.SchemaObject} + """ + name = node.name + parent = self.top().resolved + if parent is None: + result, ancestry = self.query(name, node) + else: + result, ancestry = self.getchild(name, parent) + known = self.known(node) + if result is None: + return result + if push: + frame = Frame(result, resolved=known, ancestry=ancestry) + pushed = self.push(frame) + if resolved: + result = result.resolve() + return result + + def findattr(self, name, resolved=True): + """ + Find an attribute type definition. + @param name: An attribute name. + @type name: basestring + @param resolved: A flag indicating that the fully resolved type should be + returned. + @type resolved: boolean + @return: The found schema I{type} + @rtype: L{xsd.sxbase.SchemaObject} + """ + name = '@%s'%name + parent = self.top().resolved + if parent is None: + result, ancestry = self.query(name, node) + else: + result, ancestry = self.getchild(name, parent) + if result is None: + return result + if resolved: + result = result.resolve() + return result + + def query(self, name, node): + """ blindly query the schema by name """ + log.debug('searching schema for (%s)', name) + qref = qualify(name, node, node.namespace()) + query = BlindQuery(qref) + result = query.execute(self.schema) + return (result, []) + + def known(self, node): + """ resolve type referenced by @xsi:type """ + ref = node.get('type', Namespace.xsins) + if ref is None: + return None + qref = qualify(ref, node, node.namespace()) + query = BlindQuery(qref) + return query.execute(self.schema) + + +class GraphResolver(TreeResolver): + """ + The graph resolver is a I{stateful} L{Object} graph resolver + used to resolve each node in a tree. As such, it mirrors + the tree structure to ensure that nodes are resolved in + context. + """ + + def __init__(self, schema): + """ + @param schema: A schema object. + @type schema: L{xsd.schema.Schema} + """ + TreeResolver.__init__(self, schema) + + def find(self, name, object, resolved=False, push=True): + """ + @param name: The name of the object to be resolved. + @type name: basestring + @param object: The name's value. + @type object: (any|L{Object}) + @param resolved: A flag indicating that the fully resolved type + should be returned. + @type resolved: boolean + @param push: Indicates that the resolved type should be + pushed onto the stack. + @type push: boolean + @return: The found schema I{type} + @rtype: L{xsd.sxbase.SchemaObject} + """ + known = None + parent = self.top().resolved + if parent is None: + result, ancestry = self.query(name) + else: + result, ancestry = self.getchild(name, parent) + if result is None: + return None + if isinstance(object, Object): + known = self.known(object) + if push: + frame = Frame(result, resolved=known, ancestry=ancestry) + pushed = self.push(frame) + if resolved: + if known is None: + result = result.resolve() + else: + result = known + return result + + def query(self, name): + """ blindly query the schema by name """ + log.debug('searching schema for (%s)', name) + schema = self.schema + wsdl = self.wsdl() + if wsdl is None: + qref = qualify(name, schema.root, schema.tns) + else: + qref = qualify(name, wsdl.root, wsdl.tns) + query = BlindQuery(qref) + result = query.execute(schema) + return (result, []) + + def wsdl(self): + """ get the wsdl """ + container = self.schema.container + if container is None: + return None + else: + return container.wsdl + + def known(self, object): + """ get the type specified in the object's metadata """ + try: + md = object.__metadata__ + known = md.sxtype + return known + except: + pass + + +class Frame: + def __init__(self, type, resolved=None, ancestry=()): + self.type = type + if resolved is None: + resolved = type.resolve() + self.resolved = resolved.resolve() + self.ancestry = ancestry + + def __str__(self): + return '%s\n%s\n%s' % \ + (Repr(self.type), + Repr(self.resolved), + [Repr(t) for t in self.ancestry]) + + class Empty: + def __getattr__(self, name): + if name == 'ancestry': + return () + else: + return None + + +class Stack(list): + def __repr__(self): + result = [] + for item in self: + result.append(repr(item)) + return '\n'.join(result) \ No newline at end of file diff --git a/awx/lib/site-packages/suds/sax/__init__.py b/awx/lib/site-packages/suds/sax/__init__.py new file mode 100644 index 0000000000..3d71432fdf --- /dev/null +++ b/awx/lib/site-packages/suds/sax/__init__.py @@ -0,0 +1,109 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +The sax module contains a collection of classes that provide a +(D)ocument (O)bject (M)odel representation of an XML document. +The goal is to provide an easy, intuative interface for managing XML +documents. Although, the term, DOM, is used above, this model is +B{far} better. + +XML namespaces in suds are represented using a (2) element tuple +containing the prefix and the URI. Eg: I{('tns', 'http://myns')} + +@var encoder: A I{pluggable} XML special character processor used to + encode/decode strings. +@type encoder: L{Encoder} +""" + +from suds.sax.enc import Encoder + +# +# pluggable XML special character encoder. +# +encoder = Encoder() + + +def splitPrefix(name): + """ + Split the name into a tuple (I{prefix}, I{name}). The first element in + the tuple is I{None} when the name does't have a prefix. + @param name: A node name containing an optional prefix. + @type name: basestring + @return: A tuple containing the (2) parts of I{name} + @rtype: (I{prefix}, I{name}) + """ + if isinstance(name, basestring) \ + and ':' in name: + return tuple(name.split(':', 1)) + else: + return (None, name) + + +class Namespace: + """ + The namespace class represents XML namespaces. + """ + + default = (None, None) + xmlns = ('xml', 'http://www.w3.org/XML/1998/namespace') + xsdns = ('xs', 'http://www.w3.org/2001/XMLSchema') + xsins = ('xsi', 'http://www.w3.org/2001/XMLSchema-instance') + all = (xsdns, xsins) + + @classmethod + def create(cls, p=None, u=None): + return (p, u) + + @classmethod + def none(cls, ns): + return ( ns == cls.default ) + + @classmethod + def xsd(cls, ns): + try: + return cls.w3(ns) and ns[1].endswith('XMLSchema') + except: + pass + return False + + @classmethod + def xsi(cls, ns): + try: + return cls.w3(ns) and ns[1].endswith('XMLSchema-instance') + except: + pass + return False + + @classmethod + def xs(cls, ns): + return ( cls.xsd(ns) or cls.xsi(ns) ) + + @classmethod + def w3(cls, ns): + try: + return ns[1].startswith('http://www.w3.org') + except: + pass + return False + + @classmethod + def isns(cls, ns): + try: + return isinstance(ns, tuple) and len(ns) == len(cls.default) + except: + pass + return False diff --git a/awx/lib/site-packages/suds/sax/attribute.py b/awx/lib/site-packages/suds/sax/attribute.py new file mode 100644 index 0000000000..86dfb1116f --- /dev/null +++ b/awx/lib/site-packages/suds/sax/attribute.py @@ -0,0 +1,181 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Provides XML I{attribute} classes. +""" + +import suds.sax +from logging import getLogger +from suds import * +from suds.sax import * +from suds.sax.text import Text + +log = getLogger(__name__) + +class Attribute: + """ + An XML attribute object. + @ivar parent: The node containing this attribute + @type parent: L{element.Element} + @ivar prefix: The I{optional} namespace prefix. + @type prefix: basestring + @ivar name: The I{unqualified} name of the attribute + @type name: basestring + @ivar value: The attribute's value + @type value: basestring + """ + def __init__(self, name, value=None): + """ + @param name: The attribute's name with I{optional} namespace prefix. + @type name: basestring + @param value: The attribute's value + @type value: basestring + """ + self.parent = None + self.prefix, self.name = splitPrefix(name) + self.setValue(value) + + def clone(self, parent=None): + """ + Clone this object. + @param parent: The parent for the clone. + @type parent: L{element.Element} + @return: A copy of this object assigned to the new parent. + @rtype: L{Attribute} + """ + a = Attribute(self.qname(), self.value) + a.parent = parent + return a + + def qname(self): + """ + Get the B{fully} qualified name of this attribute + @return: The fully qualified name. + @rtype: basestring + """ + if self.prefix is None: + return self.name + else: + return ':'.join((self.prefix, self.name)) + + def setValue(self, value): + """ + Set the attributes value + @param value: The new value (may be None) + @type value: basestring + @return: self + @rtype: L{Attribute} + """ + if isinstance(value, Text): + self.value = value + else: + self.value = Text(value) + return self + + def getValue(self, default=Text('')): + """ + Get the attributes value with optional default. + @param default: An optional value to be return when the + attribute's has not been set. + @type default: basestring + @return: The attribute's value, or I{default} + @rtype: L{Text} + """ + if self.hasText(): + return self.value + else: + return default + + def hasText(self): + """ + Get whether the attribute has I{text} and that it is not an empty + (zero length) string. + @return: True when has I{text}. + @rtype: boolean + """ + return ( self.value is not None and len(self.value) ) + + def namespace(self): + """ + Get the attributes namespace. This may either be the namespace + defined by an optional prefix, or its parent's namespace. + @return: The attribute's namespace + @rtype: (I{prefix}, I{name}) + """ + if self.prefix is None: + return Namespace.default + else: + return self.resolvePrefix(self.prefix) + + def resolvePrefix(self, prefix): + """ + Resolve the specified prefix to a known namespace. + @param prefix: A declared prefix + @type prefix: basestring + @return: The namespace that has been mapped to I{prefix} + @rtype: (I{prefix}, I{name}) + """ + ns = Namespace.default + if self.parent is not None: + ns = self.parent.resolvePrefix(prefix) + return ns + + def match(self, name=None, ns=None): + """ + Match by (optional) name and/or (optional) namespace. + @param name: The optional attribute tag name. + @type name: str + @param ns: An optional namespace. + @type ns: (I{prefix}, I{name}) + @return: True if matched. + @rtype: boolean + """ + if name is None: + byname = True + else: + byname = ( self.name == name ) + if ns is None: + byns = True + else: + byns = ( self.namespace()[1] == ns[1] ) + return ( byname and byns ) + + def __eq__(self, rhs): + """ equals operator """ + return rhs is not None and \ + isinstance(rhs, Attribute) and \ + self.prefix == rhs.name and \ + self.name == rhs.name + + def __repr__(self): + """ get a string representation """ + return \ + 'attr (prefix=%s, name=%s, value=(%s))' %\ + (self.prefix, self.name, self.value) + + def __str__(self): + """ get an xml string representation """ + return unicode(self).encode('utf-8') + + def __unicode__(self): + """ get an xml string representation """ + n = self.qname() + if self.hasText(): + v = self.value.escape() + else: + v = self.value + return u'%s="%s"' % (n, v) diff --git a/awx/lib/site-packages/suds/sax/date.py b/awx/lib/site-packages/suds/sax/date.py new file mode 100644 index 0000000000..6e31c4c77c --- /dev/null +++ b/awx/lib/site-packages/suds/sax/date.py @@ -0,0 +1,378 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Nathan Van Gheem (vangheem@gmail.com) + +""" +The I{xdate} module provides classes for converstion +between XML dates and python objects. +""" + +from logging import getLogger +from suds import * +from suds.xsd import * +import time +import datetime as dt +import re + +log = getLogger(__name__) + + +class Date: + """ + An XML date object. + Supported formats: + - YYYY-MM-DD + - YYYY-MM-DD(z|Z) + - YYYY-MM-DD+06:00 + - YYYY-MM-DD-06:00 + @ivar date: The object value. + @type date: B{datetime}.I{date} + """ + def __init__(self, date): + """ + @param date: The value of the object. + @type date: (date|str) + @raise ValueError: When I{date} is invalid. + """ + if isinstance(date, dt.date): + self.date = date + return + if isinstance(date, basestring): + self.date = self.__parse(date) + return + raise ValueError, type(date) + + def year(self): + """ + Get the I{year} component. + @return: The year. + @rtype: int + """ + return self.date.year + + def month(self): + """ + Get the I{month} component. + @return: The month. + @rtype: int + """ + return self.date.month + + def day(self): + """ + Get the I{day} component. + @return: The day. + @rtype: int + """ + return self.date.day + + def __parse(self, s): + """ + Parse the string date. + Supported formats: + - YYYY-MM-DD + - YYYY-MM-DD(z|Z) + - YYYY-MM-DD+06:00 + - YYYY-MM-DD-06:00 + Although, the TZ is ignored because it's meaningless + without the time, right? + @param s: A date string. + @type s: str + @return: A date object. + @rtype: I{date} + """ + try: + year, month, day = s[:10].split('-', 2) + year = int(year) + month = int(month) + day = int(day) + return dt.date(year, month, day) + except: + log.debug(s, exec_info=True) + raise ValueError, 'Invalid format "%s"' % s + + def __str__(self): + return unicode(self) + + def __unicode__(self): + return self.date.isoformat() + + +class Time: + """ + An XML time object. + Supported formats: + - HH:MI:SS + - HH:MI:SS(z|Z) + - HH:MI:SS.ms + - HH:MI:SS.ms(z|Z) + - HH:MI:SS(+|-)06:00 + - HH:MI:SS.ms(+|-)06:00 + @ivar tz: The timezone + @type tz: L{Timezone} + @ivar date: The object value. + @type date: B{datetime}.I{time} + """ + + def __init__(self, time, adjusted=True): + """ + @param time: The value of the object. + @type time: (time|str) + @param adjusted: Adjust for I{local} Timezone. + @type adjusted: boolean + @raise ValueError: When I{time} is invalid. + """ + self.tz = Timezone() + if isinstance(time, dt.time): + self.time = time + return + if isinstance(time, basestring): + self.time = self.__parse(time) + if adjusted: + self.__adjust() + return + raise ValueError, type(time) + + def hour(self): + """ + Get the I{hour} component. + @return: The hour. + @rtype: int + """ + return self.time.hour + + def minute(self): + """ + Get the I{minute} component. + @return: The minute. + @rtype: int + """ + return self.time.minute + + def second(self): + """ + Get the I{seconds} component. + @return: The seconds. + @rtype: int + """ + return self.time.second + + def microsecond(self): + """ + Get the I{microsecond} component. + @return: The microsecond. + @rtype: int + """ + return self.time.microsecond + + def __adjust(self): + """ + Adjust for TZ offset. + """ + if hasattr(self, 'offset'): + today = dt.date.today() + delta = self.tz.adjustment(self.offset) + d = dt.datetime.combine(today, self.time) + d = ( d + delta ) + self.time = d.time() + + def __parse(self, s): + """ + Parse the string date. + Patterns: + - HH:MI:SS + - HH:MI:SS(z|Z) + - HH:MI:SS.ms + - HH:MI:SS.ms(z|Z) + - HH:MI:SS(+|-)06:00 + - HH:MI:SS.ms(+|-)06:00 + @param s: A time string. + @type s: str + @return: A time object. + @rtype: B{datetime}.I{time} + """ + try: + offset = None + part = Timezone.split(s) + hour, minute, second = part[0].split(':', 2) + hour = int(hour) + minute = int(minute) + second, ms = self.__second(second) + if len(part) == 2: + self.offset = self.__offset(part[1]) + if ms is None: + return dt.time(hour, minute, second) + else: + return dt.time(hour, minute, second, ms) + except: + log.debug(s, exec_info=True) + raise ValueError, 'Invalid format "%s"' % s + + def __second(self, s): + """ + Parse the seconds and microseconds. + The microseconds are truncated to 999999 due to a restriction in + the python datetime.datetime object. + @param s: A string representation of the seconds. + @type s: str + @return: Tuple of (sec,ms) + @rtype: tuple. + """ + part = s.split('.') + if len(part) > 1: + return (int(part[0]), int(part[1][:6])) + else: + return (int(part[0]), None) + + def __offset(self, s): + """ + Parse the TZ offset. + @param s: A string representation of the TZ offset. + @type s: str + @return: The signed offset in hours. + @rtype: str + """ + if len(s) == len('-00:00'): + return int(s[:3]) + if len(s) == 0: + return self.tz.local + if len(s) == 1: + return 0 + raise Exception() + + def __str__(self): + return unicode(self) + + def __unicode__(self): + time = self.time.isoformat() + if self.tz.local: + return '%s%+.2d:00' % (time, self.tz.local) + else: + return '%sZ' % time + + +class DateTime(Date,Time): + """ + An XML time object. + Supported formats: + - YYYY-MM-DDB{T}HH:MI:SS + - YYYY-MM-DDB{T}HH:MI:SS(z|Z) + - YYYY-MM-DDB{T}HH:MI:SS.ms + - YYYY-MM-DDB{T}HH:MI:SS.ms(z|Z) + - YYYY-MM-DDB{T}HH:MI:SS(+|-)06:00 + - YYYY-MM-DDB{T}HH:MI:SS.ms(+|-)06:00 + @ivar datetime: The object value. + @type datetime: B{datetime}.I{datedate} + """ + def __init__(self, date): + """ + @param date: The value of the object. + @type date: (datetime|str) + @raise ValueError: When I{tm} is invalid. + """ + if isinstance(date, dt.datetime): + Date.__init__(self, date.date()) + Time.__init__(self, date.time()) + self.datetime = \ + dt.datetime.combine(self.date, self.time) + return + if isinstance(date, basestring): + part = date.split('T') + Date.__init__(self, part[0]) + Time.__init__(self, part[1], 0) + self.datetime = \ + dt.datetime.combine(self.date, self.time) + self.__adjust() + return + raise ValueError, type(date) + + def __adjust(self): + """ + Adjust for TZ offset. + """ + if not hasattr(self, 'offset'): + return + delta = self.tz.adjustment(self.offset) + try: + d = ( self.datetime + delta ) + self.datetime = d + self.date = d.date() + self.time = d.time() + except OverflowError: + log.warn('"%s" caused overflow, not-adjusted', self.datetime) + + def __str__(self): + return unicode(self) + + def __unicode__(self): + s = [] + s.append(Date.__unicode__(self)) + s.append(Time.__unicode__(self)) + return 'T'.join(s) + + +class UTC(DateTime): + """ + Represents current UTC time. + """ + + def __init__(self, date=None): + if date is None: + date = dt.datetime.utcnow() + DateTime.__init__(self, date) + self.tz.local = 0 + + +class Timezone: + """ + Timezone object used to do TZ conversions + @cvar local: The (A) local TZ offset. + @type local: int + @cvar patten: The regex patten to match TZ. + @type patten: re.Pattern + """ + + pattern = re.compile('([zZ])|([\-\+][0-9]{2}:[0-9]{2})') + + LOCAL = ( 0-time.timezone/60/60 ) + + def __init__(self, offset=None): + if offset is None: + offset = self.LOCAL + self.local = offset + + @classmethod + def split(cls, s): + """ + Split the TZ from string. + @param s: A string containing a timezone + @type s: basestring + @return: The split parts. + @rtype: tuple + """ + m = cls.pattern.search(s) + if m is None: + return (s,) + x = m.start(0) + return (s[:x], s[x:]) + + def adjustment(self, offset): + """ + Get the adjustment to the I{local} TZ. + @return: The delta between I{offset} and local TZ. + @rtype: B{datetime}.I{timedelta} + """ + delta = ( self.local - offset ) + return dt.timedelta(hours=delta) diff --git a/awx/lib/site-packages/suds/sax/document.py b/awx/lib/site-packages/suds/sax/document.py new file mode 100644 index 0000000000..5a004eb5a0 --- /dev/null +++ b/awx/lib/site-packages/suds/sax/document.py @@ -0,0 +1,61 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Provides XML I{document} classes. +""" + +from logging import getLogger +from suds import * +from suds.sax import * +from suds.sax.element import Element + +log = getLogger(__name__) + +class Document(Element): + """ simple document """ + + DECL = '' + + def __init__(self, root=None): + Element.__init__(self, 'document') + if root is not None: + self.append(root) + + def root(self): + if len(self.children): + return self.children[0] + else: + return None + + def str(self): + s = [] + s.append(self.DECL) + s.append('\n') + s.append(self.root().str()) + return ''.join(s) + + def plain(self): + s = [] + s.append(self.DECL) + s.append(self.root().plain()) + return ''.join(s) + + def __str__(self): + return unicode(self).encode('utf-8') + + def __unicode__(self): + return self.str() \ No newline at end of file diff --git a/awx/lib/site-packages/suds/sax/element.py b/awx/lib/site-packages/suds/sax/element.py new file mode 100644 index 0000000000..9dec1f9429 --- /dev/null +++ b/awx/lib/site-packages/suds/sax/element.py @@ -0,0 +1,1147 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Provides XML I{element} classes. +""" + +from logging import getLogger +from suds import * +from suds.sax import * +from suds.sax.text import Text +from suds.sax.attribute import Attribute +import sys +if sys.version_info < (2, 4, 0): + from sets import Set as set + del sys + +log = getLogger(__name__) + +class Element: + """ + An XML element object. + @ivar parent: The node containing this attribute + @type parent: L{Element} + @ivar prefix: The I{optional} namespace prefix. + @type prefix: basestring + @ivar name: The I{unqualified} name of the attribute + @type name: basestring + @ivar expns: An explicit namespace (xmlns="..."). + @type expns: (I{prefix}, I{name}) + @ivar nsprefixes: A mapping of prefixes to namespaces. + @type nsprefixes: dict + @ivar attributes: A list of XML attributes. + @type attributes: [I{Attribute},] + @ivar text: The element's I{text} content. + @type text: basestring + @ivar children: A list of child elements. + @type children: [I{Element},] + @cvar matcher: A collection of I{lambda} for string matching. + @cvar specialprefixes: A dictionary of builtin-special prefixes. + """ + + matcher = \ + { + 'eq': lambda a,b: a == b, + 'startswith' : lambda a,b: a.startswith(b), + 'endswith' : lambda a,b: a.endswith(b), + 'contains' : lambda a,b: b in a + } + + specialprefixes = { Namespace.xmlns[0] : Namespace.xmlns[1] } + + @classmethod + def buildPath(self, parent, path): + """ + Build the specifed pat as a/b/c where missing intermediate nodes are built + automatically. + @param parent: A parent element on which the path is built. + @type parent: I{Element} + @param path: A simple path separated by (/). + @type path: basestring + @return: The leaf node of I{path}. + @rtype: L{Element} + """ + for tag in path.split('/'): + child = parent.getChild(tag) + if child is None: + child = Element(tag, parent) + parent = child + return child + + def __init__(self, name, parent=None, ns=None): + """ + @param name: The element's (tag) name. May cotain a prefix. + @type name: basestring + @param parent: An optional parent element. + @type parent: I{Element} + @param ns: An optional namespace + @type ns: (I{prefix}, I{name}) + """ + + self.rename(name) + self.expns = None + self.nsprefixes = {} + self.attributes = [] + self.text = None + if parent is not None: + if isinstance(parent, Element): + self.parent = parent + else: + raise Exception('parent (%s) not-valid', parent.__class__.__name__) + else: + self.parent = None + self.children = [] + self.applyns(ns) + + def rename(self, name): + """ + Rename the element. + @param name: A new name for the element. + @type name: basestring + """ + if name is None: + raise Exception('name (%s) not-valid' % name) + else: + self.prefix, self.name = splitPrefix(name) + + def setPrefix(self, p, u=None): + """ + Set the element namespace prefix. + @param p: A new prefix for the element. + @type p: basestring + @param u: A namespace URI to be mapped to the prefix. + @type u: basestring + @return: self + @rtype: L{Element} + """ + self.prefix = p + if p is not None and u is not None: + self.addPrefix(p, u) + return self + + def qname(self): + """ + Get the B{fully} qualified name of this element + @return: The fully qualified name. + @rtype: basestring + """ + if self.prefix is None: + return self.name + else: + return '%s:%s' % (self.prefix, self.name) + + def getRoot(self): + """ + Get the root (top) node of the tree. + @return: The I{top} node of this tree. + @rtype: I{Element} + """ + if self.parent is None: + return self + else: + return self.parent.getRoot() + + def clone(self, parent=None): + """ + Deep clone of this element and children. + @param parent: An optional parent for the copied fragment. + @type parent: I{Element} + @return: A deep copy parented by I{parent} + @rtype: I{Element} + """ + root = Element(self.qname(), parent, self.namespace()) + for a in self.attributes: + root.append(a.clone(self)) + for c in self.children: + root.append(c.clone(self)) + for item in self.nsprefixes.items(): + root.addPrefix(item[0], item[1]) + return root + + def detach(self): + """ + Detach from parent. + @return: This element removed from its parent's + child list and I{parent}=I{None} + @rtype: L{Element} + """ + if self.parent is not None: + if self in self.parent.children: + self.parent.children.remove(self) + self.parent = None + return self + + def set(self, name, value): + """ + Set an attribute's value. + @param name: The name of the attribute. + @type name: basestring + @param value: The attribute value. + @type value: basestring + @see: __setitem__() + """ + attr = self.getAttribute(name) + if attr is None: + attr = Attribute(name, value) + self.append(attr) + else: + attr.setValue(value) + + def unset(self, name): + """ + Unset (remove) an attribute. + @param name: The attribute name. + @type name: str + @return: self + @rtype: L{Element} + """ + try: + attr = self.getAttribute(name) + self.attributes.remove(attr) + except: + pass + return self + + + def get(self, name, ns=None, default=None): + """ + Get the value of an attribute by name. + @param name: The name of the attribute. + @type name: basestring + @param ns: The optional attribute's namespace. + @type ns: (I{prefix}, I{name}) + @param default: An optional value to be returned when either + the attribute does not exist of has not value. + @type default: basestring + @return: The attribute's value or I{default} + @rtype: basestring + @see: __getitem__() + """ + attr = self.getAttribute(name, ns) + if attr is None or attr.value is None: + return default + else: + return attr.getValue() + + def setText(self, value): + """ + Set the element's L{Text} content. + @param value: The element's text value. + @type value: basestring + @return: self + @rtype: I{Element} + """ + if isinstance(value, Text): + self.text = value + else: + self.text = Text(value) + return self + + def getText(self, default=None): + """ + Get the element's L{Text} content with optional default + @param default: A value to be returned when no text content exists. + @type default: basestring + @return: The text content, or I{default} + @rtype: L{Text} + """ + if self.hasText(): + return self.text + else: + return default + + def trim(self): + """ + Trim leading and trailing whitespace. + @return: self + @rtype: L{Element} + """ + if self.hasText(): + self.text = self.text.trim() + return self + + def hasText(self): + """ + Get whether the element has I{text} and that it is not an empty + (zero length) string. + @return: True when has I{text}. + @rtype: boolean + """ + return ( self.text is not None and len(self.text) ) + + def namespace(self): + """ + Get the element's namespace. + @return: The element's namespace by resolving the prefix, the explicit + namespace or the inherited namespace. + @rtype: (I{prefix}, I{name}) + """ + if self.prefix is None: + return self.defaultNamespace() + else: + return self.resolvePrefix(self.prefix) + + def defaultNamespace(self): + """ + Get the default (unqualified namespace). + This is the expns of the first node (looking up the tree) + that has it set. + @return: The namespace of a node when not qualified. + @rtype: (I{prefix}, I{name}) + """ + p = self + while p is not None: + if p.expns is not None: + return (None, p.expns) + else: + p = p.parent + return Namespace.default + + def append(self, objects): + """ + Append the specified child based on whether it is an + element or an attrbuite. + @param objects: A (single|collection) of attribute(s) or element(s) + to be added as children. + @type objects: (L{Element}|L{Attribute}) + @return: self + @rtype: L{Element} + """ + if not isinstance(objects, (list, tuple)): + objects = (objects,) + for child in objects: + if isinstance(child, Element): + self.children.append(child) + child.parent = self + continue + if isinstance(child, Attribute): + self.attributes.append(child) + child.parent = self + continue + raise Exception('append %s not-valid' % child.__class__.__name__) + return self + + def insert(self, objects, index=0): + """ + Insert an L{Element} content at the specified index. + @param objects: A (single|collection) of attribute(s) or element(s) + to be added as children. + @type objects: (L{Element}|L{Attribute}) + @param index: The position in the list of children to insert. + @type index: int + @return: self + @rtype: L{Element} + """ + objects = (objects,) + for child in objects: + if isinstance(child, Element): + self.children.insert(index, child) + child.parent = self + else: + raise Exception('append %s not-valid' % child.__class__.__name__) + return self + + def remove(self, child): + """ + Remove the specified child element or attribute. + @param child: A child to remove. + @type child: L{Element}|L{Attribute} + @return: The detached I{child} when I{child} is an element, else None. + @rtype: L{Element}|None + """ + if isinstance(child, Element): + return child.detach() + if isinstance(child, Attribute): + self.attributes.remove(child) + return None + + def replaceChild(self, child, content): + """ + Replace I{child} with the specified I{content}. + @param child: A child element. + @type child: L{Element} + @param content: An element or collection of elements. + @type content: L{Element} or [L{Element},] + """ + if child not in self.children: + raise Exception('child not-found') + index = self.children.index(child) + self.remove(child) + if not isinstance(content, (list, tuple)): + content = (content,) + for node in content: + self.children.insert(index, node.detach()) + node.parent = self + index += 1 + + def getAttribute(self, name, ns=None, default=None): + """ + Get an attribute by name and (optional) namespace + @param name: The name of a contained attribute (may contain prefix). + @type name: basestring + @param ns: An optional namespace + @type ns: (I{prefix}, I{name}) + @param default: Returned when attribute not-found. + @type default: L{Attribute} + @return: The requested attribute object. + @rtype: L{Attribute} + """ + if ns is None: + prefix, name = splitPrefix(name) + if prefix is None: + ns = None + else: + ns = self.resolvePrefix(prefix) + for a in self.attributes: + if a.match(name, ns): + return a + return default + + def getChild(self, name, ns=None, default=None): + """ + Get a child by (optional) name and/or (optional) namespace. + @param name: The name of a child element (may contain prefix). + @type name: basestring + @param ns: An optional namespace used to match the child. + @type ns: (I{prefix}, I{name}) + @param default: Returned when child not-found. + @type default: L{Element} + @return: The requested child, or I{default} when not-found. + @rtype: L{Element} + """ + if ns is None: + prefix, name = splitPrefix(name) + if prefix is None: + ns = None + else: + ns = self.resolvePrefix(prefix) + for c in self.children: + if c.match(name, ns): + return c + return default + + def childAtPath(self, path): + """ + Get a child at I{path} where I{path} is a (/) separated + list of element names that are expected to be children. + @param path: A (/) separated list of element names. + @type path: basestring + @return: The leaf node at the end of I{path} + @rtype: L{Element} + """ + result = None + node = self + for name in [p for p in path.split('/') if len(p) > 0]: + ns = None + prefix, name = splitPrefix(name) + if prefix is not None: + ns = node.resolvePrefix(prefix) + result = node.getChild(name, ns) + if result is None: + break; + else: + node = result + return result + + def childrenAtPath(self, path): + """ + Get a list of children at I{path} where I{path} is a (/) separated + list of element names that are expected to be children. + @param path: A (/) separated list of element names. + @type path: basestring + @return: The collection leaf nodes at the end of I{path} + @rtype: [L{Element},...] + """ + parts = [p for p in path.split('/') if len(p) > 0] + if len(parts) == 1: + result = self.getChildren(path) + else: + result = self.__childrenAtPath(parts) + return result + + def getChildren(self, name=None, ns=None): + """ + Get a list of children by (optional) name and/or (optional) namespace. + @param name: The name of a child element (may contain prefix). + @type name: basestring + @param ns: An optional namespace used to match the child. + @type ns: (I{prefix}, I{name}) + @return: The list of matching children. + @rtype: [L{Element},...] + """ + if ns is None: + if name is None: + return self.children + prefix, name = splitPrefix(name) + if prefix is None: + ns = None + else: + ns = self.resolvePrefix(prefix) + return [c for c in self.children if c.match(name, ns)] + + def detachChildren(self): + """ + Detach and return this element's children. + @return: The element's children (detached). + @rtype: [L{Element},...] + """ + detached = self.children + self.children = [] + for child in detached: + child.parent = None + return detached + + def resolvePrefix(self, prefix, default=Namespace.default): + """ + Resolve the specified prefix to a namespace. The I{nsprefixes} is + searched. If not found, it walks up the tree until either resolved or + the top of the tree is reached. Searching up the tree provides for + inherited mappings. + @param prefix: A namespace prefix to resolve. + @type prefix: basestring + @param default: An optional value to be returned when the prefix + cannot be resolved. + @type default: (I{prefix},I{URI}) + @return: The namespace that is mapped to I{prefix} in this context. + @rtype: (I{prefix},I{URI}) + """ + n = self + while n is not None: + if prefix in n.nsprefixes: + return (prefix, n.nsprefixes[prefix]) + if prefix in self.specialprefixes: + return (prefix, self.specialprefixes[prefix]) + n = n.parent + return default + + def addPrefix(self, p, u): + """ + Add or update a prefix mapping. + @param p: A prefix. + @type p: basestring + @param u: A namespace URI. + @type u: basestring + @return: self + @rtype: L{Element} + """ + self.nsprefixes[p] = u + return self + + def updatePrefix(self, p, u): + """ + Update (redefine) a prefix mapping for the branch. + @param p: A prefix. + @type p: basestring + @param u: A namespace URI. + @type u: basestring + @return: self + @rtype: L{Element} + @note: This method traverses down the entire branch! + """ + if p in self.nsprefixes: + self.nsprefixes[p] = u + for c in self.children: + c.updatePrefix(p, u) + return self + + def clearPrefix(self, prefix): + """ + Clear the specified prefix from the prefix mappings. + @param prefix: A prefix to clear. + @type prefix: basestring + @return: self + @rtype: L{Element} + """ + if prefix in self.nsprefixes: + del self.nsprefixes[prefix] + return self + + def findPrefix(self, uri, default=None): + """ + Find the first prefix that has been mapped to a namespace URI. + The local mapping is searched, then it walks up the tree until + it reaches the top or finds a match. + @param uri: A namespace URI. + @type uri: basestring + @param default: A default prefix when not found. + @type default: basestring + @return: A mapped prefix. + @rtype: basestring + """ + for item in self.nsprefixes.items(): + if item[1] == uri: + prefix = item[0] + return prefix + for item in self.specialprefixes.items(): + if item[1] == uri: + prefix = item[0] + return prefix + if self.parent is not None: + return self.parent.findPrefix(uri, default) + else: + return default + + def findPrefixes(self, uri, match='eq'): + """ + Find all prefixes that has been mapped to a namespace URI. + The local mapping is searched, then it walks up the tree until + it reaches the top collecting all matches. + @param uri: A namespace URI. + @type uri: basestring + @param match: A matching function L{Element.matcher}. + @type match: basestring + @return: A list of mapped prefixes. + @rtype: [basestring,...] + """ + result = [] + for item in self.nsprefixes.items(): + if self.matcher[match](item[1], uri): + prefix = item[0] + result.append(prefix) + for item in self.specialprefixes.items(): + if self.matcher[match](item[1], uri): + prefix = item[0] + result.append(prefix) + if self.parent is not None: + result += self.parent.findPrefixes(uri, match) + return result + + def promotePrefixes(self): + """ + Push prefix declarations up the tree as far as possible. Prefix + mapping are pushed to its parent unless the parent has the + prefix mapped to another URI or the parent has the prefix. + This is propagated up the tree until the top is reached. + @return: self + @rtype: L{Element} + """ + for c in self.children: + c.promotePrefixes() + if self.parent is None: + return + for p,u in self.nsprefixes.items(): + if p in self.parent.nsprefixes: + pu = self.parent.nsprefixes[p] + if pu == u: + del self.nsprefixes[p] + continue + if p != self.parent.prefix: + self.parent.nsprefixes[p] = u + del self.nsprefixes[p] + return self + + def refitPrefixes(self): + """ + Refit namespace qualification by replacing prefixes + with explicit namespaces. Also purges prefix mapping table. + @return: self + @rtype: L{Element} + """ + for c in self.children: + c.refitPrefixes() + if self.prefix is not None: + ns = self.resolvePrefix(self.prefix) + if ns[1] is not None: + self.expns = ns[1] + self.prefix = None + self.nsprefixes = {} + return self + + def normalizePrefixes(self): + """ + Normalize the namespace prefixes. + This generates unique prefixes for all namespaces. Then retrofits all + prefixes and prefix mappings. Further, it will retrofix attribute values + that have values containing (:). + @return: self + @rtype: L{Element} + """ + PrefixNormalizer.apply(self) + return self + + def isempty(self, content=True): + """ + Get whether the element has no children. + @param content: Test content (children & text) only. + @type content: boolean + @return: True when element has not children. + @rtype: boolean + """ + noattrs = not len(self.attributes) + nochildren = not len(self.children) + notext = ( self.text is None ) + nocontent = ( nochildren and notext ) + if content: + return nocontent + else: + return ( nocontent and noattrs ) + + + def isnil(self): + """ + Get whether the element is I{nil} as defined by having + an attribute in the I{xsi:nil="true"} + @return: True if I{nil}, else False + @rtype: boolean + """ + nilattr = self.getAttribute('nil', ns=Namespace.xsins) + if nilattr is None: + return False + else: + return ( nilattr.getValue().lower() == 'true' ) + + def setnil(self, flag=True): + """ + Set this node to I{nil} as defined by having an + attribute I{xsi:nil}=I{flag}. + @param flag: A flag inidcating how I{xsi:nil} will be set. + @type flag: boolean + @return: self + @rtype: L{Element} + """ + p, u = Namespace.xsins + name = ':'.join((p, 'nil')) + self.set(name, str(flag).lower()) + self.addPrefix(p, u) + if flag: + self.text = None + return self + + def applyns(self, ns): + """ + Apply the namespace to this node. If the prefix is I{None} then + this element's explicit namespace I{expns} is set to the + URI defined by I{ns}. Otherwise, the I{ns} is simply mapped. + @param ns: A namespace. + @type ns: (I{prefix},I{URI}) + """ + if ns is None: + return + if not isinstance(ns, (tuple,list)): + raise Exception('namespace must be tuple') + if ns[0] is None: + self.expns = ns[1] + else: + self.prefix = ns[0] + self.nsprefixes[ns[0]] = ns[1] + + def str(self, indent=0): + """ + Get a string representation of this XML fragment. + @param indent: The indent to be used in formatting the output. + @type indent: int + @return: A I{pretty} string. + @rtype: basestring + """ + tab = '%*s'%(indent*3,'') + result = [] + result.append('%s<%s' % (tab, self.qname())) + result.append(self.nsdeclarations()) + for a in [unicode(a) for a in self.attributes]: + result.append(' %s' % a) + if self.isempty(): + result.append('/>') + return ''.join(result) + result.append('>') + if self.hasText(): + result.append(self.text.escape()) + for c in self.children: + result.append('\n') + result.append(c.str(indent+1)) + if len(self.children): + result.append('\n%s' % tab) + result.append('' % self.qname()) + result = ''.join(result) + return result + + def plain(self): + """ + Get a string representation of this XML fragment. + @return: A I{plain} string. + @rtype: basestring + """ + result = [] + result.append('<%s' % self.qname()) + result.append(self.nsdeclarations()) + for a in [unicode(a) for a in self.attributes]: + result.append(' %s' % a) + if self.isempty(): + result.append('/>') + return ''.join(result) + result.append('>') + if self.hasText(): + result.append(self.text.escape()) + for c in self.children: + result.append(c.plain()) + result.append('' % self.qname()) + result = ''.join(result) + return result + + def nsdeclarations(self): + """ + Get a string representation for all namespace declarations + as xmlns="" and xmlns:p="". + @return: A separated list of declarations. + @rtype: basestring + """ + s = [] + myns = (None, self.expns) + if self.parent is None: + pns = Namespace.default + else: + pns = (None, self.parent.expns) + if myns[1] != pns[1]: + if self.expns is not None: + d = ' xmlns="%s"' % self.expns + s.append(d) + for item in self.nsprefixes.items(): + (p,u) = item + if self.parent is not None: + ns = self.parent.resolvePrefix(p) + if ns[1] == u: continue + d = ' xmlns:%s="%s"' % (p, u) + s.append(d) + return ''.join(s) + + def match(self, name=None, ns=None): + """ + Match by (optional) name and/or (optional) namespace. + @param name: The optional element tag name. + @type name: str + @param ns: An optional namespace. + @type ns: (I{prefix}, I{name}) + @return: True if matched. + @rtype: boolean + """ + if name is None: + byname = True + else: + byname = ( self.name == name ) + if ns is None: + byns = True + else: + byns = ( self.namespace()[1] == ns[1] ) + return ( byname and byns ) + + def branch(self): + """ + Get a flattened representation of the branch. + @return: A flat list of nodes. + @rtype: [L{Element},..] + """ + branch = [self] + for c in self.children: + branch += c.branch() + return branch + + def ancestors(self): + """ + Get a list of ancestors. + @return: A list of ancestors. + @rtype: [L{Element},..] + """ + ancestors = [] + p = self.parent + while p is not None: + ancestors.append(p) + p = p.parent + return ancestors + + def walk(self, visitor): + """ + Walk the branch and call the visitor function + on each node. + @param visitor: A function. + @return: self + @rtype: L{Element} + """ + visitor(self) + for c in self.children: + c.walk(visitor) + return self + + def prune(self): + """ + Prune the branch of empty nodes. + """ + pruned = [] + for c in self.children: + c.prune() + if c.isempty(False): + pruned.append(c) + for p in pruned: + self.children.remove(p) + + + def __childrenAtPath(self, parts): + result = [] + node = self + last = len(parts)-1 + ancestors = parts[:last] + leaf = parts[last] + for name in ancestors: + ns = None + prefix, name = splitPrefix(name) + if prefix is not None: + ns = node.resolvePrefix(prefix) + child = node.getChild(name, ns) + if child is None: + break + else: + node = child + if child is not None: + ns = None + prefix, leaf = splitPrefix(leaf) + if prefix is not None: + ns = node.resolvePrefix(prefix) + result = child.getChildren(leaf) + return result + + def __len__(self): + return len(self.children) + + def __getitem__(self, index): + if isinstance(index, basestring): + return self.get(index) + else: + if index < len(self.children): + return self.children[index] + else: + return None + + def __setitem__(self, index, value): + if isinstance(index, basestring): + self.set(index, value) + else: + if index < len(self.children) and \ + isinstance(value, Element): + self.children.insert(index, value) + + def __eq__(self, rhs): + return rhs is not None and \ + isinstance(rhs, Element) and \ + self.name == rhs.name and \ + self.namespace()[1] == rhs.namespace()[1] + + def __repr__(self): + return \ + 'Element (prefix=%s, name=%s)' % (self.prefix, self.name) + + def __str__(self): + return unicode(self).encode('utf-8') + + def __unicode__(self): + return self.str() + + def __iter__(self): + return NodeIterator(self) + + +class NodeIterator: + """ + The L{Element} child node iterator. + @ivar pos: The current position + @type pos: int + @ivar children: A list of a child nodes. + @type children: [L{Element},..] + """ + + def __init__(self, parent): + """ + @param parent: An element to iterate. + @type parent: L{Element} + """ + self.pos = 0 + self.children = parent.children + + def next(self): + """ + Get the next child. + @return: The next child. + @rtype: L{Element} + @raise StopIterator: At the end. + """ + try: + child = self.children[self.pos] + self.pos += 1 + return child + except: + raise StopIteration() + + +class PrefixNormalizer: + """ + The prefix normalizer provides namespace prefix normalization. + @ivar node: A node to normalize. + @type node: L{Element} + @ivar branch: The nodes flattened branch. + @type branch: [L{Element},..] + @ivar namespaces: A unique list of namespaces (URI). + @type namespaces: [str,] + @ivar prefixes: A reverse dict of prefixes. + @type prefixes: {u, p} + """ + + @classmethod + def apply(cls, node): + """ + Normalize the specified node. + @param node: A node to normalize. + @type node: L{Element} + @return: The normalized node. + @rtype: L{Element} + """ + pn = PrefixNormalizer(node) + return pn.refit() + + def __init__(self, node): + """ + @param node: A node to normalize. + @type node: L{Element} + """ + self.node = node + self.branch = node.branch() + self.namespaces = self.getNamespaces() + self.prefixes = self.genPrefixes() + + def getNamespaces(self): + """ + Get the I{unique} set of namespaces referenced in the branch. + @return: A set of namespaces. + @rtype: set + """ + s = set() + for n in self.branch + self.node.ancestors(): + if self.permit(n.expns): + s.add(n.expns) + s = s.union(self.pset(n)) + return s + + def pset(self, n): + """ + Convert the nodes nsprefixes into a set. + @param n: A node. + @type n: L{Element} + @return: A set of namespaces. + @rtype: set + """ + s = set() + for ns in n.nsprefixes.items(): + if self.permit(ns): + s.add(ns[1]) + return s + + def genPrefixes(self): + """ + Generate a I{reverse} mapping of unique prefixes for all namespaces. + @return: A referse dict of prefixes. + @rtype: {u, p} + """ + prefixes = {} + n = 0 + for u in self.namespaces: + p = 'ns%d' % n + prefixes[u] = p + n += 1 + return prefixes + + def refit(self): + """ + Refit (normalize) the prefixes in the node. + """ + self.refitNodes() + self.refitMappings() + + def refitNodes(self): + """ + Refit (normalize) all of the nodes in the branch. + """ + for n in self.branch: + if n.prefix is not None: + ns = n.namespace() + if self.permit(ns): + n.prefix = self.prefixes[ns[1]] + self.refitAttrs(n) + + def refitAttrs(self, n): + """ + Refit (normalize) all of the attributes in the node. + @param n: A node. + @type n: L{Element} + """ + for a in n.attributes: + self.refitAddr(a) + + def refitAddr(self, a): + """ + Refit (normalize) the attribute. + @param a: An attribute. + @type a: L{Attribute} + """ + if a.prefix is not None: + ns = a.namespace() + if self.permit(ns): + a.prefix = self.prefixes[ns[1]] + self.refitValue(a) + + def refitValue(self, a): + """ + Refit (normalize) the attribute's value. + @param a: An attribute. + @type a: L{Attribute} + """ + p,name = splitPrefix(a.getValue()) + if p is None: return + ns = a.resolvePrefix(p) + if self.permit(ns): + u = ns[1] + p = self.prefixes[u] + a.setValue(':'.join((p, name))) + + def refitMappings(self): + """ + Refit (normalize) all of the nsprefix mappings. + """ + for n in self.branch: + n.nsprefixes = {} + n = self.node + for u, p in self.prefixes.items(): + n.addPrefix(p, u) + + def permit(self, ns): + """ + Get whether the I{ns} is to be normalized. + @param ns: A namespace. + @type ns: (p,u) + @return: True if to be included. + @rtype: boolean + """ + return not self.skip(ns) + + def skip(self, ns): + """ + Get whether the I{ns} is to B{not} be normalized. + @param ns: A namespace. + @type ns: (p,u) + @return: True if to be skipped. + @rtype: boolean + """ + return ns is None or \ + ( ns == Namespace.default ) or \ + ( ns == Namespace.xsdns ) or \ + ( ns == Namespace.xsins) or \ + ( ns == Namespace.xmlns ) \ No newline at end of file diff --git a/awx/lib/site-packages/suds/sax/enc.py b/awx/lib/site-packages/suds/sax/enc.py new file mode 100644 index 0000000000..efc7274422 --- /dev/null +++ b/awx/lib/site-packages/suds/sax/enc.py @@ -0,0 +1,79 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Provides XML I{special character} encoder classes. +""" + +import re + +class Encoder: + """ + An XML special character encoder/decoder. + @cvar encodings: A mapping of special characters encoding. + @type encodings: [(str,str)] + @cvar decodings: A mapping of special characters decoding. + @type decodings: [(str,str)] + @cvar special: A list of special characters + @type special: [char] + """ + + encodings = \ + (( '&(?!(amp|lt|gt|quot|apos);)', '&' ),( '<', '<' ),( '>', '>' ),( '"', '"' ),("'", ''' )) + decodings = \ + (( '<', '<' ),( '>', '>' ),( '"', '"' ),( ''', "'" ),( '&', '&' )) + special = \ + ('&', '<', '>', '"', "'") + + def needsEncoding(self, s): + """ + Get whether string I{s} contains special characters. + @param s: A string to check. + @type s: str + @return: True if needs encoding. + @rtype: boolean + """ + if isinstance(s, basestring): + for c in self.special: + if c in s: + return True + return False + + def encode(self, s): + """ + Encode special characters found in string I{s}. + @param s: A string to encode. + @type s: str + @return: The encoded string. + @rtype: str + """ + if isinstance(s, basestring) and self.needsEncoding(s): + for x in self.encodings: + s = re.sub(x[0], x[1], s) + return s + + def decode(self, s): + """ + Decode special characters encodings found in string I{s}. + @param s: A string to decode. + @type s: str + @return: The decoded string. + @rtype: str + """ + if isinstance(s, basestring) and '&' in s: + for x in self.decodings: + s = s.replace(x[0], x[1]) + return s diff --git a/awx/lib/site-packages/suds/sax/parser.py b/awx/lib/site-packages/suds/sax/parser.py new file mode 100644 index 0000000000..69f871b44e --- /dev/null +++ b/awx/lib/site-packages/suds/sax/parser.py @@ -0,0 +1,139 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +The sax module contains a collection of classes that provide a +(D)ocument (O)bject (M)odel representation of an XML document. +The goal is to provide an easy, intuative interface for managing XML +documents. Although, the term, DOM, is used above, this model is +B{far} better. + +XML namespaces in suds are represented using a (2) element tuple +containing the prefix and the URI. Eg: I{('tns', 'http://myns')} + +""" + +from logging import getLogger +import suds.metrics +from suds import * +from suds.sax import * +from suds.sax.document import Document +from suds.sax.element import Element +from suds.sax.text import Text +from suds.sax.attribute import Attribute +from xml.sax import make_parser, InputSource, ContentHandler +from xml.sax.handler import feature_external_ges +from cStringIO import StringIO + +log = getLogger(__name__) + + +class Handler(ContentHandler): + """ sax hanlder """ + + def __init__(self): + self.nodes = [Document()] + + def startElement(self, name, attrs): + top = self.top() + node = Element(unicode(name), parent=top) + for a in attrs.getNames(): + n = unicode(a) + v = unicode(attrs.getValue(a)) + attribute = Attribute(n,v) + if self.mapPrefix(node, attribute): + continue + node.append(attribute) + node.charbuffer = [] + top.append(node) + self.push(node) + + def mapPrefix(self, node, attribute): + skip = False + if attribute.name == 'xmlns': + if len(attribute.value): + node.expns = unicode(attribute.value) + skip = True + elif attribute.prefix == 'xmlns': + prefix = attribute.name + node.nsprefixes[prefix] = unicode(attribute.value) + skip = True + return skip + + def endElement(self, name): + name = unicode(name) + current = self.top() + if len(current.charbuffer): + current.text = Text(u''.join(current.charbuffer)) + del current.charbuffer + if len(current): + current.trim() + currentqname = current.qname() + if name == currentqname: + self.pop() + else: + raise Exception('malformed document') + + def characters(self, content): + text = unicode(content) + node = self.top() + node.charbuffer.append(text) + + def push(self, node): + self.nodes.append(node) + return node + + def pop(self): + return self.nodes.pop() + + def top(self): + return self.nodes[len(self.nodes)-1] + + +class Parser: + """ SAX Parser """ + + @classmethod + def saxparser(cls): + p = make_parser() + p.setFeature(feature_external_ges, 0) + h = Handler() + p.setContentHandler(h) + return (p, h) + + def parse(self, file=None, string=None): + """ + SAX parse XML text. + @param file: Parse a python I{file-like} object. + @type file: I{file-like} object. + @param string: Parse string XML. + @type string: str + """ + timer = metrics.Timer() + timer.start() + sax, handler = self.saxparser() + if file is not None: + sax.parse(file) + timer.stop() + metrics.log.debug('sax (%s) duration: %s', file, timer) + return handler.nodes[0] + if string is not None: + source = InputSource(None) + source.setByteStream(StringIO(string)) + sax.parse(source) + timer.stop() + metrics.log.debug('%s\nsax duration: %s', string, timer) + return handler.nodes[0] \ No newline at end of file diff --git a/awx/lib/site-packages/suds/sax/text.py b/awx/lib/site-packages/suds/sax/text.py new file mode 100644 index 0000000000..0d58ee8283 --- /dev/null +++ b/awx/lib/site-packages/suds/sax/text.py @@ -0,0 +1,116 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Contains XML text classes. +""" + +from suds import * +from suds.sax import * + + +class Text(unicode): + """ + An XML text object used to represent text content. + @ivar lang: The (optional) language flag. + @type lang: bool + @ivar escaped: The (optional) XML special character escaped flag. + @type escaped: bool + """ + __slots__ = ('lang', 'escaped',) + + @classmethod + def __valid(cls, *args): + return ( len(args) and args[0] is not None ) + + def __new__(cls, *args, **kwargs): + if cls.__valid(*args): + lang = kwargs.pop('lang', None) + escaped = kwargs.pop('escaped', False) + result = super(Text, cls).__new__(cls, *args, **kwargs) + result.lang = lang + result.escaped = escaped + else: + result = None + return result + + def escape(self): + """ + Encode (escape) special XML characters. + @return: The text with XML special characters escaped. + @rtype: L{Text} + """ + if not self.escaped: + post = sax.encoder.encode(self) + escaped = ( post != self ) + return Text(post, lang=self.lang, escaped=escaped) + return self + + def unescape(self): + """ + Decode (unescape) special XML characters. + @return: The text with escaped XML special characters decoded. + @rtype: L{Text} + """ + if self.escaped: + post = sax.encoder.decode(self) + return Text(post, lang=self.lang) + return self + + def trim(self): + post = self.strip() + return Text(post, lang=self.lang, escaped=self.escaped) + + def __add__(self, other): + joined = u''.join((self, other)) + result = Text(joined, lang=self.lang, escaped=self.escaped) + if isinstance(other, Text): + result.escaped = ( self.escaped or other.escaped ) + return result + + def __repr__(self): + s = [self] + if self.lang is not None: + s.append(' [%s]' % self.lang) + if self.escaped: + s.append(' ') + return ''.join(s) + + def __getstate__(self): + state = {} + for k in self.__slots__: + state[k] = getattr(self, k) + return state + + def __setstate__(self, state): + for k in self.__slots__: + setattr(self, k, state[k]) + + +class Raw(Text): + """ + Raw text which is not XML escaped. + This may include I{string} XML. + """ + def escape(self): + return self + + def unescape(self): + return self + + def __add__(self, other): + joined = u''.join((self, other)) + return Raw(joined, lang=self.lang) diff --git a/awx/lib/site-packages/suds/servicedefinition.py b/awx/lib/site-packages/suds/servicedefinition.py new file mode 100644 index 0000000000..81b5a0d51d --- /dev/null +++ b/awx/lib/site-packages/suds/servicedefinition.py @@ -0,0 +1,248 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +The I{service definition} provides a textual representation of a service. +""" + +from logging import getLogger +from suds import * +import suds.metrics as metrics +from suds.sax import Namespace + +log = getLogger(__name__) + +class ServiceDefinition: + """ + A service definition provides an object used to generate a textual description + of a service. + @ivar wsdl: A wsdl. + @type wsdl: L{wsdl.Definitions} + @ivar service: The service object. + @type service: L{suds.wsdl.Service} + @ivar ports: A list of port-tuple: (port, [(method-name, pdef)]) + @type ports: [port-tuple,..] + @ivar prefixes: A list of remapped prefixes. + @type prefixes: [(prefix,uri),..] + @ivar types: A list of type definitions + @type types: [I{Type},..] + """ + + def __init__(self, wsdl, service): + """ + @param wsdl: A wsdl object + @type wsdl: L{Definitions} + @param service: A service B{name}. + @type service: str + """ + self.wsdl = wsdl + self.service = service + self.ports = [] + self.params = [] + self.types = [] + self.prefixes = [] + self.addports() + self.paramtypes() + self.publictypes() + self.getprefixes() + self.pushprefixes() + + def pushprefixes(self): + """ + Add our prefixes to the wsdl so that when users invoke methods + and reference the prefixes, the will resolve properly. + """ + for ns in self.prefixes: + self.wsdl.root.addPrefix(ns[0], ns[1]) + + def addports(self): + """ + Look through the list of service ports and construct a list of tuples where + each tuple is used to describe a port and it's list of methods as: + (port, [method]). Each method is tuple: (name, [pdef,..] where each pdef is + a tuple: (param-name, type). + """ + timer = metrics.Timer() + timer.start() + for port in self.service.ports: + p = self.findport(port) + for op in port.binding.operations.values(): + m = p[0].method(op.name) + binding = m.binding.input + method = (m.name, binding.param_defs(m)) + p[1].append(method) + metrics.log.debug("method '%s' created: %s", m.name, timer) + p[1].sort() + timer.stop() + + def findport(self, port): + """ + Find and return a port tuple for the specified port. + Created and added when not found. + @param port: A port. + @type port: I{service.Port} + @return: A port tuple. + @rtype: (port, [method]) + """ + for p in self.ports: + if p[0] == p: return p + p = (port, []) + self.ports.append(p) + return p + + def getprefixes(self): + """ + Add prefixes foreach namespace referenced by parameter types. + """ + namespaces = [] + for l in (self.params, self.types): + for t,r in l: + ns = r.namespace() + if ns[1] is None: continue + if ns[1] in namespaces: continue + if Namespace.xs(ns) or Namespace.xsd(ns): + continue + namespaces.append(ns[1]) + if t == r: continue + ns = t.namespace() + if ns[1] is None: continue + if ns[1] in namespaces: continue + namespaces.append(ns[1]) + i = 0 + namespaces.sort() + for u in namespaces: + p = self.nextprefix() + ns = (p, u) + self.prefixes.append(ns) + + def paramtypes(self): + """ get all parameter types """ + for m in [p[1] for p in self.ports]: + for p in [p[1] for p in m]: + for pd in p: + if pd[1] in self.params: continue + item = (pd[1], pd[1].resolve()) + self.params.append(item) + + def publictypes(self): + """ get all public types """ + for t in self.wsdl.schema.types.values(): + if t in self.params: continue + if t in self.types: continue + item = (t, t) + self.types.append(item) + tc = lambda x,y: cmp(x[0].name, y[0].name) + self.types.sort(cmp=tc) + + def nextprefix(self): + """ + Get the next available prefix. This means a prefix starting with 'ns' with + a number appended as (ns0, ns1, ..) that is not already defined on the + wsdl document. + """ + used = [ns[0] for ns in self.prefixes] + used += [ns[0] for ns in self.wsdl.root.nsprefixes.items()] + for n in range(0,1024): + p = 'ns%d'%n + if p not in used: + return p + raise Exception('prefixes exhausted') + + def getprefix(self, u): + """ + Get the prefix for the specified namespace (uri) + @param u: A namespace uri. + @type u: str + @return: The namspace. + @rtype: (prefix, uri). + """ + for ns in Namespace.all: + if u == ns[1]: return ns[0] + for ns in self.prefixes: + if u == ns[1]: return ns[0] + raise Exception('ns (%s) not mapped' % u) + + def xlate(self, type): + """ + Get a (namespace) translated I{qualified} name for specified type. + @param type: A schema type. + @type type: I{suds.xsd.sxbasic.SchemaObject} + @return: A translated I{qualified} name. + @rtype: str + """ + resolved = type.resolve() + name = resolved.name + if type.unbounded(): + name += '[]' + ns = resolved.namespace() + if ns[1] == self.wsdl.tns[1]: + return name + prefix = self.getprefix(ns[1]) + return ':'.join((prefix, name)) + + def description(self): + """ + Get a textual description of the service for which this object represents. + @return: A textual description. + @rtype: str + """ + s = [] + indent = (lambda n : '\n%*s'%(n*3,' ')) + s.append('Service ( %s ) tns="%s"' % (self.service.name, self.wsdl.tns[1])) + s.append(indent(1)) + s.append('Prefixes (%d)' % len(self.prefixes)) + for p in self.prefixes: + s.append(indent(2)) + s.append('%s = "%s"' % p) + s.append(indent(1)) + s.append('Ports (%d):' % len(self.ports)) + for p in self.ports: + s.append(indent(2)) + s.append('(%s)' % p[0].name) + s.append(indent(3)) + s.append('Methods (%d):' % len(p[1])) + for m in p[1]: + sig = [] + s.append(indent(4)) + sig.append(m[0]) + sig.append('(') + for p in m[1]: + sig.append(self.xlate(p[1])) + sig.append(' ') + sig.append(p[0]) + sig.append(', ') + sig.append(')') + try: + s.append(''.join(sig)) + except: + pass + s.append(indent(3)) + s.append('Types (%d):' % len(self.types)) + for t in self.types: + s.append(indent(4)) + s.append(self.xlate(t[0])) + s.append('\n\n') + return ''.join(s) + + def __str__(self): + return unicode(self).encode('utf-8') + + def __unicode__(self): + try: + return self.description() + except Exception, e: + log.exception(e) + return tostr(e) \ No newline at end of file diff --git a/awx/lib/site-packages/suds/serviceproxy.py b/awx/lib/site-packages/suds/serviceproxy.py new file mode 100644 index 0000000000..6e7105091f --- /dev/null +++ b/awx/lib/site-packages/suds/serviceproxy.py @@ -0,0 +1,86 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +The service proxy provides access to web services. + +Replaced by: L{client.Client} +""" + +from logging import getLogger +from suds import * +from suds.client import Client + +log = getLogger(__name__) + + +class ServiceProxy(object): + + """ + A lightweight soap based web service proxy. + @ivar __client__: A client. + Everything is delegated to the 2nd generation API. + @type __client__: L{Client} + @note: Deprecated, replaced by L{Client}. + """ + + def __init__(self, url, **kwargs): + """ + @param url: The URL for the WSDL. + @type url: str + @param kwargs: keyword arguments. + @keyword faults: Raise faults raised by server (default:True), + else return tuple from service method invocation as (http code, object). + @type faults: boolean + @keyword proxy: An http proxy to be specified on requests (default:{}). + The proxy is defined as {protocol:proxy,} + @type proxy: dict + """ + client = Client(url, **kwargs) + self.__client__ = client + + def get_instance(self, name): + """ + Get an instance of a WSDL type by name + @param name: The name of a type defined in the WSDL. + @type name: str + @return: An instance on success, else None + @rtype: L{sudsobject.Object} + """ + return self.__client__.factory.create(name) + + def get_enum(self, name): + """ + Get an instance of an enumeration defined in the WSDL by name. + @param name: The name of a enumeration defined in the WSDL. + @type name: str + @return: An instance on success, else None + @rtype: L{sudsobject.Object} + """ + return self.__client__.factory.create(name) + + def __str__(self): + return str(self.__client__) + + def __unicode__(self): + return unicode(self.__client__) + + def __getattr__(self, name): + builtin = name.startswith('__') and name.endswith('__') + if builtin: + return self.__dict__[name] + else: + return getattr(self.__client__.service, name) \ No newline at end of file diff --git a/awx/lib/site-packages/suds/soaparray.py b/awx/lib/site-packages/suds/soaparray.py new file mode 100644 index 0000000000..04847d5061 --- /dev/null +++ b/awx/lib/site-packages/suds/soaparray.py @@ -0,0 +1,72 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +The I{soaparray} module provides XSD extensions for handling +soap (section 5) encoded arrays. +""" + +from suds import * +from logging import getLogger +from suds.xsd.sxbasic import Factory as SXFactory +from suds.xsd.sxbasic import Attribute as SXAttribute + + +class Attribute(SXAttribute): + """ + Represents an XSD that handles special + attributes that are extensions for WSDLs. + @ivar aty: Array type information. + @type aty: The value of wsdl:arrayType. + """ + + def __init__(self, schema, root, aty): + """ + @param aty: Array type information. + @type aty: The value of wsdl:arrayType. + """ + SXAttribute.__init__(self, schema, root) + if aty.endswith('[]'): + self.aty = aty[:-2] + else: + self.aty = aty + + def autoqualified(self): + aqs = SXAttribute.autoqualified(self) + aqs.append('aty') + return aqs + + def description(self): + d = SXAttribute.description(self) + d = d+('aty',) + return d + +# +# Builder function, only builds Attribute when arrayType +# attribute is defined on root. +# +def __fn(x, y): + ns = (None, "http://schemas.xmlsoap.org/wsdl/") + aty = y.get('arrayType', ns=ns) + if aty is None: + return SXAttribute(x, y) + else: + return Attribute(x, y, aty) + +# +# Remap tags to __fn() builder. +# +SXFactory.maptag('attribute', __fn) \ No newline at end of file diff --git a/awx/lib/site-packages/suds/store.py b/awx/lib/site-packages/suds/store.py new file mode 100644 index 0000000000..85e0943916 --- /dev/null +++ b/awx/lib/site-packages/suds/store.py @@ -0,0 +1,594 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Contains XML text for documents to be distributed +with the suds lib. Also, contains classes for accessing +these documents. +""" + +from StringIO import StringIO +from logging import getLogger + +log = getLogger(__name__) + + +# +# Soap section 5 encoding schema. +# +encoding = \ +""" + + + + + + 'root' can be used to distinguish serialization roots from other + elements that are present in a serialization but are not roots of + a serialized value graph + + + + + + + + + + + + + Attributes common to all elements that function as accessors or + represent independent (multi-ref) values. The href attribute is + intended to be used in a manner like CONREF. That is, the element + content should be empty iff the href attribute appears + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 'Array' is a complex type for accessors identified by position + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +""" + + +class DocumentStore: + """ + The I{suds} document store provides a local repository + for xml documnts. + @cvar protocol: The URL protocol for the store. + @type protocol: str + @cvar store: The mapping of URL location to documents. + @type store: dict + """ + + protocol = 'suds' + + store = { + 'schemas.xmlsoap.org/soap/encoding/' : encoding + } + + def open(self, url): + """ + Open a document at the specified url. + @param url: A document URL. + @type url: str + @return: A file pointer to the document. + @rtype: StringIO + """ + protocol, location = self.split(url) + if protocol == self.protocol: + return self.find(location) + else: + return None + + def find(self, location): + """ + Find the specified location in the store. + @param location: The I{location} part of a URL. + @type location: str + @return: An input stream to the document. + @rtype: StringIO + """ + try: + content = self.store[location] + return StringIO(content) + except: + reason = 'location "%s" not in document store' % location + raise Exception, reason + + def split(self, url): + """ + Split the url into I{protocol} and I{location} + @param url: A URL. + @param url: str + @return: (I{url}, I{location}) + @rtype: tuple + """ + parts = url.split('://', 1) + if len(parts) == 2: + return parts + else: + return (None, url) \ No newline at end of file diff --git a/awx/lib/site-packages/suds/sudsobject.py b/awx/lib/site-packages/suds/sudsobject.py new file mode 100644 index 0000000000..1f6168d19d --- /dev/null +++ b/awx/lib/site-packages/suds/sudsobject.py @@ -0,0 +1,390 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +The I{sudsobject} module provides a collection of suds objects +that are primarily used for the highly dynamic interactions with +wsdl/xsd defined types. +""" + +from logging import getLogger +from suds import * +from new import classobj + +log = getLogger(__name__) + + +def items(sobject): + """ + Extract the I{items} from a suds object much like the + items() method works on I{dict}. + @param sobject: A suds object + @type sobject: L{Object} + @return: A list of items contained in I{sobject}. + @rtype: [(key, value),...] + """ + for item in sobject: + yield item + + +def asdict(sobject): + """ + Convert a sudsobject into a dictionary. + @param sobject: A suds object + @type sobject: L{Object} + @return: A python dictionary containing the + items contained in I{sobject}. + @rtype: dict + """ + return dict(items(sobject)) + +def merge(a, b): + """ + Merge all attributes and metadata from I{a} to I{b}. + @param a: A I{source} object + @type a: L{Object} + @param b: A I{destination} object + @type b: L{Object} + """ + for item in a: + setattr(b, item[0], item[1]) + b.__metadata__ = b.__metadata__ + return b + +def footprint(sobject): + """ + Get the I{virtual footprint} of the object. + This is really a count of the attributes in the branch with a significant value. + @param sobject: A suds object. + @type sobject: L{Object} + @return: The branch footprint. + @rtype: int + """ + n = 0 + for a in sobject.__keylist__: + v = getattr(sobject, a) + if v is None: continue + if isinstance(v, Object): + n += footprint(v) + continue + if hasattr(v, '__len__'): + if len(v): n += 1 + continue + n +=1 + return n + + +class Factory: + + cache = {} + + @classmethod + def subclass(cls, name, bases, dict={}): + if not isinstance(bases, tuple): + bases = (bases,) + name = name.encode('utf-8') + key = '.'.join((name, str(bases))) + subclass = cls.cache.get(key) + if subclass is None: + subclass = classobj(name, bases, dict) + cls.cache[key] = subclass + return subclass + + @classmethod + def object(cls, classname=None, dict={}): + if classname is not None: + subclass = cls.subclass(classname, Object) + inst = subclass() + else: + inst = Object() + for a in dict.items(): + setattr(inst, a[0], a[1]) + return inst + + @classmethod + def metadata(cls): + return Metadata() + + @classmethod + def property(cls, name, value=None): + subclass = cls.subclass(name, Property) + return subclass(value) + + +class Object: + + def __init__(self): + self.__keylist__ = [] + self.__printer__ = Printer() + self.__metadata__ = Metadata() + + def __setattr__(self, name, value): + builtin = name.startswith('__') and name.endswith('__') + if not builtin and \ + name not in self.__keylist__: + self.__keylist__.append(name) + self.__dict__[name] = value + + def __delattr__(self, name): + try: + del self.__dict__[name] + builtin = name.startswith('__') and name.endswith('__') + if not builtin: + self.__keylist__.remove(name) + except: + cls = self.__class__.__name__ + raise AttributeError, "%s has no attribute '%s'" % (cls, name) + + def __getitem__(self, name): + if isinstance(name, int): + name = self.__keylist__[int(name)] + return getattr(self, name) + + def __setitem__(self, name, value): + setattr(self, name, value) + + def __iter__(self): + return Iter(self) + + def __len__(self): + return len(self.__keylist__) + + def __contains__(self, name): + return name in self.__keylist__ + + def __repr__(self): + return str(self) + + def __str__(self): + return unicode(self).encode('utf-8') + + def __unicode__(self): + return self.__printer__.tostr(self) + + +class Iter: + + def __init__(self, sobject): + self.sobject = sobject + self.keylist = self.__keylist(sobject) + self.index = 0 + + def next(self): + keylist = self.keylist + nkeys = len(self.keylist) + while self.index < nkeys: + k = keylist[self.index] + self.index += 1 + if hasattr(self.sobject, k): + v = getattr(self.sobject, k) + return (k, v) + raise StopIteration() + + def __keylist(self, sobject): + keylist = sobject.__keylist__ + try: + keyset = set(keylist) + ordering = sobject.__metadata__.ordering + ordered = set(ordering) + if not ordered.issuperset(keyset): + log.debug( + '%s must be superset of %s, ordering ignored', + keylist, + ordering) + raise KeyError() + return ordering + except: + return keylist + + def __iter__(self): + return self + + +class Metadata(Object): + def __init__(self): + self.__keylist__ = [] + self.__printer__ = Printer() + + +class Facade(Object): + def __init__(self, name): + Object.__init__(self) + md = self.__metadata__ + md.facade = name + + +class Property(Object): + + def __init__(self, value): + Object.__init__(self) + self.value = value + + def items(self): + for item in self: + if item[0] != 'value': + yield item + + def get(self): + return self.value + + def set(self, value): + self.value = value + return self + + +class Printer: + """ + Pretty printing of a Object object. + """ + + @classmethod + def indent(cls, n): return '%*s'%(n*3,' ') + + def tostr(self, object, indent=-2): + """ get s string representation of object """ + history = [] + return self.process(object, history, indent) + + def process(self, object, h, n=0, nl=False): + """ print object using the specified indent (n) and newline (nl). """ + if object is None: + return 'None' + if isinstance(object, Object): + if len(object) == 0: + return '' + else: + return self.print_object(object, h, n+2, nl) + if isinstance(object, dict): + if len(object) == 0: + return '' + else: + return self.print_dictionary(object, h, n+2, nl) + if isinstance(object, (list,tuple)): + if len(object) == 0: + return '' + else: + return self.print_collection(object, h, n+2) + if isinstance(object, basestring): + return '"%s"' % tostr(object) + return '%s' % tostr(object) + + def print_object(self, d, h, n, nl=False): + """ print complex using the specified indent (n) and newline (nl). """ + s = [] + cls = d.__class__ + md = d.__metadata__ + if d in h: + s.append('(') + s.append(cls.__name__) + s.append(')') + s.append('...') + return ''.join(s) + h.append(d) + if nl: + s.append('\n') + s.append(self.indent(n)) + if cls != Object: + s.append('(') + if isinstance(d, Facade): + s.append(md.facade) + else: + s.append(cls.__name__) + s.append(')') + s.append('{') + for item in d: + if self.exclude(d, item): + continue + item = self.unwrap(d, item) + s.append('\n') + s.append(self.indent(n+1)) + if isinstance(item[1], (list,tuple)): + s.append(item[0]) + s.append('[]') + else: + s.append(item[0]) + s.append(' = ') + s.append(self.process(item[1], h, n, True)) + s.append('\n') + s.append(self.indent(n)) + s.append('}') + h.pop() + return ''.join(s) + + def print_dictionary(self, d, h, n, nl=False): + """ print complex using the specified indent (n) and newline (nl). """ + if d in h: return '{}...' + h.append(d) + s = [] + if nl: + s.append('\n') + s.append(self.indent(n)) + s.append('{') + for item in d.items(): + s.append('\n') + s.append(self.indent(n+1)) + if isinstance(item[1], (list,tuple)): + s.append(tostr(item[0])) + s.append('[]') + else: + s.append(tostr(item[0])) + s.append(' = ') + s.append(self.process(item[1], h, n, True)) + s.append('\n') + s.append(self.indent(n)) + s.append('}') + h.pop() + return ''.join(s) + + def print_collection(self, c, h, n): + """ print collection using the specified indent (n) and newline (nl). """ + if c in h: return '[]...' + h.append(c) + s = [] + for item in c: + s.append('\n') + s.append(self.indent(n)) + s.append(self.process(item, h, n-2)) + s.append(',') + h.pop() + return ''.join(s) + + def unwrap(self, d, item): + """ translate (unwrap) using an optional wrapper function """ + nopt = ( lambda x: x ) + try: + md = d.__metadata__ + pmd = getattr(md, '__print__', None) + if pmd is None: + return item + wrappers = getattr(pmd, 'wrappers', {}) + fn = wrappers.get(item[0], nopt) + return (item[0], fn(item[1])) + except: + pass + return item + + def exclude(self, d, item): + """ check metadata for excluded items """ + try: + md = d.__metadata__ + pmd = getattr(md, '__print__', None) + if pmd is None: + return False + excludes = getattr(pmd, 'excludes', []) + return ( item[0] in excludes ) + except: + pass + return False \ No newline at end of file diff --git a/awx/lib/site-packages/suds/transport/__init__.py b/awx/lib/site-packages/suds/transport/__init__.py new file mode 100644 index 0000000000..e1e00d73ad --- /dev/null +++ b/awx/lib/site-packages/suds/transport/__init__.py @@ -0,0 +1,130 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Contains transport interface (classes). +""" + + +class TransportError(Exception): + def __init__(self, reason, httpcode, fp=None): + Exception.__init__(self, reason) + self.httpcode = httpcode + self.fp = fp + +class Request: + """ + A transport request + @ivar url: The url for the request. + @type url: str + @ivar message: The message to be sent in a POST request. + @type message: str + @ivar headers: The http headers to be used for the request. + @type headers: dict + """ + + def __init__(self, url, message=None): + """ + @param url: The url for the request. + @type url: str + @param message: The (optional) message to be send in the request. + @type message: str + """ + self.url = url + self.headers = {} + self.message = message + + def __str__(self): + s = [] + s.append('URL:%s' % self.url) + s.append('HEADERS: %s' % self.headers) + s.append('MESSAGE:') + s.append(self.message) + return '\n'.join(s) + + +class Reply: + """ + A transport reply + @ivar code: The http code returned. + @type code: int + @ivar message: The message to be sent in a POST request. + @type message: str + @ivar headers: The http headers to be used for the request. + @type headers: dict + """ + + def __init__(self, code, headers, message): + """ + @param code: The http code returned. + @type code: int + @param headers: The http returned headers. + @type headers: dict + @param message: The (optional) reply message received. + @type message: str + """ + self.code = code + self.headers = headers + self.message = message + + def __str__(self): + s = [] + s.append('CODE: %s' % self.code) + s.append('HEADERS: %s' % self.headers) + s.append('MESSAGE:') + s.append(self.message) + return '\n'.join(s) + + +class Transport: + """ + The transport I{interface}. + """ + + def __init__(self): + """ + Constructor. + """ + from suds.transport.options import Options + self.options = Options() + del Options + + def open(self, request): + """ + Open the url in the specified request. + @param request: A transport request. + @type request: L{Request} + @return: An input stream. + @rtype: stream + @raise TransportError: On all transport errors. + """ + raise Exception('not-implemented') + + def send(self, request): + """ + Send soap message. Implementations are expected to handle: + - proxies + - I{http} headers + - cookies + - sending message + - brokering exceptions into L{TransportError} + @param request: A transport request. + @type request: L{Request} + @return: The reply + @rtype: L{Reply} + @raise TransportError: On all transport errors. + """ + raise Exception('not-implemented') diff --git a/awx/lib/site-packages/suds/transport/http.py b/awx/lib/site-packages/suds/transport/http.py new file mode 100644 index 0000000000..6d85b09c6f --- /dev/null +++ b/awx/lib/site-packages/suds/transport/http.py @@ -0,0 +1,187 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Contains classes for basic HTTP transport implementations. +""" + +import urllib2 as u2 +import base64 +import socket +from suds.transport import * +from suds.properties import Unskin +from urlparse import urlparse +from cookielib import CookieJar +from logging import getLogger + +log = getLogger(__name__) + + +class HttpTransport(Transport): + """ + HTTP transport using urllib2. Provided basic http transport + that provides for cookies, proxies but no authentication. + """ + + def __init__(self, **kwargs): + """ + @param kwargs: Keyword arguments. + - B{proxy} - An http proxy to be specified on requests. + The proxy is defined as {protocol:proxy,} + - type: I{dict} + - default: {} + - B{timeout} - Set the url open timeout (seconds). + - type: I{float} + - default: 90 + """ + Transport.__init__(self) + Unskin(self.options).update(kwargs) + self.cookiejar = CookieJar() + self.proxy = {} + self.urlopener = None + + def open(self, request): + try: + url = request.url + log.debug('opening (%s)', url) + u2request = u2.Request(url) + self.proxy = self.options.proxy + return self.u2open(u2request) + except u2.HTTPError, e: + raise TransportError(str(e), e.code, e.fp) + + def send(self, request): + result = None + url = request.url + msg = request.message + headers = request.headers + try: + u2request = u2.Request(url, msg, headers) + self.addcookies(u2request) + self.proxy = self.options.proxy + request.headers.update(u2request.headers) + log.debug('sending:\n%s', request) + fp = self.u2open(u2request) + self.getcookies(fp, u2request) + result = Reply(200, fp.headers.dict, fp.read()) + log.debug('received:\n%s', result) + except u2.HTTPError, e: + if e.code in (202,204): + result = None + else: + raise TransportError(e.msg, e.code, e.fp) + return result + + def addcookies(self, u2request): + """ + Add cookies in the cookiejar to the request. + @param u2request: A urllib2 request. + @rtype: u2request: urllib2.Requet. + """ + self.cookiejar.add_cookie_header(u2request) + + def getcookies(self, fp, u2request): + """ + Add cookies in the request to the cookiejar. + @param u2request: A urllib2 request. + @rtype: u2request: urllib2.Requet. + """ + self.cookiejar.extract_cookies(fp, u2request) + + def u2open(self, u2request): + """ + Open a connection. + @param u2request: A urllib2 request. + @type u2request: urllib2.Requet. + @return: The opened file-like urllib2 object. + @rtype: fp + """ + tm = self.options.timeout + url = self.u2opener() + if self.u2ver() < 2.6: + socket.setdefaulttimeout(tm) + return url.open(u2request) + else: + return url.open(u2request, timeout=tm) + + def u2opener(self): + """ + Create a urllib opener. + @return: An opener. + @rtype: I{OpenerDirector} + """ + if self.urlopener is None: + return u2.build_opener(*self.u2handlers()) + else: + return self.urlopener + + def u2handlers(self): + """ + Get a collection of urllib handlers. + @return: A list of handlers to be installed in the opener. + @rtype: [Handler,...] + """ + handlers = [] + handlers.append(u2.ProxyHandler(self.proxy)) + return handlers + + def u2ver(self): + """ + Get the major/minor version of the urllib2 lib. + @return: The urllib2 version. + @rtype: float + """ + try: + part = u2.__version__.split('.', 1) + n = float('.'.join(part)) + return n + except Exception, e: + log.exception(e) + return 0 + + def __deepcopy__(self, memo={}): + clone = self.__class__() + p = Unskin(self.options) + cp = Unskin(clone.options) + cp.update(p) + return clone + + +class HttpAuthenticated(HttpTransport): + """ + Provides basic http authentication for servers that don't follow + the specified challenge / response model. This implementation + appends the I{Authorization} http header with base64 encoded + credentials on every http request. + """ + + def open(self, request): + self.addcredentials(request) + return HttpTransport.open(self, request) + + def send(self, request): + self.addcredentials(request) + return HttpTransport.send(self, request) + + def addcredentials(self, request): + credentials = self.credentials() + if not (None in credentials): + encoded = base64.encodestring(':'.join(credentials)) + basic = 'Basic %s' % encoded[:-1] + request.headers['Authorization'] = basic + + def credentials(self): + return (self.options.username, self.options.password) \ No newline at end of file diff --git a/awx/lib/site-packages/suds/transport/https.py b/awx/lib/site-packages/suds/transport/https.py new file mode 100644 index 0000000000..ed23fd550b --- /dev/null +++ b/awx/lib/site-packages/suds/transport/https.py @@ -0,0 +1,98 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Contains classes for basic HTTP (authenticated) transport implementations. +""" + +import urllib2 as u2 +from suds.transport import * +from suds.transport.http import HttpTransport +from logging import getLogger + +log = getLogger(__name__) + + +class HttpAuthenticated(HttpTransport): + """ + Provides basic http authentication that follows the RFC-2617 specification. + As defined by specifications, credentials are provided to the server + upon request (HTTP/1.0 401 Authorization Required) by the server only. + @ivar pm: The password manager. + @ivar handler: The authentication handler. + """ + + def __init__(self, **kwargs): + """ + @param kwargs: Keyword arguments. + - B{proxy} - An http proxy to be specified on requests. + The proxy is defined as {protocol:proxy,} + - type: I{dict} + - default: {} + - B{timeout} - Set the url open timeout (seconds). + - type: I{float} + - default: 90 + - B{username} - The username used for http authentication. + - type: I{str} + - default: None + - B{password} - The password used for http authentication. + - type: I{str} + - default: None + """ + HttpTransport.__init__(self, **kwargs) + self.pm = u2.HTTPPasswordMgrWithDefaultRealm() + + def open(self, request): + self.addcredentials(request) + return HttpTransport.open(self, request) + + def send(self, request): + self.addcredentials(request) + return HttpTransport.send(self, request) + + def addcredentials(self, request): + credentials = self.credentials() + if not (None in credentials): + u = credentials[0] + p = credentials[1] + self.pm.add_password(None, request.url, u, p) + + def credentials(self): + return (self.options.username, self.options.password) + + def u2handlers(self): + handlers = HttpTransport.u2handlers(self) + handlers.append(u2.HTTPBasicAuthHandler(self.pm)) + return handlers + + +class WindowsHttpAuthenticated(HttpAuthenticated): + """ + Provides Windows (NTLM) http authentication. + @ivar pm: The password manager. + @ivar handler: The authentication handler. + @author: Christopher Bess + """ + + def u2handlers(self): + # try to import ntlm support + try: + from ntlm import HTTPNtlmAuthHandler + except ImportError: + raise Exception("Cannot import python-ntlm module") + handlers = HttpTransport.u2handlers(self) + handlers.append(HTTPNtlmAuthHandler.HTTPNtlmAuthHandler(self.pm)) + return handlers diff --git a/awx/lib/site-packages/suds/transport/options.py b/awx/lib/site-packages/suds/transport/options.py new file mode 100644 index 0000000000..8b0d194a41 --- /dev/null +++ b/awx/lib/site-packages/suds/transport/options.py @@ -0,0 +1,57 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Contains classes for transport options. +""" + + +from suds.transport import * +from suds.properties import * + + +class Options(Skin): + """ + Options: + - B{proxy} - An http proxy to be specified on requests. + The proxy is defined as {protocol:proxy,} + - type: I{dict} + - default: {} + - B{timeout} - Set the url open timeout (seconds). + - type: I{float} + - default: 90 + - B{headers} - Extra HTTP headers. + - type: I{dict} + - I{str} B{http} - The I{http} protocol proxy URL. + - I{str} B{https} - The I{https} protocol proxy URL. + - default: {} + - B{username} - The username used for http authentication. + - type: I{str} + - default: None + - B{password} - The password used for http authentication. + - type: I{str} + - default: None + """ + def __init__(self, **kwargs): + domain = __name__ + definitions = [ + Definition('proxy', dict, {}), + Definition('timeout', (int,float), 90), + Definition('headers', dict, {}), + Definition('username', basestring, None), + Definition('password', basestring, None), + ] + Skin.__init__(self, domain, definitions, kwargs) \ No newline at end of file diff --git a/awx/lib/site-packages/suds/umx/__init__.py b/awx/lib/site-packages/suds/umx/__init__.py new file mode 100644 index 0000000000..9d06b40877 --- /dev/null +++ b/awx/lib/site-packages/suds/umx/__init__.py @@ -0,0 +1,56 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Provides modules containing classes to support +unmarshalling (XML). +""" + +from suds.sudsobject import Object + + + +class Content(Object): + """ + @ivar node: The content source node. + @type node: L{sax.element.Element} + @ivar data: The (optional) content data. + @type data: L{Object} + @ivar text: The (optional) content (xml) text. + @type text: basestring + """ + + extensions = [] + + def __init__(self, node, **kwargs): + Object.__init__(self) + self.node = node + self.data = None + self.text = None + for k,v in kwargs.items(): + setattr(self, k, v) + + def __getattr__(self, name): + if name not in self.__dict__: + if name in self.extensions: + v = None + setattr(self, name, v) + else: + raise AttributeError, \ + 'Content has no attribute %s' % name + else: + v = self.__dict__[name] + return v \ No newline at end of file diff --git a/awx/lib/site-packages/suds/umx/attrlist.py b/awx/lib/site-packages/suds/umx/attrlist.py new file mode 100644 index 0000000000..369432749f --- /dev/null +++ b/awx/lib/site-packages/suds/umx/attrlist.py @@ -0,0 +1,88 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Provides filtered attribute list classes. +""" + +from suds import * +from suds.umx import * +from suds.sax import Namespace + + +class AttrList: + """ + A filtered attribute list. + Items are included during iteration if they are in either the (xs) or + (xml) namespaces. + @ivar raw: The I{raw} attribute list. + @type raw: list + """ + def __init__(self, attributes): + """ + @param attributes: A list of attributes + @type attributes: list + """ + self.raw = attributes + + def real(self): + """ + Get list of I{real} attributes which exclude xs and xml attributes. + @return: A list of I{real} attributes. + @rtype: I{generator} + """ + for a in self.raw: + if self.skip(a): continue + yield a + + def rlen(self): + """ + Get the number of I{real} attributes which exclude xs and xml attributes. + @return: A count of I{real} attributes. + @rtype: L{int} + """ + n = 0 + for a in self.real(): + n += 1 + return n + + def lang(self): + """ + Get list of I{filtered} attributes which exclude xs. + @return: A list of I{filtered} attributes. + @rtype: I{generator} + """ + for a in self.raw: + if a.qname() == 'xml:lang': + return a.value + return None + + def skip(self, attr): + """ + Get whether to skip (filter-out) the specified attribute. + @param attr: An attribute. + @type attr: I{Attribute} + @return: True if should be skipped. + @rtype: bool + """ + ns = attr.namespace() + skip = ( + Namespace.xmlns[1], + 'http://schemas.xmlsoap.org/soap/encoding/', + 'http://schemas.xmlsoap.org/soap/envelope/', + 'http://www.w3.org/2003/05/soap-envelope', + ) + return ( Namespace.xs(ns) or ns[1] in skip ) diff --git a/awx/lib/site-packages/suds/umx/basic.py b/awx/lib/site-packages/suds/umx/basic.py new file mode 100644 index 0000000000..cdc1e66e62 --- /dev/null +++ b/awx/lib/site-packages/suds/umx/basic.py @@ -0,0 +1,41 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Provides basic unmarshaller classes. +""" + +from logging import getLogger +from suds import * +from suds.umx import * +from suds.umx.core import Core + + +class Basic(Core): + """ + A object builder (unmarshaller). + """ + + def process(self, node): + """ + Process an object graph representation of the xml I{node}. + @param node: An XML tree. + @type node: L{sax.element.Element} + @return: A suds object. + @rtype: L{Object} + """ + content = Content(node) + return Core.process(self, content) \ No newline at end of file diff --git a/awx/lib/site-packages/suds/umx/core.py b/awx/lib/site-packages/suds/umx/core.py new file mode 100644 index 0000000000..07d33c4894 --- /dev/null +++ b/awx/lib/site-packages/suds/umx/core.py @@ -0,0 +1,216 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Provides base classes for XML->object I{unmarshalling}. +""" + +from logging import getLogger +from suds import * +from suds.umx import * +from suds.umx.attrlist import AttrList +from suds.sax.text import Text +from suds.sudsobject import Factory, merge + + +log = getLogger(__name__) + +reserved = { 'class':'cls', 'def':'dfn', } + +class Core: + """ + The abstract XML I{node} unmarshaller. This class provides the + I{core} unmarshalling functionality. + """ + + def process(self, content): + """ + Process an object graph representation of the xml I{node}. + @param content: The current content being unmarshalled. + @type content: L{Content} + @return: A suds object. + @rtype: L{Object} + """ + self.reset() + return self.append(content) + + def append(self, content): + """ + Process the specified node and convert the XML document into + a I{suds} L{object}. + @param content: The current content being unmarshalled. + @type content: L{Content} + @return: A I{append-result} tuple as: (L{Object}, I{value}) + @rtype: I{append-result} + @note: This is not the proper entry point. + @see: L{process()} + """ + self.start(content) + self.append_attributes(content) + self.append_children(content) + self.append_text(content) + self.end(content) + return self.postprocess(content) + + def postprocess(self, content): + """ + Perform final processing of the resulting data structure as follows: + - Mixed values (children and text) will have a result of the I{content.node}. + - Simi-simple values (attributes, no-children and text) will have a result of a + property object. + - Simple values (no-attributes, no-children with text nodes) will have a string + result equal to the value of the content.node.getText(). + @param content: The current content being unmarshalled. + @type content: L{Content} + @return: The post-processed result. + @rtype: I{any} + """ + node = content.node + if len(node.children) and node.hasText(): + return node + attributes = AttrList(node.attributes) + if attributes.rlen() and \ + not len(node.children) and \ + node.hasText(): + p = Factory.property(node.name, node.getText()) + return merge(content.data, p) + if len(content.data): + return content.data + lang = attributes.lang() + if content.node.isnil(): + return None + if not len(node.children) and content.text is None: + if self.nillable(content): + return None + else: + return Text('', lang=lang) + if isinstance(content.text, basestring): + return Text(content.text, lang=lang) + else: + return content.text + + def append_attributes(self, content): + """ + Append attribute nodes into L{Content.data}. + Attributes in the I{schema} or I{xml} namespaces are skipped. + @param content: The current content being unmarshalled. + @type content: L{Content} + """ + attributes = AttrList(content.node.attributes) + for attr in attributes.real(): + name = attr.name + value = attr.value + self.append_attribute(name, value, content) + + def append_attribute(self, name, value, content): + """ + Append an attribute name/value into L{Content.data}. + @param name: The attribute name + @type name: basestring + @param value: The attribute's value + @type value: basestring + @param content: The current content being unmarshalled. + @type content: L{Content} + """ + key = name + key = '_%s' % reserved.get(key, key) + setattr(content.data, key, value) + + def append_children(self, content): + """ + Append child nodes into L{Content.data} + @param content: The current content being unmarshalled. + @type content: L{Content} + """ + for child in content.node: + cont = Content(child) + cval = self.append(cont) + key = reserved.get(child.name, child.name) + if key in content.data: + v = getattr(content.data, key) + if isinstance(v, list): + v.append(cval) + else: + setattr(content.data, key, [v, cval]) + continue + if self.unbounded(cont): + if cval is None: + setattr(content.data, key, []) + else: + setattr(content.data, key, [cval,]) + else: + setattr(content.data, key, cval) + + def append_text(self, content): + """ + Append text nodes into L{Content.data} + @param content: The current content being unmarshalled. + @type content: L{Content} + """ + if content.node.hasText(): + content.text = content.node.getText() + + def reset(self): + pass + + def start(self, content): + """ + Processing on I{node} has started. Build and return + the proper object. + @param content: The current content being unmarshalled. + @type content: L{Content} + @return: A subclass of Object. + @rtype: L{Object} + """ + content.data = Factory.object(content.node.name) + + def end(self, content): + """ + Processing on I{node} has ended. + @param content: The current content being unmarshalled. + @type content: L{Content} + """ + pass + + def bounded(self, content): + """ + Get whether the content is bounded (not a list). + @param content: The current content being unmarshalled. + @type content: L{Content} + @return: True if bounded, else False + @rtype: boolean + '""" + return ( not self.unbounded(content) ) + + def unbounded(self, content): + """ + Get whether the object is unbounded (a list). + @param content: The current content being unmarshalled. + @type content: L{Content} + @return: True if unbounded, else False + @rtype: boolean + '""" + return False + + def nillable(self, content): + """ + Get whether the object is nillable. + @param content: The current content being unmarshalled. + @type content: L{Content} + @return: True if nillable, else False + @rtype: boolean + '""" + return False \ No newline at end of file diff --git a/awx/lib/site-packages/suds/umx/encoded.py b/awx/lib/site-packages/suds/umx/encoded.py new file mode 100644 index 0000000000..afe7374cb4 --- /dev/null +++ b/awx/lib/site-packages/suds/umx/encoded.py @@ -0,0 +1,128 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Provides soap encoded unmarshaller classes. +""" + +from logging import getLogger +from suds import * +from suds.umx import * +from suds.umx.typed import Typed +from suds.sax import splitPrefix, Namespace + +log = getLogger(__name__) + +# +# Add encoded extensions +# aty = The soap (section 5) encoded array type. +# +Content.extensions.append('aty') + + +class Encoded(Typed): + """ + A SOAP section (5) encoding unmarshaller. + This marshaller supports rpc/encoded soap styles. + """ + + def start(self, content): + # + # Grab the array type and continue + # + self.setaty(content) + Typed.start(self, content) + + def end(self, content): + # + # Squash soap encoded arrays into python lists. This is + # also where we insure that empty arrays are represented + # as empty python lists. + # + aty = content.aty + if aty is not None: + self.promote(content) + return Typed.end(self, content) + + def postprocess(self, content): + # + # Ensure proper rendering of empty arrays. + # + if content.aty is None: + return Typed.postprocess(self, content) + else: + return content.data + + def setaty(self, content): + """ + Grab the (aty) soap-enc:arrayType and attach it to the + content for proper array processing later in end(). + @param content: The current content being unmarshalled. + @type content: L{Content} + @return: self + @rtype: L{Encoded} + """ + name = 'arrayType' + ns = (None, 'http://schemas.xmlsoap.org/soap/encoding/') + aty = content.node.get(name, ns) + if aty is not None: + content.aty = aty + parts = aty.split('[') + ref = parts[0] + if len(parts) == 2: + self.applyaty(content, ref) + else: + pass # (2) dimensional array + return self + + def applyaty(self, content, xty): + """ + Apply the type referenced in the I{arrayType} to the content + (child nodes) of the array. Each element (node) in the array + that does not have an explicit xsi:type attribute is given one + based on the I{arrayType}. + @param content: An array content. + @type content: L{Content} + @param xty: The XSI type reference. + @type xty: str + @return: self + @rtype: L{Encoded} + """ + name = 'type' + ns = Namespace.xsins + parent = content.node + for child in parent.getChildren(): + ref = child.get(name, ns) + if ref is None: + parent.addPrefix(ns[0], ns[1]) + attr = ':'.join((ns[0], name)) + child.set(attr, xty) + return self + + def promote(self, content): + """ + Promote (replace) the content.data with the first attribute + of the current content.data that is a I{list}. Note: the + content.data may be empty or contain only _x attributes. + In either case, the content.data is assigned an empty list. + @param content: An array content. + @type content: L{Content} + """ + for n,v in content.data: + if isinstance(v, list): + content.data = v + return + content.data = [] \ No newline at end of file diff --git a/awx/lib/site-packages/suds/umx/typed.py b/awx/lib/site-packages/suds/umx/typed.py new file mode 100644 index 0000000000..f272a259d4 --- /dev/null +++ b/awx/lib/site-packages/suds/umx/typed.py @@ -0,0 +1,141 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +Provides typed unmarshaller classes. +""" + +from logging import getLogger +from suds import * +from suds.umx import * +from suds.umx.core import Core +from suds.resolver import NodeResolver, Frame +from suds.sudsobject import Factory + +log = getLogger(__name__) + + +# +# Add typed extensions +# type = The expected xsd type +# real = The 'true' XSD type +# +Content.extensions.append('type') +Content.extensions.append('real') + + +class Typed(Core): + """ + A I{typed} XML unmarshaller + @ivar resolver: A schema type resolver. + @type resolver: L{NodeResolver} + """ + + def __init__(self, schema): + """ + @param schema: A schema object. + @type schema: L{xsd.schema.Schema} + """ + self.resolver = NodeResolver(schema) + + def process(self, node, type): + """ + Process an object graph representation of the xml L{node}. + @param node: An XML tree. + @type node: L{sax.element.Element} + @param type: The I{optional} schema type. + @type type: L{xsd.sxbase.SchemaObject} + @return: A suds object. + @rtype: L{Object} + """ + content = Content(node) + content.type = type + return Core.process(self, content) + + def reset(self): + log.debug('reset') + self.resolver.reset() + + def start(self, content): + # + # Resolve to the schema type; build an object and setup metadata. + # + if content.type is None: + found = self.resolver.find(content.node) + if found is None: + log.error(self.resolver.schema) + raise TypeNotFound(content.node.qname()) + content.type = found + else: + known = self.resolver.known(content.node) + frame = Frame(content.type, resolved=known) + self.resolver.push(frame) + real = self.resolver.top().resolved + content.real = real + cls_name = real.name + if cls_name is None: + cls_name = content.node.name + content.data = Factory.object(cls_name) + md = content.data.__metadata__ + md.sxtype = real + + def end(self, content): + self.resolver.pop() + + def unbounded(self, content): + return content.type.unbounded() + + def nillable(self, content): + resolved = content.type.resolve() + return ( content.type.nillable or \ + (resolved.builtin() and resolved.nillable ) ) + + def append_attribute(self, name, value, content): + """ + Append an attribute name/value into L{Content.data}. + @param name: The attribute name + @type name: basestring + @param value: The attribute's value + @type value: basestring + @param content: The current content being unmarshalled. + @type content: L{Content} + """ + type = self.resolver.findattr(name) + if type is None: + log.warn('attribute (%s) type, not-found', name) + else: + value = self.translated(value, type) + Core.append_attribute(self, name, value, content) + + def append_text(self, content): + """ + Append text nodes into L{Content.data} + Here is where the I{true} type is used to translate the value + into the proper python type. + @param content: The current content being unmarshalled. + @type content: L{Content} + """ + Core.append_text(self, content) + known = self.resolver.top().resolved + content.text = self.translated(content.text, known) + + def translated(self, value, type): + """ translate using the schema type """ + if value is not None: + resolved = type.resolve() + return resolved.translate(value) + else: + return value \ No newline at end of file diff --git a/awx/lib/site-packages/suds/wsdl.py b/awx/lib/site-packages/suds/wsdl.py new file mode 100644 index 0000000000..8bba88f995 --- /dev/null +++ b/awx/lib/site-packages/suds/wsdl.py @@ -0,0 +1,922 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +The I{wsdl} module provides an objectification of the WSDL. +The primary class is I{Definitions} as it represends the root element +found in the document. +""" + +from logging import getLogger +from suds import * +from suds.sax import splitPrefix +from suds.sax.element import Element +from suds.bindings.document import Document +from suds.bindings.rpc import RPC, Encoded +from suds.xsd import qualify, Namespace +from suds.xsd.schema import Schema, SchemaCollection +from suds.xsd.query import ElementQuery +from suds.sudsobject import Object, Facade, Metadata +from suds.reader import DocumentReader, DefinitionsReader +from urlparse import urljoin +import re, soaparray + +log = getLogger(__name__) + +wsdlns = (None, "http://schemas.xmlsoap.org/wsdl/") +soapns = (None, 'http://schemas.xmlsoap.org/wsdl/soap/') +soap12ns = (None, 'http://schemas.xmlsoap.org/wsdl/soap12/') + + +class WObject(Object): + """ + Base object for wsdl types. + @ivar root: The XML I{root} element. + @type root: L{Element} + """ + + def __init__(self, root, definitions=None): + """ + @param root: An XML root element. + @type root: L{Element} + @param definitions: A definitions object. + @type definitions: L{Definitions} + """ + Object.__init__(self) + self.root = root + pmd = Metadata() + pmd.excludes = ['root'] + pmd.wrappers = dict(qname=repr) + self.__metadata__.__print__ = pmd + + def resolve(self, definitions): + """ + Resolve named references to other WSDL objects. + @param definitions: A definitions object. + @type definitions: L{Definitions} + """ + pass + + +class NamedObject(WObject): + """ + A B{named} WSDL object. + @ivar name: The name of the object. + @type name: str + @ivar qname: The I{qualified} name of the object. + @type qname: (name, I{namespace-uri}). + """ + + def __init__(self, root, definitions): + """ + @param root: An XML root element. + @type root: L{Element} + @param definitions: A definitions object. + @type definitions: L{Definitions} + """ + WObject.__init__(self, root, definitions) + self.name = root.get('name') + self.qname = (self.name, definitions.tns[1]) + pmd = self.__metadata__.__print__ + pmd.wrappers['qname'] = repr + + +class Definitions(WObject): + """ + Represents the I{root} container of the WSDL objects as defined + by + @ivar id: The object id. + @type id: str + @ivar options: An options dictionary. + @type options: L{options.Options} + @ivar url: The URL used to load the object. + @type url: str + @ivar tns: The target namespace for the WSDL. + @type tns: str + @ivar schema: The collective WSDL schema object. + @type schema: L{SchemaCollection} + @ivar children: The raw list of child objects. + @type children: [L{WObject},...] + @ivar imports: The list of L{Import} children. + @type imports: [L{Import},...] + @ivar messages: The dictionary of L{Message} children key'd by I{qname} + @type messages: [L{Message},...] + @ivar port_types: The dictionary of L{PortType} children key'd by I{qname} + @type port_types: [L{PortType},...] + @ivar bindings: The dictionary of L{Binding} children key'd by I{qname} + @type bindings: [L{Binding},...] + @ivar service: The service object. + @type service: L{Service} + """ + + Tag = 'definitions' + + def __init__(self, url, options): + """ + @param url: A URL to the WSDL. + @type url: str + @param options: An options dictionary. + @type options: L{options.Options} + """ + log.debug('reading wsdl at: %s ...', url) + reader = DocumentReader(options) + d = reader.open(url) + root = d.root() + WObject.__init__(self, root) + self.id = objid(self) + self.options = options + self.url = url + self.tns = self.mktns(root) + self.types = [] + self.schema = None + self.children = [] + self.imports = [] + self.messages = {} + self.port_types = {} + self.bindings = {} + self.services = [] + self.add_children(self.root) + self.children.sort() + pmd = self.__metadata__.__print__ + pmd.excludes.append('children') + pmd.excludes.append('wsdl') + pmd.wrappers['schema'] = repr + self.open_imports() + self.resolve() + self.build_schema() + self.set_wrapped() + for s in self.services: + self.add_methods(s) + log.debug("wsdl at '%s' loaded:\n%s", url, self) + + def mktns(self, root): + """ Get/create the target namespace """ + tns = root.get('targetNamespace') + prefix = root.findPrefix(tns) + if prefix is None: + log.debug('warning: tns (%s), not mapped to prefix', tns) + prefix = 'tns' + return (prefix, tns) + + def add_children(self, root): + """ Add child objects using the factory """ + for c in root.getChildren(ns=wsdlns): + child = Factory.create(c, self) + if child is None: continue + self.children.append(child) + if isinstance(child, Import): + self.imports.append(child) + continue + if isinstance(child, Types): + self.types.append(child) + continue + if isinstance(child, Message): + self.messages[child.qname] = child + continue + if isinstance(child, PortType): + self.port_types[child.qname] = child + continue + if isinstance(child, Binding): + self.bindings[child.qname] = child + continue + if isinstance(child, Service): + self.services.append(child) + continue + + def open_imports(self): + """ Import the I{imported} WSDLs. """ + for imp in self.imports: + imp.load(self) + + def resolve(self): + """ Tell all children to resolve themselves """ + for c in self.children: + c.resolve(self) + + def build_schema(self): + """ Process L{Types} objects and create the schema collection """ + container = SchemaCollection(self) + for t in [t for t in self.types if t.local()]: + for root in t.contents(): + schema = Schema(root, self.url, self.options, container) + container.add(schema) + if not len(container): # empty + root = Element.buildPath(self.root, 'types/schema') + schema = Schema(root, self.url, self.options, container) + container.add(schema) + self.schema = container.load(self.options) + for s in [t.schema() for t in self.types if t.imported()]: + self.schema.merge(s) + return self.schema + + def add_methods(self, service): + """ Build method view for service """ + bindings = { + 'document/literal' : Document(self), + 'rpc/literal' : RPC(self), + 'rpc/encoded' : Encoded(self) + } + for p in service.ports: + binding = p.binding + ptype = p.binding.type + operations = p.binding.type.operations.values() + for name in [op.name for op in operations]: + m = Facade('Method') + m.name = name + m.location = p.location + m.binding = Facade('binding') + op = binding.operation(name) + m.soap = op.soap + key = '/'.join((op.soap.style, op.soap.input.body.use)) + m.binding.input = bindings.get(key) + key = '/'.join((op.soap.style, op.soap.output.body.use)) + m.binding.output = bindings.get(key) + op = ptype.operation(name) + p.methods[name] = m + + def set_wrapped(self): + """ set (wrapped|bare) flag on messages """ + for b in self.bindings.values(): + for op in b.operations.values(): + for body in (op.soap.input.body, op.soap.output.body): + body.wrapped = False + if len(body.parts) != 1: + continue + for p in body.parts: + if p.element is None: + continue + query = ElementQuery(p.element) + pt = query.execute(self.schema) + if pt is None: + raise TypeNotFound(query.ref) + resolved = pt.resolve() + if resolved.builtin(): + continue + body.wrapped = True + + def __getstate__(self): + nopickle = ('options',) + state = self.__dict__.copy() + for k in nopickle: + if k in state: + del state[k] + return state + + def __repr__(self): + return 'Definitions (id=%s)' % self.id + + +class Import(WObject): + """ + Represents the . + @ivar location: The value of the I{location} attribute. + @type location: str + @ivar ns: The value of the I{namespace} attribute. + @type ns: str + @ivar imported: The imported object. + @type imported: L{Definitions} + """ + + def __init__(self, root, definitions): + """ + @param root: An XML root element. + @type root: L{Element} + @param definitions: A definitions object. + @type definitions: L{Definitions} + """ + WObject.__init__(self, root, definitions) + self.location = root.get('location') + self.ns = root.get('namespace') + self.imported = None + pmd = self.__metadata__.__print__ + pmd.wrappers['imported'] = repr + + def load(self, definitions): + """ Load the object by opening the URL """ + url = self.location + log.debug('importing (%s)', url) + if '://' not in url: + url = urljoin(definitions.url, url) + options = definitions.options + d = Definitions(url, options) + if d.root.match(Definitions.Tag, wsdlns): + self.import_definitions(definitions, d) + return + if d.root.match(Schema.Tag, Namespace.xsdns): + self.import_schema(definitions, d) + return + raise Exception('document at "%s" is unknown' % url) + + def import_definitions(self, definitions, d): + """ import/merge wsdl definitions """ + definitions.types += d.types + definitions.messages.update(d.messages) + definitions.port_types.update(d.port_types) + definitions.bindings.update(d.bindings) + self.imported = d + log.debug('imported (WSDL):\n%s', d) + + def import_schema(self, definitions, d): + """ import schema as content """ + if not len(definitions.types): + types = Types.create(definitions) + definitions.types.append(types) + else: + types = definitions.types[-1] + types.root.append(d.root) + log.debug('imported (XSD):\n%s', d.root) + + def __gt__(self, other): + return False + + +class Types(WObject): + """ + Represents . + """ + + @classmethod + def create(cls, definitions): + root = Element('types', ns=wsdlns) + definitions.root.insert(root) + return Types(root, definitions) + + def __init__(self, root, definitions): + """ + @param root: An XML root element. + @type root: L{Element} + @param definitions: A definitions object. + @type definitions: L{Definitions} + """ + WObject.__init__(self, root, definitions) + self.definitions = definitions + + def contents(self): + return self.root.getChildren('schema', Namespace.xsdns) + + def schema(self): + return self.definitions.schema + + def local(self): + return ( self.definitions.schema is None ) + + def imported(self): + return ( not self.local() ) + + def __gt__(self, other): + return isinstance(other, Import) + + +class Part(NamedObject): + """ + Represents . + @ivar element: The value of the {element} attribute. + Stored as a I{qref} as converted by L{suds.xsd.qualify}. + @type element: str + @ivar type: The value of the {type} attribute. + Stored as a I{qref} as converted by L{suds.xsd.qualify}. + @type type: str + """ + + def __init__(self, root, definitions): + """ + @param root: An XML root element. + @type root: L{Element} + @param definitions: A definitions object. + @type definitions: L{Definitions} + """ + NamedObject.__init__(self, root, definitions) + pmd = Metadata() + pmd.wrappers = dict(element=repr, type=repr) + self.__metadata__.__print__ = pmd + tns = definitions.tns + self.element = self.__getref('element', tns) + self.type = self.__getref('type', tns) + + def __getref(self, a, tns): + """ Get the qualified value of attribute named 'a'.""" + s = self.root.get(a) + if s is None: + return s + else: + return qualify(s, self.root, tns) + + +class Message(NamedObject): + """ + Represents . + @ivar parts: A list of message parts. + @type parts: [I{Part},...] + """ + + def __init__(self, root, definitions): + """ + @param root: An XML root element. + @type root: L{Element} + @param definitions: A definitions object. + @type definitions: L{Definitions} + """ + NamedObject.__init__(self, root, definitions) + self.parts = [] + for p in root.getChildren('part'): + part = Part(p, definitions) + self.parts.append(part) + + def __gt__(self, other): + return isinstance(other, (Import, Types)) + + +class PortType(NamedObject): + """ + Represents . + @ivar operations: A list of contained operations. + @type operations: list + """ + + def __init__(self, root, definitions): + """ + @param root: An XML root element. + @type root: L{Element} + @param definitions: A definitions object. + @type definitions: L{Definitions} + """ + NamedObject.__init__(self, root, definitions) + self.operations = {} + for c in root.getChildren('operation'): + op = Facade('Operation') + op.name = c.get('name') + op.tns = definitions.tns + input = c.getChild('input') + if input is None: + op.input = None + else: + op.input = input.get('message') + output = c.getChild('output') + if output is None: + op.output = None + else: + op.output = output.get('message') + faults = [] + for fault in c.getChildren('fault'): + f = Facade('Fault') + f.name = fault.get('name') + f.message = fault.get('message') + faults.append(f) + op.faults = faults + self.operations[op.name] = op + + def resolve(self, definitions): + """ + Resolve named references to other WSDL objects. + @param definitions: A definitions object. + @type definitions: L{Definitions} + """ + for op in self.operations.values(): + if op.input is None: + op.input = Message(Element('no-input'), definitions) + else: + qref = qualify(op.input, self.root, definitions.tns) + msg = definitions.messages.get(qref) + if msg is None: + raise Exception("msg '%s', not-found" % op.input) + else: + op.input = msg + if op.output is None: + op.output = Message(Element('no-output'), definitions) + else: + qref = qualify(op.output, self.root, definitions.tns) + msg = definitions.messages.get(qref) + if msg is None: + raise Exception("msg '%s', not-found" % op.output) + else: + op.output = msg + for f in op.faults: + qref = qualify(f.message, self.root, definitions.tns) + msg = definitions.messages.get(qref) + if msg is None: + raise Exception, "msg '%s', not-found" % f.message + f.message = msg + + def operation(self, name): + """ + Shortcut used to get a contained operation by name. + @param name: An operation name. + @type name: str + @return: The named operation. + @rtype: Operation + @raise L{MethodNotFound}: When not found. + """ + try: + return self.operations[name] + except Exception, e: + raise MethodNotFound(name) + + def __gt__(self, other): + return isinstance(other, (Import, Types, Message)) + + +class Binding(NamedObject): + """ + Represents + @ivar operations: A list of contained operations. + @type operations: list + """ + + def __init__(self, root, definitions): + """ + @param root: An XML root element. + @type root: L{Element} + @param definitions: A definitions object. + @type definitions: L{Definitions} + """ + NamedObject.__init__(self, root, definitions) + self.operations = {} + self.type = root.get('type') + sr = self.soaproot() + if sr is None: + self.soap = None + log.debug('binding: "%s" not a soap binding', self.name) + return + soap = Facade('soap') + self.soap = soap + self.soap.style = sr.get('style', default='document') + self.add_operations(self.root, definitions) + + def soaproot(self): + """ get the soap:binding """ + for ns in (soapns, soap12ns): + sr = self.root.getChild('binding', ns=ns) + if sr is not None: + return sr + return None + + def add_operations(self, root, definitions): + """ Add children """ + dsop = Element('operation', ns=soapns) + for c in root.getChildren('operation'): + op = Facade('Operation') + op.name = c.get('name') + sop = c.getChild('operation', default=dsop) + soap = Facade('soap') + soap.action = '"%s"' % sop.get('soapAction', default='') + soap.style = sop.get('style', default=self.soap.style) + soap.input = Facade('Input') + soap.input.body = Facade('Body') + soap.input.headers = [] + soap.output = Facade('Output') + soap.output.body = Facade('Body') + soap.output.headers = [] + op.soap = soap + input = c.getChild('input') + if input is None: + input = Element('input', ns=wsdlns) + body = input.getChild('body') + self.body(definitions, soap.input.body, body) + for header in input.getChildren('header'): + self.header(definitions, soap.input, header) + output = c.getChild('output') + if output is None: + output = Element('output', ns=wsdlns) + body = output.getChild('body') + self.body(definitions, soap.output.body, body) + for header in output.getChildren('header'): + self.header(definitions, soap.output, header) + faults = [] + for fault in c.getChildren('fault'): + sf = fault.getChild('fault') + if sf is None: + continue + fn = fault.get('name') + f = Facade('Fault') + f.name = sf.get('name', default=fn) + f.use = sf.get('use', default='literal') + faults.append(f) + soap.faults = faults + self.operations[op.name] = op + + def body(self, definitions, body, root): + """ add the input/output body properties """ + if root is None: + body.use = 'literal' + body.namespace = definitions.tns + body.parts = () + return + parts = root.get('parts') + if parts is None: + body.parts = () + else: + body.parts = re.split('[\s,]', parts) + body.use = root.get('use', default='literal') + ns = root.get('namespace') + if ns is None: + body.namespace = definitions.tns + else: + prefix = root.findPrefix(ns, 'b0') + body.namespace = (prefix, ns) + + def header(self, definitions, parent, root): + """ add the input/output header properties """ + if root is None: + return + header = Facade('Header') + parent.headers.append(header) + header.use = root.get('use', default='literal') + ns = root.get('namespace') + if ns is None: + header.namespace = definitions.tns + else: + prefix = root.findPrefix(ns, 'h0') + header.namespace = (prefix, ns) + msg = root.get('message') + if msg is not None: + header.message = msg + part = root.get('part') + if part is not None: + header.part = part + + def resolve(self, definitions): + """ + Resolve named references to other WSDL objects. This includes + cross-linking information (from) the portType (to) the I{soap} + protocol information on the binding for each operation. + @param definitions: A definitions object. + @type definitions: L{Definitions} + """ + self.resolveport(definitions) + for op in self.operations.values(): + self.resolvesoapbody(definitions, op) + self.resolveheaders(definitions, op) + self.resolvefaults(definitions, op) + + def resolveport(self, definitions): + """ + Resolve port_type reference. + @param definitions: A definitions object. + @type definitions: L{Definitions} + """ + ref = qualify(self.type, self.root, definitions.tns) + port_type = definitions.port_types.get(ref) + if port_type is None: + raise Exception("portType '%s', not-found" % self.type) + else: + self.type = port_type + + def resolvesoapbody(self, definitions, op): + """ + Resolve soap body I{message} parts by + cross-referencing with operation defined in port type. + @param definitions: A definitions object. + @type definitions: L{Definitions} + @param op: An I{operation} object. + @type op: I{operation} + """ + ptop = self.type.operation(op.name) + if ptop is None: + raise Exception, \ + "operation '%s' not defined in portType" % op.name + soap = op.soap + parts = soap.input.body.parts + if len(parts): + pts = [] + for p in ptop.input.parts: + if p.name in parts: + pts.append(p) + soap.input.body.parts = pts + else: + soap.input.body.parts = ptop.input.parts + parts = soap.output.body.parts + if len(parts): + pts = [] + for p in ptop.output.parts: + if p.name in parts: + pts.append(p) + soap.output.body.parts = pts + else: + soap.output.body.parts = ptop.output.parts + + def resolveheaders(self, definitions, op): + """ + Resolve soap header I{message} references. + @param definitions: A definitions object. + @type definitions: L{Definitions} + @param op: An I{operation} object. + @type op: I{operation} + """ + soap = op.soap + headers = soap.input.headers + soap.output.headers + for header in headers: + mn = header.message + ref = qualify(mn, self.root, definitions.tns) + message = definitions.messages.get(ref) + if message is None: + raise Exception, "message'%s', not-found" % mn + pn = header.part + for p in message.parts: + if p.name == pn: + header.part = p + break + if pn == header.part: + raise Exception, \ + "message '%s' has not part named '%s'" % (ref, pn) + + def resolvefaults(self, definitions, op): + """ + Resolve soap fault I{message} references by + cross-referencing with operation defined in port type. + @param definitions: A definitions object. + @type definitions: L{Definitions} + @param op: An I{operation} object. + @type op: I{operation} + """ + ptop = self.type.operation(op.name) + if ptop is None: + raise Exception, \ + "operation '%s' not defined in portType" % op.name + soap = op.soap + for fault in soap.faults: + for f in ptop.faults: + if f.name == fault.name: + fault.parts = f.message.parts + continue + if hasattr(fault, 'parts'): + continue + raise Exception, \ + "fault '%s' not defined in portType '%s'" % (fault.name, self.type.name) + + def operation(self, name): + """ + Shortcut used to get a contained operation by name. + @param name: An operation name. + @type name: str + @return: The named operation. + @rtype: Operation + @raise L{MethodNotFound}: When not found. + """ + try: + return self.operations[name] + except: + raise MethodNotFound(name) + + def __gt__(self, other): + return ( not isinstance(other, Service) ) + + +class Port(NamedObject): + """ + Represents a service port. + @ivar service: A service. + @type service: L{Service} + @ivar binding: A binding name. + @type binding: str + @ivar location: The service location (url). + @type location: str + """ + + def __init__(self, root, definitions, service): + """ + @param root: An XML root element. + @type root: L{Element} + @param definitions: A definitions object. + @type definitions: L{Definitions} + @param service: A service object. + @type service: L{Service} + """ + NamedObject.__init__(self, root, definitions) + self.__service = service + self.binding = root.get('binding') + address = root.getChild('address') + if address is None: + self.location = None + else: + self.location = address.get('location').encode('utf-8') + self.methods = {} + + def method(self, name): + """ + Get a method defined in this portType by name. + @param name: A method name. + @type name: str + @return: The requested method object. + @rtype: I{Method} + """ + return self.methods.get(name) + + +class Service(NamedObject): + """ + Represents . + @ivar port: The contained ports. + @type port: [Port,..] + @ivar methods: The contained methods for all ports. + @type methods: [Method,..] + """ + + def __init__(self, root, definitions): + """ + @param root: An XML root element. + @type root: L{Element} + @param definitions: A definitions object. + @type definitions: L{Definitions} + """ + NamedObject.__init__(self, root, definitions) + self.ports = [] + for p in root.getChildren('port'): + port = Port(p, definitions, self) + self.ports.append(port) + + def port(self, name): + """ + Locate a port by name. + @param name: A port name. + @type name: str + @return: The port object. + @rtype: L{Port} + """ + for p in self.ports: + if p.name == name: + return p + return None + + def setlocation(self, url, names=None): + """ + Override the invocation location (url) for service method. + @param url: A url location. + @type url: A url. + @param names: A list of method names. None=ALL + @type names: [str,..] + """ + for p in self.ports: + for m in p.methods.values(): + if names is None or m.name in names: + m.location = url + + def resolve(self, definitions): + """ + Resolve named references to other WSDL objects. + Ports without soap bindings are discarded. + @param definitions: A definitions object. + @type definitions: L{Definitions} + """ + filtered = [] + for p in self.ports: + ref = qualify(p.binding, self.root, definitions.tns) + binding = definitions.bindings.get(ref) + if binding is None: + raise Exception("binding '%s', not-found" % p.binding) + if binding.soap is None: + log.debug('binding "%s" - not a soap, discarded', binding.name) + continue + p.binding = binding + filtered.append(p) + self.ports = filtered + + def __gt__(self, other): + return True + + +class Factory: + """ + Simple WSDL object factory. + @cvar tags: Dictionary of tag->constructor mappings. + @type tags: dict + """ + + tags =\ + { + 'import' : Import, + 'types' : Types, + 'message' : Message, + 'portType' : PortType, + 'binding' : Binding, + 'service' : Service, + } + + @classmethod + def create(cls, root, definitions): + """ + Create an object based on the root tag name. + @param root: An XML root element. + @type root: L{Element} + @param definitions: A definitions object. + @type definitions: L{Definitions} + @return: The created object. + @rtype: L{WObject} + """ + fn = cls.tags.get(root.name) + if fn is not None: + return fn(root, definitions) + else: + return None diff --git a/awx/lib/site-packages/suds/wsse.py b/awx/lib/site-packages/suds/wsse.py new file mode 100644 index 0000000000..2a697c1c1f --- /dev/null +++ b/awx/lib/site-packages/suds/wsse.py @@ -0,0 +1,212 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +The I{wsse} module provides WS-Security. +""" + +from logging import getLogger +from suds import * +from suds.sudsobject import Object +from suds.sax.element import Element +from suds.sax.date import UTC +from datetime import datetime, timedelta + +try: + from hashlib import md5 +except ImportError: + # Python 2.4 compatibility + from md5 import md5 + + +dsns = \ + ('ds', + 'http://www.w3.org/2000/09/xmldsig#') +wssens = \ + ('wsse', + 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd') +wsuns = \ + ('wsu', + 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd') +wsencns = \ + ('wsenc', + 'http://www.w3.org/2001/04/xmlenc#') + + +class Security(Object): + """ + WS-Security object. + @ivar tokens: A list of security tokens + @type tokens: [L{Token},...] + @ivar signatures: A list of signatures. + @type signatures: TBD + @ivar references: A list of references. + @type references: TBD + @ivar keys: A list of encryption keys. + @type keys: TBD + """ + + def __init__(self): + """ """ + Object.__init__(self) + self.mustUnderstand = True + self.tokens = [] + self.signatures = [] + self.references = [] + self.keys = [] + + def xml(self): + """ + Get xml representation of the object. + @return: The root node. + @rtype: L{Element} + """ + root = Element('Security', ns=wssens) + root.set('mustUnderstand', str(self.mustUnderstand).lower()) + for t in self.tokens: + root.append(t.xml()) + return root + + +class Token(Object): + """ I{Abstract} security token. """ + + @classmethod + def now(cls): + return datetime.now() + + @classmethod + def utc(cls): + return datetime.utcnow() + + @classmethod + def sysdate(cls): + utc = UTC() + return str(utc) + + def __init__(self): + Object.__init__(self) + + +class UsernameToken(Token): + """ + Represents a basic I{UsernameToken} WS-Secuirty token. + @ivar username: A username. + @type username: str + @ivar password: A password. + @type password: str + @ivar nonce: A set of bytes to prevent reply attacks. + @type nonce: str + @ivar created: The token created. + @type created: L{datetime} + """ + + def __init__(self, username=None, password=None): + """ + @param username: A username. + @type username: str + @param password: A password. + @type password: str + """ + Token.__init__(self) + self.username = username + self.password = password + self.nonce = None + self.created = None + + def setnonce(self, text=None): + """ + Set I{nonce} which is arbitraty set of bytes to prevent + reply attacks. + @param text: The nonce text value. + Generated when I{None}. + @type text: str + """ + if text is None: + s = [] + s.append(self.username) + s.append(self.password) + s.append(Token.sysdate()) + m = md5() + m.update(':'.join(s)) + self.nonce = m.hexdigest() + else: + self.nonce = text + + def setcreated(self, dt=None): + """ + Set I{created}. + @param dt: The created date & time. + Set as datetime.utc() when I{None}. + @type dt: L{datetime} + """ + if dt is None: + self.created = Token.utc() + else: + self.created = dt + + + def xml(self): + """ + Get xml representation of the object. + @return: The root node. + @rtype: L{Element} + """ + root = Element('UsernameToken', ns=wssens) + u = Element('Username', ns=wssens) + u.setText(self.username) + root.append(u) + p = Element('Password', ns=wssens) + p.setText(self.password) + root.append(p) + if self.nonce is not None: + n = Element('Nonce', ns=wssens) + n.setText(self.nonce) + root.append(n) + if self.created is not None: + n = Element('Created', ns=wsuns) + n.setText(str(UTC(self.created))) + root.append(n) + return root + + +class Timestamp(Token): + """ + Represents the I{Timestamp} WS-Secuirty token. + @ivar created: The token created. + @type created: L{datetime} + @ivar expires: The token expires. + @type expires: L{datetime} + """ + + def __init__(self, validity=90): + """ + @param validity: The time in seconds. + @type validity: int + """ + Token.__init__(self) + self.created = Token.utc() + self.expires = self.created + timedelta(seconds=validity) + + def xml(self): + root = Element("Timestamp", ns=wsuns) + created = Element('Created', ns=wsuns) + created.setText(str(UTC(self.created))) + expires = Element('Expires', ns=wsuns) + expires.setText(str(UTC(self.expires))) + root.append(created) + root.append(expires) + return root \ No newline at end of file diff --git a/awx/lib/site-packages/suds/xsd/__init__.py b/awx/lib/site-packages/suds/xsd/__init__.py new file mode 100644 index 0000000000..0917f3f3b8 --- /dev/null +++ b/awx/lib/site-packages/suds/xsd/__init__.py @@ -0,0 +1,86 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +The I{schema} module provides a intelligent representation of +an XSD schema. The I{raw} model is the XML tree and the I{model} +is the denormalized, objectified and intelligent view of the schema. +Most of the I{value-add} provided by the model is centered around +tranparent referenced type resolution and targeted denormalization. +""" + +from logging import getLogger +from suds import * +from suds.sax import Namespace, splitPrefix + +log = getLogger(__name__) + + +def qualify(ref, resolvers, defns=Namespace.default): + """ + Get a reference that is I{qualified} by namespace. + @param ref: A referenced schema type name. + @type ref: str + @param resolvers: A list of objects to be used to resolve types. + @type resolvers: [L{sax.element.Element},] + @param defns: An optional target namespace used to qualify references + when no prefix is specified. + @type defns: A default namespace I{tuple: (prefix,uri)} used when ref not prefixed. + @return: A qualified reference. + @rtype: (name, namespace-uri) + """ + ns = None + p, n = splitPrefix(ref) + if p is not None: + if not isinstance(resolvers, (list, tuple)): + resolvers = (resolvers,) + for r in resolvers: + resolved = r.resolvePrefix(p) + if resolved[1] is not None: + ns = resolved + break + if ns is None: + raise Exception('prefix (%s) not resolved' % p) + else: + ns = defns + return (n, ns[1]) + +def isqref(object): + """ + Get whether the object is a I{qualified reference}. + @param object: An object to be tested. + @type object: I{any} + @rtype: boolean + @see: L{qualify} + """ + return (\ + isinstance(object, tuple) and \ + len(object) == 2 and \ + isinstance(object[0], basestring) and \ + isinstance(object[1], basestring)) + + +class Filter: + def __init__(self, inclusive=False, *items): + self.inclusive = inclusive + self.items = items + def __contains__(self, x): + if self.inclusive: + result = ( x in self.items ) + else: + result = ( x not in self.items ) + return result + diff --git a/awx/lib/site-packages/suds/xsd/deplist.py b/awx/lib/site-packages/suds/xsd/deplist.py new file mode 100644 index 0000000000..14ae19c0e4 --- /dev/null +++ b/awx/lib/site-packages/suds/xsd/deplist.py @@ -0,0 +1,140 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +The I{depsolve} module defines a class for performing dependancy solving. +""" + +from logging import getLogger +from suds import * + +log = getLogger(__name__) + + +class DepList: + """ + Dependancy solving list. + Items are tuples: (object, (deps,)) + @ivar raw: The raw (unsorted) items. + @type raw: list + @ivar index: The index of (unsorted) items. + @type index: list + @ivar stack: The sorting stack. + @type stack: list + @ivar pushed: The I{pushed} set tracks items that have been + processed. + @type pushed: set + @ivar sorted: The sorted list of items. + @type sorted: list + """ + + def __init__(self): + """ """ + self.unsorted = [] + self.index = {} + self.stack = [] + self.pushed = set() + self.sorted = None + + def add(self, *items): + """ + Add items to be sorted. + @param items: One or more items to be added. + @type items: I{item} + @return: self + @rtype: L{DepList} + """ + for item in items: + self.unsorted.append(item) + key = item[0] + self.index[key] = item + return self + + def sort(self): + """ + Sort the list based on dependancies. + @return: The sorted items. + @rtype: list + """ + self.sorted = list() + self.pushed = set() + for item in self.unsorted: + popped = [] + self.push(item) + while len(self.stack): + try: + top = self.top() + ref = top[1].next() + refd = self.index.get(ref) + if refd is None: + log.debug('"%s" not found, skipped', Repr(ref)) + continue + self.push(refd) + except StopIteration: + popped.append(self.pop()) + continue + for p in popped: + self.sorted.append(p) + self.unsorted = self.sorted + return self.sorted + + def top(self): + """ + Get the item at the top of the stack. + @return: The top item. + @rtype: (item, iter) + """ + return self.stack[-1] + + def push(self, item): + """ + Push and item onto the sorting stack. + @param item: An item to push. + @type item: I{item} + @return: The number of items pushed. + @rtype: int + """ + if item in self.pushed: + return + frame = (item, iter(item[1])) + self.stack.append(frame) + self.pushed.add(item) + + def pop(self): + """ + Pop the top item off the stack and append + it to the sorted list. + @return: The popped item. + @rtype: I{item} + """ + try: + frame = self.stack.pop() + return frame[0] + except: + pass + + +if __name__ == '__main__': + a = ('a', ('x',)) + b = ('b', ('a',)) + c = ('c', ('a','b')) + d = ('d', ('c',)) + e = ('e', ('d','a')) + f = ('f', ('e','c','d','a')) + x = ('x', ()) + L = DepList() + L.add(c, e, d, b, f, a, x) + print [x[0] for x in L.sort()] \ No newline at end of file diff --git a/awx/lib/site-packages/suds/xsd/doctor.py b/awx/lib/site-packages/suds/xsd/doctor.py new file mode 100644 index 0000000000..d7bbc14ea7 --- /dev/null +++ b/awx/lib/site-packages/suds/xsd/doctor.py @@ -0,0 +1,226 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +The I{doctor} module provides classes for fixing broken (sick) +schema(s). +""" + +from logging import getLogger +from suds.sax import splitPrefix, Namespace +from suds.sax.element import Element +from suds.plugin import DocumentPlugin, DocumentContext + +log = getLogger(__name__) + + +class Doctor: + """ + Schema Doctor. + """ + def examine(self, root): + """ + Examine and repair the schema (if necessary). + @param root: A schema root element. + @type root: L{Element} + """ + pass + + +class Practice(Doctor): + """ + A collection of doctors. + @ivar doctors: A list of doctors. + @type doctors: list + """ + + def __init__(self): + self.doctors = [] + + def add(self, doctor): + """ + Add a doctor to the practice + @param doctor: A doctor to add. + @type doctor: L{Doctor} + """ + self.doctors.append(doctor) + + def examine(self, root): + for d in self.doctors: + d.examine(root) + return root + + +class TnsFilter: + """ + Target Namespace filter. + @ivar tns: A list of target namespaces. + @type tns: [str,...] + """ + + def __init__(self, *tns): + """ + @param tns: A list of target namespaces. + @type tns: [str,...] + """ + self.tns = [] + self.add(*tns) + + def add(self, *tns): + """ + Add I{targetNamesapces} to be added. + @param tns: A list of target namespaces. + @type tns: [str,...] + """ + self.tns += tns + + def match(self, root, ns): + """ + Match by I{targetNamespace} excluding those that + are equal to the specified namespace to prevent + adding an import to itself. + @param root: A schema root. + @type root: L{Element} + """ + tns = root.get('targetNamespace') + if len(self.tns): + matched = ( tns in self.tns ) + else: + matched = 1 + itself = ( ns == tns ) + return ( matched and not itself ) + + +class Import: + """ + An to be applied. + @cvar xsdns: The XSD namespace. + @type xsdns: (p,u) + @ivar ns: An import namespace. + @type ns: str + @ivar location: An optional I{schemaLocation}. + @type location: str + @ivar filter: A filter used to restrict application to + a particular schema. + @type filter: L{TnsFilter} + """ + + xsdns = Namespace.xsdns + + def __init__(self, ns, location=None): + """ + @param ns: An import namespace. + @type ns: str + @param location: An optional I{schemaLocation}. + @type location: str + """ + self.ns = ns + self.location = location + self.filter = TnsFilter() + + def setfilter(self, filter): + """ + Set the filter. + @param filter: A filter to set. + @type filter: L{TnsFilter} + """ + self.filter = filter + + def apply(self, root): + """ + Apply the import (rule) to the specified schema. + If the schema does not already contain an import for the + I{namespace} specified here, it is added. + @param root: A schema root. + @type root: L{Element} + """ + if not self.filter.match(root, self.ns): + return + if self.exists(root): + return + node = Element('import', ns=self.xsdns) + node.set('namespace', self.ns) + if self.location is not None: + node.set('schemaLocation', self.location) + log.debug('inserting: %s', node) + root.insert(node) + + def add(self, root): + """ + Add an to the specified schema root. + @param root: A schema root. + @type root: L{Element} + """ + node = Element('import', ns=self.xsdns) + node.set('namespace', self.ns) + if self.location is not None: + node.set('schemaLocation', self.location) + log.debug('%s inserted', node) + root.insert(node) + + def exists(self, root): + """ + Check to see if the already exists + in the specified schema root by matching I{namesapce}. + @param root: A schema root. + @type root: L{Element} + """ + for node in root.children: + if node.name != 'import': + continue + ns = node.get('namespace') + if self.ns == ns: + return 1 + return 0 + + +class ImportDoctor(Doctor, DocumentPlugin): + """ + Doctor used to fix missing imports. + @ivar imports: A list of imports to apply. + @type imports: [L{Import},...] + """ + + def __init__(self, *imports): + """ + """ + self.imports = [] + self.add(*imports) + + def add(self, *imports): + """ + Add a namesapce to be checked. + @param imports: A list of L{Import} objects. + @type imports: [L{Import},..] + """ + self.imports += imports + + def examine(self, node): + for imp in self.imports: + imp.apply(node) + + def parsed(self, context): + node = context.document + # xsd root + if node.name == 'schema' and Namespace.xsd(node.namespace()): + self.examine(node) + return + # look deeper + context = DocumentContext() + for child in node: + context.document = child + self.parsed(context) + \ No newline at end of file diff --git a/awx/lib/site-packages/suds/xsd/query.py b/awx/lib/site-packages/suds/xsd/query.py new file mode 100644 index 0000000000..c88b2202ab --- /dev/null +++ b/awx/lib/site-packages/suds/xsd/query.py @@ -0,0 +1,208 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +The I{query} module defines a class for performing schema queries. +""" + +from logging import getLogger +from suds import * +from suds.sudsobject import * +from suds.xsd import qualify, isqref +from suds.xsd.sxbuiltin import Factory + +log = getLogger(__name__) + + +class Query(Object): + """ + Schema query base class. + """ + + def __init__(self, ref=None): + """ + @param ref: The schema reference being queried. + @type ref: qref + """ + Object.__init__(self) + self.id = objid(self) + self.ref = ref + self.history = [] + self.resolved = False + if not isqref(self.ref): + raise Exception('%s, must be qref' % tostr(self.ref)) + + def execute(self, schema): + """ + Execute this query using the specified schema. + @param schema: The schema associated with the query. The schema + is used by the query to search for items. + @type schema: L{schema.Schema} + @return: The item matching the search criteria. + @rtype: L{sxbase.SchemaObject} + """ + raise Exception, 'not-implemented by subclass' + + def filter(self, result): + """ + Filter the specified result based on query criteria. + @param result: A potential result. + @type result: L{sxbase.SchemaObject} + @return: True if result should be excluded. + @rtype: boolean + """ + if result is None: + return True + reject = ( result in self.history ) + if reject: + log.debug('result %s, rejected by\n%s', Repr(result), self) + return reject + + def result(self, result): + """ + Query result post processing. + @param result: A query result. + @type result: L{sxbase.SchemaObject} + """ + if result is None: + log.debug('%s, not-found', self.ref) + return + if self.resolved: + result = result.resolve() + log.debug('%s, found as: %s', self.ref, Repr(result)) + self.history.append(result) + return result + + +class BlindQuery(Query): + """ + Schema query class that I{blindly} searches for a reference in + the specified schema. It may be used to find Elements and Types but + will match on an Element first. This query will also find builtins. + """ + + def execute(self, schema): + if schema.builtin(self.ref): + name = self.ref[0] + b = Factory.create(schema, name) + log.debug('%s, found builtin (%s)', self.id, name) + return b + result = None + for d in (schema.elements, schema.types): + result = d.get(self.ref) + if self.filter(result): + result = None + else: + break + if result is None: + eq = ElementQuery(self.ref) + eq.history = self.history + result = eq.execute(schema) + return self.result(result) + + +class TypeQuery(Query): + """ + Schema query class that searches for Type references in + the specified schema. Matches on root types only. + """ + + def execute(self, schema): + if schema.builtin(self.ref): + name = self.ref[0] + b = Factory.create(schema, name) + log.debug('%s, found builtin (%s)', self.id, name) + return b + result = schema.types.get(self.ref) + if self.filter(result): + result = None + return self.result(result) + + +class GroupQuery(Query): + """ + Schema query class that searches for Group references in + the specified schema. + """ + + def execute(self, schema): + result = schema.groups.get(self.ref) + if self.filter(result): + result = None + return self.result(result) + + +class AttrQuery(Query): + """ + Schema query class that searches for Attribute references in + the specified schema. Matches on root Attribute by qname first, then searches + deep into the document. + """ + + def execute(self, schema): + result = schema.attributes.get(self.ref) + if self.filter(result): + result = self.__deepsearch(schema) + return self.result(result) + + def __deepsearch(self, schema): + from suds.xsd.sxbasic import Attribute + result = None + for e in schema.all: + result = e.find(self.ref, (Attribute,)) + if self.filter(result): + result = None + else: + break + return result + + +class AttrGroupQuery(Query): + """ + Schema query class that searches for attributeGroup references in + the specified schema. + """ + + def execute(self, schema): + result = schema.agrps.get(self.ref) + if self.filter(result): + result = None + return self.result(result) + + +class ElementQuery(Query): + """ + Schema query class that searches for Element references in + the specified schema. Matches on root Elements by qname first, then searches + deep into the document. + """ + + def execute(self, schema): + result = schema.elements.get(self.ref) + if self.filter(result): + result = self.__deepsearch(schema) + return self.result(result) + + def __deepsearch(self, schema): + from suds.xsd.sxbasic import Element + result = None + for e in schema.all: + result = e.find(self.ref, (Element,)) + if self.filter(result): + result = None + else: + break + return result \ No newline at end of file diff --git a/awx/lib/site-packages/suds/xsd/schema.py b/awx/lib/site-packages/suds/xsd/schema.py new file mode 100644 index 0000000000..cb7d678b46 --- /dev/null +++ b/awx/lib/site-packages/suds/xsd/schema.py @@ -0,0 +1,422 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +The I{schema} module provides a intelligent representation of +an XSD schema. The I{raw} model is the XML tree and the I{model} +is the denormalized, objectified and intelligent view of the schema. +Most of the I{value-add} provided by the model is centered around +tranparent referenced type resolution and targeted denormalization. +""" + + +import suds.metrics +from suds import * +from suds.xsd import * +from suds.xsd.sxbuiltin import * +from suds.xsd.sxbasic import Factory as BasicFactory +from suds.xsd.sxbuiltin import Factory as BuiltinFactory +from suds.xsd.sxbase import SchemaObject +from suds.xsd.deplist import DepList +from suds.sax.element import Element +from suds.sax import splitPrefix, Namespace +from logging import getLogger + +log = getLogger(__name__) + + +class SchemaCollection: + """ + A collection of schema objects. This class is needed because WSDLs + may contain more then one node. + @ivar wsdl: A wsdl object. + @type wsdl: L{suds.wsdl.Definitions} + @ivar children: A list contained schemas. + @type children: [L{Schema},...] + @ivar namespaces: A dictionary of contained schemas by namespace. + @type namespaces: {str:L{Schema}} + """ + + def __init__(self, wsdl): + """ + @param wsdl: A wsdl object. + @type wsdl: L{suds.wsdl.Definitions} + """ + self.wsdl = wsdl + self.children = [] + self.namespaces = {} + + def add(self, schema): + """ + Add a schema node to the collection. Schema(s) within the same target + namespace are consolidated. + @param schema: A schema object. + @type schema: (L{Schema}) + """ + key = schema.tns[1] + existing = self.namespaces.get(key) + if existing is None: + self.children.append(schema) + self.namespaces[key] = schema + else: + existing.root.children += schema.root.children + existing.root.nsprefixes.update(schema.root.nsprefixes) + + def load(self, options): + """ + Load the schema objects for the root nodes. + - de-references schemas + - merge schemas + @param options: An options dictionary. + @type options: L{options.Options} + @return: The merged schema. + @rtype: L{Schema} + """ + if options.autoblend: + self.autoblend() + for child in self.children: + child.build() + for child in self.children: + child.open_imports(options) + for child in self.children: + child.dereference() + log.debug('loaded:\n%s', self) + merged = self.merge() + log.debug('MERGED:\n%s', merged) + return merged + + def autoblend(self): + """ + Ensure that all schemas within the collection + import each other which has a blending effect. + @return: self + @rtype: L{SchemaCollection} + """ + namespaces = self.namespaces.keys() + for s in self.children: + for ns in namespaces: + tns = s.root.get('targetNamespace') + if tns == ns: + continue + for imp in s.root.getChildren('import'): + if imp.get('namespace') == ns: + continue + imp = Element('import', ns=Namespace.xsdns) + imp.set('namespace', ns) + s.root.append(imp) + return self + + def locate(self, ns): + """ + Find a schema by namespace. Only the URI portion of + the namespace is compared to each schema's I{targetNamespace} + @param ns: A namespace. + @type ns: (prefix,URI) + @return: The schema matching the namesapce, else None. + @rtype: L{Schema} + """ + return self.namespaces.get(ns[1]) + + def merge(self): + """ + Merge the contained schemas into one. + @return: The merged schema. + @rtype: L{Schema} + """ + if len(self): + schema = self.children[0] + for s in self.children[1:]: + schema.merge(s) + return schema + else: + return None + + def __len__(self): + return len(self.children) + + def __str__(self): + return unicode(self).encode('utf-8') + + def __unicode__(self): + result = ['\nschema collection'] + for s in self.children: + result.append(s.str(1)) + return '\n'.join(result) + + +class Schema: + """ + The schema is an objectification of a (xsd) definition. + It provides inspection, lookup and type resolution. + @ivar root: The root node. + @type root: L{sax.element.Element} + @ivar baseurl: The I{base} URL for this schema. + @type baseurl: str + @ivar container: A schema collection containing this schema. + @type container: L{SchemaCollection} + @ivar children: A list of direct top level children. + @type children: [L{SchemaObject},...] + @ivar all: A list of all (includes imported) top level children. + @type all: [L{SchemaObject},...] + @ivar types: A schema types cache. + @type types: {name:L{SchemaObject}} + @ivar imports: A list of import objects. + @type imports: [L{SchemaObject},...] + @ivar elements: A list of objects. + @type elements: [L{SchemaObject},...] + @ivar attributes: A list of objects. + @type attributes: [L{SchemaObject},...] + @ivar groups: A list of group objects. + @type groups: [L{SchemaObject},...] + @ivar agrps: A list of attribute group objects. + @type agrps: [L{SchemaObject},...] + @ivar form_qualified: The flag indicating: + (@elementFormDefault). + @type form_qualified: bool + """ + + Tag = 'schema' + + def __init__(self, root, baseurl, options, container=None): + """ + @param root: The xml root. + @type root: L{sax.element.Element} + @param baseurl: The base url used for importing. + @type baseurl: basestring + @param options: An options dictionary. + @type options: L{options.Options} + @param container: An optional container. + @type container: L{SchemaCollection} + """ + self.root = root + self.id = objid(self) + self.tns = self.mktns() + self.baseurl = baseurl + self.container = container + self.children = [] + self.all = [] + self.types = {} + self.imports = [] + self.elements = {} + self.attributes = {} + self.groups = {} + self.agrps = {} + if options.doctor is not None: + options.doctor.examine(root) + form = self.root.get('elementFormDefault') + if form is None: + self.form_qualified = False + else: + self.form_qualified = ( form == 'qualified' ) + if container is None: + self.build() + self.open_imports(options) + log.debug('built:\n%s', self) + self.dereference() + log.debug('dereferenced:\n%s', self) + + def mktns(self): + """ + Make the schema's target namespace. + @return: The namespace representation of the schema's + targetNamespace value. + @rtype: (prefix, uri) + """ + tns = [None, self.root.get('targetNamespace')] + if tns[1] is not None: + tns[0] = self.root.findPrefix(tns[1]) + return tuple(tns) + + def build(self): + """ + Build the schema (object graph) using the root node + using the factory. + - Build the graph. + - Collate the children. + """ + self.children = BasicFactory.build(self.root, self) + collated = BasicFactory.collate(self.children) + self.children = collated[0] + self.attributes = collated[2] + self.imports = collated[1] + self.elements = collated[3] + self.types = collated[4] + self.groups = collated[5] + self.agrps = collated[6] + + def merge(self, schema): + """ + Merge the contents from the schema. Only objects not already contained + in this schema's collections are merged. This is to provide for bidirectional + import which produce cyclic includes. + @returns: self + @rtype: L{Schema} + """ + for item in schema.attributes.items(): + if item[0] in self.attributes: + continue + self.all.append(item[1]) + self.attributes[item[0]] = item[1] + for item in schema.elements.items(): + if item[0] in self.elements: + continue + self.all.append(item[1]) + self.elements[item[0]] = item[1] + for item in schema.types.items(): + if item[0] in self.types: + continue + self.all.append(item[1]) + self.types[item[0]] = item[1] + for item in schema.groups.items(): + if item[0] in self.groups: + continue + self.all.append(item[1]) + self.groups[item[0]] = item[1] + for item in schema.agrps.items(): + if item[0] in self.agrps: + continue + self.all.append(item[1]) + self.agrps[item[0]] = item[1] + schema.merged = True + return self + + def open_imports(self, options): + """ + Instruct all contained L{sxbasic.Import} children to import + the schema's which they reference. The contents of the + imported schema are I{merged} in. + @param options: An options dictionary. + @type options: L{options.Options} + """ + for imp in self.imports: + imported = imp.open(options) + if imported is None: + continue + imported.open_imports(options) + log.debug('imported:\n%s', imported) + self.merge(imported) + + def dereference(self): + """ + Instruct all children to perform dereferencing. + """ + all = [] + indexes = {} + for child in self.children: + child.content(all) + deplist = DepList() + for x in all: + x.qualify() + midx, deps = x.dependencies() + item = (x, tuple(deps)) + deplist.add(item) + indexes[x] = midx + for x, deps in deplist.sort(): + midx = indexes.get(x) + if midx is None: continue + d = deps[midx] + log.debug('(%s) merging %s <== %s', self.tns[1], Repr(x), Repr(d)) + x.merge(d) + + def locate(self, ns): + """ + Find a schema by namespace. Only the URI portion of + the namespace is compared to each schema's I{targetNamespace}. + The request is passed to the container. + @param ns: A namespace. + @type ns: (prefix,URI) + @return: The schema matching the namesapce, else None. + @rtype: L{Schema} + """ + if self.container is not None: + return self.container.locate(ns) + else: + return None + + def custom(self, ref, context=None): + """ + Get whether the specified reference is B{not} an (xs) builtin. + @param ref: A str or qref. + @type ref: (str|qref) + @return: True if B{not} a builtin, else False. + @rtype: bool + """ + if ref is None: + return True + else: + return ( not self.builtin(ref, context) ) + + def builtin(self, ref, context=None): + """ + Get whether the specified reference is an (xs) builtin. + @param ref: A str or qref. + @type ref: (str|qref) + @return: True if builtin, else False. + @rtype: bool + """ + w3 = 'http://www.w3.org' + try: + if isqref(ref): + ns = ref[1] + return ( ref[0] in Factory.tags and ns.startswith(w3) ) + if context is None: + context = self.root + prefix = splitPrefix(ref)[0] + prefixes = context.findPrefixes(w3, 'startswith') + return ( prefix in prefixes and ref[0] in Factory.tags ) + except: + return False + + def instance(self, root, baseurl, options): + """ + Create and return an new schema object using the + specified I{root} and I{url}. + @param root: A schema root node. + @type root: L{sax.element.Element} + @param baseurl: A base URL. + @type baseurl: str + @param options: An options dictionary. + @type options: L{options.Options} + @return: The newly created schema object. + @rtype: L{Schema} + @note: This is only used by Import children. + """ + return Schema(root, baseurl, options) + + def str(self, indent=0): + tab = '%*s'%(indent*3, '') + result = [] + result.append('%s%s' % (tab, self.id)) + result.append('%s(raw)' % tab) + result.append(self.root.str(indent+1)) + result.append('%s(model)' % tab) + for c in self.children: + result.append(c.str(indent+1)) + result.append('') + return '\n'.join(result) + + def __repr__(self): + myrep = '<%s tns="%s"/>' % (self.id, self.tns[1]) + return myrep.encode('utf-8') + + def __str__(self): + return unicode(self).encode('utf-8') + + def __unicode__(self): + return self.str() + + + diff --git a/awx/lib/site-packages/suds/xsd/sxbase.py b/awx/lib/site-packages/suds/xsd/sxbase.py new file mode 100644 index 0000000000..2577ffd516 --- /dev/null +++ b/awx/lib/site-packages/suds/xsd/sxbase.py @@ -0,0 +1,669 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +The I{sxbase} module provides I{base} classes that represent +schema objects. +""" + +from logging import getLogger +from suds import * +from suds.xsd import * +from suds.sax.element import Element +from suds.sax import Namespace + +log = getLogger(__name__) + + +class SchemaObject(object): + """ + A schema object is an extension to object object with + with schema awareness. + @ivar root: The XML root element. + @type root: L{Element} + @ivar schema: The schema containing this object. + @type schema: L{schema.Schema} + @ivar form_qualified: A flag that inidcates that @elementFormDefault + has a value of I{qualified}. + @type form_qualified: boolean + @ivar nillable: A flag that inidcates that @nillable + has a value of I{true}. + @type nillable: boolean + @ivar default: The default value. + @type default: object + @ivar rawchildren: A list raw of all children. + @type rawchildren: [L{SchemaObject},...] + """ + + @classmethod + def prepend(cls, d, s, filter=Filter()): + """ + Prepend schema object's from B{s}ource list to + the B{d}estination list while applying the filter. + @param d: The destination list. + @type d: list + @param s: The source list. + @type s: list + @param filter: A filter that allows items to be prepended. + @type filter: L{Filter} + """ + i = 0 + for x in s: + if x in filter: + d.insert(i, x) + i += 1 + + @classmethod + def append(cls, d, s, filter=Filter()): + """ + Append schema object's from B{s}ource list to + the B{d}estination list while applying the filter. + @param d: The destination list. + @type d: list + @param s: The source list. + @type s: list + @param filter: A filter that allows items to be appended. + @type filter: L{Filter} + """ + for item in s: + if item in filter: + d.append(item) + + def __init__(self, schema, root): + """ + @param schema: The containing schema. + @type schema: L{schema.Schema} + @param root: The xml root node. + @type root: L{Element} + """ + self.schema = schema + self.root = root + self.id = objid(self) + self.name = root.get('name') + self.qname = (self.name, schema.tns[1]) + self.min = root.get('minOccurs') + self.max = root.get('maxOccurs') + self.type = root.get('type') + self.ref = root.get('ref') + self.form_qualified = schema.form_qualified + self.nillable = False + self.default = root.get('default') + self.rawchildren = [] + self.cache = {} + + def attributes(self, filter=Filter()): + """ + Get only the attribute content. + @param filter: A filter to constrain the result. + @type filter: L{Filter} + @return: A list of tuples (attr, ancestry) + @rtype: [(L{SchemaObject}, [L{SchemaObject},..]),..] + """ + result = [] + for child, ancestry in self: + if child.isattr() and child in filter: + result.append((child, ancestry)) + return result + + def children(self, filter=Filter()): + """ + Get only the I{direct} or non-attribute content. + @param filter: A filter to constrain the result. + @type filter: L{Filter} + @return: A list tuples: (child, ancestry) + @rtype: [(L{SchemaObject}, [L{SchemaObject},..]),..] + """ + result = [] + for child, ancestry in self: + if not child.isattr() and child in filter: + result.append((child, ancestry)) + return result + + def get_attribute(self, name): + """ + Get (find) a I{non-attribute} attribute by name. + @param name: A attribute name. + @type name: str + @return: A tuple: the requested (attribute, ancestry). + @rtype: (L{SchemaObject}, [L{SchemaObject},..]) + """ + for child, ancestry in self.attributes(): + if child.name == name: + return (child, ancestry) + return (None, []) + + def get_child(self, name): + """ + Get (find) a I{non-attribute} child by name. + @param name: A child name. + @type name: str + @return: A tuple: the requested (child, ancestry). + @rtype: (L{SchemaObject}, [L{SchemaObject},..]) + """ + for child, ancestry in self.children(): + if child.any() or child.name == name: + return (child, ancestry) + return (None, []) + + def namespace(self, prefix=None): + """ + Get this properties namespace + @param prefix: The default prefix. + @type prefix: str + @return: The schema's target namespace + @rtype: (I{prefix},I{URI}) + """ + ns = self.schema.tns + if ns[0] is None: + ns = (prefix, ns[1]) + return ns + + def default_namespace(self): + return self.root.defaultNamespace() + + def unbounded(self): + """ + Get whether this node is unbounded I{(a collection)} + @return: True if unbounded, else False. + @rtype: boolean + """ + max = self.max + if max is None: + max = '1' + if max.isdigit(): + return (int(max) > 1) + else: + return ( max == 'unbounded' ) + + def optional(self): + """ + Get whether this type is optional. + @return: True if optional, else False + @rtype: boolean + """ + min = self.min + if min is None: + min = '1' + return ( min == '0' ) + + def required(self): + """ + Get whether this type is required. + @return: True if required, else False + @rtype: boolean + """ + return ( not self.optional() ) + + + def resolve(self, nobuiltin=False): + """ + Resolve and return the nodes true self. + @param nobuiltin: Flag indicates that resolution must + not continue to include xsd builtins. + @return: The resolved (true) type. + @rtype: L{SchemaObject} + """ + return self.cache.get(nobuiltin, self) + + def sequence(self): + """ + Get whether this is an + @return: True if , else False + @rtype: boolean + """ + return False + + def xslist(self): + """ + Get whether this is an + @return: True if any, else False + @rtype: boolean + """ + return False + + def all(self): + """ + Get whether this is an + @return: True if any, else False + @rtype: boolean + """ + return False + + def choice(self): + """ + Get whether this is n + @return: True if any, else False + @rtype: boolean + """ + return False + + def any(self): + """ + Get whether this is an + @return: True if any, else False + @rtype: boolean + """ + return False + + def builtin(self): + """ + Get whether this is a schema-instance (xs) type. + @return: True if any, else False + @rtype: boolean + """ + return False + + def enum(self): + """ + Get whether this is a simple-type containing an enumeration. + @return: True if any, else False + @rtype: boolean + """ + return False + + def isattr(self): + """ + Get whether the object is a schema I{attribute} definition. + @return: True if an attribute, else False. + @rtype: boolean + """ + return False + + def extension(self): + """ + Get whether the object is an extension of another type. + @return: True if an extension, else False. + @rtype: boolean + """ + return False + + def restriction(self): + """ + Get whether the object is an restriction of another type. + @return: True if an restriction, else False. + @rtype: boolean + """ + return False + + def mixed(self): + """ + Get whether this I{mixed} content. + """ + return False + + def find(self, qref, classes=()): + """ + Find a referenced type in self or children. + @param qref: A qualified reference. + @type qref: qref + @param classes: A list of classes used to qualify the match. + @type classes: [I{class},...] + @return: The referenced type. + @rtype: L{SchemaObject} + @see: L{qualify()} + """ + if not len(classes): + classes = (self.__class__,) + if self.qname == qref and self.__class__ in classes: + return self + for c in self.rawchildren: + p = c.find(qref, classes) + if p is not None: + return p + return None + + def translate(self, value, topython=True): + """ + Translate a value (type) to/from a python type. + @param value: A value to translate. + @return: The converted I{language} type. + """ + return value + + def childtags(self): + """ + Get a list of valid child tag names. + @return: A list of child tag names. + @rtype: [str,...] + """ + return () + + def dependencies(self): + """ + Get a list of dependancies for dereferencing. + @return: A merge dependancy index and a list of dependancies. + @rtype: (int, [L{SchemaObject},...]) + """ + return (None, []) + + def autoqualified(self): + """ + The list of I{auto} qualified attribute values. + Qualification means to convert values into I{qref}. + @return: A list of attibute names. + @rtype: list + """ + return ['type', 'ref'] + + def qualify(self): + """ + Convert attribute values, that are references to other + objects, into I{qref}. Qualfied using default document namespace. + Since many wsdls are written improperly: when the document does + not define a default namespace, the schema target namespace is used + to qualify references. + """ + defns = self.root.defaultNamespace() + if Namespace.none(defns): + defns = self.schema.tns + for a in self.autoqualified(): + ref = getattr(self, a) + if ref is None: + continue + if isqref(ref): + continue + qref = qualify(ref, self.root, defns) + log.debug('%s, convert %s="%s" to %s', self.id, a, ref, qref) + setattr(self, a, qref) + + def merge(self, other): + """ + Merge another object as needed. + """ + other.qualify() + for n in ('name', + 'qname', + 'min', + 'max', + 'default', + 'type', + 'nillable', + 'form_qualified',): + if getattr(self, n) is not None: + continue + v = getattr(other, n) + if v is None: + continue + setattr(self, n, v) + + + def content(self, collection=None, filter=Filter(), history=None): + """ + Get a I{flattened} list of this nodes contents. + @param collection: A list to fill. + @type collection: list + @param filter: A filter used to constrain the result. + @type filter: L{Filter} + @param history: The history list used to prevent cyclic dependency. + @type history: list + @return: The filled list. + @rtype: list + """ + if collection is None: + collection = [] + if history is None: + history = [] + if self in history: + return collection + history.append(self) + if self in filter: + collection.append(self) + for c in self.rawchildren: + c.content(collection, filter, history[:]) + return collection + + def str(self, indent=0, history=None): + """ + Get a string representation of this object. + @param indent: The indent. + @type indent: int + @return: A string. + @rtype: str + """ + if history is None: + history = [] + if self in history: + return '%s ...' % Repr(self) + history.append(self) + tab = '%*s'%(indent*3, '') + result = [] + result.append('%s<%s' % (tab, self.id)) + for n in self.description(): + if not hasattr(self, n): + continue + v = getattr(self, n) + if v is None: + continue + result.append(' %s="%s"' % (n, v)) + if len(self): + result.append('>') + for c in self.rawchildren: + result.append('\n') + result.append(c.str(indent+1, history[:])) + if c.isattr(): + result.append('@') + result.append('\n%s' % tab) + result.append('' % self.__class__.__name__) + else: + result.append(' />') + return ''.join(result) + + def description(self): + """ + Get the names used for str() and repr() description. + @return: A dictionary of relavent attributes. + @rtype: [str,...] + """ + return () + + def __str__(self): + return unicode(self).encode('utf-8') + + def __unicode__(self): + return unicode(self.str()) + + def __repr__(self): + s = [] + s.append('<%s' % self.id) + for n in self.description(): + if not hasattr(self, n): + continue + v = getattr(self, n) + if v is None: + continue + s.append(' %s="%s"' % (n, v)) + s.append(' />') + myrep = ''.join(s) + return myrep.encode('utf-8') + + def __len__(self): + n = 0 + for x in self: n += 1 + return n + + def __iter__(self): + return Iter(self) + + def __getitem__(self, index): + i = 0 + for c in self: + if i == index: + return c + + +class Iter: + """ + The content iterator - used to iterate the L{Content} children. The iterator + provides a I{view} of the children that is free of container elements + such as and . + @ivar stack: A stack used to control nesting. + @type stack: list + """ + + class Frame: + """ A content iterator frame. """ + + def __init__(self, sx): + """ + @param sx: A schema object. + @type sx: L{SchemaObject} + """ + self.sx = sx + self.items = sx.rawchildren + self.index = 0 + + def next(self): + """ + Get the I{next} item in the frame's collection. + @return: The next item or None + @rtype: L{SchemaObject} + """ + if self.index < len(self.items): + result = self.items[self.index] + self.index += 1 + return result + + def __init__(self, sx): + """ + @param sx: A schema object. + @type sx: L{SchemaObject} + """ + self.stack = [] + self.push(sx) + + def push(self, sx): + """ + Create a frame and push the specified object. + @param sx: A schema object to push. + @type sx: L{SchemaObject} + """ + self.stack.append(Iter.Frame(sx)) + + def pop(self): + """ + Pop the I{top} frame. + @return: The popped frame. + @rtype: L{Frame} + @raise StopIteration: when stack is empty. + """ + if len(self.stack): + return self.stack.pop() + else: + raise StopIteration() + + def top(self): + """ + Get the I{top} frame. + @return: The top frame. + @rtype: L{Frame} + @raise StopIteration: when stack is empty. + """ + if len(self.stack): + return self.stack[-1] + else: + raise StopIteration() + + def next(self): + """ + Get the next item. + @return: A tuple: the next (child, ancestry). + @rtype: (L{SchemaObject}, [L{SchemaObject},..]) + @raise StopIteration: A the end. + """ + frame = self.top() + while True: + result = frame.next() + if result is None: + self.pop() + return self.next() + if isinstance(result, Content): + ancestry = [f.sx for f in self.stack] + return (result, ancestry) + self.push(result) + return self.next() + + def __iter__(self): + return self + + +class XBuiltin(SchemaObject): + """ + Represents an (xsd) schema node + """ + + def __init__(self, schema, name): + """ + @param schema: The containing schema. + @type schema: L{schema.Schema} + """ + root = Element(name) + SchemaObject.__init__(self, schema, root) + self.name = name + self.nillable = True + + def namespace(self, prefix=None): + return Namespace.xsdns + + def builtin(self): + return True + + def resolve(self, nobuiltin=False): + return self + + +class Content(SchemaObject): + """ + This class represents those schema objects that represent + real XML document content. + """ + pass + + +class NodeFinder: + """ + Find nodes based on flexable criteria. The I{matcher} is + may be any object that implements a match(n) method. + @ivar matcher: An object used as criteria for match. + @type matcher: I{any}.match(n) + @ivar limit: Limit the number of matches. 0=unlimited. + @type limit: int + """ + def __init__(self, matcher, limit=0): + """ + @param matcher: An object used as criteria for match. + @type matcher: I{any}.match(n) + @param limit: Limit the number of matches. 0=unlimited. + @type limit: int + """ + self.matcher = matcher + self.limit = limit + + def find(self, node, list): + """ + Traverse the tree looking for matches. + @param node: A node to match on. + @type node: L{SchemaObject} + @param list: A list to fill. + @type list: list + """ + if self.matcher.match(node): + list.append(node) + self.limit -= 1 + if self.limit == 0: + return + for c in node.rawchildren: + self.find(c, list) + return self \ No newline at end of file diff --git a/awx/lib/site-packages/suds/xsd/sxbasic.py b/awx/lib/site-packages/suds/xsd/sxbasic.py new file mode 100644 index 0000000000..2506e04147 --- /dev/null +++ b/awx/lib/site-packages/suds/xsd/sxbasic.py @@ -0,0 +1,825 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +The I{sxbasic} module provides classes that represent +I{basic} schema objects. +""" + +from logging import getLogger +from suds import * +from suds.xsd import * +from suds.xsd.sxbase import * +from suds.xsd.query import * +from suds.sax import splitPrefix, Namespace +from suds.transport import TransportError +from suds.reader import DocumentReader +from urlparse import urljoin + + +log = getLogger(__name__) + + +class RestrictionMatcher: + """ + For use with L{NodeFinder} to match restriction. + """ + def match(self, n): + return isinstance(n, Restriction) + + +class TypedContent(Content): + """ + Represents any I{typed} content. + """ + def resolve(self, nobuiltin=False): + qref = self.qref() + if qref is None: + return self + key = 'resolved:nb=%s' % nobuiltin + cached = self.cache.get(key) + if cached is not None: + return cached + result = self + query = TypeQuery(qref) + query.history = [self] + log.debug('%s, resolving: %s\n using:%s', self.id, qref, query) + resolved = query.execute(self.schema) + if resolved is None: + log.debug(self.schema) + raise TypeNotFound(qref) + self.cache[key] = resolved + if resolved.builtin(): + if nobuiltin: + result = self + else: + result = resolved + else: + result = resolved.resolve(nobuiltin) + return result + + def qref(self): + """ + Get the I{type} qualified reference to the referenced xsd type. + This method takes into account simple types defined through + restriction with are detected by determining that self is simple + (len=0) and by finding a restriction child. + @return: The I{type} qualified reference. + @rtype: qref + """ + qref = self.type + if qref is None and len(self) == 0: + ls = [] + m = RestrictionMatcher() + finder = NodeFinder(m, 1) + finder.find(self, ls) + if len(ls): + return ls[0].ref + return qref + + +class Complex(SchemaObject): + """ + Represents an (xsd) schema node. + @cvar childtags: A list of valid child node names + @type childtags: (I{str},...) + """ + + def childtags(self): + return ( + 'attribute', + 'attributeGroup', + 'sequence', + 'all', + 'choice', + 'complexContent', + 'simpleContent', + 'any', + 'group') + + def description(self): + return ('name',) + + def extension(self): + for c in self.rawchildren: + if c.extension(): + return True + return False + + def mixed(self): + for c in self.rawchildren: + if isinstance(c, SimpleContent) and c.mixed(): + return True + return False + + +class Group(SchemaObject): + """ + Represents an (xsd) schema node. + @cvar childtags: A list of valid child node names + @type childtags: (I{str},...) + """ + + def childtags(self): + return ('sequence', 'all', 'choice') + + def dependencies(self): + deps = [] + midx = None + if self.ref is not None: + query = GroupQuery(self.ref) + g = query.execute(self.schema) + if g is None: + log.debug(self.schema) + raise TypeNotFound(self.ref) + deps.append(g) + midx = 0 + return (midx, deps) + + def merge(self, other): + SchemaObject.merge(self, other) + self.rawchildren = other.rawchildren + + def description(self): + return ('name', 'ref',) + + +class AttributeGroup(SchemaObject): + """ + Represents an (xsd) schema node. + @cvar childtags: A list of valid child node names + @type childtags: (I{str},...) + """ + + def childtags(self): + return ('attribute', 'attributeGroup') + + def dependencies(self): + deps = [] + midx = None + if self.ref is not None: + query = AttrGroupQuery(self.ref) + ag = query.execute(self.schema) + if ag is None: + log.debug(self.schema) + raise TypeNotFound(self.ref) + deps.append(ag) + midx = 0 + return (midx, deps) + + def merge(self, other): + SchemaObject.merge(self, other) + self.rawchildren = other.rawchildren + + def description(self): + return ('name', 'ref',) + + +class Simple(SchemaObject): + """ + Represents an (xsd) schema node + """ + + def childtags(self): + return ('restriction', 'any', 'list',) + + def enum(self): + for child, ancestry in self.children(): + if isinstance(child, Enumeration): + return True + return False + + def mixed(self): + return len(self) + + def description(self): + return ('name',) + + def extension(self): + for c in self.rawchildren: + if c.extension(): + return True + return False + + def restriction(self): + for c in self.rawchildren: + if c.restriction(): + return True + return False + + +class List(SchemaObject): + """ + Represents an (xsd) schema node + """ + + def childtags(self): + return () + + def description(self): + return ('name',) + + def xslist(self): + return True + + +class Restriction(SchemaObject): + """ + Represents an (xsd) schema node + """ + + def __init__(self, schema, root): + SchemaObject.__init__(self, schema, root) + self.ref = root.get('base') + + def childtags(self): + return ('enumeration', 'attribute', 'attributeGroup') + + def dependencies(self): + deps = [] + midx = None + if self.ref is not None: + query = TypeQuery(self.ref) + super = query.execute(self.schema) + if super is None: + log.debug(self.schema) + raise TypeNotFound(self.ref) + if not super.builtin(): + deps.append(super) + midx = 0 + return (midx, deps) + + def restriction(self): + return True + + def merge(self, other): + SchemaObject.merge(self, other) + filter = Filter(False, self.rawchildren) + self.prepend(self.rawchildren, other.rawchildren, filter) + + def description(self): + return ('ref',) + + +class Collection(SchemaObject): + """ + Represents an (xsd) schema collection node: + - sequence + - choice + - all + """ + + def childtags(self): + return ('element', 'sequence', 'all', 'choice', 'any', 'group') + + +class Sequence(Collection): + """ + Represents an (xsd) schema node. + """ + def sequence(self): + return True + + +class All(Collection): + """ + Represents an (xsd) schema node. + """ + def all(self): + return True + +class Choice(Collection): + """ + Represents an (xsd) schema node. + """ + def choice(self): + return True + + +class ComplexContent(SchemaObject): + """ + Represents an (xsd) schema node. + """ + + def childtags(self): + return ('attribute', 'attributeGroup', 'extension', 'restriction') + + def extension(self): + for c in self.rawchildren: + if c.extension(): + return True + return False + + def restriction(self): + for c in self.rawchildren: + if c.restriction(): + return True + return False + + +class SimpleContent(SchemaObject): + """ + Represents an (xsd) schema node. + """ + + def childtags(self): + return ('extension', 'restriction') + + def extension(self): + for c in self.rawchildren: + if c.extension(): + return True + return False + + def restriction(self): + for c in self.rawchildren: + if c.restriction(): + return True + return False + + def mixed(self): + return len(self) + + +class Enumeration(Content): + """ + Represents an (xsd) schema node + """ + + def __init__(self, schema, root): + Content.__init__(self, schema, root) + self.name = root.get('value') + + def enum(self): + return True + + +class Element(TypedContent): + """ + Represents an (xsd) schema node. + """ + + def __init__(self, schema, root): + TypedContent.__init__(self, schema, root) + a = root.get('form') + if a is not None: + self.form_qualified = ( a == 'qualified' ) + a = self.root.get('nillable') + if a is not None: + self.nillable = ( a in ('1', 'true') ) + self.implany() + + def implany(self): + """ + Set the type as any when implicit. + An implicit is when an element has not + body and no type defined. + @return: self + @rtype: L{Element} + """ + if self.type is None and \ + self.ref is None and \ + self.root.isempty(): + self.type = self.anytype() + return self + + def childtags(self): + return ('attribute', 'simpleType', 'complexType', 'any',) + + def extension(self): + for c in self.rawchildren: + if c.extension(): + return True + return False + + def restriction(self): + for c in self.rawchildren: + if c.restriction(): + return True + return False + + def dependencies(self): + deps = [] + midx = None + if self.ref is not None: + query = ElementQuery(self.ref) + e = query.execute(self.schema) + if e is None: + log.debug(self.schema) + raise TypeNotFound(self.ref) + deps.append(e) + midx = 0 + return (midx, deps) + + def merge(self, other): + SchemaObject.merge(self, other) + self.rawchildren = other.rawchildren + + def description(self): + return ('name', 'ref', 'type') + + def anytype(self): + """ create an xsd:anyType reference """ + p,u = Namespace.xsdns + mp = self.root.findPrefix(u) + if mp is None: + mp = p + self.root.addPrefix(p, u) + return ':'.join((mp, 'anyType')) + + +class Extension(SchemaObject): + """ + Represents an (xsd) schema node. + """ + + def __init__(self, schema, root): + SchemaObject.__init__(self, schema, root) + self.ref = root.get('base') + + def childtags(self): + return ('attribute', + 'attributeGroup', + 'sequence', + 'all', + 'choice', + 'group') + + def dependencies(self): + deps = [] + midx = None + if self.ref is not None: + query = TypeQuery(self.ref) + super = query.execute(self.schema) + if super is None: + log.debug(self.schema) + raise TypeNotFound(self.ref) + if not super.builtin(): + deps.append(super) + midx = 0 + return (midx, deps) + + def merge(self, other): + SchemaObject.merge(self, other) + filter = Filter(False, self.rawchildren) + self.prepend(self.rawchildren, other.rawchildren, filter) + + def extension(self): + return ( self.ref is not None ) + + def description(self): + return ('ref',) + + +class Import(SchemaObject): + """ + Represents an (xsd) schema node + @cvar locations: A dictionary of namespace locations. + @type locations: dict + @ivar ns: The imported namespace. + @type ns: str + @ivar location: The (optional) location. + @type location: namespace-uri + @ivar opened: Opened and I{imported} flag. + @type opened: boolean + """ + + locations = {} + + @classmethod + def bind(cls, ns, location=None): + """ + Bind a namespace to a schema location (URI). + This is used for imports that don't specify a schemaLocation. + @param ns: A namespace-uri. + @type ns: str + @param location: The (optional) schema location for the + namespace. (default=ns). + @type location: str + """ + if location is None: + location = ns + cls.locations[ns] = location + + def __init__(self, schema, root): + SchemaObject.__init__(self, schema, root) + self.ns = (None, root.get('namespace')) + self.location = root.get('schemaLocation') + if self.location is None: + self.location = self.locations.get(self.ns[1]) + self.opened = False + + def open(self, options): + """ + Open and import the refrenced schema. + @param options: An options dictionary. + @type options: L{options.Options} + @return: The referenced schema. + @rtype: L{Schema} + """ + if self.opened: + return + self.opened = True + log.debug('%s, importing ns="%s", location="%s"', self.id, self.ns[1], self.location) + result = self.locate() + if result is None: + if self.location is None: + log.debug('imported schema (%s) not-found', self.ns[1]) + else: + result = self.download(options) + log.debug('imported:\n%s', result) + return result + + def locate(self): + """ find the schema locally """ + if self.ns[1] == self.schema.tns[1]: + return None + else: + return self.schema.locate(self.ns) + + def download(self, options): + """ download the schema """ + url = self.location + try: + if '://' not in url: + url = urljoin(self.schema.baseurl, url) + reader = DocumentReader(options) + d = reader.open(url) + root = d.root() + root.set('url', url) + return self.schema.instance(root, url, options) + except TransportError: + msg = 'imported schema (%s) at (%s), failed' % (self.ns[1], url) + log.error('%s, %s', self.id, msg, exc_info=True) + raise Exception(msg) + + def description(self): + return ('ns', 'location') + + +class Include(SchemaObject): + """ + Represents an (xsd) schema node + @ivar location: The (optional) location. + @type location: namespace-uri + @ivar opened: Opened and I{imported} flag. + @type opened: boolean + """ + + locations = {} + + def __init__(self, schema, root): + SchemaObject.__init__(self, schema, root) + self.location = root.get('schemaLocation') + if self.location is None: + self.location = self.locations.get(self.ns[1]) + self.opened = False + + def open(self, options): + """ + Open and include the refrenced schema. + @param options: An options dictionary. + @type options: L{options.Options} + @return: The referenced schema. + @rtype: L{Schema} + """ + if self.opened: + return + self.opened = True + log.debug('%s, including location="%s"', self.id, self.location) + result = self.download(options) + log.debug('included:\n%s', result) + return result + + def download(self, options): + """ download the schema """ + url = self.location + try: + if '://' not in url: + url = urljoin(self.schema.baseurl, url) + reader = DocumentReader(options) + d = reader.open(url) + root = d.root() + root.set('url', url) + self.__applytns(root) + return self.schema.instance(root, url, options) + except TransportError: + msg = 'include schema at (%s), failed' % url + log.error('%s, %s', self.id, msg, exc_info=True) + raise Exception(msg) + + def __applytns(self, root): + """ make sure included schema has same tns. """ + TNS = 'targetNamespace' + tns = root.get(TNS) + if tns is None: + tns = self.schema.tns[1] + root.set(TNS, tns) + else: + if self.schema.tns[1] != tns: + raise Exception, '%s mismatch' % TNS + + + def description(self): + return ('location') + + +class Attribute(TypedContent): + """ + Represents an (xsd) node + """ + + def __init__(self, schema, root): + TypedContent.__init__(self, schema, root) + self.use = root.get('use', default='') + + def childtags(self): + return ('restriction',) + + def isattr(self): + return True + + def get_default(self): + """ + Gets the attribute value. + @return: The default value for the attribute + @rtype: str + """ + return self.root.get('default', default='') + + def optional(self): + return ( self.use != 'required' ) + + def dependencies(self): + deps = [] + midx = None + if self.ref is not None: + query = AttrQuery(self.ref) + a = query.execute(self.schema) + if a is None: + log.debug(self.schema) + raise TypeNotFound(self.ref) + deps.append(a) + midx = 0 + return (midx, deps) + + def description(self): + return ('name', 'ref', 'type') + + +class Any(Content): + """ + Represents an (xsd) node + """ + + def get_child(self, name): + root = self.root.clone() + root.set('note', 'synthesized (any) child') + child = Any(self.schema, root) + return (child, []) + + def get_attribute(self, name): + root = self.root.clone() + root.set('note', 'synthesized (any) attribute') + attribute = Any(self.schema, root) + return (attribute, []) + + def any(self): + return True + + +class Factory: + """ + @cvar tags: A factory to create object objects based on tag. + @type tags: {tag:fn,} + """ + + tags =\ + { + 'import' : Import, + 'include' : Include, + 'complexType' : Complex, + 'group' : Group, + 'attributeGroup' : AttributeGroup, + 'simpleType' : Simple, + 'list' : List, + 'element' : Element, + 'attribute' : Attribute, + 'sequence' : Sequence, + 'all' : All, + 'choice' : Choice, + 'complexContent' : ComplexContent, + 'simpleContent' : SimpleContent, + 'restriction' : Restriction, + 'enumeration' : Enumeration, + 'extension' : Extension, + 'any' : Any, + } + + @classmethod + def maptag(cls, tag, fn): + """ + Map (override) tag => I{class} mapping. + @param tag: An xsd tag name. + @type tag: str + @param fn: A function or class. + @type fn: fn|class. + """ + cls.tags[tag] = fn + + @classmethod + def create(cls, root, schema): + """ + Create an object based on the root tag name. + @param root: An XML root element. + @type root: L{Element} + @param schema: A schema object. + @type schema: L{schema.Schema} + @return: The created object. + @rtype: L{SchemaObject} + """ + fn = cls.tags.get(root.name) + if fn is not None: + return fn(schema, root) + else: + return None + + @classmethod + def build(cls, root, schema, filter=('*',)): + """ + Build an xsobject representation. + @param root: An schema XML root. + @type root: L{sax.element.Element} + @param filter: A tag filter. + @type filter: [str,...] + @return: A schema object graph. + @rtype: L{sxbase.SchemaObject} + """ + children = [] + for node in root.getChildren(ns=Namespace.xsdns): + if '*' in filter or node.name in filter: + child = cls.create(node, schema) + if child is None: + continue + children.append(child) + c = cls.build(node, schema, child.childtags()) + child.rawchildren = c + return children + + @classmethod + def collate(cls, children): + imports = [] + elements = {} + attributes = {} + types = {} + groups = {} + agrps = {} + for c in children: + if isinstance(c, (Import, Include)): + imports.append(c) + continue + if isinstance(c, Attribute): + attributes[c.qname] = c + continue + if isinstance(c, Element): + elements[c.qname] = c + continue + if isinstance(c, Group): + groups[c.qname] = c + continue + if isinstance(c, AttributeGroup): + agrps[c.qname] = c + continue + types[c.qname] = c + for i in imports: + children.remove(i) + return (children, imports, attributes, elements, types, groups, agrps) + + + + +####################################################### +# Static Import Bindings :-( +####################################################### +Import.bind( + 'http://schemas.xmlsoap.org/soap/encoding/', + 'suds://schemas.xmlsoap.org/soap/encoding/') +Import.bind( + 'http://www.w3.org/XML/1998/namespace', + 'http://www.w3.org/2001/xml.xsd') +Import.bind( + 'http://www.w3.org/2001/XMLSchema', + 'http://www.w3.org/2001/XMLSchema.xsd') diff --git a/awx/lib/site-packages/suds/xsd/sxbuiltin.py b/awx/lib/site-packages/suds/xsd/sxbuiltin.py new file mode 100644 index 0000000000..f8cf428baa --- /dev/null +++ b/awx/lib/site-packages/suds/xsd/sxbuiltin.py @@ -0,0 +1,274 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the (LGPL) GNU Lesser General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Library Lesser General Public License for more details at +# ( http://www.gnu.org/licenses/lgpl.html ). +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# written by: Jeff Ortel ( jortel@redhat.com ) + +""" +The I{sxbuiltin} module provides classes that represent +XSD I{builtin} schema objects. +""" + +from logging import getLogger +from suds import * +from suds.xsd import * +from suds.sax.date import * +from suds.xsd.sxbase import XBuiltin +import datetime as dt + + +log = getLogger(__name__) + + +class XString(XBuiltin): + """ + Represents an (xsd) node + """ + pass + + +class XAny(XBuiltin): + """ + Represents an (xsd) node + """ + + def __init__(self, schema, name): + XBuiltin.__init__(self, schema, name) + self.nillable = False + + def get_child(self, name): + child = XAny(self.schema, name) + return (child, []) + + def any(self): + return True + + +class XBoolean(XBuiltin): + """ + Represents an (xsd) boolean builtin type. + """ + + translation = ( + { '1':True,'true':True,'0':False,'false':False }, + { True:'true',1:'true',False:'false',0:'false' }, + ) + + def translate(self, value, topython=True): + if topython: + if isinstance(value, basestring): + return XBoolean.translation[0].get(value) + else: + return None + else: + if isinstance(value, (bool,int)): + return XBoolean.translation[1].get(value) + else: + return value + + +class XInteger(XBuiltin): + """ + Represents an (xsd) xs:int builtin type. + """ + + def translate(self, value, topython=True): + if topython: + if isinstance(value, basestring) and len(value): + return int(value) + else: + return None + else: + if isinstance(value, int): + return str(value) + else: + return value + +class XLong(XBuiltin): + """ + Represents an (xsd) xs:long builtin type. + """ + + def translate(self, value, topython=True): + if topython: + if isinstance(value, basestring) and len(value): + return long(value) + else: + return None + else: + if isinstance(value, (int,long)): + return str(value) + else: + return value + + +class XFloat(XBuiltin): + """ + Represents an (xsd) xs:float builtin type. + """ + + def translate(self, value, topython=True): + if topython: + if isinstance(value, basestring) and len(value): + return float(value) + else: + return None + else: + if isinstance(value, float): + return str(value) + else: + return value + + +class XDate(XBuiltin): + """ + Represents an (xsd) xs:date builtin type. + """ + + def translate(self, value, topython=True): + if topython: + if isinstance(value, basestring) and len(value): + return Date(value).date + else: + return None + else: + if isinstance(value, dt.date): + return str(Date(value)) + else: + return value + + +class XTime(XBuiltin): + """ + Represents an (xsd) xs:time builtin type. + """ + + def translate(self, value, topython=True): + if topython: + if isinstance(value, basestring) and len(value): + return Time(value).time + else: + return None + else: + if isinstance(value, dt.date): + return str(Time(value)) + else: + return value + + +class XDateTime(XBuiltin): + """ + Represents an (xsd) xs:datetime builtin type. + """ + + def translate(self, value, topython=True): + if topython: + if isinstance(value, basestring) and len(value): + return DateTime(value).datetime + else: + return None + else: + if isinstance(value, dt.date): + return str(DateTime(value)) + else: + return value + + +class Factory: + + tags =\ + { + # any + 'anyType' : XAny, + # strings + 'string' : XString, + 'normalizedString' : XString, + 'ID' : XString, + 'Name' : XString, + 'QName' : XString, + 'NCName' : XString, + 'anySimpleType' : XString, + 'anyURI' : XString, + 'NOTATION' : XString, + 'token' : XString, + 'language' : XString, + 'IDREFS' : XString, + 'ENTITIES' : XString, + 'IDREF' : XString, + 'ENTITY' : XString, + 'NMTOKEN' : XString, + 'NMTOKENS' : XString, + # binary + 'hexBinary' : XString, + 'base64Binary' : XString, + # integers + 'int' : XInteger, + 'integer' : XInteger, + 'unsignedInt' : XInteger, + 'positiveInteger' : XInteger, + 'negativeInteger' : XInteger, + 'nonPositiveInteger' : XInteger, + 'nonNegativeInteger' : XInteger, + # longs + 'long' : XLong, + 'unsignedLong' : XLong, + # shorts + 'short' : XInteger, + 'unsignedShort' : XInteger, + 'byte' : XInteger, + 'unsignedByte' : XInteger, + # floats + 'float' : XFloat, + 'double' : XFloat, + 'decimal' : XFloat, + # dates & times + 'date' : XDate, + 'time' : XTime, + 'dateTime': XDateTime, + 'duration': XString, + 'gYearMonth' : XString, + 'gYear' : XString, + 'gMonthDay' : XString, + 'gDay' : XString, + 'gMonth' : XString, + # boolean + 'boolean' : XBoolean, + } + + @classmethod + def maptag(cls, tag, fn): + """ + Map (override) tag => I{class} mapping. + @param tag: An xsd tag name. + @type tag: str + @param fn: A function or class. + @type fn: fn|class. + """ + cls.tags[tag] = fn + + @classmethod + def create(cls, schema, name): + """ + Create an object based on the root tag name. + @param schema: A schema object. + @type schema: L{schema.Schema} + @param name: The name. + @type name: str + @return: The created object. + @rtype: L{XBuiltin} + """ + fn = cls.tags.get(name) + if fn is not None: + return fn(schema, name) + else: + return XBuiltin(schema, name) diff --git a/awx/main/constants.py b/awx/main/constants.py index 7c92022301..a278246d4d 100644 --- a/awx/main/constants.py +++ b/awx/main/constants.py @@ -1,4 +1,4 @@ # Copyright (c) 2014 AnsibleWorks, Inc. # All Rights Reserved. -CLOUD_PROVIDERS = ('azure', 'ec2', 'gce', 'rax') +CLOUD_PROVIDERS = ('azure', 'ec2', 'gce', 'rax', 'vmware') diff --git a/awx/main/models/inventory.py b/awx/main/models/inventory.py index 8b53aa4348..e5c266c140 100644 --- a/awx/main/models/inventory.py +++ b/awx/main/models/inventory.py @@ -736,11 +736,12 @@ class InventorySourceOptions(BaseModel): ''' SOURCE_CHOICES = [ - ('file', _('Local File, Directory or Script')), - ('rax', _('Rackspace Cloud Servers')), - ('ec2', _('Amazon EC2')), - ('gce', _('Google Compute Engine')), - ('azure', _('Windows Azure')), + ('file', _('Local File, Directory or Script')), + ('rax', _('Rackspace Cloud Servers')), + ('ec2', _('Amazon EC2')), + ('gce', _('Google Compute Engine')), + ('azure', _('Windows Azure')), + ('vmware', _('VMWare')), ] class Meta: @@ -839,6 +840,13 @@ class InventorySourceOptions(BaseModel): regions.insert(0, ('all', 'All')) return regions + @classmethod + def get_vmware_region_choices(self): + """Return a complete list of regions in VMWare, as a list of two-tuples + (but note that VMWare doesn't actually have regions!). + """ + return [('all', 'All')] + def clean_credential(self): if not self.source: return None diff --git a/awx/main/tasks.py b/awx/main/tasks.py index e65b9f07c2..e8dc229617 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -512,6 +512,10 @@ class RunJob(BaseTask): elif cloud_cred and cloud_cred.kind == 'azure': env['AZURE_SUBSCRIPTION_ID'] = cloud_cred.username env['AZURE_CERT_PATH'] = kwargs['private_data_file'] + elif cloud_cred and cloud_cred.kind == 'vmware': + env['VMWARE_USER'] = cloud_cred.username + env['VMWARE_PASSWORD'] = decrypt_field(cloud_cred, 'password') + env['VMWARE_HOST'] = cloud_cred.host return env diff --git a/awx/main/tests/tasks.py b/awx/main/tests/tasks.py index f12ca2cebe..73331f0648 100644 --- a/awx/main/tests/tasks.py +++ b/awx/main/tests/tasks.py @@ -1048,6 +1048,9 @@ class RunJobTest(BaseCeleryTest): elif kind == 'azure': env_var1 = 'AZURE_SUBSCRIPTION_ID' env_var2 = 'AZURE_CERT_PATH' + elif kind == 'vmware': + env_var1 = 'VMWARE_USER' + env_var2 = 'VMWARE_PASSWORD' self.create_test_cloud_credential(name='%s cred' % kind, kind=kind, username='my %s access' % kind, password='my %s secret' % kind, @@ -1077,6 +1080,9 @@ class RunJobTest(BaseCeleryTest): def test_azure_cloud_credential_environment_variables(self): self._test_cloud_credential_environment_variables('azure') + def test_vmware_cloud_credential_environment_variables(self): + self._test_cloud_credential_environment_variables('vmware') + def test_run_async_job(self): self.create_test_project(TEST_ASYNC_OK_PLAYBOOK) job_template = self.create_test_job_template() diff --git a/awx/plugins/inventory/vmware.py b/awx/plugins/inventory/vmware.py index 0ba83137cd..7e17e4d10b 100755 --- a/awx/plugins/inventory/vmware.py +++ b/awx/plugins/inventory/vmware.py @@ -31,6 +31,11 @@ except ImportError: def save_cache(cache_item, data, config): ''' saves item to cache ''' + + # Sanity check: Is caching enabled? If not, don't cache. + if not config.has_option('defaults', 'cache_dir'): + return + dpath = config.get('defaults', 'cache_dir') try: cache = open('/'.join([dpath,cache_item]), 'w') @@ -42,6 +47,11 @@ def save_cache(cache_item, data, config): def get_cache(cache_item, config): ''' returns cached item ''' + + # Sanity check: Is caching enabled? If not, return None. + if not config.has_option('defaults', 'cache_dir'): + return + dpath = config.get('defaults', 'cache_dir') inv = {} try: @@ -138,8 +148,8 @@ def get_inventory(client, config): for vm in host.vm: inv['all']['hosts'].append(vm.name) inv[vm_group].append(vm.name) - if vm.tag: - taggroup = 'vmware_' + vm.tag + for tag in vm.tag: + taggroup = 'vmware_' + tag.key.lower() if taggroup in inv: inv[taggroup].append(vm.name) else: @@ -183,13 +193,15 @@ if __name__ == '__main__': config = ConfigParser.SafeConfigParser( defaults={'host': '', 'user': '', 'password': ''}, ) + for section in ('auth', 'defaults'): + config.add_section(section) for configfilename in [os.path.abspath(sys.argv[0]).rstrip('.py') + '.ini', 'vmware.ini']: if os.path.exists(configfilename): config.read(configfilename) break try: - client = Client( + client = Client( os.environ.get('VMWARE_HOST', config.get('auth','host')), os.environ.get('VMWARE_USER', config.get('auth','user')), os.environ.get('VMWARE_PASSWORD', config.get('auth','password')), diff --git a/awx/ui/static/js/forms/Credentials.js b/awx/ui/static/js/forms/Credentials.js index e07de982e4..a491db34ee 100644 --- a/awx/ui/static/js/forms/Credentials.js +++ b/awx/ui/static/js/forms/Credentials.js @@ -149,7 +149,11 @@ angular.module('CredentialFormDefinition', []) label: 'Host', type: 'text', ngShow: "kind.value == 'vmware'", - autocomplete: false + autocomplete: false, + awRequiredWhen: { + variable: 'host_required', + init: false + } }, "username": { labelBind: 'usernameLabel', @@ -216,7 +220,11 @@ angular.module('CredentialFormDefinition', []) ask: false, clear: false, associated: 'password_confirm', - autocomplete: false + autocomplete: false, + awRequiredWhen: { + variable: "password_required", + init: false + } }, "password_confirm": { label: 'Confirm Password', @@ -226,7 +234,11 @@ angular.module('CredentialFormDefinition', []) editRequired: false, awPassMatch: true, associated: 'password', - autocomplete: false + autocomplete: false, + awRequiredWhen: { + variable: "password_required", + init: false + } }, "ssh_password": { label: 'SSH Password', diff --git a/awx/ui/static/js/helpers/Credentials.js b/awx/ui/static/js/helpers/Credentials.js index e6aabd88d7..c8791888df 100644 --- a/awx/ui/static/js/helpers/Credentials.js +++ b/awx/ui/static/js/helpers/Credentials.js @@ -39,6 +39,9 @@ angular.module('CredentialsHelper', ['Utilities']) scope.subscription_required = false; scope.key_description = "Paste the contents of the SSH private key file.
esc or click to close
"; scope.key_hint= "drag and drop an SSH private key file on the field below"; + scope.host_required = false; + scope.password_required = false; + if (!Empty(scope.kind)) { // Apply kind specific settings switch (scope.kind.value) { @@ -72,6 +75,11 @@ angular.module('CredentialsHelper', ['Utilities']) scope.key_description = "Paste the contents of the PEM file that corresponds to the certificate you uploaded in the Windows Azure console.
esc or click to close
"; scope.key_hint= "drag and drop a management certificate file on the field below"; break; + case 'vmware': + scope.username_required = true; + scope.host_required = true; + scope.password_required = true; + break; } } diff --git a/awx/ui/static/js/helpers/Groups.js b/awx/ui/static/js/helpers/Groups.js index 2946fbe11a..7961d8f23f 100644 --- a/awx/ui/static/js/helpers/Groups.js +++ b/awx/ui/static/js/helpers/Groups.js @@ -252,8 +252,8 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' }]); $('#source_form').addClass('squeeze'); } - if (scope.source.value === 'rax' || scope.source.value === 'ec2'|| scope.source.value==='gce' || scope.source.value === 'azure') { - kind = (scope.source.value === 'rax') ? 'rax' : (scope.source.value==='gce') ? 'gce' : (scope.source.value==='azure') ? 'azure' : 'aws'; + if (scope.source.value === 'rax' || scope.source.value === 'ec2'|| scope.source.value==='gce' || scope.source.value === 'azure' || scope.source.value === 'vmware') { + kind = (scope.source.value === 'rax') ? 'rax' : (scope.source.value==='gce') ? 'gce' : (scope.source.value==='azure') ? 'azure' : (scope.source.value === 'vmware') ? 'vmware' : 'aws' ; url = GetBasePath('credentials') + '?cloud=true&kind=' + kind; LookUpInit({ url: url, diff --git a/requirements/psphere-0.5.2.tar.gz b/requirements/psphere-0.5.2.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..d6dad7709e5f80258f2a0487fbdc6d0d014c4eeb GIT binary patch literal 161091 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 KchV04m>~e%{ava6 literal 0 HcmV?d00001 diff --git a/requirements/suds-0.4.tar.gz b/requirements/suds-0.4.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..168d3673e9af03aadb47a2ab037a7099bf897460 GIT binary patch literal 104013 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%0A6NUpCH$~5i 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