This makes the default instance https://storyboard-dev.openstack.org. Previously, the default was https://storyboard.openstack.org. It's probably not a good idea to have everyone pointing their test python scripts at a production instance of StoryBoard by default. This should be safer. Change-Id: I937b3ea584893eeff51db10fb7b958a566f871be
		
			
				
	
	
		
			225 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			225 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
# Copyright (c) 2014 Mirantis Inc.
 | 
						|
#
 | 
						|
# 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 inspect
 | 
						|
 | 
						|
import six
 | 
						|
 | 
						|
from storyboardclient.auth import oauth
 | 
						|
from storyboardclient.openstack.common.apiclient import base
 | 
						|
from storyboardclient.openstack.common.apiclient import client
 | 
						|
 | 
						|
DEFAULT_API_URL = "https://storyboard-dev.openstack.org/api/v1"
 | 
						|
 | 
						|
 | 
						|
class BaseClient(client.BaseClient):
 | 
						|
 | 
						|
    def __init__(self, api_url=None, access_token=None):
 | 
						|
        if not api_url:
 | 
						|
            api_url = DEFAULT_API_URL
 | 
						|
 | 
						|
        self.auth_plugin = oauth.OAuthPlugin(api_url, access_token)
 | 
						|
        self.http_client = BaseHTTPClient(auth_plugin=self.auth_plugin)
 | 
						|
 | 
						|
 | 
						|
class BaseHTTPClient(client.HTTPClient):
 | 
						|
    """Base class for setting up endpoint and token.
 | 
						|
 | 
						|
    This HTTP client is overriding a client_request method to add
 | 
						|
    Authorization header if OAuth token is provided.
 | 
						|
 | 
						|
    """
 | 
						|
 | 
						|
    def client_request(self, client, method, url, **kwargs):
 | 
						|
        """Send an http request using `client`'s endpoint and specified `url`.
 | 
						|
 | 
						|
        If request was rejected as unauthorized (possibly because the token is
 | 
						|
        expired), issue one authorization attempt and send the request once
 | 
						|
        again.
 | 
						|
 | 
						|
        :param client: instance of BaseClient descendant
 | 
						|
        :param method: method of HTTP request
 | 
						|
        :param url: URL of HTTP request
 | 
						|
        :param kwargs: any other parameter that can be passed to
 | 
						|
            `HTTPClient.request`
 | 
						|
        """
 | 
						|
 | 
						|
        token, endpoint = (self.cached_token, client.cached_endpoint)
 | 
						|
        if not (token and endpoint):
 | 
						|
            token, endpoint = self.auth_plugin.token_and_endpoint()
 | 
						|
            self.cached_token = token
 | 
						|
            client.cached_endpoint = endpoint
 | 
						|
 | 
						|
        if token:
 | 
						|
            kwargs.setdefault("headers", {})["Authorization"] = \
 | 
						|
                "Bearer %s" % token
 | 
						|
 | 
						|
        return self.request(method, self.concat_url(endpoint, url), **kwargs)
 | 
						|
 | 
						|
 | 
						|
class BaseManager(base.CrudManager):
 | 
						|
 | 
						|
    def build_url(self, base_url=None, method=None, **kwargs):
 | 
						|
        # Overriding to use "url_key" instead of the "collection_key".
 | 
						|
        # "key_id" is replaced with just "id" when querying a specific object.
 | 
						|
        url = base_url if base_url is not None else ''
 | 
						|
 | 
						|
        url += '/%s' % self.url_key
 | 
						|
 | 
						|
        entity_id = kwargs.get('id')
 | 
						|
        if entity_id is not None:
 | 
						|
            url += '/%s' % entity_id
 | 
						|
 | 
						|
        elif method == 'get':
 | 
						|
            first = True
 | 
						|
            for key, value in kwargs.iteritems():
 | 
						|
                if first:
 | 
						|
                    url += '?'
 | 
						|
                    first = False
 | 
						|
                else:
 | 
						|
                    url += '&'
 | 
						|
                url += '%s=%s' % (key, value)
 | 
						|
 | 
						|
        return url
 | 
						|
 | 
						|
    def get(self, id):
 | 
						|
        """Get a resource by id.
 | 
						|
 | 
						|
        Get method is accepting id as a positional argument for simplicity.
 | 
						|
 | 
						|
        :param id: The id of resource.
 | 
						|
        :return: The resource object.
 | 
						|
        """
 | 
						|
 | 
						|
        query_kwargs = {"id": id}
 | 
						|
        return self._get(self.build_url(**query_kwargs), self.key)
 | 
						|
 | 
						|
    def get_all(self, **kwargs):
 | 
						|
        """Get resources by properties other than ID."""
 | 
						|
        kwargs = self._filter_kwargs(kwargs)
 | 
						|
        return self._get_all(self.build_url(method='get', **kwargs), self.key)
 | 
						|
 | 
						|
    def _get_all(self, url, response_key=None):
 | 
						|
        """Get collection of stuff.
 | 
						|
 | 
						|
        Put here because we can't modify base.py in the OpenStack
 | 
						|
        APIclient (probably)
 | 
						|
 | 
						|
        :param url: a partial URL, e.g., '/servers'
 | 
						|
        :param response_key: the key to be looked up in response dictionary,
 | 
						|
            e.g., 'server'. If response_key is None - all response body
 | 
						|
            will be used.
 | 
						|
        """
 | 
						|
 | 
						|
        body = self.client.get(url).json()
 | 
						|
        data = body[response_key] if response_key is not None else body
 | 
						|
        return [self.resource_class(self, item, loaded=True) for item in data]
 | 
						|
 | 
						|
    def create(self, **kwargs):
 | 
						|
        """Create a resource.
 | 
						|
 | 
						|
        The default implementation is overridden so that the dictionary is
 | 
						|
        passed 'as is' without any wrapping.
 | 
						|
        """
 | 
						|
 | 
						|
        kwargs = self._filter_kwargs(kwargs)
 | 
						|
        return self._post(self.build_url(**kwargs), kwargs)
 | 
						|
 | 
						|
    def update(self, **kwargs):
 | 
						|
        """Update a resource.
 | 
						|
 | 
						|
        The default implementation is overridden so that the dictionary is
 | 
						|
        passed 'as is' without any wrapping. The id field is removed from the
 | 
						|
        request body.
 | 
						|
        """
 | 
						|
 | 
						|
        kwargs = self._filter_kwargs(kwargs)
 | 
						|
        params = kwargs.copy()
 | 
						|
        params.pop('id')
 | 
						|
 | 
						|
        return self._put(self.build_url(**kwargs), params)
 | 
						|
 | 
						|
 | 
						|
