Add initial commands
Change-Id: I6a488185ea43b9aaa512c1eca0e5b0a89943d5f2
This commit is contained in:
		
							
								
								
									
										304
									
								
								gyanclient/api_versions.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										304
									
								
								gyanclient/api_versions.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,304 @@
 | 
				
			|||||||
 | 
					#
 | 
				
			||||||
 | 
					#    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 functools
 | 
				
			||||||
 | 
					import logging
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import pkgutil
 | 
				
			||||||
 | 
					import re
 | 
				
			||||||
 | 
					import traceback
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from oslo_utils import strutils
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from gyanclient import exceptions
 | 
				
			||||||
 | 
					from gyanclient.i18n import _
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					LOG = logging.getLogger(__name__)
 | 
				
			||||||
 | 
					if not LOG.handlers:
 | 
				
			||||||
 | 
					    LOG.addHandler(logging.StreamHandler())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					HEADER_NAME = "OpenStack-API-Version"
 | 
				
			||||||
 | 
					SERVICE_TYPE = "ml-infra"
 | 
				
			||||||
 | 
					MIN_API_VERSION = '1.1'
 | 
				
			||||||
 | 
					MAX_API_VERSION = '1.25'
 | 
				
			||||||
 | 
					DEFAULT_API_VERSION = MAX_API_VERSION
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					_SUBSTITUTIONS = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					_type_error_msg = _("'%(other)s' should be an instance of '%(cls)s'")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class APIVersion(object):
 | 
				
			||||||
 | 
					    """This class represents an API Version Request.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    This class provides convenience methods for manipulation
 | 
				
			||||||
 | 
					    and comparison of version numbers that we need to do to
 | 
				
			||||||
 | 
					    implement microversions.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, version_str=None):
 | 
				
			||||||
 | 
					        """Create an API version object.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param version_str: String representation of APIVersionRequest.
 | 
				
			||||||
 | 
					                            Correct format is 'X.Y', where 'X' and 'Y'
 | 
				
			||||||
 | 
					                            are int values. None value should be used
 | 
				
			||||||
 | 
					                            to create Null APIVersionRequest, which is
 | 
				
			||||||
 | 
					                            equal to 0.0
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.ver_major = 0
 | 
				
			||||||
 | 
					        self.ver_minor = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if version_str is not None:
 | 
				
			||||||
 | 
					            match = re.match(r"^([1-9]\d*)\.([1-9]\d*|0|latest)$", version_str)
 | 
				
			||||||
 | 
					            if match:
 | 
				
			||||||
 | 
					                self.ver_major = int(match.group(1))
 | 
				
			||||||
 | 
					                if match.group(2) == "latest":
 | 
				
			||||||
 | 
					                    self.ver_minor = float("inf")
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    self.ver_minor = int(match.group(2))
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                msg = _("Invalid format of client version '%s'. "
 | 
				
			||||||
 | 
					                        "Expected format 'X.Y', where X is a major part and Y "
 | 
				
			||||||
 | 
					                        "is a minor part of version.") % version_str
 | 
				
			||||||
 | 
					                raise exceptions.UnsupportedVersion(msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __str__(self):
 | 
				
			||||||
 | 
					        """Debug/Logging representation of object."""
 | 
				
			||||||
 | 
					        if self.is_latest():
 | 
				
			||||||
 | 
					            return "Latest API Version Major: %s" % self.ver_major
 | 
				
			||||||
 | 
					        return ("API Version Major: %s, Minor: %s"
 | 
				
			||||||
 | 
					                % (self.ver_major, self.ver_minor))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __repr__(self):
 | 
				
			||||||
 | 
					        if self.is_null():
 | 
				
			||||||
 | 
					            return "<APIVersion: null>"
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            return "<APIVersion: %s>" % self.get_string()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def is_null(self):
 | 
				
			||||||
 | 
					        return self.ver_major == 0 and self.ver_minor == 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def is_latest(self):
 | 
				
			||||||
 | 
					        return self.ver_minor == float("inf")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __lt__(self, other):
 | 
				
			||||||
 | 
					        if not isinstance(other, APIVersion):
 | 
				
			||||||
 | 
					            raise TypeError(_type_error_msg % {"other": other,
 | 
				
			||||||
 | 
					                                               "cls": self.__class__})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return ((self.ver_major, self.ver_minor) <
 | 
				
			||||||
 | 
					                (other.ver_major, other.ver_minor))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __eq__(self, other):
 | 
				
			||||||
 | 
					        if not isinstance(other, APIVersion):
 | 
				
			||||||
 | 
					            raise TypeError(_type_error_msg % {"other": other,
 | 
				
			||||||
 | 
					                                               "cls": self.__class__})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return ((self.ver_major, self.ver_minor) ==
 | 
				
			||||||
 | 
					                (other.ver_major, other.ver_minor))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __gt__(self, other):
 | 
				
			||||||
 | 
					        if not isinstance(other, APIVersion):
 | 
				
			||||||
 | 
					            raise TypeError(_type_error_msg % {"other": other,
 | 
				
			||||||
 | 
					                                               "cls": self.__class__})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return ((self.ver_major, self.ver_minor) >
 | 
				
			||||||
 | 
					                (other.ver_major, other.ver_minor))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __le__(self, other):
 | 
				
			||||||
 | 
					        return self < other or self == other
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __ne__(self, other):
 | 
				
			||||||
 | 
					        return not self.__eq__(other)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __ge__(self, other):
 | 
				
			||||||
 | 
					        return self > other or self == other
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def matches(self, min_version, max_version):
 | 
				
			||||||
 | 
					        """Matches the version object.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Returns whether the version object represents a version
 | 
				
			||||||
 | 
					        greater than or equal to the minimum version and less than
 | 
				
			||||||
 | 
					        or equal to the maximum version.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param min_version: Minimum acceptable version.
 | 
				
			||||||
 | 
					        :param max_version: Maximum acceptable version.
 | 
				
			||||||
 | 
					        :returns: boolean
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        If min_version is null then there is no minimum limit.
 | 
				
			||||||
 | 
					        If max_version is null then there is no maximum limit.
 | 
				
			||||||
 | 
					        If self is null then raise ValueError
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.is_null():
 | 
				
			||||||
 | 
					            raise ValueError(_("Null APIVersion doesn't support 'matches'."))
 | 
				
			||||||
 | 
					        if max_version.is_null() and min_version.is_null():
 | 
				
			||||||
 | 
					            return True
 | 
				
			||||||
 | 
					        elif max_version.is_null():
 | 
				
			||||||
 | 
					            return min_version <= self
 | 
				
			||||||
 | 
					        elif min_version.is_null():
 | 
				
			||||||
 | 
					            return self <= max_version
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            return min_version <= self <= max_version
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_string(self):
 | 
				
			||||||
 | 
					        """Version string representation.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Converts object to string representation which if used to create
 | 
				
			||||||
 | 
					        an APIVersion object results in the same version.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if self.is_null():
 | 
				
			||||||
 | 
					            raise ValueError(
 | 
				
			||||||
 | 
					                _("Null APIVersion cannot be converted to string."))
 | 
				
			||||||
 | 
					        elif self.is_latest():
 | 
				
			||||||
 | 
					            return "%s.%s" % (self.ver_major, "latest")
 | 
				
			||||||
 | 
					        return "%s.%s" % (self.ver_major, self.ver_minor)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class VersionedMethod(object):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, name, start_version, end_version, func):
 | 
				
			||||||
 | 
					        """Versioning information for a single method
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param name: Name of the method
 | 
				
			||||||
 | 
					        :param start_version: Minimum acceptable version
 | 
				
			||||||
 | 
					        :param end_version: Maximum acceptable_version
 | 
				
			||||||
 | 
					        :param func: Method to call
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Minimum and maximums are inclusive
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.name = name
 | 
				
			||||||
 | 
					        self.start_version = start_version
 | 
				
			||||||
 | 
					        self.end_version = end_version
 | 
				
			||||||
 | 
					        self.func = func
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __str__(self):
 | 
				
			||||||
 | 
					        return ("Version Method %s: min: %s, max: %s"
 | 
				
			||||||
 | 
					                % (self.name, self.start_version, self.end_version))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __repr__(self):
 | 
				
			||||||
 | 
					        return "<VersionedMethod %s>" % self.name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_available_major_versions():
 | 
				
			||||||
 | 
					    matcher = re.compile(r"v[0-9]*$")
 | 
				
			||||||
 | 
					    submodules = pkgutil.iter_modules([os.path.dirname(__file__)])
 | 
				
			||||||
 | 
					    available_versions = [name[1:] for loader, name, ispkg in submodules
 | 
				
			||||||
 | 
					                          if matcher.search(name)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return available_versions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def check_major_version(api_version):
 | 
				
			||||||
 | 
					    """Checks major part of ``APIVersion`` obj is supported.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :raises exceptions.UnsupportedVersion: if major part is not supported
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    available_versions = get_available_major_versions()
 | 
				
			||||||
 | 
					    if (not api_version.is_null() and
 | 
				
			||||||
 | 
					            str(api_version.ver_major) not in available_versions):
 | 
				
			||||||
 | 
					        if len(available_versions) == 1:
 | 
				
			||||||
 | 
					            msg = _("Invalid client version '%(version)s'. "
 | 
				
			||||||
 | 
					                    "Major part should be '%(major)s'") % {
 | 
				
			||||||
 | 
					                "version": api_version.get_string(),
 | 
				
			||||||
 | 
					                "major": available_versions[0]}
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            msg = _("Invalid client version '%(version)s'. "
 | 
				
			||||||
 | 
					                    "Major part must be one of: '%(major)s'") % {
 | 
				
			||||||
 | 
					                "version": api_version.get_string(),
 | 
				
			||||||
 | 
					                "major": ", ".join(available_versions)}
 | 
				
			||||||
 | 
					        raise exceptions.UnsupportedVersion(msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_api_version(version_string):
 | 
				
			||||||
 | 
					    """Returns checked APIVersion object"""
 | 
				
			||||||
 | 
					    version_string = str(version_string)
 | 
				
			||||||
 | 
					    if strutils.is_int_like(version_string):
 | 
				
			||||||
 | 
					        version_string = "%s.0" % version_string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    api_version = APIVersion(version_string)
 | 
				
			||||||
 | 
					    check_major_version(api_version)
 | 
				
			||||||
 | 
					    return api_version
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def update_headers(headers, api_version):
 | 
				
			||||||
 | 
					    """Set microversion headers if api_version is not null"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if not api_version.is_null() and api_version.ver_minor != 0:
 | 
				
			||||||
 | 
					        version_string = api_version.get_string()
 | 
				
			||||||
 | 
					        headers[HEADER_NAME] = '%s %s' % (SERVICE_TYPE, version_string)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _add_substitution(versioned_method):
 | 
				
			||||||
 | 
					    _SUBSTITUTIONS.setdefault(versioned_method.name, [])
 | 
				
			||||||
 | 
					    _SUBSTITUTIONS[versioned_method.name].append(versioned_method)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _get_function_name(func):
 | 
				
			||||||
 | 
					    filename, _lineno, _name, line = traceback.extract_stack()[-4]
 | 
				
			||||||
 | 
					    module, _file_extension = os.path.splitext(filename)
 | 
				
			||||||
 | 
					    module = module.replace("/", ".")
 | 
				
			||||||
 | 
					    if module.endswith(func.__module__):
 | 
				
			||||||
 | 
					        return "%s.[%s].%s" % (func.__module__, line, func.__name__)
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        return "%s.%s" % (func.__module__, func.__name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_substitutions(func_name, api_version=None):
 | 
				
			||||||
 | 
					    if hasattr(func_name, "__id__"):
 | 
				
			||||||
 | 
					        func_name = func_name.__id__
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    substitutions = _SUBSTITUTIONS.get(func_name, [])
 | 
				
			||||||
 | 
					    if api_version and not api_version.is_null():
 | 
				
			||||||
 | 
					        return [m for m in substitutions
 | 
				
			||||||
 | 
					                if api_version.matches(m.start_version, m.end_version)]
 | 
				
			||||||
 | 
					    return sorted(substitutions, key=lambda m: m.start_version)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def wraps(start_version, end_version=None):
 | 
				
			||||||
 | 
					    start_version = APIVersion(start_version)
 | 
				
			||||||
 | 
					    if end_version:
 | 
				
			||||||
 | 
					        end_version = APIVersion(end_version)
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        end_version = APIVersion("%s.latest" % start_version.ver_major)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def decor(func):
 | 
				
			||||||
 | 
					        func.versioned = True
 | 
				
			||||||
 | 
					        name = _get_function_name(func)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        versioned_method = VersionedMethod(name, start_version,
 | 
				
			||||||
 | 
					                                           end_version, func)
 | 
				
			||||||
 | 
					        _add_substitution(versioned_method)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @functools.wraps(func)
 | 
				
			||||||
 | 
					        def substitution(obj, *args, **kwargs):
 | 
				
			||||||
 | 
					            methods = get_substitutions(name, obj.api_version)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if not methods:
 | 
				
			||||||
 | 
					                raise exceptions.VersionNotFoundForAPIMethod(
 | 
				
			||||||
 | 
					                    obj.api_version.get_string(), name)
 | 
				
			||||||
 | 
					            return methods[-1].func(obj, *args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Let's share "arguments" with original method and substitution to
 | 
				
			||||||
 | 
					        # allow put cliutils.arg and wraps decorators in any order
 | 
				
			||||||
 | 
					        if not hasattr(func, 'arguments'):
 | 
				
			||||||
 | 
					            func.arguments = []
 | 
				
			||||||
 | 
					        substitution.arguments = func.arguments
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        substitution.__id__ = name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return substitution
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return decor
 | 
				
			||||||
							
								
								
									
										93
									
								
								gyanclient/client.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								gyanclient/client.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,93 @@
 | 
				
			|||||||
 | 
					# 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 warnings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from oslo_utils import importutils
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from gyanclient import api_versions
 | 
				
			||||||
 | 
					from gyanclient import exceptions
 | 
				
			||||||
 | 
					from gyanclient.i18n import _
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					osprofiler_profiler = importutils.try_import("osprofiler.profiler")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _get_client_class_and_version(version):
 | 
				
			||||||
 | 
					    if not isinstance(version, api_versions.APIVersion):
 | 
				
			||||||
 | 
					        version = api_versions.get_api_version(version)
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        api_versions.check_major_version(version)
 | 
				
			||||||
 | 
					    if version.is_latest():
 | 
				
			||||||
 | 
					        raise exceptions.UnsupportedVersion(
 | 
				
			||||||
 | 
					            _('The version should be explicit, not latest.'))
 | 
				
			||||||
 | 
					    return version, importutils.import_class(
 | 
				
			||||||
 | 
					        'gyanclient.v%s.client.Client' % version.ver_major)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _check_arguments(kwargs, release, deprecated_name, right_name=None):
 | 
				
			||||||
 | 
					    """Process deprecation of arguments.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Check presence of deprecated argument in kwargs, prints proper warning
 | 
				
			||||||
 | 
					    message, renames key to right one it needed.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    if deprecated_name in kwargs:
 | 
				
			||||||
 | 
					        if right_name:
 | 
				
			||||||
 | 
					            if right_name in kwargs:
 | 
				
			||||||
 | 
					                msg = ('The %(old)s argument is deprecated in %(release)s'
 | 
				
			||||||
 | 
					                       'and its use may result in errors in future releases.'
 | 
				
			||||||
 | 
					                       'As %(new)s is provided, the %(old)s argument will '
 | 
				
			||||||
 | 
					                       'be ignored.') % {'old': deprecated_name,
 | 
				
			||||||
 | 
					                                         'release': release,
 | 
				
			||||||
 | 
					                                         'new': right_name}
 | 
				
			||||||
 | 
					                kwargs.pop(deprecated_name)
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                msg = ('The %(old)s argument is deprecated in %(release)s '
 | 
				
			||||||
 | 
					                       'and its use may result in errors in future releases. '
 | 
				
			||||||
 | 
					                       'Use %(new)s instead.') % {'old': deprecated_name,
 | 
				
			||||||
 | 
					                                                  'release': release,
 | 
				
			||||||
 | 
					                                                  'new': right_name}
 | 
				
			||||||
 | 
					                kwargs[right_name] = kwargs.pop(deprecated_name)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            msg = ('The %(old)s argument is deprecated in %(release)s '
 | 
				
			||||||
 | 
					                   'and its use may result in errors in future '
 | 
				
			||||||
 | 
					                   'releases') % {'old': deprecated_name,
 | 
				
			||||||
 | 
					                                  'release': release}
 | 
				
			||||||
 | 
					            kwargs.pop(deprecated_name)
 | 
				
			||||||
 | 
					        warnings.warn(msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def Client(version='1', username=None, auth_url=None, **kwargs):
 | 
				
			||||||
 | 
					    """Initialize client objects based on given version"""
 | 
				
			||||||
 | 
					    _check_arguments(kwargs, 'Queens', 'api_key', right_name='password')
 | 
				
			||||||
 | 
					    _check_arguments(kwargs, 'Queens', 'endpoint_type',
 | 
				
			||||||
 | 
					                     right_name='interface')
 | 
				
			||||||
 | 
					    _check_arguments(kwargs, 'Queens', 'gyan_url',
 | 
				
			||||||
 | 
					                     right_name='endpoint_override')
 | 
				
			||||||
 | 
					    _check_arguments(kwargs, 'Queens', 'tenant_name',
 | 
				
			||||||
 | 
					                     right_name='project_name')
 | 
				
			||||||
 | 
					    _check_arguments(kwargs, 'Queens', 'tenant_id', right_name='project_id')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    profile = kwargs.pop('profile', None)
 | 
				
			||||||
 | 
					    if osprofiler_profiler and profile:
 | 
				
			||||||
 | 
					        # Initialize the root of the future trace: the created trace ID
 | 
				
			||||||
 | 
					        # will be used as the very first parent to which all related
 | 
				
			||||||
 | 
					        # traces will be bound to. The given HMAC key must correspond to
 | 
				
			||||||
 | 
					        # the one set in gyan-api gyan.conf, otherwise the latter
 | 
				
			||||||
 | 
					        # will fail to check the request signature and will skip
 | 
				
			||||||
 | 
					        # initialization of osprofiler on the server side.
 | 
				
			||||||
 | 
					        osprofiler_profiler.init(profile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    api_version, client_class = _get_client_class_and_version(version)
 | 
				
			||||||
 | 
					    return client_class(api_version=api_version,
 | 
				
			||||||
 | 
					                        auth_url=auth_url,
 | 
				
			||||||
 | 
					                        username=username,
 | 
				
			||||||
 | 
					                        **kwargs)
 | 
				
			||||||
							
								
								
									
										0
									
								
								gyanclient/common/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								gyanclient/common/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										0
									
								
								gyanclient/common/apiclient/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								gyanclient/common/apiclient/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										148
									
								
								gyanclient/common/apiclient/auth.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								gyanclient/common/apiclient/auth.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,148 @@
 | 
				
			|||||||
 | 
					#    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 abc
 | 
				
			||||||
 | 
					import argparse
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import six
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from gyanclient.common.apiclient import exceptions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					_discovered_plugins = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def load_auth_system_opts(parser):
 | 
				
			||||||
 | 
					    """Load options needed by the available auth-systems into a parser.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    This function will try to populate the parser with options from the
 | 
				
			||||||
 | 
					    available plugins.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    group = parser.add_argument_group("Common auth options")
 | 
				
			||||||
 | 
					    BaseAuthPlugin.add_common_opts(group)
 | 
				
			||||||
 | 
					    for name, auth_plugin in _discovered_plugins.items():
 | 
				
			||||||
 | 
					        group = parser.add_argument_group(
 | 
				
			||||||
 | 
					            "Auth-system '%s' options" % name,
 | 
				
			||||||
 | 
					            conflict_handler="resolve")
 | 
				
			||||||
 | 
					        auth_plugin.add_opts(group)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def load_plugin(auth_system):
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        plugin_class = _discovered_plugins[auth_system]
 | 
				
			||||||
 | 
					    except KeyError:
 | 
				
			||||||
 | 
					        raise exceptions.AuthSystemNotFound(auth_system)
 | 
				
			||||||
 | 
					    return plugin_class(auth_system=auth_system)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@six.add_metaclass(abc.ABCMeta)
 | 
				
			||||||
 | 
					class BaseAuthPlugin(object):
 | 
				
			||||||
 | 
					    """Base class for authentication plugins.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    An authentication plugin needs to override at least the authenticate
 | 
				
			||||||
 | 
					    method to be a valid plugin.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    auth_system = None
 | 
				
			||||||
 | 
					    opt_names = []
 | 
				
			||||||
 | 
					    common_opt_names = [
 | 
				
			||||||
 | 
					        "auth_system",
 | 
				
			||||||
 | 
					        "username",
 | 
				
			||||||
 | 
					        "password",
 | 
				
			||||||
 | 
					        "auth_url",
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, auth_system=None, **kwargs):
 | 
				
			||||||
 | 
					        self.auth_system = auth_system or self.auth_system
 | 
				
			||||||
 | 
					        self.opts = dict((name, kwargs.get(name))
 | 
				
			||||||
 | 
					                         for name in self.opt_names)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @staticmethod
 | 
				
			||||||
 | 
					    def _parser_add_opt(parser, opt):
 | 
				
			||||||
 | 
					        """Add an option to parser in two variants.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param opt: option name (with underscores)
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        dashed_opt = opt.replace("_", "-")
 | 
				
			||||||
 | 
					        env_var = "OS_%s" % opt.upper()
 | 
				
			||||||
 | 
					        arg_default = os.environ.get(env_var, "")
 | 
				
			||||||
 | 
					        arg_help = "Defaults to env[%s]." % env_var
 | 
				
			||||||
 | 
					        parser.add_argument(
 | 
				
			||||||
 | 
					            "--os-%s" % dashed_opt,
 | 
				
			||||||
 | 
					            metavar="<%s>" % dashed_opt,
 | 
				
			||||||
 | 
					            default=arg_default,
 | 
				
			||||||
 | 
					            help=arg_help)
 | 
				
			||||||
 | 
					        parser.add_argument(
 | 
				
			||||||
 | 
					            "--os_%s" % opt,
 | 
				
			||||||
 | 
					            metavar="<%s>" % dashed_opt,
 | 
				
			||||||
 | 
					            help=argparse.SUPPRESS)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def add_opts(cls, parser):
 | 
				
			||||||
 | 
					        """Populate the parser with the options for this plugin."""
 | 
				
			||||||
 | 
					        for opt in cls.opt_names:
 | 
				
			||||||
 | 
					            # use `BaseAuthPlugin.common_opt_names` since it is never
 | 
				
			||||||
 | 
					            # changed in child classes
 | 
				
			||||||
 | 
					            if opt not in BaseAuthPlugin.common_opt_names:
 | 
				
			||||||
 | 
					                cls._parser_add_opt(parser, opt)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def add_common_opts(cls, parser):
 | 
				
			||||||
 | 
					        """Add options that are common for several plugins."""
 | 
				
			||||||
 | 
					        for opt in cls.common_opt_names:
 | 
				
			||||||
 | 
					            cls._parser_add_opt(parser, opt)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @staticmethod
 | 
				
			||||||
 | 
					    def get_opt(opt_name, args):
 | 
				
			||||||
 | 
					        """Return option name and value.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param opt_name: name of the option, e.g., "username"
 | 
				
			||||||
 | 
					        :param args: parsed arguments
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        return (opt_name, getattr(args, "os_%s" % opt_name, None))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def parse_opts(self, args):
 | 
				
			||||||
 | 
					        """Parse the actual auth-system options if any.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        This method is expected to populate the attribute `self.opts` with a
 | 
				
			||||||
 | 
					        dict containing the options and values needed to make authentication.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.opts.update(dict(self.get_opt(opt_name, args)
 | 
				
			||||||
 | 
					                              for opt_name in self.opt_names))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def authenticate(self, http_client):
 | 
				
			||||||
 | 
					        """Authenticate using plugin defined method.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        The method usually analyses `self.opts` and performs
 | 
				
			||||||
 | 
					        a request to authentication server.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param http_client: client object that needs authentication
 | 
				
			||||||
 | 
					        :type http_client: HTTPClient
 | 
				
			||||||
 | 
					        :raises: AuthorizationFailure
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.sufficient_options()
 | 
				
			||||||
 | 
					        self._do_authenticate(http_client)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @abc.abstractmethod
 | 
				
			||||||
 | 
					    def _do_authenticate(self, http_client):
 | 
				
			||||||
 | 
					        """Protected method for authentication."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def sufficient_options(self):
 | 
				
			||||||
 | 
					        """Check if all required options are present.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :raises: AuthPluginOptionsMissing
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        missing = [opt
 | 
				
			||||||
 | 
					                   for opt in self.opt_names
 | 
				
			||||||
 | 
					                   if not self.opts.get(opt)]
 | 
				
			||||||
 | 
					        if missing:
 | 
				
			||||||
 | 
					            raise exceptions.AuthPluginOptionsMissing(missing)
 | 
				
			||||||
							
								
								
									
										98
									
								
								gyanclient/common/apiclient/base.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								gyanclient/common/apiclient/base.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,98 @@
 | 
				
			|||||||
 | 
					#    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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					Base utilities to build API operation managers and objects on top of.
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import copy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Resource(object):
 | 
				
			||||||
 | 
					    """Base class for OpenStack resources (tenant, user, etc.).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    This is pretty much just a bag for attributes.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, manager, info, loaded=False):
 | 
				
			||||||
 | 
					        """Populate and bind to a manager.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param manager: BaseManager object
 | 
				
			||||||
 | 
					        :param info: dictionary representing resource attributes
 | 
				
			||||||
 | 
					        :param loaded: prevent lazy-loading if set to True
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.manager = manager
 | 
				
			||||||
 | 
					        self._info = info
 | 
				
			||||||
 | 
					        self._add_details(info)
 | 
				
			||||||
 | 
					        self._loaded = loaded
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __repr__(self):
 | 
				
			||||||
 | 
					        reprkeys = sorted(k
 | 
				
			||||||
 | 
					                          for k in self.__dict__.keys()
 | 
				
			||||||
 | 
					                          if k[0] != '_' and k != 'manager')
 | 
				
			||||||
 | 
					        info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys)
 | 
				
			||||||
 | 
					        return "<%s %s>" % (self.__class__.__name__, info)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _add_details(self, info):
 | 
				
			||||||
 | 
					        for (k, v) in info.items():
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                setattr(self, k, v)
 | 
				
			||||||
 | 
					                self._info[k] = v
 | 
				
			||||||
 | 
					            except AttributeError:
 | 
				
			||||||
 | 
					                # In this case we already defined the attribute on the class
 | 
				
			||||||
 | 
					                pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __getattr__(self, k):
 | 
				
			||||||
 | 
					        if k not in self.__dict__:
 | 
				
			||||||
 | 
					            if not self.is_loaded():
 | 
				
			||||||
 | 
					                self.get()
 | 
				
			||||||
 | 
					                return self.__getattr__(k)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            raise AttributeError(k)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            return self.__dict__[k]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get(self):
 | 
				
			||||||
 | 
					        """Support for lazy loading details.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Some clients, such as novaclient have the option to lazy load the
 | 
				
			||||||
 | 
					        details, details which can be loaded with this function.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        # set_loaded() first ... so if we have to bail, we know we tried.
 | 
				
			||||||
 | 
					        self.set_loaded(True)
 | 
				
			||||||
 | 
					        if not hasattr(self.manager, 'get'):
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        new = self.manager.get(self.id)
 | 
				
			||||||
 | 
					        if new:
 | 
				
			||||||
 | 
					            self._add_details(new._info)
 | 
				
			||||||
 | 
					            self._add_details(
 | 
				
			||||||
 | 
					                {'x_request_id': self.manager.client.last_request_id})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __eq__(self, other):
 | 
				
			||||||
 | 
					        if not isinstance(other, Resource):
 | 
				
			||||||
 | 
					            return NotImplemented
 | 
				
			||||||
 | 
					        # two resources of different types are not equal
 | 
				
			||||||
 | 
					        if not isinstance(other, self.__class__):
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					        if hasattr(self, 'id') and hasattr(other, 'id'):
 | 
				
			||||||
 | 
					            return self.id == other.id
 | 
				
			||||||
 | 
					        return self._info == other._info
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def is_loaded(self):
 | 
				
			||||||
 | 
					        return self._loaded
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def set_loaded(self, val):
 | 
				
			||||||
 | 
					        self._loaded = val
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def to_dict(self):
 | 
				
			||||||
 | 
					        return copy.deepcopy(self._info)
 | 
				
			||||||
							
								
								
									
										463
									
								
								gyanclient/common/apiclient/exceptions.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										463
									
								
								gyanclient/common/apiclient/exceptions.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,463 @@
 | 
				
			|||||||
 | 
					#    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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					Exception definitions.
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import inspect
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import six
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from gyanclient.i18n import _
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class VersionNotFoundForAPIMethod(Exception):
 | 
				
			||||||
 | 
					    msg_fmt = "API version '%(vers)s' is not supported on '%(method)s' method."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, version, method):
 | 
				
			||||||
 | 
					        self.version = version
 | 
				
			||||||
 | 
					        self.method = method
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __str__(self):
 | 
				
			||||||
 | 
					        return self.msg_fmt % {"vers": self.version, "method": self.method}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ClientException(Exception):
 | 
				
			||||||
 | 
					    """The base exception class for all exceptions this library raises."""
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UnsupportedVersion(ClientException):
 | 
				
			||||||
 | 
					    """User is trying to use an unsupported version of the API."""
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CommandError(ClientException):
 | 
				
			||||||
 | 
					    """Error in CLI tool."""
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AuthorizationFailure(ClientException):
 | 
				
			||||||
 | 
					    """Cannot authorize API client."""
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ConnectionError(ClientException):
 | 
				
			||||||
 | 
					    """Cannot connect to API service."""
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ConnectionRefused(ConnectionError):
 | 
				
			||||||
 | 
					    """Connection refused while trying to connect to API service."""
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AuthPluginOptionsMissing(AuthorizationFailure):
 | 
				
			||||||
 | 
					    """Auth plugin misses some options."""
 | 
				
			||||||
 | 
					    def __init__(self, opt_names):
 | 
				
			||||||
 | 
					        super(AuthPluginOptionsMissing, self).__init__(
 | 
				
			||||||
 | 
					            _("Authentication failed. Missing options: %s") %
 | 
				
			||||||
 | 
					            ", ".join(opt_names))
 | 
				
			||||||
 | 
					        self.opt_names = opt_names
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AuthSystemNotFound(AuthorizationFailure):
 | 
				
			||||||
 | 
					    """User has specified an AuthSystem that is not installed."""
 | 
				
			||||||
 | 
					    def __init__(self, auth_system):
 | 
				
			||||||
 | 
					        super(AuthSystemNotFound, self).__init__(
 | 
				
			||||||
 | 
					            _("AuthSystemNotFound: %r") % auth_system)
 | 
				
			||||||
 | 
					        self.auth_system = auth_system
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class NoUniqueMatch(ClientException):
 | 
				
			||||||
 | 
					    """Multiple entities found instead of one."""
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class EndpointException(ClientException):
 | 
				
			||||||
 | 
					    """Something is rotten in Service Catalog."""
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class EndpointNotFound(EndpointException):
 | 
				
			||||||
 | 
					    """Could not find requested endpoint in Service Catalog."""
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AmbiguousEndpoints(EndpointException):
 | 
				
			||||||
 | 
					    """Found more than one matching endpoint in Service Catalog."""
 | 
				
			||||||
 | 
					    def __init__(self, endpoints=None):
 | 
				
			||||||
 | 
					        super(AmbiguousEndpoints, self).__init__(
 | 
				
			||||||
 | 
					            _("AmbiguousEndpoints: %r") % endpoints)
 | 
				
			||||||
 | 
					        self.endpoints = endpoints
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class HttpError(ClientException):
 | 
				
			||||||
 | 
					    """The base exception class for all HTTP exceptions."""
 | 
				
			||||||
 | 
					    http_status = 0
 | 
				
			||||||
 | 
					    message = _("HTTP Error")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, message=None, details=None,
 | 
				
			||||||
 | 
					                 response=None, request_id=None,
 | 
				
			||||||
 | 
					                 url=None, method=None, http_status=None):
 | 
				
			||||||
 | 
					        self.http_status = http_status or self.http_status
 | 
				
			||||||
 | 
					        self.message = message or self.message
 | 
				
			||||||
 | 
					        self.details = details
 | 
				
			||||||
 | 
					        self.request_id = request_id
 | 
				
			||||||
 | 
					        self.response = response
 | 
				
			||||||
 | 
					        self.url = url
 | 
				
			||||||
 | 
					        self.method = method
 | 
				
			||||||
 | 
					        formatted_string = "%s (HTTP %s)" % (self.message, self.http_status)
 | 
				
			||||||
 | 
					        if request_id:
 | 
				
			||||||
 | 
					            formatted_string += " (Request-ID: %s)" % request_id
 | 
				
			||||||
 | 
					        super(HttpError, self).__init__(formatted_string)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class HTTPRedirection(HttpError):
 | 
				
			||||||
 | 
					    """HTTP Redirection."""
 | 
				
			||||||
 | 
					    message = _("HTTP Redirection")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class HTTPClientError(HttpError):
 | 
				
			||||||
 | 
					    """Client-side HTTP error.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Exception for cases in which the client seems to have erred.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    message = _("HTTP Client Error")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class HttpServerError(HttpError):
 | 
				
			||||||
 | 
					    """Server-side HTTP error.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Exception for cases in which the server is aware that it has
 | 
				
			||||||
 | 
					    erred or is incapable of performing the request.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    message = _("HTTP Server Error")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class MultipleChoices(HTTPRedirection):
 | 
				
			||||||
 | 
					    """HTTP 300 - Multiple Choices.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Indicates multiple options for the resource that the client may follow.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    http_status = 300
 | 
				
			||||||
 | 
					    message = _("Multiple Choices")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BadRequest(HTTPClientError):
 | 
				
			||||||
 | 
					    """HTTP 400 - Bad Request.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    The request cannot be fulfilled due to bad syntax.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    http_status = 400
 | 
				
			||||||
 | 
					    message = _("Bad Request")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Unauthorized(HTTPClientError):
 | 
				
			||||||
 | 
					    """HTTP 401 - Unauthorized.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Similar to 403 Forbidden, but specifically for use when authentication
 | 
				
			||||||
 | 
					    is required and has failed or has not yet been provided.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    http_status = 401
 | 
				
			||||||
 | 
					    message = _("Unauthorized")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PaymentRequired(HTTPClientError):
 | 
				
			||||||
 | 
					    """HTTP 402 - Payment Required.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Reserved for future use.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    http_status = 402
 | 
				
			||||||
 | 
					    message = _("Payment Required")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Forbidden(HTTPClientError):
 | 
				
			||||||
 | 
					    """HTTP 403 - Forbidden.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    The request was a valid request, but the server is refusing to respond
 | 
				
			||||||
 | 
					    to it.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    http_status = 403
 | 
				
			||||||
 | 
					    message = _("Forbidden")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class NotFound(HTTPClientError):
 | 
				
			||||||
 | 
					    """HTTP 404 - Not Found.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    The requested resource could not be found but may be available again
 | 
				
			||||||
 | 
					    in the future.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    http_status = 404
 | 
				
			||||||
 | 
					    message = _("Not Found")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class MethodNotAllowed(HTTPClientError):
 | 
				
			||||||
 | 
					    """HTTP 405 - Method Not Allowed.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    A request was made of a resource using a request method not supported
 | 
				
			||||||
 | 
					    by that resource.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    http_status = 405
 | 
				
			||||||
 | 
					    message = _("Method Not Allowed")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class NotAcceptable(HTTPClientError):
 | 
				
			||||||
 | 
					    """HTTP 406 - Not Acceptable.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    The requested resource is only capable of generating content not
 | 
				
			||||||
 | 
					    acceptable according to the Accept headers sent in the request.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    http_status = 406
 | 
				
			||||||
 | 
					    message = _("Not Acceptable")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ProxyAuthenticationRequired(HTTPClientError):
 | 
				
			||||||
 | 
					    """HTTP 407 - Proxy Authentication Required.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    The client must first authenticate itself with the proxy.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    http_status = 407
 | 
				
			||||||
 | 
					    message = _("Proxy Authentication Required")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class RequestTimeout(HTTPClientError):
 | 
				
			||||||
 | 
					    """HTTP 408 - Request Timeout.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    The server timed out waiting for the request.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    http_status = 408
 | 
				
			||||||
 | 
					    message = _("Request Timeout")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Conflict(HTTPClientError):
 | 
				
			||||||
 | 
					    """HTTP 409 - Conflict.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Indicates that the request could not be processed because of conflict
 | 
				
			||||||
 | 
					    in the request, such as an edit conflict.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    http_status = 409
 | 
				
			||||||
 | 
					    message = _("Conflict")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Gone(HTTPClientError):
 | 
				
			||||||
 | 
					    """HTTP 410 - Gone.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Indicates that the resource requested is no longer available and will
 | 
				
			||||||
 | 
					    not be available again.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    http_status = 410
 | 
				
			||||||
 | 
					    message = _("Gone")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class LengthRequired(HTTPClientError):
 | 
				
			||||||
 | 
					    """HTTP 411 - Length Required.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    The request did not specify the length of its content, which is
 | 
				
			||||||
 | 
					    required by the requested resource.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    http_status = 411
 | 
				
			||||||
 | 
					    message = _("Length Required")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PreconditionFailed(HTTPClientError):
 | 
				
			||||||
 | 
					    """HTTP 412 - Precondition Failed.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    The server does not meet one of the preconditions that the requester
 | 
				
			||||||
 | 
					    put on the request.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    http_status = 412
 | 
				
			||||||
 | 
					    message = _("Precondition Failed")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class RequestEntityTooLarge(HTTPClientError):
 | 
				
			||||||
 | 
					    """HTTP 413 - Request Entity Too Large.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    The request is larger than the server is willing or able to process.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    http_status = 413
 | 
				
			||||||
 | 
					    message = _("Request Entity Too Large")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, *args, **kwargs):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            self.retry_after = int(kwargs.pop('retry_after'))
 | 
				
			||||||
 | 
					        except (KeyError, ValueError):
 | 
				
			||||||
 | 
					            self.retry_after = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        super(RequestEntityTooLarge, self).__init__(*args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class RequestUriTooLong(HTTPClientError):
 | 
				
			||||||
 | 
					    """HTTP 414 - Request-URI Too Long.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    The URI provided was too long for the server to process.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    http_status = 414
 | 
				
			||||||
 | 
					    message = _("Request-URI Too Long")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UnsupportedMediaType(HTTPClientError):
 | 
				
			||||||
 | 
					    """HTTP 415 - Unsupported Media Type.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    The request entity has a media type which the server or resource does
 | 
				
			||||||
 | 
					    not support.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    http_status = 415
 | 
				
			||||||
 | 
					    message = _("Unsupported Media Type")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class RequestedRangeNotSatisfiable(HTTPClientError):
 | 
				
			||||||
 | 
					    """HTTP 416 - Requested Range Not Satisfiable.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    The client has asked for a portion of the file, but the server cannot
 | 
				
			||||||
 | 
					    supply that portion.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    http_status = 416
 | 
				
			||||||
 | 
					    message = _("Requested Range Not Satisfiable")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ExpectationFailed(HTTPClientError):
 | 
				
			||||||
 | 
					    """HTTP 417 - Expectation Failed.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    The server cannot meet the requirements of the Expect request-header field.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    http_status = 417
 | 
				
			||||||
 | 
					    message = _("Expectation Failed")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UnprocessableEntity(HTTPClientError):
 | 
				
			||||||
 | 
					    """HTTP 422 - Unprocessable Entity.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    The request was well-formed but was unable to be followed due to semantic
 | 
				
			||||||
 | 
					    errors.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    http_status = 422
 | 
				
			||||||
 | 
					    message = _("Unprocessable Entity")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class InternalServerError(HttpServerError):
 | 
				
			||||||
 | 
					    """HTTP 500 - Internal Server Error.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    A generic error message, given when no more specific message is suitable.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    http_status = 500
 | 
				
			||||||
 | 
					    message = _("Internal Server Error")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# NotImplemented is a python keyword.
 | 
				
			||||||
 | 
					class HttpNotImplemented(HttpServerError):
 | 
				
			||||||
 | 
					    """HTTP 501 - Not Implemented.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    The server either does not recognize the request method, or it lacks
 | 
				
			||||||
 | 
					    the ability to fulfill the request.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    http_status = 501
 | 
				
			||||||
 | 
					    message = _("Not Implemented")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BadGateway(HttpServerError):
 | 
				
			||||||
 | 
					    """HTTP 502 - Bad Gateway.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    The server was acting as a gateway or proxy and received an invalid
 | 
				
			||||||
 | 
					    response from the upstream server.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    http_status = 502
 | 
				
			||||||
 | 
					    message = _("Bad Gateway")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ServiceUnavailable(HttpServerError):
 | 
				
			||||||
 | 
					    """HTTP 503 - Service Unavailable.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    The server is currently unavailable.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    http_status = 503
 | 
				
			||||||
 | 
					    message = _("Service Unavailable")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class GatewayTimeout(HttpServerError):
 | 
				
			||||||
 | 
					    """HTTP 504 - Gateway Timeout.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    The server was acting as a gateway or proxy and did not receive a timely
 | 
				
			||||||
 | 
					    response from the upstream server.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    http_status = 504
 | 
				
			||||||
 | 
					    message = _("Gateway Timeout")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class HttpVersionNotSupported(HttpServerError):
 | 
				
			||||||
 | 
					    """HTTP 505 - HttpVersion Not Supported.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    The server does not support the HTTP protocol version used in the request.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    http_status = 505
 | 
				
			||||||
 | 
					    message = _("HTTP Version Not Supported")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# _code_map contains all the classes that have http_status attribute.
 | 
				
			||||||
 | 
					_code_map = dict(
 | 
				
			||||||
 | 
					    (getattr(obj, 'http_status', None), obj)
 | 
				
			||||||
 | 
					    for name, obj in vars(sys.modules[__name__]).items()
 | 
				
			||||||
 | 
					    if inspect.isclass(obj) and getattr(obj, 'http_status', False)
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def from_response(response, method, url):
 | 
				
			||||||
 | 
					    """Returns an instance of :class:`HttpError` or subclass based on response.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :param response: instance of `requests.Response` class
 | 
				
			||||||
 | 
					    :param method: HTTP method used for request
 | 
				
			||||||
 | 
					    :param url: URL used for request
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    req_id = response.headers.get("x-openstack-request-id")
 | 
				
			||||||
 | 
					    if not req_id:
 | 
				
			||||||
 | 
					        req_id = response.headers.get("x-compute-request-id")
 | 
				
			||||||
 | 
					    kwargs = {
 | 
				
			||||||
 | 
					        "http_status": response.status_code,
 | 
				
			||||||
 | 
					        "response": response,
 | 
				
			||||||
 | 
					        "method": method,
 | 
				
			||||||
 | 
					        "url": url,
 | 
				
			||||||
 | 
					        "request_id": req_id,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if "retry-after" in response.headers:
 | 
				
			||||||
 | 
					        kwargs["retry_after"] = response.headers["retry-after"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    content_type = response.headers.get("Content-Type", "")
 | 
				
			||||||
 | 
					    if content_type.startswith("application/json"):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            body = response.json()
 | 
				
			||||||
 | 
					        except ValueError:
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            if isinstance(body, dict):
 | 
				
			||||||
 | 
					                error = body.get(list(body)[0])
 | 
				
			||||||
 | 
					                if isinstance(error, dict):
 | 
				
			||||||
 | 
					                    kwargs["message"] = (error.get("message") or
 | 
				
			||||||
 | 
					                                         error.get("faultstring"))
 | 
				
			||||||
 | 
					                    kwargs["details"] = (error.get("details") or
 | 
				
			||||||
 | 
					                                         six.text_type(body))
 | 
				
			||||||
 | 
					    elif content_type.startswith("text/"):
 | 
				
			||||||
 | 
					        kwargs["details"] = getattr(response, 'text', '')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        cls = _code_map[response.status_code]
 | 
				
			||||||
 | 
					    except KeyError:
 | 
				
			||||||
 | 
					        if 500 <= response.status_code < 600:
 | 
				
			||||||
 | 
					            cls = HttpServerError
 | 
				
			||||||
 | 
					        elif 400 <= response.status_code < 500:
 | 
				
			||||||
 | 
					            cls = HTTPClientError
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            cls = HttpError
 | 
				
			||||||
 | 
					    return cls(**kwargs)
 | 
				
			||||||
							
								
								
									
										160
									
								
								gyanclient/common/base.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										160
									
								
								gyanclient/common/base.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,160 @@
 | 
				
			|||||||
 | 
					#    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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					Base utilities to build API operation managers and objects on top of.
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import copy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import six.moves.urllib.parse as urlparse
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from gyanclient.common.apiclient import base
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def getid(obj):
 | 
				
			||||||
 | 
					    """Wrapper to get  object's ID.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Abstracts the common pattern of allowing both an object or an
 | 
				
			||||||
 | 
					    object's ID (UUID) as a parameter when dealing with relationships.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        return obj.id
 | 
				
			||||||
 | 
					    except AttributeError:
 | 
				
			||||||
 | 
					        return obj
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Manager(object):
 | 
				
			||||||
 | 
					    """Provides  CRUD operations with a particular API."""
 | 
				
			||||||
 | 
					    resource_class = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, api):
 | 
				
			||||||
 | 
					        self.api = api
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def api_version(self):
 | 
				
			||||||
 | 
					        return self.api.api_version
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _create(self, url, body):
 | 
				
			||||||
 | 
					        resp, body = self.api.json_request('POST', url, body=body)
 | 
				
			||||||
 | 
					        if body:
 | 
				
			||||||
 | 
					            return self.resource_class(self, body)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _format_body_data(self, body, response_key):
 | 
				
			||||||
 | 
					        if response_key:
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                data = body[response_key]
 | 
				
			||||||
 | 
					            except KeyError:
 | 
				
			||||||
 | 
					                return []
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            data = body
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not isinstance(data, list):
 | 
				
			||||||
 | 
					            data = [data]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _list_pagination(self, url, response_key=None, obj_class=None,
 | 
				
			||||||
 | 
					                         limit=None):
 | 
				
			||||||
 | 
					        """Retrieve a list of items.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        The Gyan API is configured to return a maximum number of
 | 
				
			||||||
 | 
					        items per request, (FIXME: see Gyan's api.max_limit option). This
 | 
				
			||||||
 | 
					        iterates over the 'next' link (pagination) in the responses,
 | 
				
			||||||
 | 
					        to get the number of items specified by 'limit'. If 'limit'
 | 
				
			||||||
 | 
					        is None this function will continue pagination until there are
 | 
				
			||||||
 | 
					        no more values to be returned.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param url: a partial URL, e.g. '/nodes'
 | 
				
			||||||
 | 
					        :param response_key: the key to be looked up in response
 | 
				
			||||||
 | 
					            dictionary, e.g. 'nodes'
 | 
				
			||||||
 | 
					        :param obj_class: class for constructing the returned objects.
 | 
				
			||||||
 | 
					        :param limit: maximum number of items to return. If None returns
 | 
				
			||||||
 | 
					            everything.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if obj_class is None:
 | 
				
			||||||
 | 
					            obj_class = self.resource_class
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if limit is not None:
 | 
				
			||||||
 | 
					            limit = int(limit)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        object_list = []
 | 
				
			||||||
 | 
					        object_count = 0
 | 
				
			||||||
 | 
					        limit_reached = False
 | 
				
			||||||
 | 
					        while url:
 | 
				
			||||||
 | 
					            resp, body = self.api.json_request('GET', url)
 | 
				
			||||||
 | 
					            data = self._format_body_data(body, response_key)
 | 
				
			||||||
 | 
					            for obj in data:
 | 
				
			||||||
 | 
					                object_list.append(obj_class(self, obj, loaded=True))
 | 
				
			||||||
 | 
					                object_count += 1
 | 
				
			||||||
 | 
					                if limit and object_count >= limit:
 | 
				
			||||||
 | 
					                    # break the for loop
 | 
				
			||||||
 | 
					                    limit_reached = True
 | 
				
			||||||
 | 
					                    break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # break the while loop and return
 | 
				
			||||||
 | 
					            if limit_reached:
 | 
				
			||||||
 | 
					                break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            url = body.get('next')
 | 
				
			||||||
 | 
					            if url:
 | 
				
			||||||
 | 
					                url_parts = list(urlparse.urlparse(url))
 | 
				
			||||||
 | 
					                url_parts[0] = url_parts[1] = ''
 | 
				
			||||||
 | 
					                url = urlparse.urlunparse(url_parts)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return object_list
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _list(self, url, response_key=None, obj_class=None, body=None,
 | 
				
			||||||
 | 
					              qparams=None):
 | 
				
			||||||
 | 
					        if qparams:
 | 
				
			||||||
 | 
					            url = "%s?%s" % (url, urlparse.urlencode(qparams))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        resp, body = self.api.json_request('GET', url)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if obj_class is None:
 | 
				
			||||||
 | 
					            obj_class = self.resource_class
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        data = self._format_body_data(body, response_key)
 | 
				
			||||||
 | 
					        return [obj_class(self, res, loaded=True) for res in data if res]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _update(self, url, body, method='PATCH', response_key=None):
 | 
				
			||||||
 | 
					        resp, body = self.api.json_request(method, url, body=body)
 | 
				
			||||||
 | 
					        # PATCH/PUT requests may not return a body
 | 
				
			||||||
 | 
					        if body:
 | 
				
			||||||
 | 
					            return self.resource_class(self, body)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _delete(self, url, qparams=None):
 | 
				
			||||||
 | 
					        if qparams:
 | 
				
			||||||
 | 
					            url = "%s?%s" % (url, urlparse.urlencode(qparams))
 | 
				
			||||||
 | 
					        self.api.raw_request('DELETE', url)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _search(self, url, qparams=None, response_key=None, obj_class=None,
 | 
				
			||||||
 | 
					                body=None):
 | 
				
			||||||
 | 
					        if qparams:
 | 
				
			||||||
 | 
					            url = "%s?%s" % (url, urlparse.urlencode(qparams))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        resp, body = self.api.json_request('GET', url, body=body)
 | 
				
			||||||
 | 
					        data = self._format_body_data(body, response_key)
 | 
				
			||||||
 | 
					        if obj_class is None:
 | 
				
			||||||
 | 
					            obj_class = self.resource_class
 | 
				
			||||||
 | 
					        return [obj_class(self, res, loaded=True) for res in data if res]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Resource(base.Resource):
 | 
				
			||||||
 | 
					    """Represents a particular instance of an object (tenant, user, etc).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    This is pretty much just a bag for attributes.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def to_dict(self):
 | 
				
			||||||
 | 
					        return copy.deepcopy(self._info)
 | 
				
			||||||
							
								
								
									
										319
									
								
								gyanclient/common/cliutils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										319
									
								
								gyanclient/common/cliutils.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,319 @@
 | 
				
			|||||||
 | 
					#    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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from __future__ import print_function
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import collections
 | 
				
			||||||
 | 
					import getpass
 | 
				
			||||||
 | 
					import inspect
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					import textwrap
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import decorator
 | 
				
			||||||
 | 
					from oslo_utils import encodeutils
 | 
				
			||||||
 | 
					from oslo_utils import strutils
 | 
				
			||||||
 | 
					import prettytable
 | 
				
			||||||
 | 
					import six
 | 
				
			||||||
 | 
					from six import moves
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from gyanclient.i18n import _
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class MissingArgs(Exception):
 | 
				
			||||||
 | 
					    """Supplied arguments are not sufficient for calling a function."""
 | 
				
			||||||
 | 
					    def __init__(self, missing):
 | 
				
			||||||
 | 
					        self.missing = missing
 | 
				
			||||||
 | 
					        msg = _("Missing arguments: %s") % ", ".join(missing)
 | 
				
			||||||
 | 
					        super(MissingArgs, self).__init__(msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def validate_args(fn, *args, **kwargs):
 | 
				
			||||||
 | 
					    """Check that the supplied args are sufficient for calling a function.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    >>> validate_args(lambda a: None)
 | 
				
			||||||
 | 
					    Traceback (most recent call last):
 | 
				
			||||||
 | 
					        ...
 | 
				
			||||||
 | 
					    MissingArgs: Missing argument(s): a
 | 
				
			||||||
 | 
					    >>> validate_args(lambda a, b, c, d: None, 0, c=1)
 | 
				
			||||||
 | 
					    Traceback (most recent call last):
 | 
				
			||||||
 | 
					        ...
 | 
				
			||||||
 | 
					    MissingArgs: Missing argument(s): b, d
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :param fn: the function to check
 | 
				
			||||||
 | 
					    :param arg: the positional arguments supplied
 | 
				
			||||||
 | 
					    :param kwargs: the keyword arguments supplied
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    argspec = inspect.getargspec(fn)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    num_defaults = len(argspec.defaults or [])
 | 
				
			||||||
 | 
					    required_args = argspec.args[:len(argspec.args) - num_defaults]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def isbound(method):
 | 
				
			||||||
 | 
					        return getattr(method, '__self__', None) is not None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if isbound(fn):
 | 
				
			||||||
 | 
					        required_args.pop(0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    missing = [arg for arg in required_args if arg not in kwargs]
 | 
				
			||||||
 | 
					    missing = missing[len(args):]
 | 
				
			||||||
 | 
					    if missing:
 | 
				
			||||||
 | 
					        raise MissingArgs(missing)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def arg(*args, **kwargs):
 | 
				
			||||||
 | 
					    """Decorator for CLI args.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Example:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    >>> @arg("name", help="Name of the new entity")
 | 
				
			||||||
 | 
					    ... def entity_create(args):
 | 
				
			||||||
 | 
					    ...     pass
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    def _decorator(func):
 | 
				
			||||||
 | 
					        add_arg(func, *args, **kwargs)
 | 
				
			||||||
 | 
					        return func
 | 
				
			||||||
 | 
					    return _decorator
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def exclusive_arg(group_name, *args, **kwargs):
 | 
				
			||||||
 | 
					    """Decorator for CLI mutually exclusive args."""
 | 
				
			||||||
 | 
					    def _decorator(func):
 | 
				
			||||||
 | 
					        required = kwargs.pop('required', None)
 | 
				
			||||||
 | 
					        add_exclusive_arg(func, group_name, required, *args, **kwargs)
 | 
				
			||||||
 | 
					        return func
 | 
				
			||||||
 | 
					    return _decorator
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def env(*args, **kwargs):
 | 
				
			||||||
 | 
					    """Returns the first environment variable set.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    If all are empty, defaults to '' or keyword arg `default`.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    for arg in args:
 | 
				
			||||||
 | 
					        value = os.environ.get(arg)
 | 
				
			||||||
 | 
					        if value:
 | 
				
			||||||
 | 
					            return value
 | 
				
			||||||
 | 
					    return kwargs.get('default', '')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def add_arg(func, *args, **kwargs):
 | 
				
			||||||
 | 
					    """Bind CLI arguments to a shell.py `do_foo` function."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if not hasattr(func, 'arguments'):
 | 
				
			||||||
 | 
					        func.arguments = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (args, kwargs) not in func.arguments:
 | 
				
			||||||
 | 
					        # Because of the semantics of decorator composition if we just append
 | 
				
			||||||
 | 
					        # to the options list positional options will appear to be backwards.
 | 
				
			||||||
 | 
					        func.arguments.insert(0, (args, kwargs))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def add_exclusive_arg(func, group_name, required, *args, **kwargs):
 | 
				
			||||||
 | 
					    """Bind CLI mutally exclusive arguments to a shell.py `do_foo` function."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if not hasattr(func, 'exclusive_args'):
 | 
				
			||||||
 | 
					        func.exclusive_args = collections.defaultdict(list)
 | 
				
			||||||
 | 
					        # Default required to False
 | 
				
			||||||
 | 
					        func.exclusive_args['__required__'] = collections.defaultdict(bool)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (args, kwargs) not in func.exclusive_args[group_name]:
 | 
				
			||||||
 | 
					        # Because of the semantics of decorator composition if we just append
 | 
				
			||||||
 | 
					        # to the options list positional options will appear to be backwards.
 | 
				
			||||||
 | 
					        func.exclusive_args[group_name].insert(0, (args, kwargs))
 | 
				
			||||||
 | 
					        if required is not None:
 | 
				
			||||||
 | 
					            func.exclusive_args['__required__'][group_name] = required
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def unauthenticated(func):
 | 
				
			||||||
 | 
					    """Adds 'unauthenticated' attribute to decorated function.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Usage:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    >>> @unauthenticated
 | 
				
			||||||
 | 
					    ... def mymethod(f):
 | 
				
			||||||
 | 
					    ...     pass
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    func.unauthenticated = True
 | 
				
			||||||
 | 
					    return func
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def isunauthenticated(func):
 | 
				
			||||||
 | 
					    """Checks if the function does not require authentication.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Mark such functions with the `@unauthenticated` decorator.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :returns: bool
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    return getattr(func, 'unauthenticated', False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def print_list(objs, fields, formatters=None, sortby_index=0,
 | 
				
			||||||
 | 
					               mixed_case_fields=None, field_labels=None):
 | 
				
			||||||
 | 
					    """Print a list or objects as a table, one row per object.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :param objs: iterable of :class:`Resource`
 | 
				
			||||||
 | 
					    :param fields: attributes that correspond to columns, in order
 | 
				
			||||||
 | 
					    :param formatters: `dict` of callables for field formatting
 | 
				
			||||||
 | 
					    :param sortby_index: index of the field for sorting table rows
 | 
				
			||||||
 | 
					    :param mixed_case_fields: fields corresponding to object attributes that
 | 
				
			||||||
 | 
					        have mixed case names (e.g., 'serverId')
 | 
				
			||||||
 | 
					    :param field_labels: Labels to use in the heading of the table, default to
 | 
				
			||||||
 | 
					        fields.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    formatters = formatters or {}
 | 
				
			||||||
 | 
					    mixed_case_fields = mixed_case_fields or []
 | 
				
			||||||
 | 
					    field_labels = field_labels or fields
 | 
				
			||||||
 | 
					    if len(field_labels) != len(fields):
 | 
				
			||||||
 | 
					        raise ValueError(_("Field labels list %(labels)s has different number "
 | 
				
			||||||
 | 
					                           "of elements than fields list %(fields)s"),
 | 
				
			||||||
 | 
					                         {'labels': field_labels, 'fields': fields})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if sortby_index is None:
 | 
				
			||||||
 | 
					        kwargs = {}
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        kwargs = {'sortby': field_labels[sortby_index]}
 | 
				
			||||||
 | 
					    pt = prettytable.PrettyTable(field_labels)
 | 
				
			||||||
 | 
					    pt.align = 'l'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for o in objs:
 | 
				
			||||||
 | 
					        row = []
 | 
				
			||||||
 | 
					        for field in fields:
 | 
				
			||||||
 | 
					            if field in formatters:
 | 
				
			||||||
 | 
					                row.append(formatters[field](o))
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                if field in mixed_case_fields:
 | 
				
			||||||
 | 
					                    field_name = field.replace(' ', '_')
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    field_name = field.lower().replace(' ', '_')
 | 
				
			||||||
 | 
					                data = getattr(o, field_name, '')
 | 
				
			||||||
 | 
					                row.append(data)
 | 
				
			||||||
 | 
					        pt.add_row(row)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if six.PY3:
 | 
				
			||||||
 | 
					        print(encodeutils.safe_encode(pt.get_string(**kwargs)).decode())
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        print(encodeutils.safe_encode(pt.get_string(**kwargs)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def keys_and_vals_to_strs(dictionary):
 | 
				
			||||||
 | 
					    """Recursively convert a dictionary's keys and values to strings.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :param dictionary: dictionary whose keys/vals are to be converted to strs
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    def to_str(k_or_v):
 | 
				
			||||||
 | 
					        if isinstance(k_or_v, dict):
 | 
				
			||||||
 | 
					            return keys_and_vals_to_strs(k_or_v)
 | 
				
			||||||
 | 
					        elif isinstance(k_or_v, six.text_type):
 | 
				
			||||||
 | 
					            return str(k_or_v)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            return k_or_v
 | 
				
			||||||
 | 
					    return dict((to_str(k), to_str(v)) for k, v in dictionary.items())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def print_dict(dct, dict_property="Property", wrap=0):
 | 
				
			||||||
 | 
					    """Print a `dict` as a table of two columns.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :param dct: `dict` to print
 | 
				
			||||||
 | 
					    :param dict_property: name of the first column
 | 
				
			||||||
 | 
					    :param wrap: wrapping for the second column
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    pt = prettytable.PrettyTable([dict_property, 'Value'])
 | 
				
			||||||
 | 
					    pt.align = 'l'
 | 
				
			||||||
 | 
					    for k, v in dct.items():
 | 
				
			||||||
 | 
					        # convert dict to str to check length
 | 
				
			||||||
 | 
					        if isinstance(v, dict):
 | 
				
			||||||
 | 
					            v = six.text_type(keys_and_vals_to_strs(v))
 | 
				
			||||||
 | 
					        if wrap > 0:
 | 
				
			||||||
 | 
					            v = textwrap.fill(six.text_type(v), wrap)
 | 
				
			||||||
 | 
					        elif wrap < 0:
 | 
				
			||||||
 | 
					            raise ValueError(_("Wrap argument should be a positive integer"))
 | 
				
			||||||
 | 
					        # if value has a newline, add in multiple rows
 | 
				
			||||||
 | 
					        # e.g. fault with stacktrace
 | 
				
			||||||
 | 
					        if v and isinstance(v, six.string_types) and r'\n' in v:
 | 
				
			||||||
 | 
					            lines = v.strip().split(r'\n')
 | 
				
			||||||
 | 
					            col1 = k
 | 
				
			||||||
 | 
					            for line in lines:
 | 
				
			||||||
 | 
					                pt.add_row([col1, line])
 | 
				
			||||||
 | 
					                col1 = ''
 | 
				
			||||||
 | 
					        elif isinstance(v, list):
 | 
				
			||||||
 | 
					            val = str([str(i) for i in v])
 | 
				
			||||||
 | 
					            pt.add_row([k, val])
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            pt.add_row([k, v])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if six.PY3:
 | 
				
			||||||
 | 
					        print(encodeutils.safe_encode(pt.get_string()).decode())
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        print(encodeutils.safe_encode(pt.get_string()))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_password(max_password_prompts=3):
 | 
				
			||||||
 | 
					    """Read password from TTY."""
 | 
				
			||||||
 | 
					    verify = strutils.bool_from_string(env("OS_VERIFY_PASSWORD"))
 | 
				
			||||||
 | 
					    pw = None
 | 
				
			||||||
 | 
					    if hasattr(sys.stdin, "isatty") and sys.stdin.isatty():
 | 
				
			||||||
 | 
					        # Check for Ctrl-D
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            for __ in moves.range(max_password_prompts):
 | 
				
			||||||
 | 
					                pw1 = getpass.getpass("OS Password: ")
 | 
				
			||||||
 | 
					                if verify:
 | 
				
			||||||
 | 
					                    pw2 = getpass.getpass("Please verify: ")
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    pw2 = pw1
 | 
				
			||||||
 | 
					                if pw1 == pw2 and pw1:
 | 
				
			||||||
 | 
					                    pw = pw1
 | 
				
			||||||
 | 
					                    break
 | 
				
			||||||
 | 
					        except EOFError:
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					    return pw
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def service_type(stype):
 | 
				
			||||||
 | 
					    """Adds 'service_type' attribute to decorated function.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Usage:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .. code-block:: python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       @service_type('volume')
 | 
				
			||||||
 | 
					       def mymethod(f):
 | 
				
			||||||
 | 
					       ...
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    def inner(f):
 | 
				
			||||||
 | 
					        f.service_type = stype
 | 
				
			||||||
 | 
					        return f
 | 
				
			||||||
 | 
					    return inner
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_service_type(f):
 | 
				
			||||||
 | 
					    """Retrieves service type from function."""
 | 
				
			||||||
 | 
					    return getattr(f, 'service_type', None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def pretty_choice_list(l):
 | 
				
			||||||
 | 
					    return ', '.join("'%s'" % i for i in l)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def exit(msg=''):
 | 
				
			||||||
 | 
					    if msg:
 | 
				
			||||||
 | 
					        print(msg, file=sys.stderr)
 | 
				
			||||||
 | 
					    sys.exit(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def deprecated(message):
 | 
				
			||||||
 | 
					    @decorator.decorator
 | 
				
			||||||
 | 
					    def wrapper(func, *args, **kwargs):
 | 
				
			||||||
 | 
					        print(message)
 | 
				
			||||||
 | 
					        return func(*args, **kwargs)
 | 
				
			||||||
 | 
					    return wrapper
 | 
				
			||||||
							
								
								
									
										421
									
								
								gyanclient/common/httpclient.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										421
									
								
								gyanclient/common/httpclient.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,421 @@
 | 
				
			|||||||
 | 
					#    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 copy
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					from oslo_log import log as logging
 | 
				
			||||||
 | 
					import socket
 | 
				
			||||||
 | 
					import ssl
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from keystoneauth1 import adapter
 | 
				
			||||||
 | 
					from oslo_utils import importutils
 | 
				
			||||||
 | 
					import six
 | 
				
			||||||
 | 
					import six.moves.urllib.parse as urlparse
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from gyanclient import api_versions
 | 
				
			||||||
 | 
					from gyanclient import exceptions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					osprofiler_web = importutils.try_import("osprofiler.web")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					LOG = logging.getLogger(__name__)
 | 
				
			||||||
 | 
					USER_AGENT = 'python-gyanclient'
 | 
				
			||||||
 | 
					CHUNKSIZE = 1024 * 64  # 64kB
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					API_VERSION = '/v1'
 | 
				
			||||||
 | 
					DEFAULT_API_VERSION = '1.latest'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _extract_error_json(body):
 | 
				
			||||||
 | 
					    """Return error_message from the HTTP response body."""
 | 
				
			||||||
 | 
					    error_json = {}
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        body_json = json.loads(body)
 | 
				
			||||||
 | 
					        if 'error_message' in body_json:
 | 
				
			||||||
 | 
					            raw_msg = body_json['error_message']
 | 
				
			||||||
 | 
					            error_json = json.loads(raw_msg)
 | 
				
			||||||
 | 
					        elif 'error' in body_json:
 | 
				
			||||||
 | 
					            error_body = body_json['error']
 | 
				
			||||||
 | 
					            error_json = {'faultstring': error_body['title'],
 | 
				
			||||||
 | 
					                          'debuginfo': error_body['message']}
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            error_body = body_json['errors'][0]
 | 
				
			||||||
 | 
					            error_json = {'faultstring': error_body['title']}
 | 
				
			||||||
 | 
					            if 'detail' in error_body:
 | 
				
			||||||
 | 
					                error_json['debuginfo'] = error_body['detail']
 | 
				
			||||||
 | 
					            elif 'description' in error_body:
 | 
				
			||||||
 | 
					                error_json['debuginfo'] = error_body['description']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    except ValueError:
 | 
				
			||||||
 | 
					        return {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return error_json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class HTTPClient(object):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, endpoint, api_version=DEFAULT_API_VERSION, **kwargs):
 | 
				
			||||||
 | 
					        self.endpoint = endpoint
 | 
				
			||||||
 | 
					        self.auth_token = kwargs.get('token')
 | 
				
			||||||
 | 
					        self.auth_ref = kwargs.get('auth_ref')
 | 
				
			||||||
 | 
					        self.api_version = api_version or api_versions.APIVersion()
 | 
				
			||||||
 | 
					        self.connection_params = self.get_connection_params(endpoint, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @staticmethod
 | 
				
			||||||
 | 
					    def get_connection_params(endpoint, **kwargs):
 | 
				
			||||||
 | 
					        parts = urlparse.urlparse(endpoint)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # trim API version and trailing slash from endpoint
 | 
				
			||||||
 | 
					        path = parts.path
 | 
				
			||||||
 | 
					        path = path.rstrip('/').rstrip(API_VERSION)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        _args = (parts.hostname, parts.port, path)
 | 
				
			||||||
 | 
					        _kwargs = {'timeout': (float(kwargs.get('timeout'))
 | 
				
			||||||
 | 
					                               if kwargs.get('timeout') else 600)}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if parts.scheme == 'https':
 | 
				
			||||||
 | 
					            _class = VerifiedHTTPSConnection
 | 
				
			||||||
 | 
					            _kwargs['ca_file'] = kwargs.get('ca_file', None)
 | 
				
			||||||
 | 
					            _kwargs['cert_file'] = kwargs.get('cert_file', None)
 | 
				
			||||||
 | 
					            _kwargs['key_file'] = kwargs.get('key_file', None)
 | 
				
			||||||
 | 
					            _kwargs['insecure'] = kwargs.get('insecure', False)
 | 
				
			||||||
 | 
					        elif parts.scheme == 'http':
 | 
				
			||||||
 | 
					            _class = six.moves.http_client.HTTPConnection
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            msg = 'Unsupported scheme: %s' % parts.scheme
 | 
				
			||||||
 | 
					            raise exceptions.EndpointException(msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return (_class, _args, _kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_connection(self):
 | 
				
			||||||
 | 
					        _class = self.connection_params[0]
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            return _class(*self.connection_params[1][0:2],
 | 
				
			||||||
 | 
					                          **self.connection_params[2])
 | 
				
			||||||
 | 
					        except six.moves.http_client.InvalidURL:
 | 
				
			||||||
 | 
					            raise exceptions.EndpointException()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def log_curl_request(self, method, url, kwargs):
 | 
				
			||||||
 | 
					        curl = ['curl -i -X %s' % method]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (key, value) in kwargs['headers'].items():
 | 
				
			||||||
 | 
					            header = '-H \'%s: %s\'' % (key, value)
 | 
				
			||||||
 | 
					            curl.append(header)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        conn_params_fmt = [
 | 
				
			||||||
 | 
					            ('key_file', '--key %s'),
 | 
				
			||||||
 | 
					            ('cert_file', '--cert %s'),
 | 
				
			||||||
 | 
					            ('ca_file', '--cacert %s'),
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					        for (key, fmt) in conn_params_fmt:
 | 
				
			||||||
 | 
					            value = self.connection_params[2].get(key)
 | 
				
			||||||
 | 
					            if value:
 | 
				
			||||||
 | 
					                curl.append(fmt % value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.connection_params[2].get('insecure'):
 | 
				
			||||||
 | 
					            curl.append('-k')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if 'body' in kwargs:
 | 
				
			||||||
 | 
					            curl.append('-d \'%s\'' % kwargs['body'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        curl.append('%s/%s' % (self.endpoint, url.lstrip(API_VERSION)))
 | 
				
			||||||
 | 
					        LOG.debug(' '.join(curl))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @staticmethod
 | 
				
			||||||
 | 
					    def log_http_response(resp, body=None):
 | 
				
			||||||
 | 
					        status = (resp.version / 10.0, resp.status, resp.reason)
 | 
				
			||||||
 | 
					        dump = ['\nHTTP/%.1f %s %s' % status]
 | 
				
			||||||
 | 
					        dump.extend(['%s: %s' % (k, v) for k, v in resp.getheaders()])
 | 
				
			||||||
 | 
					        dump.append('')
 | 
				
			||||||
 | 
					        if body:
 | 
				
			||||||
 | 
					            dump.extend([body, ''])
 | 
				
			||||||
 | 
					        LOG.debug('\n'.join(dump))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _make_connection_url(self, url):
 | 
				
			||||||
 | 
					        (_class, _args, _kwargs) = self.connection_params
 | 
				
			||||||
 | 
					        base_url = _args[2]
 | 
				
			||||||
 | 
					        return '%s/%s' % (base_url, url.lstrip('/'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _http_request(self, url, method, **kwargs):
 | 
				
			||||||
 | 
					        """Send an http request with the specified characteristics.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Wrapper around httplib.HTTP(S)Connection.request to handle tasks such
 | 
				
			||||||
 | 
					        as setting headers and error handling.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        # Copy the kwargs so we can reuse the original in case of redirects
 | 
				
			||||||
 | 
					        kwargs['headers'] = copy.deepcopy(kwargs.get('headers', {}))
 | 
				
			||||||
 | 
					        kwargs['headers'].setdefault('User-Agent', USER_AGENT)
 | 
				
			||||||
 | 
					        api_versions.update_headers(kwargs["headers"], self.api_version)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.auth_token:
 | 
				
			||||||
 | 
					            kwargs['headers'].setdefault('X-Auth-Token', self.auth_token)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.log_curl_request(method, url, kwargs)
 | 
				
			||||||
 | 
					        conn = self.get_connection()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            conn_url = self._make_connection_url(url)
 | 
				
			||||||
 | 
					            conn.request(method, conn_url, **kwargs)
 | 
				
			||||||
 | 
					            resp = conn.getresponse()
 | 
				
			||||||
 | 
					        except socket.gaierror as e:
 | 
				
			||||||
 | 
					            message = ("Error finding address for %(url)s: %(e)s"
 | 
				
			||||||
 | 
					                       % dict(url=url, e=e))
 | 
				
			||||||
 | 
					            raise exceptions.EndpointNotFound(message)
 | 
				
			||||||
 | 
					        except (socket.error, socket.timeout) as e:
 | 
				
			||||||
 | 
					            endpoint = self.endpoint
 | 
				
			||||||
 | 
					            message = ("Error communicating with %(endpoint)s %(e)s"
 | 
				
			||||||
 | 
					                       % dict(endpoint=endpoint, e=e))
 | 
				
			||||||
 | 
					            raise exceptions.ConnectionRefused(message)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        body_iter = ResponseBodyIterator(resp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Read body into string if it isn't obviously image data
 | 
				
			||||||
 | 
					        body_str = None
 | 
				
			||||||
 | 
					        if resp.getheader('content-type', None) != 'application/octet-stream':
 | 
				
			||||||
 | 
					            # decoding byte to string is necessary for Python 3.4 compatibility
 | 
				
			||||||
 | 
					            # this issues has not been found with Python 3.4 unit tests
 | 
				
			||||||
 | 
					            # because the test creates a fake http response of type str
 | 
				
			||||||
 | 
					            # the if statement satisfies test (str) and real (bytes) behavior
 | 
				
			||||||
 | 
					            body_list = [
 | 
				
			||||||
 | 
					                chunk.decode("utf-8") if isinstance(chunk, bytes)
 | 
				
			||||||
 | 
					                else chunk for chunk in body_iter
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					            body_str = ''.join(body_list)
 | 
				
			||||||
 | 
					            self.log_http_response(resp, body_str)
 | 
				
			||||||
 | 
					            body_iter = six.StringIO(body_str)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.log_http_response(resp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if 400 <= resp.status < 600:
 | 
				
			||||||
 | 
					            LOG.warning("Request returned failure status.")
 | 
				
			||||||
 | 
					            error_json = _extract_error_json(body_str)
 | 
				
			||||||
 | 
					            raise exceptions.from_response(
 | 
				
			||||||
 | 
					                resp, error_json.get('faultstring'),
 | 
				
			||||||
 | 
					                error_json.get('debuginfo'), method, url)
 | 
				
			||||||
 | 
					        elif resp.status in (301, 302, 305):
 | 
				
			||||||
 | 
					            # Redirected. Reissue the request to the new location.
 | 
				
			||||||
 | 
					            return self._http_request(resp['location'], method, **kwargs)
 | 
				
			||||||
 | 
					        elif resp.status == 300:
 | 
				
			||||||
 | 
					            raise exceptions.from_response(resp, method=method, url=url)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return resp, body_iter
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def json_request(self, method, url, **kwargs):
 | 
				
			||||||
 | 
					        kwargs.setdefault('headers', {})
 | 
				
			||||||
 | 
					        kwargs['headers'].setdefault('Content-Type', 'application/json')
 | 
				
			||||||
 | 
					        kwargs['headers'].setdefault('Accept', 'application/json')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if 'body' in kwargs:
 | 
				
			||||||
 | 
					            kwargs['body'] = json.dumps(kwargs['body'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        resp, body_iter = self._http_request(url, method, **kwargs)
 | 
				
			||||||
 | 
					        content_type = resp.getheader('content-type', None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if resp.status == 204 or resp.status == 205 or content_type is None:
 | 
				
			||||||
 | 
					            return resp, list()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if 'application/json' in content_type:
 | 
				
			||||||
 | 
					            body = ''.join([chunk for chunk in body_iter])
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                body = json.loads(body)
 | 
				
			||||||
 | 
					            except ValueError:
 | 
				
			||||||
 | 
					                LOG.error('Could not decode response body as JSON')
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            body = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return resp, body
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def raw_request(self, method, url, **kwargs):
 | 
				
			||||||
 | 
					        kwargs.setdefault('headers', {})
 | 
				
			||||||
 | 
					        kwargs['headers'].setdefault('Content-Type',
 | 
				
			||||||
 | 
					                                     'application/octet-stream')
 | 
				
			||||||
 | 
					        return self._http_request(url, method, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class VerifiedHTTPSConnection(six.moves.http_client.HTTPSConnection):
 | 
				
			||||||
 | 
					    """httplib-compatibile connection using client-side SSL authentication
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :see http://code.activestate.com/recipes/
 | 
				
			||||||
 | 
					            577548-https-httplib-client-connection-with-certificate-v/
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, host, port, key_file=None, cert_file=None,
 | 
				
			||||||
 | 
					                 ca_file=None, timeout=None, insecure=False):
 | 
				
			||||||
 | 
					        six.moves.http_client.HTTPSConnection.__init__(self, host, port,
 | 
				
			||||||
 | 
					                                                       key_file=key_file,
 | 
				
			||||||
 | 
					                                                       cert_file=cert_file)
 | 
				
			||||||
 | 
					        self.key_file = key_file
 | 
				
			||||||
 | 
					        self.cert_file = cert_file
 | 
				
			||||||
 | 
					        if ca_file is not None:
 | 
				
			||||||
 | 
					            self.ca_file = ca_file
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.ca_file = self.get_system_ca_file()
 | 
				
			||||||
 | 
					        self.timeout = timeout
 | 
				
			||||||
 | 
					        self.insecure = insecure
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def connect(self):
 | 
				
			||||||
 | 
					        """Connect to a host on a given (SSL) port.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        If ca_file is pointing somewhere, use it to check Server Certificate.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Redefined/copied and extended from httplib.py:1105 (Python 2.6.x).
 | 
				
			||||||
 | 
					        This is needed to pass cert_reqs=ssl.CERT_REQUIRED as parameter to
 | 
				
			||||||
 | 
					        ssl.wrap_socket(), which forces SSL to check server certificate against
 | 
				
			||||||
 | 
					        our client certificate.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        sock = socket.create_connection((self.host, self.port), self.timeout)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self._tunnel_host:
 | 
				
			||||||
 | 
					            self.sock = sock
 | 
				
			||||||
 | 
					            self._tunnel()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.insecure is True:
 | 
				
			||||||
 | 
					            kwargs = {'cert_reqs': ssl.CERT_NONE}
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            kwargs = {'cert_reqs': ssl.CERT_REQUIRED, 'ca_certs': self.ca_file}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.cert_file:
 | 
				
			||||||
 | 
					            kwargs['certfile'] = self.cert_file
 | 
				
			||||||
 | 
					            if self.key_file:
 | 
				
			||||||
 | 
					                kwargs['keyfile'] = self.key_file
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.sock = ssl.wrap_socket(sock, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @staticmethod
 | 
				
			||||||
 | 
					    def get_system_ca_file():
 | 
				
			||||||
 | 
					        """Return path to system default CA file."""
 | 
				
			||||||
 | 
					        # Standard CA file locations for Debian/Ubuntu, RedHat/Fedora,
 | 
				
			||||||
 | 
					        # Suse, FreeBSD/OpenBSD
 | 
				
			||||||
 | 
					        ca_path = ['/etc/ssl/certs/ca-certificates.crt',
 | 
				
			||||||
 | 
					                   '/etc/pki/tls/certs/ca-bundle.crt',
 | 
				
			||||||
 | 
					                   '/etc/ssl/ca-bundle.pem',
 | 
				
			||||||
 | 
					                   '/etc/ssl/cert.pem']
 | 
				
			||||||
 | 
					        for ca in ca_path:
 | 
				
			||||||
 | 
					            if os.path.exists(ca):
 | 
				
			||||||
 | 
					                return ca
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SessionClient(adapter.LegacyJsonAdapter):
 | 
				
			||||||
 | 
					    """HTTP client based on Keystone client session."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, user_agent=USER_AGENT, logger=LOG,
 | 
				
			||||||
 | 
					                 api_version=DEFAULT_API_VERSION, *args, **kwargs):
 | 
				
			||||||
 | 
					        self.user_agent = USER_AGENT
 | 
				
			||||||
 | 
					        self.api_version = api_version or api_versions.APIVersion()
 | 
				
			||||||
 | 
					        super(SessionClient, self).__init__(*args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _http_request(self, url, method, **kwargs):
 | 
				
			||||||
 | 
					        if url.startswith(API_VERSION):
 | 
				
			||||||
 | 
					            url = url[len(API_VERSION):]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        kwargs.setdefault('user_agent', self.user_agent)
 | 
				
			||||||
 | 
					        kwargs.setdefault('auth', self.auth)
 | 
				
			||||||
 | 
					        kwargs.setdefault('endpoint_override', self.endpoint_override)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Copy the kwargs so we can reuse the original in case of redirects
 | 
				
			||||||
 | 
					        kwargs['headers'] = copy.deepcopy(kwargs.get('headers', {}))
 | 
				
			||||||
 | 
					        kwargs['headers'].setdefault('User-Agent', self.user_agent)
 | 
				
			||||||
 | 
					        api_versions.update_headers(kwargs["headers"], self.api_version)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if osprofiler_web:
 | 
				
			||||||
 | 
					            kwargs['headers'].update(osprofiler_web.get_trace_id_headers())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        endpoint_filter = kwargs.setdefault('endpoint_filter', {})
 | 
				
			||||||
 | 
					        endpoint_filter.setdefault('interface', self.interface)
 | 
				
			||||||
 | 
					        endpoint_filter.setdefault('service_type', self.service_type)
 | 
				
			||||||
 | 
					        endpoint_filter.setdefault('region_name', self.region_name)
 | 
				
			||||||
 | 
					        resp = self.session.request(url, method,
 | 
				
			||||||
 | 
					                                    raise_exc=False, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if 400 <= resp.status_code < 600:
 | 
				
			||||||
 | 
					            error_json = _extract_error_json(resp.content)
 | 
				
			||||||
 | 
					            raise exceptions.from_response(
 | 
				
			||||||
 | 
					                resp, error_json.get('faultstring'),
 | 
				
			||||||
 | 
					                error_json.get('debuginfo'), method, url)
 | 
				
			||||||
 | 
					        elif resp.status_code in (301, 302, 305):
 | 
				
			||||||
 | 
					            # Redirected. Reissue the request to the new location.
 | 
				
			||||||
 | 
					            location = resp.headers.get('location')
 | 
				
			||||||
 | 
					            resp = self._http_request(location, method, **kwargs)
 | 
				
			||||||
 | 
					        elif resp.status_code == 300:
 | 
				
			||||||
 | 
					            raise exceptions.from_response(resp, method=method, url=url)
 | 
				
			||||||
 | 
					        return resp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def json_request(self, method, url, **kwargs):
 | 
				
			||||||
 | 
					        kwargs.setdefault('headers', {})
 | 
				
			||||||
 | 
					        kwargs['headers'].setdefault('Content-Type', 'application/json')
 | 
				
			||||||
 | 
					        kwargs['headers'].setdefault('Accept', 'application/json')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if 'body' in kwargs:
 | 
				
			||||||
 | 
					            kwargs['data'] = json.dumps(kwargs.pop('body'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        resp = self._http_request(url, method, **kwargs)
 | 
				
			||||||
 | 
					        body = resp.content
 | 
				
			||||||
 | 
					        content_type = resp.headers.get('content-type', None)
 | 
				
			||||||
 | 
					        status = resp.status_code
 | 
				
			||||||
 | 
					        if status == 204 or status == 205 or content_type is None:
 | 
				
			||||||
 | 
					            return resp, list()
 | 
				
			||||||
 | 
					        if 'application/json' in content_type:
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                body = resp.json()
 | 
				
			||||||
 | 
					            except ValueError:
 | 
				
			||||||
 | 
					                LOG.error('Could not decode response body as JSON')
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            body = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return resp, body
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def raw_request(self, method, url, **kwargs):
 | 
				
			||||||
 | 
					        kwargs.setdefault('headers', {})
 | 
				
			||||||
 | 
					        kwargs['headers'].setdefault('Content-Type',
 | 
				
			||||||
 | 
					                                     'application/octet-stream')
 | 
				
			||||||
 | 
					        return self._http_request(url, method, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ResponseBodyIterator(object):
 | 
				
			||||||
 | 
					    """A class that acts as an iterator over an HTTP response."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, resp):
 | 
				
			||||||
 | 
					        self.resp = resp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __iter__(self):
 | 
				
			||||||
 | 
					        while True:
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                yield self.next()
 | 
				
			||||||
 | 
					            except StopIteration:
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def next(self):
 | 
				
			||||||
 | 
					        chunk = self.resp.read(CHUNKSIZE)
 | 
				
			||||||
 | 
					        if chunk:
 | 
				
			||||||
 | 
					            return chunk
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            raise StopIteration()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _construct_http_client(*args, **kwargs):
 | 
				
			||||||
 | 
					    session = kwargs.pop('session', None)
 | 
				
			||||||
 | 
					    auth = kwargs.pop('auth', None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if session:
 | 
				
			||||||
 | 
					        service_type = kwargs.pop('service_type', 'container')
 | 
				
			||||||
 | 
					        interface = kwargs.pop('endpoint_type', None)
 | 
				
			||||||
 | 
					        region_name = kwargs.pop('region_name', None)
 | 
				
			||||||
 | 
					        return SessionClient(session=session,
 | 
				
			||||||
 | 
					                             auth=auth,
 | 
				
			||||||
 | 
					                             interface=interface,
 | 
				
			||||||
 | 
					                             service_type=service_type,
 | 
				
			||||||
 | 
					                             region_name=region_name,
 | 
				
			||||||
 | 
					                             service_name=None,
 | 
				
			||||||
 | 
					                             user_agent='python-gyanclient')
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        return HTTPClient(*args, **kwargs)
 | 
				
			||||||
							
								
								
									
										64
									
								
								gyanclient/common/template_format.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								gyanclient/common/template_format.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,64 @@
 | 
				
			|||||||
 | 
					#    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 json
 | 
				
			||||||
 | 
					import yaml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if hasattr(yaml, 'CSafeDumper'):
 | 
				
			||||||
 | 
					    yaml_dumper_base = yaml.CSafeDumper
 | 
				
			||||||
 | 
					else:
 | 
				
			||||||
 | 
					    yaml_dumper_base = yaml.SafeDumper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# We create custom class to not overriden the default yaml behavior
 | 
				
			||||||
 | 
					class yaml_loader(yaml.SafeLoader):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class yaml_dumper(yaml_dumper_base):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _construct_yaml_str(self, node):
 | 
				
			||||||
 | 
					    # Override the default string handling function
 | 
				
			||||||
 | 
					    # to always return unicode objects
 | 
				
			||||||
 | 
					    return self.construct_scalar(node)
 | 
				
			||||||
 | 
					yaml_loader.add_constructor(u'tag:yaml.org,2002:str', _construct_yaml_str)
 | 
				
			||||||
 | 
					# Unquoted dates like 2013-05-23 in yaml files get loaded as objects of type
 | 
				
			||||||
 | 
					# datetime.data which causes problems in API layer when being processed by
 | 
				
			||||||
 | 
					# openstack.common.jsonutils. Therefore, make unicode string out of timestamps
 | 
				
			||||||
 | 
					# until jsonutils can handle dates.
 | 
				
			||||||
 | 
					yaml_loader.add_constructor(u'tag:yaml.org,2002:timestamp',
 | 
				
			||||||
 | 
					                            _construct_yaml_str)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def parse(tmpl_str):
 | 
				
			||||||
 | 
					    """Takes a string and returns a dict containing the parsed structure.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    This includes determination of whether the string is using the
 | 
				
			||||||
 | 
					    JSON or YAML format.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    # strip any whitespace before the check
 | 
				
			||||||
 | 
					    tmpl_str = tmpl_str.strip()
 | 
				
			||||||
 | 
					    if tmpl_str.startswith('{'):
 | 
				
			||||||
 | 
					        tpl = json.loads(tmpl_str)
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            tpl = yaml.safe_load(tmpl_str)
 | 
				
			||||||
 | 
					        except yaml.YAMLError as yea:
 | 
				
			||||||
 | 
					            raise ValueError(yea)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            if tpl is None:
 | 
				
			||||||
 | 
					                tpl = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return tpl
 | 
				
			||||||
							
								
								
									
										85
									
								
								gyanclient/common/template_utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								gyanclient/common/template_utils.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,85 @@
 | 
				
			|||||||
 | 
					#    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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from oslo_serialization import jsonutils
 | 
				
			||||||
 | 
					import six
 | 
				
			||||||
 | 
					from six.moves.urllib import parse
 | 
				
			||||||
 | 
					from six.moves.urllib import request
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from gyanclient.common import template_format
 | 
				
			||||||
 | 
					from gyanclient.common import utils
 | 
				
			||||||
 | 
					from gyanclient import exceptions
 | 
				
			||||||
 | 
					from gyanclient.i18n import _
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_template_contents(template_file=None, template_url=None,
 | 
				
			||||||
 | 
					                          files=None):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Transform a bare file path to a file:// URL.
 | 
				
			||||||
 | 
					    if template_file:  # nosec
 | 
				
			||||||
 | 
					        template_url = utils.normalise_file_path_to_url(template_file)
 | 
				
			||||||
 | 
					        tpl = request.urlopen(template_url).read()
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        raise exceptions.CommandErrorException(_('Need to specify exactly '
 | 
				
			||||||
 | 
					                                                 'one of %(arg1)s, %(arg2)s '
 | 
				
			||||||
 | 
					                                                 'or %(arg3)s') %
 | 
				
			||||||
 | 
					                                               {'arg1': '--template-file',
 | 
				
			||||||
 | 
					                                                'arg2': '--template-url'})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if not tpl:
 | 
				
			||||||
 | 
					        raise exceptions.CommandErrorException(_('Could not fetch '
 | 
				
			||||||
 | 
					                                                 'template from %s') %
 | 
				
			||||||
 | 
					                                               template_url)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        if isinstance(tpl, six.binary_type):
 | 
				
			||||||
 | 
					            tpl = tpl.decode('utf-8')
 | 
				
			||||||
 | 
					        template = template_format.parse(tpl)
 | 
				
			||||||
 | 
					    except ValueError as e:
 | 
				
			||||||
 | 
					        raise exceptions.CommandErrorException(_('Error parsing template '
 | 
				
			||||||
 | 
					                                                 '%(url)s %(error)s') %
 | 
				
			||||||
 | 
					                                               {'url': template_url,
 | 
				
			||||||
 | 
					                                                'error': e})
 | 
				
			||||||
 | 
					    return template
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def is_template(file_content):
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        if isinstance(file_content, six.binary_type):
 | 
				
			||||||
 | 
					            file_content = file_content.decode('utf-8')
 | 
				
			||||||
 | 
					        template_format.parse(file_content)
 | 
				
			||||||
 | 
					    except (ValueError, TypeError):
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					    return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_file_contents(from_data, files, base_url=None,
 | 
				
			||||||
 | 
					                      ignore_if=None):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if isinstance(from_data, dict):
 | 
				
			||||||
 | 
					        for key, value in from_data.items():
 | 
				
			||||||
 | 
					            if ignore_if and ignore_if(key, value):
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if base_url and not base_url.endswith('/'):
 | 
				
			||||||
 | 
					                base_url = base_url + '/'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            str_url = parse.urljoin(base_url, value)
 | 
				
			||||||
 | 
					            if str_url not in files:
 | 
				
			||||||
 | 
					                file_content = utils.read_url_content(str_url)
 | 
				
			||||||
 | 
					                if is_template(file_content):
 | 
				
			||||||
 | 
					                    template = get_template_contents(
 | 
				
			||||||
 | 
					                        template_url=str_url, files=files)[1]
 | 
				
			||||||
 | 
					                    file_content = jsonutils.dumps(template)
 | 
				
			||||||
 | 
					                files[str_url] = file_content
 | 
				
			||||||
 | 
					            # replace the data value with the normalised absolute URL
 | 
				
			||||||
 | 
					            from_data[key] = str_url
 | 
				
			||||||
							
								
								
									
										173
									
								
								gyanclient/common/utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								gyanclient/common/utils.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,173 @@
 | 
				
			|||||||
 | 
					#    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 base64
 | 
				
			||||||
 | 
					import binascii
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import re
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from oslo_utils import netutils
 | 
				
			||||||
 | 
					import six
 | 
				
			||||||
 | 
					from six.moves.urllib import parse
 | 
				
			||||||
 | 
					from six.moves.urllib import request
 | 
				
			||||||
 | 
					from gyanclient.common.apiclient import exceptions as apiexec
 | 
				
			||||||
 | 
					from gyanclient.common import cliutils as utils
 | 
				
			||||||
 | 
					from gyanclient import exceptions as exc
 | 
				
			||||||
 | 
					from gyanclient.i18n import _
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					VALID_UNITS = (
 | 
				
			||||||
 | 
					    K,
 | 
				
			||||||
 | 
					    M,
 | 
				
			||||||
 | 
					    G,
 | 
				
			||||||
 | 
					) = (
 | 
				
			||||||
 | 
					    1024,
 | 
				
			||||||
 | 
					    1024 * 1024,
 | 
				
			||||||
 | 
					    1024 * 1024 * 1024,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def common_filters(marker=None, limit=None, sort_key=None,
 | 
				
			||||||
 | 
					                   sort_dir=None, all_projects=False):
 | 
				
			||||||
 | 
					    """Generate common filters for any list request.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :param all_projects: list containers in all projects or not
 | 
				
			||||||
 | 
					    :param marker: entity ID from which to start returning entities.
 | 
				
			||||||
 | 
					    :param limit: maximum number of entities to return.
 | 
				
			||||||
 | 
					    :param sort_key: field to use for sorting.
 | 
				
			||||||
 | 
					    :param sort_dir: direction of sorting: 'asc' or 'desc'.
 | 
				
			||||||
 | 
					    :returns: list of string filters.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    filters = []
 | 
				
			||||||
 | 
					    if all_projects is True:
 | 
				
			||||||
 | 
					        filters.append('all_projects=1')
 | 
				
			||||||
 | 
					    if isinstance(limit, int):
 | 
				
			||||||
 | 
					        filters.append('limit=%s' % limit)
 | 
				
			||||||
 | 
					    if marker is not None:
 | 
				
			||||||
 | 
					        filters.append('marker=%s' % marker)
 | 
				
			||||||
 | 
					    if sort_key is not None:
 | 
				
			||||||
 | 
					        filters.append('sort_key=%s' % sort_key)
 | 
				
			||||||
 | 
					    if sort_dir is not None:
 | 
				
			||||||
 | 
					        filters.append('sort_dir=%s' % sort_dir)
 | 
				
			||||||
 | 
					    return filters
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def split_and_deserialize(string):
 | 
				
			||||||
 | 
					    """Split and try to JSON deserialize a string.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Gets a string with the KEY=VALUE format, split it (using '=' as the
 | 
				
			||||||
 | 
					    separator) and try to JSON deserialize the VALUE.
 | 
				
			||||||
 | 
					    :returns: A tuple of (key, value).
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        key, value = string.split("=", 1)
 | 
				
			||||||
 | 
					    except ValueError:
 | 
				
			||||||
 | 
					        raise exc.CommandError(_('Attributes must be a list of '
 | 
				
			||||||
 | 
					                                 'PATH=VALUE not "%s"') % string)
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        value = json.loads(value)
 | 
				
			||||||
 | 
					    except ValueError:
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (key, value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def args_array_to_patch(attributes):
 | 
				
			||||||
 | 
					    patch = []
 | 
				
			||||||
 | 
					    for attr in attributes:
 | 
				
			||||||
 | 
					        path, value = split_and_deserialize(attr)
 | 
				
			||||||
 | 
					        patch.append({path: value})
 | 
				
			||||||
 | 
					    return patch
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def format_args(args, parse_comma=True):
 | 
				
			||||||
 | 
					    '''Reformat a list of key-value arguments into a dict.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Convert arguments into format expected by the API.
 | 
				
			||||||
 | 
					    '''
 | 
				
			||||||
 | 
					    if not args:
 | 
				
			||||||
 | 
					        return {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if parse_comma:
 | 
				
			||||||
 | 
					        # expect multiple invocations of --label (or other arguments) but fall
 | 
				
			||||||
 | 
					        # back to either , or ; delimited if only one --label is specified
 | 
				
			||||||
 | 
					        if len(args) == 1:
 | 
				
			||||||
 | 
					            args = args[0].replace(';', ',').split(',')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fmt_args = {}
 | 
				
			||||||
 | 
					    for arg in args:
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            (k, v) = arg.split(('='), 1)
 | 
				
			||||||
 | 
					        except ValueError:
 | 
				
			||||||
 | 
					            raise exc.CommandError(_('arguments must be a list of KEY=VALUE '
 | 
				
			||||||
 | 
					                                     'not %s') % arg)
 | 
				
			||||||
 | 
					        if k not in fmt_args:
 | 
				
			||||||
 | 
					            fmt_args[k] = v
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            if not isinstance(fmt_args[k], list):
 | 
				
			||||||
 | 
					                fmt_args[k] = [fmt_args[k]]
 | 
				
			||||||
 | 
					            fmt_args[k].append(v)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return fmt_args
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def print_list_field(field):
 | 
				
			||||||
 | 
					    return lambda obj: ', '.join(getattr(obj, field))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def remove_null_parms(**kwargs):
 | 
				
			||||||
 | 
					    new = {}
 | 
				
			||||||
 | 
					    for (key, value) in kwargs.items():
 | 
				
			||||||
 | 
					        if value is not None:
 | 
				
			||||||
 | 
					            new[key] = value
 | 
				
			||||||
 | 
					    return new
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def list_nodes(nodes):
 | 
				
			||||||
 | 
					    columns = ('uuid', 'name', 'type', 'status')
 | 
				
			||||||
 | 
					    utils.print_list(nodes, columns,
 | 
				
			||||||
 | 
					                     {'versions': print_list_field('versions')},
 | 
				
			||||||
 | 
					                     sortby_index=None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def list_models(models):
 | 
				
			||||||
 | 
					    columns = ('uuid', 'name', 'type', 'status', 'state', 'deployed_url',
 | 
				
			||||||
 | 
					               'deployed_on')
 | 
				
			||||||
 | 
					    utils.print_list(models, columns,
 | 
				
			||||||
 | 
					                     {'versions': print_list_field('versions')},
 | 
				
			||||||
 | 
					                     sortby_index=None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def normalise_file_path_to_url(path):
 | 
				
			||||||
 | 
					    if parse.urlparse(path).scheme:
 | 
				
			||||||
 | 
					        return path
 | 
				
			||||||
 | 
					    path = os.path.abspath(path)
 | 
				
			||||||
 | 
					    return parse.urljoin('file:', request.pathname2url(path))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def base_url_for_url(url):
 | 
				
			||||||
 | 
					    parsed = parse.urlparse(url)
 | 
				
			||||||
 | 
					    parsed_dir = os.path.dirname(parsed.path)
 | 
				
			||||||
 | 
					    return parse.urljoin(url, parsed_dir)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def encode_file_data(data):
 | 
				
			||||||
 | 
					    if six.PY3 and isinstance(data, str):
 | 
				
			||||||
 | 
					        data = data.encode('utf-8')
 | 
				
			||||||
 | 
					    return base64.b64encode(data).decode('utf-8')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def decode_file_data(data):
 | 
				
			||||||
 | 
					    # Py3 raises binascii.Error instead of TypeError as in Py27
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        return base64.b64decode(data)
 | 
				
			||||||
 | 
					    except (TypeError, binascii.Error):
 | 
				
			||||||
 | 
					        raise exc.CommandError(_('Invalid Base 64 file data.'))
 | 
				
			||||||
							
								
								
									
										60
									
								
								gyanclient/exceptions.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								gyanclient/exceptions.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
				
			|||||||
 | 
					#    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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from gyanclient.common.apiclient import exceptions
 | 
				
			||||||
 | 
					from gyanclient.common.apiclient.exceptions import *  # noqa
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					InvalidEndpoint = EndpointException
 | 
				
			||||||
 | 
					CommunicationError = ConnectionRefused
 | 
				
			||||||
 | 
					HTTPBadRequest = BadRequest
 | 
				
			||||||
 | 
					HTTPInternalServerError = InternalServerError
 | 
				
			||||||
 | 
					HTTPNotFound = NotFound
 | 
				
			||||||
 | 
					HTTPServiceUnavailable = ServiceUnavailable
 | 
				
			||||||
 | 
					CommandErrorException = CommandError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AmbiguousAuthSystem(ClientException):
 | 
				
			||||||
 | 
					    """Could not obtain token and endpoint using provided credentials."""
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Alias for backwards compatibility
 | 
				
			||||||
 | 
					AmbigiousAuthSystem = AmbiguousAuthSystem
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class InvalidAttribute(ClientException):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def from_response(response, message=None, traceback=None, method=None,
 | 
				
			||||||
 | 
					                  url=None):
 | 
				
			||||||
 | 
					    """Return an HttpError instance based on response from httplib/requests."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    error_body = {}
 | 
				
			||||||
 | 
					    if message:
 | 
				
			||||||
 | 
					        error_body['message'] = message
 | 
				
			||||||
 | 
					    if traceback:
 | 
				
			||||||
 | 
					        error_body['details'] = traceback
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if hasattr(response, 'status') and not hasattr(response, 'status_code'):
 | 
				
			||||||
 | 
					        response.status_code = response.status
 | 
				
			||||||
 | 
					        response.headers = {
 | 
				
			||||||
 | 
					            'Content-Type': response.getheader('content-type', "")}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if hasattr(response, 'status_code'):
 | 
				
			||||||
 | 
					        response.json = lambda: {'error': error_body}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (response.headers.get('Content-Type', '').startswith('text/') and
 | 
				
			||||||
 | 
					            not hasattr(response, 'text')):
 | 
				
			||||||
 | 
					        response.text = ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return exceptions.from_response(response, method, url)
 | 
				
			||||||
							
								
								
									
										25
									
								
								gyanclient/i18n.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								gyanclient/i18n.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					# 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""oslo_i18n integration module for gyanclient.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					See https://docs.openstack.org/oslo.i18n/latest/user/usage.html.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import oslo_i18n
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					_translators = oslo_i18n.TranslatorFactory(domain='gyanclient')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# The primary translation function using the well-known name "_"
 | 
				
			||||||
 | 
					_ = _translators.primary
 | 
				
			||||||
							
								
								
									
										701
									
								
								gyanclient/shell.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										701
									
								
								gyanclient/shell.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,701 @@
 | 
				
			|||||||
 | 
					# Licensed under the Apache License, Version 2.0 (the "License"); you may not
 | 
				
			||||||
 | 
					# use this file except in compliance with the License. You may obtain a copy
 | 
				
			||||||
 | 
					# of the License at
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					# distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					# See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					# limitations under the License.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					###
 | 
				
			||||||
 | 
					# This code is taken from python-zunclient. Goal is minimal modification.
 | 
				
			||||||
 | 
					###
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					Command-line interface to the OpenStack Gyan API.
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from __future__ import print_function
 | 
				
			||||||
 | 
					import argparse
 | 
				
			||||||
 | 
					import getpass
 | 
				
			||||||
 | 
					import logging
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from oslo_utils import encodeutils
 | 
				
			||||||
 | 
					from oslo_utils import importutils
 | 
				
			||||||
 | 
					from oslo_utils import strutils
 | 
				
			||||||
 | 
					import six
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					profiler = importutils.try_import("osprofiler.profiler")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					HAS_KEYRING = False
 | 
				
			||||||
 | 
					all_errors = ValueError
 | 
				
			||||||
 | 
					try:
 | 
				
			||||||
 | 
					    import keyring
 | 
				
			||||||
 | 
					    HAS_KEYRING = True
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        if isinstance(keyring.get_keyring(), keyring.backend.GnomeKeyring):
 | 
				
			||||||
 | 
					            import gnomekeyring
 | 
				
			||||||
 | 
					            all_errors = (ValueError,
 | 
				
			||||||
 | 
					                          gnomekeyring.IOError,
 | 
				
			||||||
 | 
					                          gnomekeyring.NoKeyringDaemonError)
 | 
				
			||||||
 | 
					    except Exception:
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
					except ImportError:
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from gyanclient import api_versions
 | 
				
			||||||
 | 
					from gyanclient import client as base_client
 | 
				
			||||||
 | 
					from gyanclient.common.apiclient import auth
 | 
				
			||||||
 | 
					from gyanclient.common import cliutils
 | 
				
			||||||
 | 
					from gyanclient import exceptions as exc
 | 
				
			||||||
 | 
					from gyanclient.i18n import _
 | 
				
			||||||
 | 
					from gyanclient.v1 import shell as shell_v1
 | 
				
			||||||
 | 
					from gyanclient import version
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DEFAULT_API_VERSION = api_versions.DEFAULT_API_VERSION
 | 
				
			||||||
 | 
					DEFAULT_ENDPOINT_TYPE = 'publicURL'
 | 
				
			||||||
 | 
					DEFAULT_SERVICE_TYPE = 'ml-infra'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					logger = logging.getLogger(__name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def positive_non_zero_float(text):
 | 
				
			||||||
 | 
					    if text is None:
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        value = float(text)
 | 
				
			||||||
 | 
					    except ValueError:
 | 
				
			||||||
 | 
					        msg = "%s must be a float" % text
 | 
				
			||||||
 | 
					        raise argparse.ArgumentTypeError(msg)
 | 
				
			||||||
 | 
					    if value <= 0:
 | 
				
			||||||
 | 
					        msg = "%s must be greater than 0" % text
 | 
				
			||||||
 | 
					        raise argparse.ArgumentTypeError(msg)
 | 
				
			||||||
 | 
					    return value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SecretsHelper(object):
 | 
				
			||||||
 | 
					    def __init__(self, args, client):
 | 
				
			||||||
 | 
					        self.args = args
 | 
				
			||||||
 | 
					        self.client = client
 | 
				
			||||||
 | 
					        self.key = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _validate_string(self, text):
 | 
				
			||||||
 | 
					        if text is None or len(text) == 0:
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _make_key(self):
 | 
				
			||||||
 | 
					        if self.key is not None:
 | 
				
			||||||
 | 
					            return self.key
 | 
				
			||||||
 | 
					        keys = [
 | 
				
			||||||
 | 
					            self.client.auth_url,
 | 
				
			||||||
 | 
					            self.client.projectid,
 | 
				
			||||||
 | 
					            self.client.user,
 | 
				
			||||||
 | 
					            self.client.region_name,
 | 
				
			||||||
 | 
					            self.client.endpoint_type,
 | 
				
			||||||
 | 
					            self.client.service_type,
 | 
				
			||||||
 | 
					            self.client.service_name,
 | 
				
			||||||
 | 
					            self.client.volume_service_name,
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					        for (index, key) in enumerate(keys):
 | 
				
			||||||
 | 
					            if key is None:
 | 
				
			||||||
 | 
					                keys[index] = '?'
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                keys[index] = str(keys[index])
 | 
				
			||||||
 | 
					        self.key = "/".join(keys)
 | 
				
			||||||
 | 
					        return self.key
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _prompt_password(self, verify=True):
 | 
				
			||||||
 | 
					        pw = None
 | 
				
			||||||
 | 
					        if hasattr(sys.stdin, 'isatty') and sys.stdin.isatty():
 | 
				
			||||||
 | 
					            # Check for Ctl-D
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                while True:
 | 
				
			||||||
 | 
					                    pw1 = getpass.getpass('OS Password: ')
 | 
				
			||||||
 | 
					                    if verify:
 | 
				
			||||||
 | 
					                        pw2 = getpass.getpass('Please verify: ')
 | 
				
			||||||
 | 
					                    else:
 | 
				
			||||||
 | 
					                        pw2 = pw1
 | 
				
			||||||
 | 
					                    if pw1 == pw2 and self._validate_string(pw1):
 | 
				
			||||||
 | 
					                        pw = pw1
 | 
				
			||||||
 | 
					                        break
 | 
				
			||||||
 | 
					            except EOFError:
 | 
				
			||||||
 | 
					                pass
 | 
				
			||||||
 | 
					        return pw
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def save(self, auth_token, management_url, tenant_id):
 | 
				
			||||||
 | 
					        if not HAS_KEYRING or not self.args.os_cache:
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        if (auth_token == self.auth_token and
 | 
				
			||||||
 | 
					                management_url == self.management_url):
 | 
				
			||||||
 | 
					            # Nothing changed....
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        if not all([management_url, auth_token, tenant_id]):
 | 
				
			||||||
 | 
					            raise ValueError("Unable to save empty management url/auth token")
 | 
				
			||||||
 | 
					        value = "|".join([str(auth_token),
 | 
				
			||||||
 | 
					                          str(management_url),
 | 
				
			||||||
 | 
					                          str(tenant_id)])
 | 
				
			||||||
 | 
					        keyring.set_password("gyanclient_auth", self._make_key(), value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def password(self):
 | 
				
			||||||
 | 
					        if self._validate_string(self.args.os_password):
 | 
				
			||||||
 | 
					            return self.args.os_password
 | 
				
			||||||
 | 
					        verify_pass = (
 | 
				
			||||||
 | 
					            strutils.bool_from_string(cliutils.env("OS_VERIFY_PASSWORD"))
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        return self._prompt_password(verify_pass)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def management_url(self):
 | 
				
			||||||
 | 
					        if not HAS_KEYRING or not self.args.os_cache:
 | 
				
			||||||
 | 
					            return None
 | 
				
			||||||
 | 
					        management_url = None
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            block = keyring.get_password('gyanclient_auth',
 | 
				
			||||||
 | 
					                                         self._make_key())
 | 
				
			||||||
 | 
					            if block:
 | 
				
			||||||
 | 
					                _token, management_url, _tenant_id = block.split('|', 2)
 | 
				
			||||||
 | 
					        except all_errors:
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					        return management_url
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def auth_token(self):
 | 
				
			||||||
 | 
					        # Now is where it gets complicated since we
 | 
				
			||||||
 | 
					        # want to look into the keyring module, if it
 | 
				
			||||||
 | 
					        # exists and see if anything was provided in that
 | 
				
			||||||
 | 
					        # file that we can use.
 | 
				
			||||||
 | 
					        if not HAS_KEYRING or not self.args.os_cache:
 | 
				
			||||||
 | 
					            return None
 | 
				
			||||||
 | 
					        token = None
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            block = keyring.get_password('gyanclient_auth',
 | 
				
			||||||
 | 
					                                         self._make_key())
 | 
				
			||||||
 | 
					            if block:
 | 
				
			||||||
 | 
					                token, _management_url, _tenant_id = block.split('|', 2)
 | 
				
			||||||
 | 
					        except all_errors:
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					        return token
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def tenant_id(self):
 | 
				
			||||||
 | 
					        if not HAS_KEYRING or not self.args.os_cache:
 | 
				
			||||||
 | 
					            return None
 | 
				
			||||||
 | 
					        tenant_id = None
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            block = keyring.get_password('gyanclient_auth',
 | 
				
			||||||
 | 
					                                         self._make_key())
 | 
				
			||||||
 | 
					            if block:
 | 
				
			||||||
 | 
					                _token, _management_url, tenant_id = block.split('|', 2)
 | 
				
			||||||
 | 
					        except all_errors:
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					        return tenant_id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class GyanClientArgumentParser(argparse.ArgumentParser):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, *args, **kwargs):
 | 
				
			||||||
 | 
					        super(GyanClientArgumentParser, self).__init__(*args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def error(self, message):
 | 
				
			||||||
 | 
					        """error(message: string)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Prints a usage message incorporating the message to stderr and
 | 
				
			||||||
 | 
					        exits.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.print_usage(sys.stderr)
 | 
				
			||||||
 | 
					        # FIXME(lzyeval): if changes occur in argparse.ArgParser._check_value
 | 
				
			||||||
 | 
					        choose_from = ' (choose from'
 | 
				
			||||||
 | 
					        progparts = self.prog.partition(' ')
 | 
				
			||||||
 | 
					        self.exit(2, "error: %(errmsg)s\nTry '%(mainp)s help %(subp)s'"
 | 
				
			||||||
 | 
					                     " for more information.\n" %
 | 
				
			||||||
 | 
					                     {'errmsg': message.split(choose_from)[0],
 | 
				
			||||||
 | 
					                      'mainp': progparts[0],
 | 
				
			||||||
 | 
					                      'subp': progparts[2]})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class OpenStackGyanShell(object):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_base_parser(self):
 | 
				
			||||||
 | 
					        parser = GyanClientArgumentParser(
 | 
				
			||||||
 | 
					            prog='gyan',
 | 
				
			||||||
 | 
					            description=__doc__.strip(),
 | 
				
			||||||
 | 
					            epilog='See "gyan help COMMAND" '
 | 
				
			||||||
 | 
					                   'for help on a specific command.',
 | 
				
			||||||
 | 
					            add_help=False,
 | 
				
			||||||
 | 
					            formatter_class=OpenStackHelpFormatter,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Global arguments
 | 
				
			||||||
 | 
					        parser.add_argument('-h', '--help',
 | 
				
			||||||
 | 
					                            action='store_true',
 | 
				
			||||||
 | 
					                            help=argparse.SUPPRESS)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        parser.add_argument('--version',
 | 
				
			||||||
 | 
					                            action='version',
 | 
				
			||||||
 | 
					                            version=version.version_info.version_string())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        parser.add_argument('--debug',
 | 
				
			||||||
 | 
					                            default=False,
 | 
				
			||||||
 | 
					                            action='store_true',
 | 
				
			||||||
 | 
					                            help="Print debugging output.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        parser.add_argument('--os-cache',
 | 
				
			||||||
 | 
					                            default=strutils.bool_from_string(
 | 
				
			||||||
 | 
					                                cliutils.env('OS_CACHE', default=False)),
 | 
				
			||||||
 | 
					                            action='store_true',
 | 
				
			||||||
 | 
					                            help="Use the auth token cache. Defaults to False "
 | 
				
			||||||
 | 
					                            "if env[OS_CACHE] is not set.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        parser.add_argument('--os-region-name',
 | 
				
			||||||
 | 
					                            metavar='<region-name>',
 | 
				
			||||||
 | 
					                            default=os.environ.get('OS_REGION_NAME'),
 | 
				
			||||||
 | 
					                            help='Region name. Default=env[OS_REGION_NAME].')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# TODO(mattf) - add get_timings support to Client
 | 
				
			||||||
 | 
					#        parser.add_argument('--timings',
 | 
				
			||||||
 | 
					#            default=False,
 | 
				
			||||||
 | 
					#            action='store_true',
 | 
				
			||||||
 | 
					#            help="Print call timing info")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# TODO(mattf) - use timeout
 | 
				
			||||||
 | 
					#        parser.add_argument('--timeout',
 | 
				
			||||||
 | 
					#            default=600,
 | 
				
			||||||
 | 
					#            metavar='<seconds>',
 | 
				
			||||||
 | 
					#            type=positive_non_zero_float,
 | 
				
			||||||
 | 
					#            help="Set HTTP call timeout (in seconds)")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        parser.add_argument('--os-project-id',
 | 
				
			||||||
 | 
					                            metavar='<auth-project-id>',
 | 
				
			||||||
 | 
					                            default=cliutils.env('OS_PROJECT_ID',
 | 
				
			||||||
 | 
					                                                 default=None),
 | 
				
			||||||
 | 
					                            help='Defaults to env[OS_PROJECT_ID].')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        parser.add_argument('--os-project-name',
 | 
				
			||||||
 | 
					                            metavar='<auth-project-name>',
 | 
				
			||||||
 | 
					                            default=cliutils.env('OS_PROJECT_NAME',
 | 
				
			||||||
 | 
					                                                 default=None),
 | 
				
			||||||
 | 
					                            help='Defaults to env[OS_PROJECT_NAME].')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        parser.add_argument('--os-user-domain-id',
 | 
				
			||||||
 | 
					                            metavar='<auth-user-domain-id>',
 | 
				
			||||||
 | 
					                            default=cliutils.env('OS_USER_DOMAIN_ID'),
 | 
				
			||||||
 | 
					                            help='Defaults to env[OS_USER_DOMAIN_ID].')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        parser.add_argument('--os-user-domain-name',
 | 
				
			||||||
 | 
					                            metavar='<auth-user-domain-name>',
 | 
				
			||||||
 | 
					                            default=cliutils.env('OS_USER_DOMAIN_NAME'),
 | 
				
			||||||
 | 
					                            help='Defaults to env[OS_USER_DOMAIN_NAME].')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        parser.add_argument('--os-project-domain-id',
 | 
				
			||||||
 | 
					                            metavar='<auth-project-domain-id>',
 | 
				
			||||||
 | 
					                            default=cliutils.env('OS_PROJECT_DOMAIN_ID'),
 | 
				
			||||||
 | 
					                            help='Defaults to env[OS_PROJECT_DOMAIN_ID].')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        parser.add_argument('--os-project-domain-name',
 | 
				
			||||||
 | 
					                            metavar='<auth-project-domain-name>',
 | 
				
			||||||
 | 
					                            default=cliutils.env('OS_PROJECT_DOMAIN_NAME'),
 | 
				
			||||||
 | 
					                            help='Defaults to env[OS_PROJECT_DOMAIN_NAME].')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        parser.add_argument('--service-type',
 | 
				
			||||||
 | 
					                            metavar='<service-type>',
 | 
				
			||||||
 | 
					                            help='Defaults to container for all '
 | 
				
			||||||
 | 
					                                 'actions.')
 | 
				
			||||||
 | 
					        parser.add_argument('--service_type',
 | 
				
			||||||
 | 
					                            help=argparse.SUPPRESS)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        parser.add_argument('--endpoint-type',
 | 
				
			||||||
 | 
					                            metavar='<endpoint-type>',
 | 
				
			||||||
 | 
					                            default=cliutils.env(
 | 
				
			||||||
 | 
					                                'OS_ENDPOINT_TYPE',
 | 
				
			||||||
 | 
					                                default=DEFAULT_ENDPOINT_TYPE),
 | 
				
			||||||
 | 
					                            help='Defaults to env[OS_ENDPOINT_TYPE] or '
 | 
				
			||||||
 | 
					                            + DEFAULT_ENDPOINT_TYPE + '.')
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        parser.add_argument('--gyan-api-version',
 | 
				
			||||||
 | 
					                            metavar='<gyan-api-ver>',
 | 
				
			||||||
 | 
					                            default=cliutils.env(
 | 
				
			||||||
 | 
					                                'GYAN_API_VERSION',
 | 
				
			||||||
 | 
					                                default=DEFAULT_API_VERSION),
 | 
				
			||||||
 | 
					                            help='Accepts X, X.Y (where X is major, Y is minor'
 | 
				
			||||||
 | 
					                                 ' part), defaults to env[GYAN_API_VERSION].')
 | 
				
			||||||
 | 
					        parser.add_argument('--gyan_api_version',
 | 
				
			||||||
 | 
					                            help=argparse.SUPPRESS)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        parser.add_argument('--os-cacert',
 | 
				
			||||||
 | 
					                            metavar='<ca-certificate>',
 | 
				
			||||||
 | 
					                            default=cliutils.env('OS_CACERT', default=None),
 | 
				
			||||||
 | 
					                            help='Specify a CA bundle file to use in '
 | 
				
			||||||
 | 
					                            'verifying a TLS (https) server certificate. '
 | 
				
			||||||
 | 
					                            'Defaults to env[OS_CACERT].')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        parser.add_argument('--bypass-url',
 | 
				
			||||||
 | 
					                            metavar='<bypass-url>',
 | 
				
			||||||
 | 
					                            default=cliutils.env('BYPASS_URL', default=None),
 | 
				
			||||||
 | 
					                            dest='bypass_url',
 | 
				
			||||||
 | 
					                            help="Use this API endpoint instead of the "
 | 
				
			||||||
 | 
					                            "Service Catalog.")
 | 
				
			||||||
 | 
					        parser.add_argument('--bypass_url',
 | 
				
			||||||
 | 
					                            help=argparse.SUPPRESS)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        parser.add_argument('--insecure',
 | 
				
			||||||
 | 
					                            default=cliutils.env('GYANCLIENT_INSECURE',
 | 
				
			||||||
 | 
					                                                 default=False),
 | 
				
			||||||
 | 
					                            action='store_true',
 | 
				
			||||||
 | 
					                            help="Do not verify https connections")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if profiler:
 | 
				
			||||||
 | 
					            parser.add_argument('--profile',
 | 
				
			||||||
 | 
					                                metavar='HMAC_KEY',
 | 
				
			||||||
 | 
					                                default=cliutils.env('OS_PROFILE',
 | 
				
			||||||
 | 
					                                                     default=None),
 | 
				
			||||||
 | 
					                                help='HMAC key to use for encrypting context '
 | 
				
			||||||
 | 
					                                     'data for performance profiling of '
 | 
				
			||||||
 | 
					                                     'operation. This key should be the '
 | 
				
			||||||
 | 
					                                     'value of the HMAC key configured for '
 | 
				
			||||||
 | 
					                                     'the OSprofiler middleware in gyan; it '
 | 
				
			||||||
 | 
					                                     'is specified in the Gyan configuration '
 | 
				
			||||||
 | 
					                                     'file at "/etc/gyan/gyan.conf". Without '
 | 
				
			||||||
 | 
					                                     'the key, profiling functions will not '
 | 
				
			||||||
 | 
					                                     'be triggered even if OSprofiler is '
 | 
				
			||||||
 | 
					                                     'enabled on the server side.')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # The auth-system-plugins might require some extra options
 | 
				
			||||||
 | 
					        auth.load_auth_system_opts(parser)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return parser
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_subcommand_parser(self, version, do_help=False):
 | 
				
			||||||
 | 
					        parser = self.get_base_parser()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.subcommands = {}
 | 
				
			||||||
 | 
					        subparsers = parser.add_subparsers(metavar='<subcommand>')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        actions_modules = shell_v1.COMMAND_MODULES
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for action_modules in actions_modules:
 | 
				
			||||||
 | 
					            self._find_actions(subparsers, action_modules, version, do_help)
 | 
				
			||||||
 | 
					        self._find_actions(subparsers, self, version, do_help)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self._add_bash_completion_subparser(subparsers)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return parser
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _add_bash_completion_subparser(self, subparsers):
 | 
				
			||||||
 | 
					        subparser = (
 | 
				
			||||||
 | 
					            subparsers.add_parser('bash_completion',
 | 
				
			||||||
 | 
					                                  add_help=False,
 | 
				
			||||||
 | 
					                                  formatter_class=OpenStackHelpFormatter)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        self.subcommands['bash_completion'] = subparser
 | 
				
			||||||
 | 
					        subparser.set_defaults(func=self.do_bash_completion)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _find_actions(self, subparsers, actions_module, version, do_help):
 | 
				
			||||||
 | 
					        msg = _(" (Supported by API versions '%(start)s' - '%(end)s')")
 | 
				
			||||||
 | 
					        for attr in (a for a in dir(actions_module) if a.startswith('do_')):
 | 
				
			||||||
 | 
					            # I prefer to be hyphen-separated instead of underscores.
 | 
				
			||||||
 | 
					            command = attr[3:].replace('_', '-')
 | 
				
			||||||
 | 
					            callback = getattr(actions_module, attr)
 | 
				
			||||||
 | 
					            desc = callback.__doc__ or ''
 | 
				
			||||||
 | 
					            if hasattr(callback, "versioned"):
 | 
				
			||||||
 | 
					                subs = api_versions.get_substitutions(callback)
 | 
				
			||||||
 | 
					                if do_help:
 | 
				
			||||||
 | 
					                    desc += msg % {'start': subs[0].start_version.get_string(),
 | 
				
			||||||
 | 
					                                   'end': subs[-1].end_version.get_string()}
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    for versioned_method in subs:
 | 
				
			||||||
 | 
					                        if version.matches(versioned_method.start_version,
 | 
				
			||||||
 | 
					                                           versioned_method.end_version):
 | 
				
			||||||
 | 
					                            callback = versioned_method.func
 | 
				
			||||||
 | 
					                            break
 | 
				
			||||||
 | 
					                    else:
 | 
				
			||||||
 | 
					                        continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            action_help = desc.strip()
 | 
				
			||||||
 | 
					            exclusive_args = getattr(callback, 'exclusive_args', {})
 | 
				
			||||||
 | 
					            arguments = getattr(callback, 'arguments', [])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            subparser = (
 | 
				
			||||||
 | 
					                subparsers.add_parser(command,
 | 
				
			||||||
 | 
					                                      help=action_help,
 | 
				
			||||||
 | 
					                                      description=desc,
 | 
				
			||||||
 | 
					                                      add_help=False,
 | 
				
			||||||
 | 
					                                      formatter_class=OpenStackHelpFormatter)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            subparser.add_argument('-h', '--help',
 | 
				
			||||||
 | 
					                                   action='help',
 | 
				
			||||||
 | 
					                                   help=argparse.SUPPRESS,)
 | 
				
			||||||
 | 
					            self.subcommands[command] = subparser
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self._add_subparser_args(subparser, arguments, version, do_help,
 | 
				
			||||||
 | 
					                                     msg)
 | 
				
			||||||
 | 
					            self._add_subparser_exclusive_args(subparser, exclusive_args,
 | 
				
			||||||
 | 
					                                               version, do_help, msg)
 | 
				
			||||||
 | 
					            subparser.set_defaults(func=callback)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _add_subparser_exclusive_args(self, subparser, exclusive_args,
 | 
				
			||||||
 | 
					                                      version, do_help, msg):
 | 
				
			||||||
 | 
					        for group_name, arguments in exclusive_args.items():
 | 
				
			||||||
 | 
					            if group_name == '__required__':
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            required = exclusive_args['__required__'][group_name]
 | 
				
			||||||
 | 
					            exclusive_group = subparser.add_mutually_exclusive_group(
 | 
				
			||||||
 | 
					                required=required)
 | 
				
			||||||
 | 
					            self._add_subparser_args(exclusive_group, arguments,
 | 
				
			||||||
 | 
					                                     version, do_help, msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _add_subparser_args(self, subparser, arguments, version, do_help, msg):
 | 
				
			||||||
 | 
					        for (args, kwargs) in arguments:
 | 
				
			||||||
 | 
					            start_version = kwargs.get("start_version", None)
 | 
				
			||||||
 | 
					            if start_version:
 | 
				
			||||||
 | 
					                start_version = api_versions.APIVersion(start_version)
 | 
				
			||||||
 | 
					                end_version = kwargs.get("end_version", None)
 | 
				
			||||||
 | 
					                if end_version:
 | 
				
			||||||
 | 
					                    end_version = api_versions.APIVersion(end_version)
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    end_version = api_versions.APIVersion(
 | 
				
			||||||
 | 
					                        "%s.latest" % start_version.ver_major)
 | 
				
			||||||
 | 
					                if do_help:
 | 
				
			||||||
 | 
					                    kwargs["help"] = kwargs.get("help", "") + (msg % {
 | 
				
			||||||
 | 
					                        "start": start_version.get_string(),
 | 
				
			||||||
 | 
					                        "end": end_version.get_string()})
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    if not version.matches(start_version, end_version):
 | 
				
			||||||
 | 
					                        continue
 | 
				
			||||||
 | 
					            kw = kwargs.copy()
 | 
				
			||||||
 | 
					            kw.pop("start_version", None)
 | 
				
			||||||
 | 
					            kw.pop("end_version", None)
 | 
				
			||||||
 | 
					            subparser.add_argument(*args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def setup_debugging(self, debug):
 | 
				
			||||||
 | 
					        if debug:
 | 
				
			||||||
 | 
					            streamformat = "%(levelname)s (%(module)s:%(lineno)d) %(message)s"
 | 
				
			||||||
 | 
					            # Set up the root logger to debug so that the submodules can
 | 
				
			||||||
 | 
					            # print debug messages
 | 
				
			||||||
 | 
					            logging.basicConfig(level=logging.DEBUG,
 | 
				
			||||||
 | 
					                                format=streamformat)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            streamformat = "%(levelname)s %(message)s"
 | 
				
			||||||
 | 
					            logging.basicConfig(level=logging.CRITICAL,
 | 
				
			||||||
 | 
					                                format=streamformat)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def main(self, argv):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        argv = list(argv)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Parse args once to find version and debug settings
 | 
				
			||||||
 | 
					        parser = self.get_base_parser()
 | 
				
			||||||
 | 
					        (options, args) = parser.parse_known_args(argv)
 | 
				
			||||||
 | 
					        self.setup_debugging(options.debug)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        api_version = api_versions.get_api_version(options.gyan_api_version)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if '--endpoint_type' in argv:
 | 
				
			||||||
 | 
					            spot = argv.index('--endpoint_type')
 | 
				
			||||||
 | 
					            argv[spot] = '--endpoint-type'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        subcommand_parser = self.get_subcommand_parser(
 | 
				
			||||||
 | 
					            api_version, do_help=("help" in args))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.parser = subcommand_parser
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if options.help or not argv:
 | 
				
			||||||
 | 
					            subcommand_parser.print_help()
 | 
				
			||||||
 | 
					            return 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        args = subcommand_parser.parse_args(argv)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Short-circuit and deal with help right away.
 | 
				
			||||||
 | 
					        if not hasattr(args, 'func') or args.func == self.do_help:
 | 
				
			||||||
 | 
					            self.do_help(args)
 | 
				
			||||||
 | 
					            return 0
 | 
				
			||||||
 | 
					        elif args.func == self.do_bash_completion:
 | 
				
			||||||
 | 
					            self.do_bash_completion(args)
 | 
				
			||||||
 | 
					            return 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        (os_username, os_project_name, os_project_id,
 | 
				
			||||||
 | 
					         os_user_domain_id, os_user_domain_name,
 | 
				
			||||||
 | 
					         os_project_domain_id, os_project_domain_name,
 | 
				
			||||||
 | 
					         os_auth_url, os_auth_system, endpoint_type,
 | 
				
			||||||
 | 
					         service_type, bypass_url, insecure, os_cacert) = (
 | 
				
			||||||
 | 
					            (args.os_username, args.os_project_name, args.os_project_id,
 | 
				
			||||||
 | 
					             args.os_user_domain_id, args.os_user_domain_name,
 | 
				
			||||||
 | 
					             args.os_project_domain_id, args.os_project_domain_name,
 | 
				
			||||||
 | 
					             args.os_auth_url, args.os_auth_system, args.endpoint_type,
 | 
				
			||||||
 | 
					             args.service_type, args.bypass_url, args.insecure,
 | 
				
			||||||
 | 
					             args.os_cacert)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if os_auth_system and os_auth_system != "keystone":
 | 
				
			||||||
 | 
					            auth_plugin = auth.load_plugin(os_auth_system)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            auth_plugin = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Fetched and set later as needed
 | 
				
			||||||
 | 
					        os_password = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not endpoint_type:
 | 
				
			||||||
 | 
					            endpoint_type = DEFAULT_ENDPOINT_TYPE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not service_type:
 | 
				
			||||||
 | 
					            service_type = DEFAULT_SERVICE_TYPE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# NA - there is only one service this CLI accesses
 | 
				
			||||||
 | 
					#            service_type = utils.get_service_type(args.func) or service_type
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # FIXME(usrleon): Here should be restrict for project id same as
 | 
				
			||||||
 | 
					        # for os_username or os_password but for compatibility it is not.
 | 
				
			||||||
 | 
					        if not cliutils.isunauthenticated(args.func):
 | 
				
			||||||
 | 
					            if auth_plugin:
 | 
				
			||||||
 | 
					                auth_plugin.parse_opts(args)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if not auth_plugin or not auth_plugin.opts:
 | 
				
			||||||
 | 
					                if not os_username:
 | 
				
			||||||
 | 
					                    raise exc.CommandError("You must provide a username "
 | 
				
			||||||
 | 
					                                           "via either --os-username or "
 | 
				
			||||||
 | 
					                                           "env[OS_USERNAME]")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if not os_project_name and not os_project_id:
 | 
				
			||||||
 | 
					                raise exc.CommandError("You must provide a project name "
 | 
				
			||||||
 | 
					                                       "or project id via --os-project-name, "
 | 
				
			||||||
 | 
					                                       "--os-project-id, env[OS_PROJECT_NAME] "
 | 
				
			||||||
 | 
					                                       "or env[OS_PROJECT_ID]")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if not os_auth_url:
 | 
				
			||||||
 | 
					                if os_auth_system and os_auth_system != 'keystone':
 | 
				
			||||||
 | 
					                    os_auth_url = auth_plugin.get_auth_url()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if not os_auth_url:
 | 
				
			||||||
 | 
					                raise exc.CommandError("You must provide an auth url "
 | 
				
			||||||
 | 
					                                       "via either --os-auth-url or "
 | 
				
			||||||
 | 
					                                       "env[OS_AUTH_URL] or specify an "
 | 
				
			||||||
 | 
					                                       "auth_system which defines a "
 | 
				
			||||||
 | 
					                                       "default url with --os-auth-system "
 | 
				
			||||||
 | 
					                                       "or env[OS_AUTH_SYSTEM]")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Now check for the password/token of which pieces of the
 | 
				
			||||||
 | 
					        # identifying keyring key can come from the underlying client
 | 
				
			||||||
 | 
					        if not cliutils.isunauthenticated(args.func):
 | 
				
			||||||
 | 
					            # NA - Client can't be used with SecretsHelper
 | 
				
			||||||
 | 
					            if (auth_plugin and auth_plugin.opts and
 | 
				
			||||||
 | 
					                    "os_password" not in auth_plugin.opts):
 | 
				
			||||||
 | 
					                use_pw = False
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                use_pw = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if use_pw:
 | 
				
			||||||
 | 
					                # Auth using token must have failed or not happened
 | 
				
			||||||
 | 
					                # at all, so now switch to password mode and save
 | 
				
			||||||
 | 
					                # the token when its gotten... using our keyring
 | 
				
			||||||
 | 
					                # saver
 | 
				
			||||||
 | 
					                os_password = args.os_password
 | 
				
			||||||
 | 
					                if not os_password:
 | 
				
			||||||
 | 
					                    raise exc.CommandError(
 | 
				
			||||||
 | 
					                        'Expecting a password provided via either '
 | 
				
			||||||
 | 
					                        '--os-password, env[OS_PASSWORD], or '
 | 
				
			||||||
 | 
					                        'prompted response')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        client = base_client
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        kwargs = {}
 | 
				
			||||||
 | 
					        if profiler:
 | 
				
			||||||
 | 
					            kwargs["profile"] = args.profile
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.cs = client.Client(version=api_version,
 | 
				
			||||||
 | 
					                                username=os_username,
 | 
				
			||||||
 | 
					                                password=os_password,
 | 
				
			||||||
 | 
					                                project_id=os_project_id,
 | 
				
			||||||
 | 
					                                project_name=os_project_name,
 | 
				
			||||||
 | 
					                                user_domain_id=os_user_domain_id,
 | 
				
			||||||
 | 
					                                user_domain_name=os_user_domain_name,
 | 
				
			||||||
 | 
					                                project_domain_id=os_project_domain_id,
 | 
				
			||||||
 | 
					                                project_domain_name=os_project_domain_name,
 | 
				
			||||||
 | 
					                                auth_url=os_auth_url,
 | 
				
			||||||
 | 
					                                service_type=service_type,
 | 
				
			||||||
 | 
					                                region_name=args.os_region_name,
 | 
				
			||||||
 | 
					                                endpoint_override=bypass_url,
 | 
				
			||||||
 | 
					                                interface=endpoint_type,
 | 
				
			||||||
 | 
					                                insecure=insecure,
 | 
				
			||||||
 | 
					                                cacert=os_cacert,
 | 
				
			||||||
 | 
					                                **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        args.func(self.cs, args)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if profiler and args.profile:
 | 
				
			||||||
 | 
					            trace_id = profiler.get().get_base_id()
 | 
				
			||||||
 | 
					            print("To display trace use the command:\n\n"
 | 
				
			||||||
 | 
					                  "  osprofiler trace show --html %s " % trace_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _dump_timings(self, timings):
 | 
				
			||||||
 | 
					        class Tyme(object):
 | 
				
			||||||
 | 
					            def __init__(self, url, seconds):
 | 
				
			||||||
 | 
					                self.url = url
 | 
				
			||||||
 | 
					                self.seconds = seconds
 | 
				
			||||||
 | 
					        results = [Tyme(url, end - start) for url, start, end in timings]
 | 
				
			||||||
 | 
					        total = 0.0
 | 
				
			||||||
 | 
					        for tyme in results:
 | 
				
			||||||
 | 
					            total += tyme.seconds
 | 
				
			||||||
 | 
					        results.append(Tyme("Total", total))
 | 
				
			||||||
 | 
					        cliutils.print_list(results, ["url", "seconds"], sortby_index=None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def do_bash_completion(self, _args):
 | 
				
			||||||
 | 
					        """Prints arguments for bash-completion.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Prints all of the commands and options to stdout so that the
 | 
				
			||||||
 | 
					        gyan.bash_completion script doesn't have to hard code them.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        commands = set()
 | 
				
			||||||
 | 
					        options = set()
 | 
				
			||||||
 | 
					        for sc_str, sc in self.subcommands.items():
 | 
				
			||||||
 | 
					            commands.add(sc_str)
 | 
				
			||||||
 | 
					            for option in sc._optionals._option_string_actions.keys():
 | 
				
			||||||
 | 
					                options.add(option)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        commands.remove('bash-completion')
 | 
				
			||||||
 | 
					        commands.remove('bash_completion')
 | 
				
			||||||
 | 
					        print(' '.join(commands | options))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @cliutils.arg('command', metavar='<subcommand>', nargs='?',
 | 
				
			||||||
 | 
					                  help='Display help for <subcommand>.')
 | 
				
			||||||
 | 
					    def do_help(self, args):
 | 
				
			||||||
 | 
					        """Display help about this program or one of its subcommands."""
 | 
				
			||||||
 | 
					        command = getattr(args, 'command', '')
 | 
				
			||||||
 | 
					        if command:
 | 
				
			||||||
 | 
					            if args.command in self.subcommands:
 | 
				
			||||||
 | 
					                self.subcommands[args.command].print_help()
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                raise exc.CommandError("'%s' is not a valid subcommand" %
 | 
				
			||||||
 | 
					                                       args.command)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.parser.print_help()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# I'm picky about my shell help.
 | 
				
			||||||
 | 
					class OpenStackHelpFormatter(argparse.HelpFormatter):
 | 
				
			||||||
 | 
					    def start_section(self, heading):
 | 
				
			||||||
 | 
					        # Title-case the headings
 | 
				
			||||||
 | 
					        heading = '%s%s' % (heading[0].upper(), heading[1:])
 | 
				
			||||||
 | 
					        super(OpenStackHelpFormatter, self).start_section(heading)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def main():
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        return OpenStackGyanShell().main(
 | 
				
			||||||
 | 
					            map(encodeutils.safe_decode, sys.argv[1:]))
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.debug(e, exc_info=1)
 | 
				
			||||||
 | 
					        print("ERROR: %s" % encodeutils.safe_encode(six.text_type(e)),
 | 
				
			||||||
 | 
					              file=sys.stderr)
 | 
				
			||||||
 | 
					        sys.exit(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == "__main__":
 | 
				
			||||||
 | 
					    sys.exit(main())
 | 
				
			||||||
							
								
								
									
										0
									
								
								gyanclient/v1/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								gyanclient/v1/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										125
									
								
								gyanclient/v1/client.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								gyanclient/v1/client.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,125 @@
 | 
				
			|||||||
 | 
					# 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from keystoneauth1 import loading
 | 
				
			||||||
 | 
					from keystoneauth1 import session as ksa_session
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from gyanclient.common import httpclient
 | 
				
			||||||
 | 
					from gyanclient.v1 import nodes
 | 
				
			||||||
 | 
					from gyanclient.v1 import models
 | 
				
			||||||
 | 
					from gyanclient.v1 import versions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Client(object):
 | 
				
			||||||
 | 
					    """Top-level object to access the OpenStack Gyan API."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, api_version=None, auth_token=None,
 | 
				
			||||||
 | 
					                 auth_type='password', auth_url=None, endpoint_override=None,
 | 
				
			||||||
 | 
					                 interface='public', insecure=False, password=None,
 | 
				
			||||||
 | 
					                 project_domain_id=None, project_domain_name=None,
 | 
				
			||||||
 | 
					                 project_id=None, project_name=None, region_name=None,
 | 
				
			||||||
 | 
					                 service_name=None, service_type='container', session=None,
 | 
				
			||||||
 | 
					                 user_domain_id=None, user_domain_name=None,
 | 
				
			||||||
 | 
					                 username=None, cacert=None, **kwargs):
 | 
				
			||||||
 | 
					        """Initialization of Client object.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param api_version: Gyan API version
 | 
				
			||||||
 | 
					        :type api_version: gyanclient.api_version.APIVersion
 | 
				
			||||||
 | 
					        :param str auth_token: Auth token
 | 
				
			||||||
 | 
					        :param str auth_url: Auth URL
 | 
				
			||||||
 | 
					        :param str auth_type: Auth Type
 | 
				
			||||||
 | 
					        :param str endpoint_override: Bypass URL
 | 
				
			||||||
 | 
					        :param str interface: Interface
 | 
				
			||||||
 | 
					        :param str insecure: Allow insecure
 | 
				
			||||||
 | 
					        :param str password: User password
 | 
				
			||||||
 | 
					        :param str project_domain_id: ID of project domain
 | 
				
			||||||
 | 
					        :param str project_domain_name: Nam of project domain
 | 
				
			||||||
 | 
					        :param str project_id: Project/Tenant ID
 | 
				
			||||||
 | 
					        :param str project_name: Project/Tenant Name
 | 
				
			||||||
 | 
					        :param str region_name: Region Name
 | 
				
			||||||
 | 
					        :param str service_name: Service Name
 | 
				
			||||||
 | 
					        :param str service_type: Service Type
 | 
				
			||||||
 | 
					        :param str session: Session
 | 
				
			||||||
 | 
					        :param str user_domain_id: ID of user domain
 | 
				
			||||||
 | 
					        :param str user_id: User ID
 | 
				
			||||||
 | 
					        :param str username: Username
 | 
				
			||||||
 | 
					        :param str cacert: CA certificate
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if endpoint_override and auth_token:
 | 
				
			||||||
 | 
					            auth_type = 'admin_token'
 | 
				
			||||||
 | 
					            session = None
 | 
				
			||||||
 | 
					            loader_kwargs = {
 | 
				
			||||||
 | 
					                'token': auth_token,
 | 
				
			||||||
 | 
					                'endpoint': endpoint_override
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        elif auth_token and not session:
 | 
				
			||||||
 | 
					            auth_type = 'token'
 | 
				
			||||||
 | 
					            loader_kwargs = {
 | 
				
			||||||
 | 
					                'token': auth_token,
 | 
				
			||||||
 | 
					                'auth_url': auth_url,
 | 
				
			||||||
 | 
					                'project_domain_id': project_domain_id,
 | 
				
			||||||
 | 
					                'project_domain_name': project_domain_name,
 | 
				
			||||||
 | 
					                'project_id': project_id,
 | 
				
			||||||
 | 
					                'project_name': project_name,
 | 
				
			||||||
 | 
					                'user_domain_id': user_domain_id,
 | 
				
			||||||
 | 
					                'user_domain_name': user_domain_name
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            loader_kwargs = {
 | 
				
			||||||
 | 
					                'auth_url': auth_url,
 | 
				
			||||||
 | 
					                'password': password,
 | 
				
			||||||
 | 
					                'project_domain_id': project_domain_id,
 | 
				
			||||||
 | 
					                'project_domain_name': project_domain_name,
 | 
				
			||||||
 | 
					                'project_id': project_id,
 | 
				
			||||||
 | 
					                'project_name': project_name,
 | 
				
			||||||
 | 
					                'user_domain_id': user_domain_id,
 | 
				
			||||||
 | 
					                'user_domain_name': user_domain_name,
 | 
				
			||||||
 | 
					                'username': username,
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Backwards compatibility for people not passing in Session
 | 
				
			||||||
 | 
					        if session is None:
 | 
				
			||||||
 | 
					            loader = loading.get_plugin_loader(auth_type)
 | 
				
			||||||
 | 
					            # This should be able to handle v2 and v3 Keystone Auth
 | 
				
			||||||
 | 
					            auth_plugin = loader.load_from_options(**loader_kwargs)
 | 
				
			||||||
 | 
					            session = ksa_session.Session(auth=auth_plugin,
 | 
				
			||||||
 | 
					                                          verify=(cacert or not insecure))
 | 
				
			||||||
 | 
					        client_kwargs = {}
 | 
				
			||||||
 | 
					        if not endpoint_override:
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                # Trigger an auth error so that we can throw the exception
 | 
				
			||||||
 | 
					                # we always have
 | 
				
			||||||
 | 
					                session.get_endpoint(
 | 
				
			||||||
 | 
					                    service_name=service_name,
 | 
				
			||||||
 | 
					                    service_type=service_type,
 | 
				
			||||||
 | 
					                    interface=interface,
 | 
				
			||||||
 | 
					                    region_name=region_name
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            except Exception:
 | 
				
			||||||
 | 
					                raise RuntimeError('Not authorized')
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            client_kwargs = {'endpoint_override': endpoint_override}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.http_client = httpclient.SessionClient(service_type=service_type,
 | 
				
			||||||
 | 
					                                                    service_name=service_name,
 | 
				
			||||||
 | 
					                                                    interface=interface,
 | 
				
			||||||
 | 
					                                                    region_name=region_name,
 | 
				
			||||||
 | 
					                                                    session=session,
 | 
				
			||||||
 | 
					                                                    api_version=api_version,
 | 
				
			||||||
 | 
					                                                    **client_kwargs)
 | 
				
			||||||
 | 
					        self.containers = nodes.NodeManager(self.http_client)
 | 
				
			||||||
 | 
					        self.images = models.ModelManager(self.http_client)
 | 
				
			||||||
 | 
					        self.versions = versions.VersionManager(self.http_client)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def api_version(self):
 | 
				
			||||||
 | 
					        return self.http_client.api_version
 | 
				
			||||||
							
								
								
									
										112
									
								
								gyanclient/v1/models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								gyanclient/v1/models.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,112 @@
 | 
				
			|||||||
 | 
					#    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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from six.moves.urllib import parse
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from gyanclient import api_versions
 | 
				
			||||||
 | 
					from gyanclient.common import base
 | 
				
			||||||
 | 
					from gyanclient.common import utils
 | 
				
			||||||
 | 
					from gyanclient import exceptions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CREATION_ATTRIBUTES = ['name', 'image', 'command', 'cpu', 'memory',
 | 
				
			||||||
 | 
					                       'environment', 'workdir', 'labels', 'image_pull_policy',
 | 
				
			||||||
 | 
					                       'restart_policy', 'interactive', 'image_driver',
 | 
				
			||||||
 | 
					                       'security_groups', 'hints', 'nets', 'auto_remove',
 | 
				
			||||||
 | 
					                       'runtime', 'hostname', 'mounts', 'disk',
 | 
				
			||||||
 | 
					                       'availability_zone', 'auto_heal', 'privileged',
 | 
				
			||||||
 | 
					                       'exposed_ports', 'healthcheck']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Model(base.Resource):
 | 
				
			||||||
 | 
					    def __repr__(self):
 | 
				
			||||||
 | 
					        return "<Model %s>" % self._info
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ModelManager(base.Manager):
 | 
				
			||||||
 | 
					    resource_class = Model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @staticmethod
 | 
				
			||||||
 | 
					    def _path(id=None):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if id:
 | 
				
			||||||
 | 
					            return '/v1/ml-models/%s' % id
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            return '/v1/ml-models'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def list_models(self, **kwargs):
 | 
				
			||||||
 | 
					        """Retrieve a list of Models.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :returns: A list of models.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return self._list_pagination(self._path(''),
 | 
				
			||||||
 | 
					                                     "models")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get(self, id):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            return self._list(self._path(id))[0]
 | 
				
			||||||
 | 
					        except IndexError:
 | 
				
			||||||
 | 
					            return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def model_train(self, **kwargs):
 | 
				
			||||||
 | 
					        new = {}
 | 
				
			||||||
 | 
					        new['name'] = kwargs["name"]
 | 
				
			||||||
 | 
					        new['ml_file'] = kwargs["ml_file"]
 | 
				
			||||||
 | 
					        return self._create(self._path(), new)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def delete_model(self, id):
 | 
				
			||||||
 | 
					        return self._delete(self._path(id))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _action(self, id, action, method='POST', **kwargs):
 | 
				
			||||||
 | 
					        kwargs.setdefault('headers', {})
 | 
				
			||||||
 | 
					        kwargs['headers'].setdefault('Content-Length', '0')
 | 
				
			||||||
 | 
					        resp, body = self.api.json_request(method,
 | 
				
			||||||
 | 
					                                           self._path(id) + action,
 | 
				
			||||||
 | 
					                                           **kwargs)
 | 
				
			||||||
 | 
					        return resp, body
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def deploy_model(self, id):
 | 
				
			||||||
 | 
					        return self._action(id, '/deploy')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def undeploy_model(self, id):
 | 
				
			||||||
 | 
					        return self._action(id, '/unstop')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def rebuild(self, id, **kwargs):
 | 
				
			||||||
 | 
					        return self._action(id, '/rebuild',
 | 
				
			||||||
 | 
					                            qparams=kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def restart(self, id, timeout):
 | 
				
			||||||
 | 
					        return self._action(id, '/reboot',
 | 
				
			||||||
 | 
					                            qparams={'timeout': timeout})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def pause(self, id):
 | 
				
			||||||
 | 
					        return self._action(id, '/pause')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def unpause(self, id):
 | 
				
			||||||
 | 
					        return self._action(id, '/unpause')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def logs(self, id, **kwargs):
 | 
				
			||||||
 | 
					        if kwargs['stdout'] is False and kwargs['stderr'] is False:
 | 
				
			||||||
 | 
					            kwargs['stdout'] = True
 | 
				
			||||||
 | 
					            kwargs['stderr'] = True
 | 
				
			||||||
 | 
					        return self._action(id, '/logs', method='GET',
 | 
				
			||||||
 | 
					                            qparams=kwargs)[1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def execute(self, id, **kwargs):
 | 
				
			||||||
 | 
					        return self._action(id, '/execute',
 | 
				
			||||||
 | 
					                            qparams=kwargs)[1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def execute_resize(self, id, exec_id, width, height):
 | 
				
			||||||
 | 
					        self._action(id, '/execute_resize',
 | 
				
			||||||
 | 
					                     qparams={'exec_id': exec_id, 'w': width, 'h': height})[1]
 | 
				
			||||||
							
								
								
									
										116
									
								
								gyanclient/v1/models_shell.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								gyanclient/v1/models_shell.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,116 @@
 | 
				
			|||||||
 | 
					#    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 argparse
 | 
				
			||||||
 | 
					from contextlib import closing
 | 
				
			||||||
 | 
					import io
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import tarfile
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					import yaml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from gyanclient.common import cliutils as utils
 | 
				
			||||||
 | 
					from gyanclient.common import utils as gyan_utils
 | 
				
			||||||
 | 
					from gyanclient import exceptions as exc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _show_model(model):
 | 
				
			||||||
 | 
					    utils.print_dict(model._info)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@utils.arg('model-id',
 | 
				
			||||||
 | 
					           metavar='<model-id>',
 | 
				
			||||||
 | 
					           nargs='+',
 | 
				
			||||||
 | 
					           help='ID of the model to delete.')
 | 
				
			||||||
 | 
					def do_model_delete(cs, args):
 | 
				
			||||||
 | 
					    """Delete specified model."""
 | 
				
			||||||
 | 
					    opts = {}
 | 
				
			||||||
 | 
					    opts['id'] = args.model_id
 | 
				
			||||||
 | 
					    opts = gyan_utils.remove_null_parms(**opts)
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        cs.models.delete_model(**opts)
 | 
				
			||||||
 | 
					        print("Request to delete model %s has been accepted." %
 | 
				
			||||||
 | 
					              args.model_id)
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        print("Delete for model %(model)s failed: %(e)s" %
 | 
				
			||||||
 | 
					              {'model': args.model_id, 'e': e})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@utils.arg('model-id',
 | 
				
			||||||
 | 
					           metavar='<model-id>',
 | 
				
			||||||
 | 
					           help='ID or name of the model to show.')
 | 
				
			||||||
 | 
					def do_model_show(cs, args):
 | 
				
			||||||
 | 
					    """Show details of a container."""
 | 
				
			||||||
 | 
					    opts = {}
 | 
				
			||||||
 | 
					    opts['model_id'] = args.model_id
 | 
				
			||||||
 | 
					    opts = gyan_utils.remove_null_parms(**opts)
 | 
				
			||||||
 | 
					    model = cs.models.get(**opts)
 | 
				
			||||||
 | 
					    _show_model(model)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@utils.arg('model-id',
 | 
				
			||||||
 | 
					           metavar='<model-id>',
 | 
				
			||||||
 | 
					           help='ID of the model to be deployed')
 | 
				
			||||||
 | 
					def do_undeploy_model(cs, args):
 | 
				
			||||||
 | 
					    """Undeploy the model."""
 | 
				
			||||||
 | 
					    opts = {}
 | 
				
			||||||
 | 
					    opts['model_id'] = args.model_id
 | 
				
			||||||
 | 
					    opts = gyan_utils.remove_null_parms(**opts)
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        model = cs.models.undeploy_model(**opts)
 | 
				
			||||||
 | 
					        _show_model(model)
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        print("Undeployment of the model %(model)s "
 | 
				
			||||||
 | 
					              "failed: %(e)s" % {'model': args.model_id, 'e': e})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@utils.arg('model-id',
 | 
				
			||||||
 | 
					           metavar='<model-id>',
 | 
				
			||||||
 | 
					           help='ID of the model to be deployed')
 | 
				
			||||||
 | 
					def do_deploy_model(cs, args):
 | 
				
			||||||
 | 
					    """Deploy already created model."""
 | 
				
			||||||
 | 
					    opts = {}
 | 
				
			||||||
 | 
					    opts['model_id'] = args.model_id
 | 
				
			||||||
 | 
					    opts = gyan_utils.remove_null_parms(**opts)
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        model = cs.models.deploy_model(**opts)
 | 
				
			||||||
 | 
					        _show_model(model)
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        print("Deployment of the model %(model)s "
 | 
				
			||||||
 | 
					              "failed: %(e)s" % {'model': args.model_id, 'e': e})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def do_model_list(cs, args):
 | 
				
			||||||
 | 
					    """List models"""
 | 
				
			||||||
 | 
					    models = cs.models.list_models()
 | 
				
			||||||
 | 
					    gyan_utils.list_models(models)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@utils.arg('name',
 | 
				
			||||||
 | 
					           metavar='<name>',
 | 
				
			||||||
 | 
					           help='ID or name of the model to train')
 | 
				
			||||||
 | 
					@utils.arg('--ml-file',
 | 
				
			||||||
 | 
					           metavar='<ml_file>',
 | 
				
			||||||
 | 
					           help='The ML model file to be trained')
 | 
				
			||||||
 | 
					def do_train_model(cs, args):
 | 
				
			||||||
 | 
					    """Remove security group for specified container."""
 | 
				
			||||||
 | 
					    opts = {}
 | 
				
			||||||
 | 
					    opts['name'] = args.name
 | 
				
			||||||
 | 
					    opts = gyan_utils.remove_null_parms(**opts)
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        opts['ml_file'] = yaml.load(open(args.ml_file)) 
 | 
				
			||||||
 | 
					        models = cs.models.model_train(**opts)
 | 
				
			||||||
 | 
					        gyan_utils.list_models(models)
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        print("Creation of model %(model)s "
 | 
				
			||||||
 | 
					              "failed: %(e)s" % {'model': args.name, 'e': e})
 | 
				
			||||||
							
								
								
									
										51
									
								
								gyanclient/v1/nodes.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								gyanclient/v1/nodes.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
				
			|||||||
 | 
					#    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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from six.moves.urllib import parse
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from gyanclient import api_versions
 | 
				
			||||||
 | 
					from gyanclient.common import base
 | 
				
			||||||
 | 
					from gyanclient.common import utils
 | 
				
			||||||
 | 
					from gyanclient import exceptions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Node(base.Resource):
 | 
				
			||||||
 | 
					    def __repr__(self):
 | 
				
			||||||
 | 
					        return "<Node %s>" % self._info
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class NodeManager(base.Manager):
 | 
				
			||||||
 | 
					    resource_class = Node
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @staticmethod
 | 
				
			||||||
 | 
					    def _path(id=None):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if id:
 | 
				
			||||||
 | 
					            return '/v1/ml-nodes/%s' % id
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            return '/v1/ml-nodes'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def list_nodes(self, **kwargs):
 | 
				
			||||||
 | 
					        """Retrieve a list of Nodes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :returns: A list of nodes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return self._list_pagination(self._path(''),
 | 
				
			||||||
 | 
					                                     "nodes")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get(self, id):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            return self._list(self._path(id))[0]
 | 
				
			||||||
 | 
					        except IndexError:
 | 
				
			||||||
 | 
					            return None
 | 
				
			||||||
							
								
								
									
										46
									
								
								gyanclient/v1/nodes_shell.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								gyanclient/v1/nodes_shell.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
				
			|||||||
 | 
					#    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 argparse
 | 
				
			||||||
 | 
					from contextlib import closing
 | 
				
			||||||
 | 
					import io
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import tarfile
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					import yaml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from gyanclient.common import cliutils as utils
 | 
				
			||||||
 | 
					from gyanclient.common import utils as gyan_utils
 | 
				
			||||||
 | 
					from gyanclient import exceptions as exc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _show_node(node):
 | 
				
			||||||
 | 
					    utils.print_dict(node._info)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@utils.arg('node-id',
 | 
				
			||||||
 | 
					           metavar='<node-id>',
 | 
				
			||||||
 | 
					           help='ID or name of the node to show.')
 | 
				
			||||||
 | 
					def do_node_show(cs, args):
 | 
				
			||||||
 | 
					    """Show details of a container."""
 | 
				
			||||||
 | 
					    opts = {}
 | 
				
			||||||
 | 
					    opts['node_id'] = args.node_id
 | 
				
			||||||
 | 
					    opts = gyan_utils.remove_null_parms(**opts)
 | 
				
			||||||
 | 
					    node = cs.nodes.get(**opts)
 | 
				
			||||||
 | 
					    _show_node(node)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def do_node_list(cs, args):
 | 
				
			||||||
 | 
					    """List Nodes"""
 | 
				
			||||||
 | 
					    nodes = cs.nodes.list_nodes()
 | 
				
			||||||
 | 
					    gyan_utils.list_nodes(nodes)
 | 
				
			||||||
							
								
								
									
										21
									
								
								gyanclient/v1/shell.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								gyanclient/v1/shell.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					# 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from gyanclient.v1 import nodes_shell
 | 
				
			||||||
 | 
					from gyanclient.v1 import models_shell
 | 
				
			||||||
 | 
					from gyanclient.v1 import versions_shell
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					COMMAND_MODULES = [
 | 
				
			||||||
 | 
					    nodes_shell,
 | 
				
			||||||
 | 
					    models_shell,
 | 
				
			||||||
 | 
					    versions_shell
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
							
								
								
									
										27
									
								
								gyanclient/v1/versions.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								gyanclient/v1/versions.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					#    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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from gyanclient.common import base
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Version(base.Resource):
 | 
				
			||||||
 | 
					    def __repr__(self):
 | 
				
			||||||
 | 
					        return "<Version>"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class VersionManager(base.Manager):
 | 
				
			||||||
 | 
					    resource_class = Version
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def list(self):
 | 
				
			||||||
 | 
					        url = "%s" % self.api.get_endpoint()
 | 
				
			||||||
 | 
					        url = "%s/" % url.rsplit("/", 1)[0]
 | 
				
			||||||
 | 
					        return self._list(url, "versions")
 | 
				
			||||||
							
								
								
									
										28
									
								
								gyanclient/v1/versions_shell.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								gyanclient/v1/versions_shell.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					#    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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from gyanclient import api_versions
 | 
				
			||||||
 | 
					from gyanclient.common import cliutils as utils
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def do_version_list(cs, args):
 | 
				
			||||||
 | 
					    """List all API versions."""
 | 
				
			||||||
 | 
					    print("Client supported API versions:")
 | 
				
			||||||
 | 
					    print("Minimum version %(v)s" %
 | 
				
			||||||
 | 
					          {'v': api_versions.MIN_API_VERSION})
 | 
				
			||||||
 | 
					    print("Maximum version %(v)s" %
 | 
				
			||||||
 | 
					          {'v': api_versions.MAX_API_VERSION})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    print("\nServer supported API versions:")
 | 
				
			||||||
 | 
					    result = cs.versions.list()
 | 
				
			||||||
 | 
					    columns = ["Id", "Status", "Min Version", "Max Version"]
 | 
				
			||||||
 | 
					    utils.print_list(result, columns)
 | 
				
			||||||
							
								
								
									
										15
									
								
								gyanclient/version.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								gyanclient/version.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					# 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from pbr import version
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					version_info = version.VersionInfo('python-gyanclient')
 | 
				
			||||||
							
								
								
									
										16
									
								
								setup.cfg
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								setup.cfg
									
									
									
									
									
								
							@@ -35,3 +35,19 @@ input_file = gyanclient/locale/gyanclient.pot
 | 
				
			|||||||
keywords = _ gettext ngettext l_ lazy_gettext
 | 
					keywords = _ gettext ngettext l_ lazy_gettext
 | 
				
			||||||
mapping_file = babel.cfg
 | 
					mapping_file = babel.cfg
 | 
				
			||||||
output_file = gyanclient/locale/gyanclient.pot
 | 
					output_file = gyanclient/locale/gyanclient.pot
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[entry_points]
 | 
				
			||||||
 | 
					console_scripts =
 | 
				
			||||||
 | 
					    gyan = gyanclient.shell:main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[build_releasenotes]
 | 
				
			||||||
 | 
					all_files = 1
 | 
				
			||||||
 | 
					build-dir = releasenotes/build
 | 
				
			||||||
 | 
					source-dir = releasenotes/source
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[wheel]
 | 
				
			||||||
 | 
					universal = 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[global]
 | 
				
			||||||
 | 
					setup-hooks =
 | 
				
			||||||
 | 
					    pbr.hooks.setup_hook
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								setup.py
									
									
									
									
									
								
							@@ -1,5 +1,3 @@
 | 
				
			|||||||
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# Licensed under the Apache License, Version 2.0 (the "License");
 | 
					# Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
# you may not use this file except in compliance with the License.
 | 
					# you may not use this file except in compliance with the License.
 | 
				
			||||||
# You may obtain a copy of the License at
 | 
					# You may obtain a copy of the License at
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user