Basic project structure
Mostly taken from other OpenStack client projects and adapted for Ironic. Change-Id: I1ef9613b9e24bbb6caac9657dc1da3add899478e
This commit is contained in:
		@@ -12,3 +12,8 @@
 | 
			
		||||
# 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 pbr.version
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
__version__ = pbr.version.VersionInfo('python-ironicclient').version_string()
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										98
									
								
								ironicclient/client.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								ironicclient/client.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.
 | 
			
		||||
 | 
			
		||||
from ironicclient.common import utils
 | 
			
		||||
from keystoneclient.v2_0 import client as ksclient
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _get_ksclient(**kwargs):
 | 
			
		||||
    """Get an endpoint and auth token from Keystone.
 | 
			
		||||
 | 
			
		||||
    :param kwargs: keyword args containing credentials:
 | 
			
		||||
            * username: name of user
 | 
			
		||||
            * password: user's password
 | 
			
		||||
            * auth_url: endpoint to authenticate against
 | 
			
		||||
            * insecure: allow insecure SSL (no cert verification)
 | 
			
		||||
            * tenant_{name|id}: name or ID of tenant
 | 
			
		||||
    """
 | 
			
		||||
    return ksclient.Client(username=kwargs.get('username'),
 | 
			
		||||
                           password=kwargs.get('password'),
 | 
			
		||||
                           tenant_id=kwargs.get('tenant_id'),
 | 
			
		||||
                           tenant_name=kwargs.get('tenant_name'),
 | 
			
		||||
                           auth_url=kwargs.get('auth_url'),
 | 
			
		||||
                           insecure=kwargs.get('insecure'))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _get_endpoint(client, **kwargs):
 | 
			
		||||
    """Get an endpoint using the provided keystone client."""
 | 
			
		||||
    return client.service_catalog.url_for(
 | 
			
		||||
        service_type=kwargs.get('service_type') or 'ironic',
 | 
			
		||||
        endpoint_type=kwargs.get('endpoint_type') or 'publicURL')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_client(api_version, **kwargs):
 | 
			
		||||
    """Get an authtenticated client, based on the credentials
 | 
			
		||||
       in the keyword args.
 | 
			
		||||
 | 
			
		||||
    :param api_version: the API version to use ('1' or '2')
 | 
			
		||||
    :param kwargs: keyword args containing credentials, either:
 | 
			
		||||
            * os_auth_token: pre-existing token to re-use
 | 
			
		||||
            * ironic_url: ironic API endpoint
 | 
			
		||||
            or:
 | 
			
		||||
            * os_username: name of user
 | 
			
		||||
            * os_password: user's password
 | 
			
		||||
            * os_auth_url: endpoint to authenticate against
 | 
			
		||||
            * insecure: allow insecure SSL (no cert verification)
 | 
			
		||||
            * os_tenant_{name|id}: name or ID of tenant
 | 
			
		||||
    """
 | 
			
		||||
    if kwargs.get('os_auth_token') and kwargs.get('ironic_url'):
 | 
			
		||||
        token = kwargs.get('os_auth_token')
 | 
			
		||||
        endpoint = kwargs.get('ironic_url')
 | 
			
		||||
    elif (kwargs.get('os_username') and
 | 
			
		||||
          kwargs.get('os_password') and
 | 
			
		||||
          kwargs.get('os_auth_url') and
 | 
			
		||||
          (kwargs.get('os_tenant_id') or kwargs.get('os_tenant_name'))):
 | 
			
		||||
 | 
			
		||||
        ks_kwargs = {
 | 
			
		||||
            'username': kwargs.get('os_username'),
 | 
			
		||||
            'password': kwargs.get('os_password'),
 | 
			
		||||
            'tenant_id': kwargs.get('os_tenant_id'),
 | 
			
		||||
            'tenant_name': kwargs.get('os_tenant_name'),
 | 
			
		||||
            'auth_url': kwargs.get('os_auth_url'),
 | 
			
		||||
            'service_type': kwargs.get('os_service_type'),
 | 
			
		||||
            'endpoint_type': kwargs.get('os_endpoint_type'),
 | 
			
		||||
            'insecure': kwargs.get('insecure'),
 | 
			
		||||
        }
 | 
			
		||||
        _ksclient = _get_ksclient(**ks_kwargs)
 | 
			
		||||
        token = ((lambda: kwargs.get('os_auth_token'))
 | 
			
		||||
                 if kwargs.get('os_auth_token')
 | 
			
		||||
                 else (lambda: _ksclient.auth_token))
 | 
			
		||||
 | 
			
		||||
        endpoint = kwargs.get('ironic_url') or \
 | 
			
		||||
            _get_endpoint(_ksclient, **ks_kwargs)
 | 
			
		||||
 | 
			
		||||
    cli_kwargs = {
 | 
			
		||||
        'token': token,
 | 
			
		||||
        'insecure': kwargs.get('insecure'),
 | 
			
		||||
        'timeout': kwargs.get('timeout'),
 | 
			
		||||
        'ca_file': kwargs.get('ca_file'),
 | 
			
		||||
        'cert_file': kwargs.get('cert_file'),
 | 
			
		||||
        'key_file': kwargs.get('key_file'),
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return Client(api_version, endpoint, **cli_kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def Client(version, *args, **kwargs):
 | 
			
		||||
    module = utils.import_versioned_module(version, 'client')
 | 
			
		||||
    client_class = getattr(module, 'Client')
 | 
			
		||||
    return client_class(*args, **kwargs)
 | 
			
		||||
							
								
								
									
										0
									
								
								ironicclient/common/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								ironicclient/common/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										142
									
								
								ironicclient/common/base.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								ironicclient/common/base.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,142 @@
 | 
			
		||||
# Copyright 2012 OpenStack LLC.
 | 
			
		||||
# All Rights Reserved.
 | 
			
		||||
#
 | 
			
		||||
#    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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Python 2.4 compat
 | 
			
		||||
try:
 | 
			
		||||
    all
 | 
			
		||||
except NameError:
 | 
			
		||||
    def all(iterable):
 | 
			
		||||
        return True not in (not x for x in iterable)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def getid(obj):
 | 
			
		||||
    """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):
 | 
			
		||||
    """Managers interact with a particular type of API and provide CRUD
 | 
			
		||||
    operations for them.
 | 
			
		||||
    """
 | 
			
		||||
    resource_class = None
 | 
			
		||||
 | 
			
		||||
    def __init__(self, api):
 | 
			
		||||
        self.api = api
 | 
			
		||||
 | 
			
		||||
    def _create(self, url, body):
 | 
			
		||||
        resp, body = self.api.json_request('POST', url, body=body)
 | 
			
		||||
        if body:
 | 
			
		||||
            return self.resource_class(self, body)
 | 
			
		||||
 | 
			
		||||
    def _list(self, url, response_key=None, obj_class=None, body=None,
 | 
			
		||||
              expect_single=False):
 | 
			
		||||
        resp, body = self.api.json_request('GET', url)
 | 
			
		||||
 | 
			
		||||
        if obj_class is None:
 | 
			
		||||
            obj_class = self.resource_class
 | 
			
		||||
 | 
			
		||||
        if response_key:
 | 
			
		||||
            try:
 | 
			
		||||
                data = body[response_key]
 | 
			
		||||
            except KeyError:
 | 
			
		||||
                return []
 | 
			
		||||
        else:
 | 
			
		||||
            data = body
 | 
			
		||||
        if expect_single:
 | 
			
		||||
            data = [data]
 | 
			
		||||
        return [obj_class(self, res, loaded=True) for res in data if res]
 | 
			
		||||
 | 
			
		||||
    def _update(self, url, body, response_key=None):
 | 
			
		||||
        resp, body = self.api.json_request('PUT', url, body=body)
 | 
			
		||||
        # PUT requests may not return a body
 | 
			
		||||
        if body:
 | 
			
		||||
            return self.resource_class(self, body)
 | 
			
		||||
 | 
			
		||||
    def _delete(self, url):
 | 
			
		||||
        self.api.raw_request('DELETE', url)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Resource(object):
 | 
			
		||||
    """A resource represents a particular instance of an object (tenant, user,
 | 
			
		||||
    etc). This is pretty much just a bag for attributes.
 | 
			
		||||
 | 
			
		||||
    :param manager: Manager object
 | 
			
		||||
    :param info: dictionary representing resource attributes
 | 
			
		||||
    :param loaded: prevent lazy-loading if set to True
 | 
			
		||||
    """
 | 
			
		||||
    def __init__(self, manager, info, loaded=False):
 | 
			
		||||
        self.manager = manager
 | 
			
		||||
        self._info = info
 | 
			
		||||
        self._add_details(info)
 | 
			
		||||
        self._loaded = loaded
 | 
			
		||||
 | 
			
		||||
    def _add_details(self, info):
 | 
			
		||||
        for (k, v) in info.iteritems():
 | 
			
		||||
            setattr(self, k, v)
 | 
			
		||||
 | 
			
		||||
    def __getattr__(self, k):
 | 
			
		||||
        if k not in self.__dict__:
 | 
			
		||||
            # NOTE(bcwaldon): disallow lazy-loading if already loaded once
 | 
			
		||||
            if not self.is_loaded():
 | 
			
		||||
                self.get()
 | 
			
		||||
                return self.__getattr__(k)
 | 
			
		||||
 | 
			
		||||
            raise AttributeError(k)
 | 
			
		||||
        else:
 | 
			
		||||
            return self.__dict__[k]
 | 
			
		||||
 | 
			
		||||
    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 get(self):
 | 
			
		||||
        # 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)
 | 
			
		||||
 | 
			
		||||
    def __eq__(self, other):
 | 
			
		||||
        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)
 | 
			
		||||
							
								
								
									
										287
									
								
								ironicclient/common/http.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										287
									
								
								ironicclient/common/http.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,287 @@
 | 
			
		||||
# Copyright 2012 OpenStack LLC.
 | 
			
		||||
# All Rights Reserved.
 | 
			
		||||
#
 | 
			
		||||
#    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 httplib
 | 
			
		||||
import logging
 | 
			
		||||
import os
 | 
			
		||||
import socket
 | 
			
		||||
import StringIO
 | 
			
		||||
import urlparse
 | 
			
		||||
 | 
			
		||||
try:
 | 
			
		||||
    import ssl
 | 
			
		||||
except ImportError:
 | 
			
		||||
    #TODO(bcwaldon): Handle this failure more gracefully
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
try:
 | 
			
		||||
    import json
 | 
			
		||||
except ImportError:
 | 
			
		||||
    import simplejson as json
 | 
			
		||||
 | 
			
		||||
# Python 2.5 compat fix
 | 
			
		||||
if not hasattr(urlparse, 'parse_qsl'):
 | 
			
		||||
    import cgi
 | 
			
		||||
    urlparse.parse_qsl = cgi.parse_qsl
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
from ironicclient import exc
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
LOG = logging.getLogger(__name__)
 | 
			
		||||
USER_AGENT = 'python-ironicclient'
 | 
			
		||||
CHUNKSIZE = 1024 * 64  # 64kB
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HTTPClient(object):
 | 
			
		||||
 | 
			
		||||
    def __init__(self, endpoint, **kwargs):
 | 
			
		||||
        self.endpoint = endpoint
 | 
			
		||||
        self.auth_token = kwargs.get('token')
 | 
			
		||||
        self.connection_params = self.get_connection_params(endpoint, **kwargs)
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def get_connection_params(endpoint, **kwargs):
 | 
			
		||||
        parts = urlparse.urlparse(endpoint)
 | 
			
		||||
 | 
			
		||||
        _args = (parts.hostname, parts.port, parts.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 = httplib.HTTPConnection
 | 
			
		||||
        else:
 | 
			
		||||
            msg = 'Unsupported scheme: %s' % parts.scheme
 | 
			
		||||
            raise exc.InvalidEndpoint(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 httplib.InvalidURL:
 | 
			
		||||
            raise exc.InvalidEndpoint()
 | 
			
		||||
 | 
			
		||||
    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))
 | 
			
		||||
        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.rstrip('/'), 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)
 | 
			
		||||
        auth_token = self.auth_token()
 | 
			
		||||
        if auth_token:
 | 
			
		||||
            kwargs['headers'].setdefault('X-Auth-Token', 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 exc.InvalidEndpoint(message=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 exc.CommunicationError(message=message)
 | 
			
		||||
 | 
			
		||||
        body_iter = ResponseBodyIterator(resp)
 | 
			
		||||
 | 
			
		||||
        # Read body into string if it isn't obviously image data
 | 
			
		||||
        if resp.getheader('content-type', None) != 'application/octet-stream':
 | 
			
		||||
            body_str = ''.join([chunk for chunk in body_iter])
 | 
			
		||||
            self.log_http_response(resp, body_str)
 | 
			
		||||
            body_iter = StringIO.StringIO(body_str)
 | 
			
		||||
        else:
 | 
			
		||||
            self.log_http_response(resp)
 | 
			
		||||
 | 
			
		||||
        if 400 <= resp.status < 600:
 | 
			
		||||
            LOG.warn("Request returned failure status.")
 | 
			
		||||
            raise exc.from_response(resp)
 | 
			
		||||
        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 exc.from_response(resp)
 | 
			
		||||
 | 
			
		||||
        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(httplib.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):
 | 
			
		||||
        httplib.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 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:
 | 
			
		||||
            yield self.next()
 | 
			
		||||
 | 
			
		||||
    def next(self):
 | 
			
		||||
        chunk = self.resp.read(CHUNKSIZE)
 | 
			
		||||
        if chunk:
 | 
			
		||||
            return chunk
 | 
			
		||||
        else:
 | 
			
		||||
            raise StopIteration()
 | 
			
		||||
							
								
								
									
										146
									
								
								ironicclient/common/utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								ironicclient/common/utils.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,146 @@
 | 
			
		||||
# Copyright 2012 OpenStack LLC.
 | 
			
		||||
# All Rights Reserved.
 | 
			
		||||
#
 | 
			
		||||
#    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 os
 | 
			
		||||
import sys
 | 
			
		||||
import textwrap
 | 
			
		||||
import uuid
 | 
			
		||||
 | 
			
		||||
import prettytable
 | 
			
		||||
 | 
			
		||||
from ironicclient import exc
 | 
			
		||||
from ironicclient.openstack.common import importutils
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Decorator for cli-args
 | 
			
		||||
def arg(*args, **kwargs):
 | 
			
		||||
    def _decorator(func):
 | 
			
		||||
        # Because of the sematics of decorator composition if we just append
 | 
			
		||||
        # to the options list positional options will appear to be backwards.
 | 
			
		||||
        func.__dict__.setdefault('arguments', []).insert(0, (args, kwargs))
 | 
			
		||||
        return func
 | 
			
		||||
    return _decorator
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def pretty_choice_list(l):
 | 
			
		||||
    return ', '.join("'%s'" % i for i in l)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def print_list(objs, fields, field_labels, formatters={}, sortby=0):
 | 
			
		||||
    pt = prettytable.PrettyTable([f for f in field_labels],
 | 
			
		||||
                                 caching=False, print_empty=False)
 | 
			
		||||
    pt.align = 'l'
 | 
			
		||||
 | 
			
		||||
    for o in objs:
 | 
			
		||||
        row = []
 | 
			
		||||
        for field in fields:
 | 
			
		||||
            if field in formatters:
 | 
			
		||||
                row.append(formatters[field](o))
 | 
			
		||||
            else:
 | 
			
		||||
                data = getattr(o, field, '')
 | 
			
		||||
                row.append(data)
 | 
			
		||||
        pt.add_row(row)
 | 
			
		||||
    print pt.get_string(sortby=field_labels[sortby])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def print_dict(d, dict_property="Property", wrap=0):
 | 
			
		||||
    pt = prettytable.PrettyTable([dict_property, 'Value'],
 | 
			
		||||
                                 caching=False, print_empty=False)
 | 
			
		||||
    pt.align = 'l'
 | 
			
		||||
    for k, v in d.iteritems():
 | 
			
		||||
        # convert dict to str to check length
 | 
			
		||||
        if isinstance(v, dict):
 | 
			
		||||
            v = str(v)
 | 
			
		||||
        if wrap > 0:
 | 
			
		||||
            v = textwrap.fill(str(v), wrap)
 | 
			
		||||
        # if value has a newline, add in multiple rows
 | 
			
		||||
        # e.g. fault with stacktrace
 | 
			
		||||
        if v and isinstance(v, basestring) and r'\n' in v:
 | 
			
		||||
            lines = v.strip().split(r'\n')
 | 
			
		||||
            col1 = k
 | 
			
		||||
            for line in lines:
 | 
			
		||||
                pt.add_row([col1, line])
 | 
			
		||||
                col1 = ''
 | 
			
		||||
        else:
 | 
			
		||||
            pt.add_row([k, v])
 | 
			
		||||
    print pt.get_string()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def find_resource(manager, name_or_id):
 | 
			
		||||
    """Helper for the _find_* methods."""
 | 
			
		||||
    # first try to get entity as integer id
 | 
			
		||||
    try:
 | 
			
		||||
        if isinstance(name_or_id, int) or name_or_id.isdigit():
 | 
			
		||||
            return manager.get(int(name_or_id))
 | 
			
		||||
    except exc.NotFound:
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    # now try to get entity as uuid
 | 
			
		||||
    try:
 | 
			
		||||
        uuid.UUID(str(name_or_id))
 | 
			
		||||
        return manager.get(name_or_id)
 | 
			
		||||
    except (ValueError, exc.NotFound):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    # finally try to find entity by name
 | 
			
		||||
    try:
 | 
			
		||||
        return manager.find(name=name_or_id)
 | 
			
		||||
    except exc.NotFound:
 | 
			
		||||
        msg = "No %s with a name or ID of '%s' exists." % \
 | 
			
		||||
              (manager.resource_class.__name__.lower(), name_or_id)
 | 
			
		||||
        raise exc.CommandError(msg)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def string_to_bool(arg):
 | 
			
		||||
    return arg.strip().lower() in ('t', 'true', 'yes', '1')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def env(*vars, **kwargs):
 | 
			
		||||
    """Search for the first defined of possibly many env vars
 | 
			
		||||
 | 
			
		||||
    Returns the first environment variable defined in vars, or
 | 
			
		||||
    returns the default defined in kwargs.
 | 
			
		||||
    """
 | 
			
		||||
    for v in vars:
 | 
			
		||||
        value = os.environ.get(v, None)
 | 
			
		||||
        if value:
 | 
			
		||||
            return value
 | 
			
		||||
    return kwargs.get('default', '')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def import_versioned_module(version, submodule=None):
 | 
			
		||||
    module = 'ironicclient.v%s' % version
 | 
			
		||||
    if submodule:
 | 
			
		||||
        module = '.'.join((module, submodule))
 | 
			
		||||
    return importutils.import_module(module)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def args_array_to_dict(kwargs, key_to_convert):
 | 
			
		||||
    values_to_convert = kwargs.get(key_to_convert)
 | 
			
		||||
    if values_to_convert:
 | 
			
		||||
        try:
 | 
			
		||||
            kwargs[key_to_convert] = dict(v.split("=", 1)
 | 
			
		||||
                                          for v in values_to_convert)
 | 
			
		||||
        except ValueError:
 | 
			
		||||
            raise exc.CommandError(
 | 
			
		||||
                '%s must be a list of key=value not "%s"' % (
 | 
			
		||||
                    key_to_convert, values_to_convert))
 | 
			
		||||
    return kwargs
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def exit(msg=''):
 | 
			
		||||
    if msg:
 | 
			
		||||
        print >> sys.stderr, msg
 | 
			
		||||
    sys.exit(1)
 | 
			
		||||
							
								
								
									
										163
									
								
								ironicclient/exc.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										163
									
								
								ironicclient/exc.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,163 @@
 | 
			
		||||
#    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 sys
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BaseException(Exception):
 | 
			
		||||
    """An error occurred."""
 | 
			
		||||
    def __init__(self, message=None):
 | 
			
		||||
        self.message = message
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return self.message or self.__class__.__doc__
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CommandError(BaseException):
 | 
			
		||||
    """Invalid usage of CLI."""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class InvalidEndpoint(BaseException):
 | 
			
		||||
    """The provided endpoint is invalid."""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CommunicationError(BaseException):
 | 
			
		||||
    """Unable to communicate with server."""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ClientException(Exception):
 | 
			
		||||
    """DEPRECATED."""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HTTPException(ClientException):
 | 
			
		||||
    """Base exception for all HTTP-derived exceptions."""
 | 
			
		||||
    code = 'N/A'
 | 
			
		||||
 | 
			
		||||
    def __init__(self, details=None):
 | 
			
		||||
        self.details = details
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return "%s (HTTP %s)" % (self.__class__.__name__, self.code)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HTTPMultipleChoices(HTTPException):
 | 
			
		||||
    code = 300
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        self.details = ("Requested version of OpenStack Images API is not"
 | 
			
		||||
                        "available.")
 | 
			
		||||
        return "%s (HTTP %s) %s" % (self.__class__.__name__, self.code,
 | 
			
		||||
                                    self.details)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BadRequest(HTTPException):
 | 
			
		||||
    """DEPRECATED."""
 | 
			
		||||
    code = 400
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HTTPBadRequest(BadRequest):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Unauthorized(HTTPException):
 | 
			
		||||
    """DEPRECATED."""
 | 
			
		||||
    code = 401
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HTTPUnauthorized(Unauthorized):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Forbidden(HTTPException):
 | 
			
		||||
    """DEPRECATED."""
 | 
			
		||||
    code = 403
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HTTPForbidden(Forbidden):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NotFound(HTTPException):
 | 
			
		||||
    """DEPRECATED."""
 | 
			
		||||
    code = 404
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HTTPNotFound(NotFound):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HTTPMethodNotAllowed(HTTPException):
 | 
			
		||||
    code = 405
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Conflict(HTTPException):
 | 
			
		||||
    """DEPRECATED."""
 | 
			
		||||
    code = 409
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HTTPConflict(Conflict):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class OverLimit(HTTPException):
 | 
			
		||||
    """DEPRECATED."""
 | 
			
		||||
    code = 413
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HTTPOverLimit(OverLimit):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HTTPInternalServerError(HTTPException):
 | 
			
		||||
    code = 500
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HTTPNotImplemented(HTTPException):
 | 
			
		||||
    code = 501
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HTTPBadGateway(HTTPException):
 | 
			
		||||
    code = 502
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ServiceUnavailable(HTTPException):
 | 
			
		||||
    """DEPRECATED."""
 | 
			
		||||
    code = 503
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HTTPServiceUnavailable(ServiceUnavailable):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#NOTE(bcwaldon): Build a mapping of HTTP codes to corresponding exception
 | 
			
		||||
# classes
 | 
			
		||||
_code_map = {}
 | 
			
		||||
for obj_name in dir(sys.modules[__name__]):
 | 
			
		||||
    if obj_name.startswith('HTTP'):
 | 
			
		||||
        obj = getattr(sys.modules[__name__], obj_name)
 | 
			
		||||
        _code_map[obj.code] = obj
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def from_response(response):
 | 
			
		||||
    """Return an instance of an HTTPException based on httplib response."""
 | 
			
		||||
    cls = _code_map.get(response.status, HTTPException)
 | 
			
		||||
    return cls()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NoTokenLookupException(Exception):
 | 
			
		||||
    """DEPRECATED."""
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EndpointNotFound(Exception):
 | 
			
		||||
    """DEPRECATED."""
 | 
			
		||||
    pass
 | 
			
		||||
							
								
								
									
										292
									
								
								ironicclient/shell.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										292
									
								
								ironicclient/shell.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,292 @@
 | 
			
		||||
#    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.
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
Command-line interface to the OpenStack Bare Metal Provisioning
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import argparse
 | 
			
		||||
import httplib2
 | 
			
		||||
import logging
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
import ironicclient
 | 
			
		||||
from ironicclient import client as iroclient
 | 
			
		||||
from ironicclient.common import utils
 | 
			
		||||
from ironicclient import exc
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class IronicShell(object):
 | 
			
		||||
 | 
			
		||||
    def get_base_parser(self):
 | 
			
		||||
        parser = argparse.ArgumentParser(
 | 
			
		||||
            prog='ironic',
 | 
			
		||||
            description=__doc__.strip(),
 | 
			
		||||
            epilog='See "ironic help COMMAND" '
 | 
			
		||||
                   'for help on a specific command.',
 | 
			
		||||
            add_help=False,
 | 
			
		||||
            formatter_class=HelpFormatter,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # Global arguments
 | 
			
		||||
        parser.add_argument('-h', '--help',
 | 
			
		||||
                            action='store_true',
 | 
			
		||||
                            help=argparse.SUPPRESS,
 | 
			
		||||
                            )
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--version',
 | 
			
		||||
                            action='version',
 | 
			
		||||
                            version=ironicclient.__version__)
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('-d', '--debug',
 | 
			
		||||
                            default=bool(utils.env('IRONICCLIENT_DEBUG')),
 | 
			
		||||
                            action='store_true',
 | 
			
		||||
                            help='Defaults to env[IRONICCLIENT_DEBUG]')
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('-v', '--verbose',
 | 
			
		||||
                            default=False, action="store_true",
 | 
			
		||||
                            help="Print more verbose output")
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('-k', '--insecure',
 | 
			
		||||
                            default=False,
 | 
			
		||||
                            action='store_true',
 | 
			
		||||
                            help="Explicitly allow ironicclient to "
 | 
			
		||||
                            "perform \"insecure\" SSL (https) requests. "
 | 
			
		||||
                            "The server's certificate will "
 | 
			
		||||
                            "not be verified against any certificate "
 | 
			
		||||
                            "authorities. This option should be used with "
 | 
			
		||||
                            "caution.")
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--cert-file',
 | 
			
		||||
                            help='Path of certificate file to use in SSL '
 | 
			
		||||
                            'connection. This file can optionally be prepended'
 | 
			
		||||
                            ' with the private key.')
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--key-file',
 | 
			
		||||
                            help='Path of client key to use in SSL connection.'
 | 
			
		||||
                            ' This option is not necessary if your key is '
 | 
			
		||||
                            'prepended to your cert file.')
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--ca-file',
 | 
			
		||||
                            help='Path of CA SSL certificate(s) used to verify'
 | 
			
		||||
                            ' the remote server certificate. Without this '
 | 
			
		||||
                            'option ironic looks for the default system '
 | 
			
		||||
                            'CA certificates.')
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--timeout',
 | 
			
		||||
                            default=600,
 | 
			
		||||
                            help='Number of seconds to wait for a response')
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--os-username',
 | 
			
		||||
                            default=utils.env('OS_USERNAME'),
 | 
			
		||||
                            help='Defaults to env[OS_USERNAME]')
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--os_username',
 | 
			
		||||
                            help=argparse.SUPPRESS)
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--os-password',
 | 
			
		||||
                            default=utils.env('OS_PASSWORD'),
 | 
			
		||||
                            help='Defaults to env[OS_PASSWORD]')
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--os_password',
 | 
			
		||||
                            help=argparse.SUPPRESS)
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--os-tenant-id',
 | 
			
		||||
                            default=utils.env('OS_TENANT_ID'),
 | 
			
		||||
                            help='Defaults to env[OS_TENANT_ID]')
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--os_tenant_id',
 | 
			
		||||
                            help=argparse.SUPPRESS)
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--os-tenant-name',
 | 
			
		||||
                            default=utils.env('OS_TENANT_NAME'),
 | 
			
		||||
                            help='Defaults to env[OS_TENANT_NAME]')
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--os_tenant_name',
 | 
			
		||||
                            help=argparse.SUPPRESS)
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--os-auth-url',
 | 
			
		||||
                            default=utils.env('OS_AUTH_URL'),
 | 
			
		||||
                            help='Defaults to env[OS_AUTH_URL]')
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--os_auth_url',
 | 
			
		||||
                            help=argparse.SUPPRESS)
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--os-region-name',
 | 
			
		||||
                            default=utils.env('OS_REGION_NAME'),
 | 
			
		||||
                            help='Defaults to env[OS_REGION_NAME]')
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--os_region_name',
 | 
			
		||||
                            help=argparse.SUPPRESS)
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--os-auth-token',
 | 
			
		||||
                            default=utils.env('OS_AUTH_TOKEN'),
 | 
			
		||||
                            help='Defaults to env[OS_AUTH_TOKEN]')
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--os_auth_token',
 | 
			
		||||
                            help=argparse.SUPPRESS)
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--ironic-url',
 | 
			
		||||
                            default=utils.env('IRONIC_URL'),
 | 
			
		||||
                            help='Defaults to env[IRONIC_URL]')
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--ironic_url',
 | 
			
		||||
                            help=argparse.SUPPRESS)
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--ironic-api-version',
 | 
			
		||||
                            default=utils.env(
 | 
			
		||||
                            'IRONIC_API_VERSION', default='1'),
 | 
			
		||||
                            help='Defaults to env[IRONIC_API_VERSION] '
 | 
			
		||||
                            'or 1')
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--ironic_api_version',
 | 
			
		||||
                            help=argparse.SUPPRESS)
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--os-service-type',
 | 
			
		||||
                            default=utils.env('OS_SERVICE_TYPE'),
 | 
			
		||||
                            help='Defaults to env[OS_SERVICE_TYPE]')
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--os_service_type',
 | 
			
		||||
                            help=argparse.SUPPRESS)
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--os-endpoint-type',
 | 
			
		||||
                            default=utils.env('OS_ENDPOINT_TYPE'),
 | 
			
		||||
                            help='Defaults to env[OS_ENDPOINT_TYPE]')
 | 
			
		||||
 | 
			
		||||
        parser.add_argument('--os_endpoint_type',
 | 
			
		||||
                            help=argparse.SUPPRESS)
 | 
			
		||||
 | 
			
		||||
        return parser
 | 
			
		||||
 | 
			
		||||
    def get_subcommand_parser(self, version):
 | 
			
		||||
        parser = self.get_base_parser()
 | 
			
		||||
 | 
			
		||||
        self.subcommands = {}
 | 
			
		||||
        subparsers = parser.add_subparsers(metavar='<subcommand>')
 | 
			
		||||
        submodule = utils.import_versioned_module(version, 'shell')
 | 
			
		||||
        self._find_actions(subparsers, submodule)
 | 
			
		||||
        self._find_actions(subparsers, self)
 | 
			
		||||
 | 
			
		||||
        return parser
 | 
			
		||||
 | 
			
		||||
    def _find_actions(self, subparsers, actions_module):
 | 
			
		||||
        for attr in (a for a in dir(actions_module) if a.startswith('do_')):
 | 
			
		||||
            # I prefer to be hypen-separated instead of underscores.
 | 
			
		||||
            command = attr[3:].replace('_', '-')
 | 
			
		||||
            callback = getattr(actions_module, attr)
 | 
			
		||||
            desc = callback.__doc__ or ''
 | 
			
		||||
            help = desc.strip().split('\n')[0]
 | 
			
		||||
            arguments = getattr(callback, 'arguments', [])
 | 
			
		||||
 | 
			
		||||
            subparser = subparsers.add_parser(command, help=help,
 | 
			
		||||
                                              description=desc,
 | 
			
		||||
                                              add_help=False,
 | 
			
		||||
                                              formatter_class=HelpFormatter)
 | 
			
		||||
            subparser.add_argument('-h', '--help', action='help',
 | 
			
		||||
                                   help=argparse.SUPPRESS)
 | 
			
		||||
            self.subcommands[command] = subparser
 | 
			
		||||
            for (args, kwargs) in arguments:
 | 
			
		||||
                subparser.add_argument(*args, **kwargs)
 | 
			
		||||
            subparser.set_defaults(func=callback)
 | 
			
		||||
 | 
			
		||||
    def _setup_debugging(self, debug):
 | 
			
		||||
        if debug:
 | 
			
		||||
            logging.basicConfig(
 | 
			
		||||
                format="%(levelname)s (%(module)s:%(lineno)d) %(message)s",
 | 
			
		||||
                level=logging.DEBUG)
 | 
			
		||||
 | 
			
		||||
            httplib2.debuglevel = 1
 | 
			
		||||
 | 
			
		||||
    def main(self, argv):
 | 
			
		||||
        # Parse args once to find version
 | 
			
		||||
        parser = self.get_base_parser()
 | 
			
		||||
        (options, args) = parser.parse_known_args(argv)
 | 
			
		||||
        self._setup_debugging(options.debug)
 | 
			
		||||
 | 
			
		||||
        # build available subcommands based on version
 | 
			
		||||
        api_version = options.ironic_api_version
 | 
			
		||||
        subcommand_parser = self.get_subcommand_parser(api_version)
 | 
			
		||||
        self.parser = subcommand_parser
 | 
			
		||||
 | 
			
		||||
        # Handle top-level --help/-h before attempting to parse
 | 
			
		||||
        # a command off the command line
 | 
			
		||||
        if options.help or not argv:
 | 
			
		||||
            self.do_help(options)
 | 
			
		||||
            return 0
 | 
			
		||||
 | 
			
		||||
        # Parse args again and call whatever callback was selected
 | 
			
		||||
        args = subcommand_parser.parse_args(argv)
 | 
			
		||||
 | 
			
		||||
        # Short-circuit and deal with help command right away.
 | 
			
		||||
        if args.func == self.do_help:
 | 
			
		||||
            self.do_help(args)
 | 
			
		||||
            return 0
 | 
			
		||||
 | 
			
		||||
        if not (args.os_auth_token and args.ironic_url):
 | 
			
		||||
            if not args.os_username:
 | 
			
		||||
                raise exc.CommandError("You must provide a username via "
 | 
			
		||||
                                       "either --os-username or via "
 | 
			
		||||
                                       "env[OS_USERNAME]")
 | 
			
		||||
 | 
			
		||||
            if not args.os_password:
 | 
			
		||||
                raise exc.CommandError("You must provide a password via "
 | 
			
		||||
                                       "either --os-password or via "
 | 
			
		||||
                                       "env[OS_PASSWORD]")
 | 
			
		||||
 | 
			
		||||
            if not (args.os_tenant_id or args.os_tenant_name):
 | 
			
		||||
                raise exc.CommandError("You must provide a tenant_id via "
 | 
			
		||||
                                       "either --os-tenant-id or via "
 | 
			
		||||
                                       "env[OS_TENANT_ID]")
 | 
			
		||||
 | 
			
		||||
            if not args.os_auth_url:
 | 
			
		||||
                raise exc.CommandError("You must provide an auth url via "
 | 
			
		||||
                                       "either --os-auth-url or via "
 | 
			
		||||
                                       "env[OS_AUTH_URL]")
 | 
			
		||||
 | 
			
		||||
        client = iroclient.get_client(api_version, **(args.__dict__))
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            args.func(client, args)
 | 
			
		||||
        except exc.Unauthorized:
 | 
			
		||||
            raise exc.CommandError("Invalid OpenStack Identity credentials.")
 | 
			
		||||
 | 
			
		||||
    @utils.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."""
 | 
			
		||||
        if getattr(args, 'command', None):
 | 
			
		||||
            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()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HelpFormatter(argparse.HelpFormatter):
 | 
			
		||||
    def start_section(self, heading):
 | 
			
		||||
        # Title-case the headings
 | 
			
		||||
        heading = '%s%s' % (heading[0].upper(), heading[1:])
 | 
			
		||||
        super(HelpFormatter, self).start_section(heading)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
    try:
 | 
			
		||||
        IronicShell().main(sys.argv[1:])
 | 
			
		||||
 | 
			
		||||
    except Exception as e:
 | 
			
		||||
        print >> sys.stderr, e
 | 
			
		||||
        sys.exit(1)
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    main()
 | 
			
		||||
							
								
								
									
										17
									
								
								ironicclient/v1/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								ironicclient/v1/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
 | 
			
		||||
 | 
			
		||||
# Copyright 2013 Red Hat, Inc.
 | 
			
		||||
# All Rights Reserved.
 | 
			
		||||
#
 | 
			
		||||
#    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.
 | 
			
		||||
							
								
								
									
										31
									
								
								ironicclient/v1/client.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								ironicclient/v1/client.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
# Copyright 2012 OpenStack LLC.
 | 
			
		||||
# All Rights Reserved.
 | 
			
		||||
#
 | 
			
		||||
#    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 ironicclient.common import http
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Client(http.HTTPClient):
 | 
			
		||||
    """Client for the Ironic v1 API.
 | 
			
		||||
 | 
			
		||||
    :param string endpoint: A user-supplied endpoint URL for the ironic
 | 
			
		||||
                            service.
 | 
			
		||||
    :param function token: Provides token for authentication.
 | 
			
		||||
    :param integer timeout: Allows customization of the timeout for client
 | 
			
		||||
                            http requests. (optional)
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        """Initialize a new client for the Ironic v1 API."""
 | 
			
		||||
        super(Client, self).__init__(*args, **kwargs)
 | 
			
		||||
							
								
								
									
										11
									
								
								ironicclient/v1/shell.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								ironicclient/v1/shell.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
#    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.
 | 
			
		||||
@@ -4,3 +4,4 @@ argparse
 | 
			
		||||
httplib2
 | 
			
		||||
lxml>=2.3
 | 
			
		||||
PrettyTable>=0.6,<0.8
 | 
			
		||||
python-keystoneclient>=0.3.2
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user