class BaseNestedManager(BaseManager):
 | 
						|
 | 
						|
    def __init__(self, client, parent_id):
 | 
						|
        super(BaseNestedManager, self).__init__(client)
 | 
						|
 | 
						|
        self.parent_id = parent_id
 | 
						|
 | 
						|
    def build_url(self, base_url=None, **kwargs):
 | 
						|
        # Overriding to use "url_key" instead of the "collection_key".
 | 
						|
        # "key_id" is replaced with just "id" when querying a specific object.
 | 
						|
        url = base_url if base_url is not None else ''
 | 
						|
 | 
						|
        url += '/%s/%s/%s' % (self.parent_url_key, self.parent_id,
 | 
						|
                              self.url_key)
 | 
						|
 | 
						|
        entity_id = kwargs.get('id')
 | 
						|
        if entity_id is not None:
 | 
						|
            url += '/%s' % entity_id
 | 
						|
 | 
						|
        return url
 | 
						|
 | 
						|
 | 
						|
class BaseObject(base.Resource):
 | 
						|
 | 
						|
    id = None
 | 
						|
    created_at = None
 | 
						|
    updated_at = None
 | 
						|
 | 
						|
    def __init__(self, manager, info, loaded=False, parent_id=None):
 | 
						|
        super(BaseObject, self).__init__(manager, info, loaded)
 | 
						|
 | 
						|
        self._parent_id = parent_id
 | 
						|
        self._init_nested_managers()
 | 
						|
 | 
						|
    def _add_details(self, info):
 | 
						|
        for field, value in six.iteritems(info):
 | 
						|
 | 
						|
            # Skip the fields which are not declared in the object
 | 
						|
            if not hasattr(self, field):
 | 
						|
                continue
 | 
						|
 | 
						|
            setattr(self, field, value)
 | 
						|
 | 
						|
    def _init_nested_managers(self):
 | 
						|
        # If an object has nested resource managers, they will be initialized
 | 
						|
        # here.
 | 
						|
 | 
						|
        manager_instances = {}
 | 
						|
 | 
						|
        for manager_name, manager_class in self._managers():
 | 
						|
            manager_instance = manager_class(client=self.manager.client,
 | 
						|
                                             parent_id=self.id)
 | 
						|
            # Saving a manager to a dict as self.__dict__ should not be
 | 
						|
            # changed while iterating
 | 
						|
            manager_instances[manager_name] = manager_instance
 | 
						|
 | 
						|
        for name, manager_instance in six.iteritems(manager_instances):
 | 
						|
            # replacing managers declarations with real managers
 | 
						|
            setattr(self, name, manager_instance)
 | 
						|
 | 
						|
    def _managers(self):
 | 
						|
        # Iterator over nested managers
 | 
						|
 | 
						|
        for attr in dir(self):
 | 
						|
            # Skip private fields
 | 
						|
            if attr.startswith("_"):
 | 
						|
                continue
 | 
						|
            val = getattr(self, attr)
 | 
						|
            if inspect.isclass(val) and issubclass(val, BaseNestedManager):
 | 
						|
                yield attr, val
 |