Basic get/list operations work
* 'glance image-list' and 'glance image-show' work * Set up tests, pep8, venvchanges/11/6211/1
parent
b5847df3e2
commit
c530de6389
|
@ -10,3 +10,4 @@ build
|
|||
dist
|
||||
python_glanceclient.egg-info
|
||||
ChangeLog
|
||||
run_tests.err.log
|
||||
|
|
34
LICENSE
34
LICENSE
|
@ -1,8 +1,3 @@
|
|||
Copyright (c) 2009 Jacob Kaplan-Moss - initial codebase (< v2.1)
|
||||
Copyright (c) 2011 Rackspace - OpenStack extensions (>= v2.1)
|
||||
Copyright (c) 2011 Nebula, Inc - Keystone refactor (>= v2.7)
|
||||
All rights reserved.
|
||||
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
|
@ -178,32 +173,3 @@ All rights reserved.
|
|||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
--- License for python-keystoneclient versions prior to 2.1 ---
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of this project nor the names of its contributors may
|
||||
be used to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
include README.rst
|
||||
include LICENSE
|
||||
recursive-include docs *
|
||||
recursive-include tests *
|
||||
|
|
22
README.rst
22
README.rst
|
@ -28,7 +28,7 @@ Python API
|
|||
By way of a quick-start::
|
||||
|
||||
# use v2.0 auth with http://example.com:5000/v2.0")
|
||||
>>> from glanceclient.v2_0 import client
|
||||
>>> from glanceclient.v1 import client
|
||||
>>> glance = client.Client(username=USERNAME, password=PASSWORD, tenant_name=TENANT, auth_url=KEYSTONE_URL)
|
||||
>>> glance.images.list()
|
||||
>>> image = glance.images.create(name="My Test Image")
|
||||
|
@ -50,20 +50,15 @@ Command-line API
|
|||
Installing this package gets you a command-line tool, ``glance``, that you
|
||||
can use to interact with Glance's Identity API.
|
||||
|
||||
You'll need to provide your OpenStack tenant, username and password. You can do this
|
||||
with the ``tenant_name``, ``--username`` and ``--password`` params, but it's
|
||||
easier to just set them as environment variables::
|
||||
You'll need to provide your OpenStack username, password, tenant, and auth
|
||||
endpoint. You can do this with the ``--tenant_id``, ``--username``,
|
||||
``--password``, and ``--auth_url`` params, but it's easier to just set them
|
||||
as environment variables::
|
||||
|
||||
export OS_TENANT_NAME=project
|
||||
export OS_TENANT_id=
|
||||
export OS_USERNAME=user
|
||||
export OS_PASSWORD=pass
|
||||
|
||||
You will also need to define the authentication url with ``--auth_url`` and the
|
||||
version of the API with ``--identity_api_version``. Or set them as an environment
|
||||
variables as well::
|
||||
|
||||
export OS_AUTH_URL=http://example.com:5000/v2.0
|
||||
export OS_IDENTITY_API_VERSION=2.0
|
||||
|
||||
Since the Identity service that Glance uses can return multiple regional image
|
||||
endpoints in the Service Catalog, you can specify the one you want with
|
||||
|
@ -74,9 +69,8 @@ You'll find complete documentation on the shell by running
|
|||
``glance help``::
|
||||
|
||||
usage: glance [--username USERNAME] [--password PASSWORD]
|
||||
[--tenant_name TENANT_NAME | --tenant_id TENANT_ID]
|
||||
[--tenant_id TENANT_id]
|
||||
[--auth_url AUTH_URL] [--region_name REGION_NAME]
|
||||
[--identity_api_version IDENTITY_API_VERSION]
|
||||
<subcommand> ...
|
||||
|
||||
Command-line interface to the OpenStack Identity API.
|
||||
|
@ -103,7 +97,5 @@ You'll find complete documentation on the shell by running
|
|||
--auth_url AUTH_URL Defaults to env[OS_AUTH_URL]
|
||||
--region_name REGION_NAME
|
||||
Defaults to env[OS_REGION_NAME]
|
||||
--identity_api_version IDENTITY_API_VERSION
|
||||
Defaults to env[OS_IDENTITY_API_VERSION] or 2.0
|
||||
|
||||
See "glance help COMMAND" for help on a specific command.
|
||||
|
|
|
@ -1,175 +0,0 @@
|
|||
# Copyright 2010 Jacob Kaplan-Moss
|
||||
# Copyright 2011 OpenStack LLC.
|
||||
# Copyright 2011 Piston Cloud Computing, Inc.
|
||||
# Copyright 2011 Nebula, Inc.
|
||||
|
||||
# All Rights Reserved.
|
||||
"""
|
||||
OpenStack Client interface. Handles the REST calls and responses.
|
||||
"""
|
||||
|
||||
import copy
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
import urllib
|
||||
import urlparse
|
||||
|
||||
import httplib2
|
||||
|
||||
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 glanceclient import exceptions
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HTTPClient(httplib2.Http):
|
||||
|
||||
USER_AGENT = 'python-glanceclient'
|
||||
|
||||
def __init__(self, username=None, tenant_id=None, tenant_name=None,
|
||||
password=None, auth_url=None, region_name=None, timeout=None,
|
||||
endpoint=None, token=None):
|
||||
super(HTTPClient, self).__init__(timeout=timeout)
|
||||
self.username = username
|
||||
self.tenant_id = tenant_id
|
||||
self.tenant_name = tenant_name
|
||||
self.password = password
|
||||
self.auth_url = auth_url.rstrip('/') if auth_url else None
|
||||
self.version = 'v2.0'
|
||||
self.region_name = region_name
|
||||
self.auth_token = token
|
||||
|
||||
self.management_url = endpoint
|
||||
|
||||
# httplib2 overrides
|
||||
self.force_exception_to_status_code = True
|
||||
|
||||
def authenticate(self):
|
||||
""" Authenticate against the keystone API.
|
||||
|
||||
Not implemented here because auth protocols should be API
|
||||
version-specific.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def _extract_service_catalog(self, url, body):
|
||||
""" Set the client's service catalog from the response data.
|
||||
|
||||
Not implemented here because data returned may be API
|
||||
version-specific.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def http_log(self, args, kwargs, resp, body):
|
||||
if os.environ.get('GLANCECLIENT_DEBUG', False):
|
||||
ch = logging.StreamHandler()
|
||||
_logger.setLevel(logging.DEBUG)
|
||||
_logger.addHandler(ch)
|
||||
elif not _logger.isEnabledFor(logging.DEBUG):
|
||||
return
|
||||
|
||||
string_parts = ['curl -i']
|
||||
for element in args:
|
||||
if element in ('GET', 'POST'):
|
||||
string_parts.append(' -X %s' % element)
|
||||
else:
|
||||
string_parts.append(' %s' % element)
|
||||
|
||||
for element in kwargs['headers']:
|
||||
header = ' -H "%s: %s"' % (element, kwargs['headers'][element])
|
||||
string_parts.append(header)
|
||||
|
||||
_logger.debug("REQ: %s\n" % "".join(string_parts))
|
||||
if 'body' in kwargs:
|
||||
_logger.debug("REQ BODY: %s\n" % (kwargs['body']))
|
||||
_logger.debug("RESP: %s\nRESP BODY: %s\n", resp, body)
|
||||
|
||||
def request(self, url, method, **kwargs):
|
||||
""" Send an http request with the specified characteristics.
|
||||
|
||||
Wrapper around httplib2.Http.request to handle tasks such as
|
||||
setting headers, JSON encoding/decoding, and error handling.
|
||||
"""
|
||||
# Copy the kwargs so we can reuse the original in case of redirects
|
||||
request_kwargs = copy.copy(kwargs)
|
||||
request_kwargs.setdefault('headers', kwargs.get('headers', {}))
|
||||
request_kwargs['headers']['User-Agent'] = self.USER_AGENT
|
||||
if 'body' in kwargs:
|
||||
request_kwargs['headers']['Content-Type'] = 'application/json'
|
||||
request_kwargs['body'] = json.dumps(kwargs['body'])
|
||||
|
||||
resp, body = super(HTTPClient, self).request(url,
|
||||
method,
|
||||
**request_kwargs)
|
||||
|
||||
self.http_log((url, method,), request_kwargs, resp, body)
|
||||
|
||||
if body:
|
||||
try:
|
||||
body = json.loads(body)
|
||||
except ValueError, e:
|
||||
_logger.debug("Could not decode JSON from body: %s" % body)
|
||||
else:
|
||||
_logger.debug("No body was returned.")
|
||||
body = None
|
||||
|
||||
if resp.status in (400, 401, 403, 404, 408, 409, 413, 500, 501):
|
||||
_logger.exception("Request returned failure status.")
|
||||
raise exceptions.from_response(resp, body)
|
||||
elif resp.status in (301, 302, 305):
|
||||
# Redirected. Reissue the request to the new location.
|
||||
return self.request(resp['location'], method, **kwargs)
|
||||
|
||||
return resp, body
|
||||
|
||||
def _cs_request(self, url, method, **kwargs):
|
||||
if not self.management_url:
|
||||
self.authenticate()
|
||||
|
||||
kwargs.setdefault('headers', {})
|
||||
if self.auth_token:
|
||||
kwargs['headers']['X-Auth-Token'] = self.auth_token
|
||||
|
||||
# Perform the request once. If we get a 401 back then it
|
||||
# might be because the auth token expired, so try to
|
||||
# re-authenticate and try again. If it still fails, bail.
|
||||
try:
|
||||
resp, body = self.request(self.management_url + url, method,
|
||||
**kwargs)
|
||||
return resp, body
|
||||
except exceptions.Unauthorized:
|
||||
try:
|
||||
if getattr(self, '_failures', 0) < 1:
|
||||
self._failures = getattr(self, '_failures', 0) + 1
|
||||
self.authenticate()
|
||||
resp, body = self.request(self.management_url + url,
|
||||
method, **kwargs)
|
||||
return resp, body
|
||||
else:
|
||||
raise
|
||||
except exceptions.Unauthorized:
|
||||
raise
|
||||
|
||||
def get(self, url, **kwargs):
|
||||
return self._cs_request(url, 'GET', **kwargs)
|
||||
|
||||
def post(self, url, **kwargs):
|
||||
return self._cs_request(url, 'POST', **kwargs)
|
||||
|
||||
def put(self, url, **kwargs):
|
||||
return self._cs_request(url, 'PUT', **kwargs)
|
||||
|
||||
def delete(self, url, **kwargs):
|
||||
return self._cs_request(url, 'DELETE', **kwargs)
|
|
@ -1,5 +1,4 @@
|
|||
# Copyright 2010 Jacob Kaplan-Moss
|
||||
# Copyright 2011 OpenStack LLC.
|
||||
# Copyright 2012 OpenStack LLC.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
|
@ -18,7 +17,7 @@
|
|||
Base utilities to build API operation managers and objects on top of.
|
||||
"""
|
||||
|
||||
from glanceclient import exceptions
|
||||
from glanceclient.common import exceptions
|
||||
|
||||
|
||||
# Python 2.4 compat
|
||||
|
@ -81,7 +80,7 @@ class Manager(object):
|
|||
return self.resource_class(self, body[response_key])
|
||||
|
||||
def _delete(self, url):
|
||||
resp, body = self.api.delete(url)
|
||||
self.api.delete(url)
|
||||
|
||||
def _update(self, url, body, response_key=None, method="PUT"):
|
||||
methods = {"PUT": self.api.put,
|
||||
|
@ -96,45 +95,6 @@ class Manager(object):
|
|||
return self.resource_class(self, body[response_key])
|
||||
|
||||
|
||||
class ManagerWithFind(Manager):
|
||||
"""
|
||||
Like a `Manager`, but with additional `find()`/`findall()` methods.
|
||||
"""
|
||||
def find(self, **kwargs):
|
||||
"""
|
||||
Find a single item with attributes matching ``**kwargs``.
|
||||
|
||||
This isn't very efficient: it loads the entire list then filters on
|
||||
the Python side.
|
||||
"""
|
||||
rl = self.findall(**kwargs)
|
||||
try:
|
||||
return rl[0]
|
||||
except IndexError:
|
||||
msg = "No %s matching %s." % (self.resource_class.__name__, kwargs)
|
||||
raise exceptions.NotFound(404, msg)
|
||||
|
||||
def findall(self, **kwargs):
|
||||
"""
|
||||
Find all items with attributes matching ``**kwargs``.
|
||||
|
||||
This isn't very efficient: it loads the entire list then filters on
|
||||
the Python side.
|
||||
"""
|
||||
found = []
|
||||
searches = kwargs.items()
|
||||
|
||||
for obj in self.list():
|
||||
try:
|
||||
if all(getattr(obj, attr) == value
|
||||
for (attr, value) in searches):
|
||||
found.append(obj)
|
||||
except AttributeError:
|
||||
continue
|
||||
|
||||
return found
|
||||
|
||||
|
||||
class Resource(object):
|
||||
"""
|
||||
A resource represents a particular instance of an object (tenant, user,
|
|
@ -1,5 +1,3 @@
|
|||
# Copyright 2010 Jacob Kaplan-Moss
|
||||
# Copyright 2011 Nebula, Inc.
|
||||
"""
|
||||
Exception definitions.
|
||||
"""
|
||||
|
@ -125,7 +123,7 @@ def from_response(response, body):
|
|||
else:
|
||||
# If we didn't get back a properly formed error message we
|
||||
# probably couldn't communicate with Keystone at all.
|
||||
message = "Unable to communicate with identity service: %s." % body
|
||||
message = "Unable to communicate with image service: %s." % body
|
||||
details = None
|
||||
return cls(code=response.status, message=message, details=details)
|
||||
else:
|
|
@ -0,0 +1,121 @@
|
|||
"""
|
||||
OpenStack Client interface. Handles the REST calls and responses.
|
||||
"""
|
||||
|
||||
import copy
|
||||
import logging
|
||||
import os
|
||||
import urlparse
|
||||
|
||||
import httplib2
|
||||
|
||||
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 glanceclient.common import exceptions
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
USER_AGENT = 'python-glanceclient'
|
||||
|
||||
|
||||
class HTTPClient(httplib2.Http):
|
||||
|
||||
def __init__(self, endpoint, token=None, timeout=600):
|
||||
super(HTTPClient, self).__init__(timeout=timeout)
|
||||
self.endpoint = endpoint
|
||||
self.auth_token = token
|
||||
|
||||
# httplib2 overrides
|
||||
self.force_exception_to_status_code = True
|
||||
|
||||
def http_log(self, args, kwargs, resp, body):
|
||||
if os.environ.get('GLANCECLIENT_DEBUG', False):
|
||||
ch = logging.StreamHandler()
|
||||
logger.setLevel(logging.DEBUG)
|
||||
logger.addHandler(ch)
|
||||
elif not logger.isEnabledFor(logging.DEBUG):
|
||||
return
|
||||
|
||||
string_parts = ['curl -i']
|
||||
for element in args:
|
||||
if element in ('GET', 'POST'):
|
||||
string_parts.append(' -X %s' % element)
|
||||
else:
|
||||
string_parts.append(' %s' % element)
|
||||
|
||||
for element in kwargs['headers']:
|
||||
header = ' -H "%s: %s"' % (element, kwargs['headers'][element])
|
||||
string_parts.append(header)
|
||||
|
||||
logger.debug("REQ: %s\n" % "".join(string_parts))
|
||||
if 'body' in kwargs:
|
||||
logger.debug("REQ BODY: %s\n" % (kwargs['body']))
|
||||
logger.debug("RESP: %s\nRESP BODY: %s\n", resp, body)
|
||||
|
||||
def _http_request(self, url, method, **kwargs):
|
||||
""" Send an http request with the specified characteristics.
|
||||
|
||||
Wrapper around httplib2.Http.request to handle tasks such as
|
||||
setting headers, JSON encoding/decoding, and error handling.
|
||||
"""
|
||||
# Copy the kwargs so we can reuse the original in case of redirects
|
||||
_kwargs = copy.copy(kwargs)
|
||||
_kwargs.setdefault('headers', kwargs.get('headers', {}))
|
||||
_kwargs['headers']['User-Agent'] = USER_AGENT
|
||||
if 'body' in kwargs:
|
||||
_kwargs['headers']['Content-Type'] = 'application/json'
|
||||
_kwargs['body'] = json.dumps(kwargs['body'])
|
||||
|
||||
resp, body = super(HTTPClient, self).request(url, method, **_kwargs)
|
||||
self.http_log((url, method,), _kwargs, resp, body)
|
||||
|
||||
if body:
|
||||
try:
|
||||
body = json.loads(body)
|
||||
except ValueError:
|
||||
logger.debug("Could not decode JSON from body: %s" % body)
|
||||
else:
|
||||
logger.debug("No body was returned.")
|
||||
body = None
|
||||
|
||||
if 400 <= resp.status < 600:
|
||||
logger.exception("Request returned failure status.")
|
||||
raise exceptions.from_response(resp, body)
|
||||
elif resp.status in (301, 302, 305):
|
||||
# Redirected. Reissue the request to the new location.
|
||||
return self._http_request(resp['location'], method, **kwargs)
|
||||
|
||||
return resp, body
|
||||
|
||||
def request(self, url, method, **kwargs):
|
||||
kwargs.setdefault('headers', {})
|
||||
if self.auth_token:
|
||||
kwargs['headers']['X-Auth-Token'] = self.auth_token
|
||||
|
||||
req_url = self.endpoint + url
|
||||
resp, body = self._http_request(req_url, method, **kwargs)
|
||||
return resp, body
|
||||
|
||||
def head(self, url, **kwargs):
|
||||
return self.request(url, 'HEAD', **kwargs)
|
||||
|
||||
def get(self, url, **kwargs):
|
||||
return self.request(url, 'GET', **kwargs)
|
||||
|
||||
def post(self, url, **kwargs):
|
||||
return self.request(url, 'POST', **kwargs)
|
||||
|
||||
def put(self, url, **kwargs):
|
||||
return self.request(url, 'PUT', **kwargs)
|
||||
|
||||
def delete(self, url, **kwargs):
|
||||
return self.request(url, 'DELETE', **kwargs)
|
|
@ -1,8 +1,24 @@
|
|||
# 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 uuid
|
||||
|
||||
import prettytable
|
||||
|
||||
from glanceclient import exceptions
|
||||
from glanceclient.common import exceptions
|
||||
|
||||
|
||||
# Decorator for cli-args
|
||||
|
@ -69,26 +85,33 @@ def find_resource(manager, name_or_id):
|
|||
raise exceptions.CommandError(msg)
|
||||
|
||||
|
||||
def unauthenticated(f):
|
||||
""" Adds 'unauthenticated' attribute to decorated function.
|
||||
|
||||
Usage:
|
||||
@unauthenticated
|
||||
def mymethod(f):
|
||||
...
|
||||
"""
|
||||
f.unauthenticated = True
|
||||
def skip_authentication(f):
|
||||
"""Function decorator used to indicate a caller may be unauthenticated."""
|
||||
f.require_authentication = False
|
||||
return f
|
||||
|
||||
|
||||
def isunauthenticated(f):
|
||||
def is_authentication_required(f):
|
||||
"""Checks to see if the function requires authentication.
|
||||
|
||||
Use the skip_authentication decorator to indicate a caller may
|
||||
skip the authentication step.
|
||||
"""
|
||||
Checks to see if the function is marked as not requiring authentication
|
||||
with the @unauthenticated decorator. Returns True if decorator is
|
||||
set to True, False otherwise.
|
||||
"""
|
||||
return getattr(f, 'unauthenticated', False)
|
||||
return getattr(f, 'require_authentication', True)
|
||||
|
||||
|
||||
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', '')
|
|
@ -1,205 +0,0 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 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 logging
|
||||
import urlparse
|
||||
|
||||
from glanceclient import client
|
||||
from glanceclient import exceptions
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Client(client.HTTPClient):
|
||||
"""Client for the OpenStack Images pre-version calls API.
|
||||
|
||||
:param string endpoint: A user-supplied endpoint URL for the glance
|
||||
service.
|
||||
:param integer timeout: Allows customization of the timeout for client
|
||||
http requests. (optional)
|
||||
|
||||
Example::
|
||||
|
||||
>>> from glanceclient.generic import client
|
||||
>>> root = client.Client(auth_url=KEYSTONE_URL)
|
||||
>>> versions = root.discover()
|
||||
...
|
||||
>>> from glanceclient.v1_1 import client as v11client
|
||||
>>> glance = v11client.Client(auth_url=versions['v1.1']['url'])
|
||||
...
|
||||
>>> image = glance.images.get(IMAGE_ID)
|
||||
>>> image.delete()
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, endpoint=None, **kwargs):
|
||||
""" Initialize a new client for the Glance v2.0 API. """
|
||||
super(Client, self).__init__(endpoint=endpoint, **kwargs)
|
||||
self.endpoint = endpoint
|
||||
|
||||
def discover(self, url=None):
|
||||
""" Discover Glance servers and return API versions supported.
|
||||
|
||||
:param url: optional url to test (without version)
|
||||
|
||||
Returns::
|
||||
|
||||
{
|
||||
'message': 'Glance found at http://127.0.0.1:5000/',
|
||||
'v2.0': {
|
||||
'status': 'beta',
|
||||
'url': 'http://127.0.0.1:5000/v2.0/',
|
||||
'id': 'v2.0'
|
||||
},
|
||||
}
|
||||
|
||||
"""
|
||||
if url:
|
||||
return self._check_glance_versions(url)
|
||||
else:
|
||||
return self._local_glance_exists()
|
||||
|
||||
def _local_glance_exists(self):
|
||||
""" Checks if Glance is available on default local port 9292 """
|
||||
return self._check_glance_versions("http://localhost:9292")
|
||||
|
||||
def _check_glance_versions(self, url):
|
||||
""" Calls Glance URL and detects the available API versions """
|
||||
try:
|
||||
httpclient = client.HTTPClient()
|
||||
resp, body = httpclient.request(url, "GET",
|
||||
headers={'Accept': 'application/json'})
|
||||
if resp.status in (300): # Glance returns a 300 Multiple Choices
|
||||
try:
|
||||
results = {}
|
||||
if 'version' in body:
|
||||
results['message'] = "Glance found at %s" % url
|
||||
version = body['version']
|
||||
# Stable/diablo incorrect format
|
||||
id, status, version_url = self._get_version_info(
|
||||
version, url)
|
||||
results[str(id)] = {"id": id,
|
||||
"status": status,
|
||||
"url": version_url}
|
||||
return results
|
||||
elif 'versions' in body:
|
||||
# Correct format
|
||||
results['message'] = "Glance found at %s" % url
|
||||
for version in body['versions']['values']:
|
||||
id, status, version_url = self._get_version_info(
|
||||
version, url)
|
||||
results[str(id)] = {"id": id,
|
||||
"status": status,
|
||||
"url": version_url}
|
||||
return results
|
||||
else:
|
||||
results['message'] = "Unrecognized response from %s" \
|
||||
% url
|
||||
return results
|
||||
except KeyError:
|
||||
raise exceptions.AuthorizationFailure()
|
||||
elif resp.status == 305:
|
||||
return self._check_glance_versions(resp['location'])
|
||||
else:
|
||||
raise exceptions.from_response(resp, body)
|
||||
except Exception as e:
|
||||
_logger.exception(e)
|
||||
|
||||
def discover_extensions(self, url=None):
|
||||
""" Discover Glance extensions supported.
|
||||
|
||||
:param url: optional url to test (should have a version in it)
|
||||
|
||||
Returns::
|
||||
|
||||
{
|
||||
'message': 'Glance extensions at http://127.0.0.1:35357/v2',
|
||||
'OS-KSEC2': 'OpenStack EC2 Credentials Extension',
|
||||
}
|
||||
|
||||
"""
|
||||
if url:
|
||||
return self._check_glance_extensions(url)
|
||||
|
||||
def _check_glance_extensions(self, url):
|
||||
""" Calls Glance URL and detects the available extensions """
|
||||
try:
|
||||
httpclient = client.HTTPClient()
|
||||
if not url.endswith("/"):
|
||||
url += '/'
|
||||
resp, body = httpclient.request("%sextensions" % url, "GET",
|
||||
headers={'Accept': 'application/json'})
|
||||
if resp.status in (200, 204): # in some cases we get No Content
|
||||
try:
|
||||
results = {}
|
||||
if 'extensions' in body:
|
||||
if 'values' in body['extensions']:
|
||||
# Parse correct format (per contract)
|
||||
for extension in body['extensions']['values']:
|
||||
alias, name = self._get_extension_info(
|
||||
extension['extension'])
|
||||
results[alias] = name
|
||||
return results
|
||||
else:
|
||||
# Support incorrect, but prevalent format
|
||||
for extension in body['extensions']:
|
||||
alias, name = self._get_extension_info(
|
||||
extension)
|
||||
results[alias] = name
|
||||
return results
|
||||
else:
|
||||
results['message'] = "Unrecognized extensions" \
|
||||
" response from %s" % url
|
||||
return results
|
||||
except KeyError:
|
||||
raise exceptions.AuthorizationFailure()
|
||||
elif resp.status == 305:
|
||||
return self._check_glance_extensions(resp['location'])
|
||||
else:
|
||||
raise exceptions.from_response(resp, body)
|
||||
except Exception as e:
|
||||
_logger.exception(e)
|
||||
|
||||
@staticmethod
|
||||
def _get_version_info(version, root_url):
|
||||
""" Parses version information
|
||||
|
||||
:param version: a dict of a Glance version response
|
||||
:param root_url: string url used to construct
|
||||
the version if no URL is provided.
|
||||
:returns: tuple - (verionId, versionStatus, versionUrl)
|
||||
"""
|
||||
id = version['id']
|
||||
status = version['status']
|
||||
ref = urlparse.urljoin(root_url, id)
|
||||
if 'links' in version:
|
||||
for link in version['links']:
|
||||
if link['rel'] == 'self':
|
||||
ref = link['href']
|
||||
break
|
||||
return (id, status, ref)
|
||||
|
||||
@staticmethod
|
||||
def _get_extension_info(extension):
|
||||
""" Parses extension information
|
||||
|
||||
:param extension: a dict of a Glance extension response
|
||||
:returns: tuple - (alias, name)
|
||||
"""
|
||||
alias = extension['alias']
|
||||
name = extension['name']
|
||||
return (alias, name)
|
|
@ -1,57 +0,0 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 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 glanceclient import utils
|
||||
from glanceclient.generic import client
|
||||
|
||||
CLIENT_CLASS = client.Client
|
||||
|
||||
|
||||
@utils.unauthenticated
|
||||
def do_discover(cs, args):
|
||||
"""
|
||||
Discover Keystone servers and show authentication protocols and
|
||||
extensions supported.
|
||||
|
||||
Usage::
|
||||
$ glance discover
|
||||
Image Service found at http://localhost:9292
|
||||
- supports version v1.0 (DEPRECATED) here http://localhost:9292/v1.0
|
||||
- supports version v1.1 (CURRENT) here http://localhost:9292/v1.1
|
||||
- supports version v2.0 (BETA) here http://localhost:9292/v2.0
|
||||
- and RAX-KSKEY: Rackspace API Key Authentication Admin Extension
|
||||
- and RAX-KSGRP: Rackspace Keystone Group Extensions
|
||||
"""
|
||||
if cs.auth_url:
|
||||
versions = cs.discover(cs.auth_url)
|
||||
else:
|
||||
versions = cs.discover()
|
||||
if versions:
|
||||
if 'message' in versions:
|
||||
print versions['message']
|
||||
for key, version in versions.iteritems():
|
||||
if key != 'message':
|
||||
print " - supports version %s (%s) here %s" % \
|
||||
(version['id'], version['status'], version['url'])
|
||||
extensions = cs.discover_extensions(version['url'])
|
||||
if extensions:
|
||||
for key, extension in extensions.iteritems():
|
||||
if key != 'message':
|
||||
print " - and %s: %s" % \
|
||||
(key, extension)
|
||||
else:
|
||||
print "No Glance-compatible endpoint found"
|
|
@ -1,81 +0,0 @@
|
|||
# Copyright 2011 OpenStack LLC.
|
||||
# Copyright 2011, Piston Cloud Computing, Inc.
|
||||
# Copyright 2011 Nebula, 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.
|
||||
|
||||
|
||||
from glanceclient import exceptions
|
||||
|
||||
|
||||
class ServiceCatalog(object):
|
||||
"""
|
||||
Helper methods for dealing with an OpenStack Identity
|
||||
Service Catalog.
|
||||
"""
|
||||
|
||||
def __init__(self, resource_dict):
|
||||
self.catalog = resource_dict
|
||||
|
||||
def get_token(self):
|
||||
"""Fetch token details fron service catalog"""
|
||||
token = {'id': self.catalog['token']['id'],
|
||||
'expires': self.catalog['token']['expires']}
|
||||
try:
|
||||
token['tenant'] = self.catalog['token']['tenant']['id']
|
||||
except:
|
||||
# just leave the tenant out if it doesn't exist
|
||||
pass
|
||||
return token
|
||||
|
||||
def url_for(self, attr=None, filter_value=None,
|
||||
service_type='image', endpoint_type='publicURL'):
|
||||
"""Fetch an endpoint from the service catalog.
|
||||
|
||||
Fetch the specified endpoint from the service catalog for
|
||||
a particular endpoint attribute. If no attribute is given, return
|
||||
the first endpoint of the specified type.
|
||||
|
||||
See tests for a sample service catalog.
|
||||
"""
|
||||
catalog = self.catalog.get('serviceCatalog', [])
|
||||
|
||||
for service in catalog:
|
||||
if service['type'] != service_type:
|
||||
continue
|
||||
|
||||
endpoints = service['endpoints']
|
||||
for endpoint in endpoints:
|
||||
if not filter_value or endpoint.get(attr) == filter_value:
|
||||
return endpoint[endpoint_type]
|
||||
|
||||
raise exceptions.EndpointNotFound('Endpoint not found.')
|
||||
|
||||
def get_endpoints(self, service_type=None, endpoint_type=None):
|
||||
"""Fetch and filter endpoints for the specified service(s)
|
||||
|
||||
Returns endpoints for the specified service (or all) and
|
||||
that contain the specified type (or all).
|
||||
"""
|
||||
sc = {}
|
||||
for service in self.catalog.get('serviceCatalog', []):
|
||||
if service_type and service_type != service['type']:
|
||||
continue
|
||||
sc[service['type']] = []
|
||||
for endpoint in service['endpoints']:
|
||||
if endpoint_type and endpoint_type not in endpoint.keys():
|
||||
continue
|
||||
sc[service['type']].append(endpoint)
|
||||
return sc
|
|
@ -1,5 +1,4 @@
|
|||
# Copyright 2010 Jacob Kaplan-Moss
|
||||
# Copyright 2011 OpenStack LLC.
|
||||
# Copyright 2012 OpenStack LLC.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
|
@ -20,27 +19,14 @@ Command-line interface to the OpenStack Images API.
|
|||
|
||||
import argparse
|
||||
import httplib2
|
||||
import os
|
||||
import sys
|
||||
|
||||
from glanceclient import exceptions as exc
|
||||
from glanceclient import utils
|
||||
from glanceclient.v2_0 import shell as shell_v2_0
|
||||
from glanceclient.generic import shell as shell_generic
|
||||
from keystoneclient.v2_0 import client as ksclient
|
||||
|
||||
|
||||
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', '')
|
||||
from glanceclient.common import exceptions as exc
|
||||
from glanceclient.common import utils
|
||||
from glanceclient.v1 import shell as shell_v1
|
||||
from glanceclient.v1 import client as client_v1
|
||||
|
||||
|
||||
class OpenStackImagesShell(object):
|
||||
|
@ -52,7 +38,7 @@ class OpenStackImagesShell(object):
|
|||
epilog='See "glance help COMMAND" '\
|
||||
'for help on a specific command.',
|
||||
add_help=False,
|
||||
formatter_class=OpenStackHelpFormatter,
|
||||
formatter_class=HelpFormatter,
|
||||
)
|
||||
|
||||
# Global arguments
|
||||
|
@ -66,33 +52,33 @@ class OpenStackImagesShell(object):
|
|||
action='store_true',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--username',
|
||||
default=env('OS_USERNAME'),
|
||||
parser.add_argument('--os-username',
|
||||
default=utils.env('OS_USERNAME'),
|
||||
help='Defaults to env[OS_USERNAME]')
|
||||
|
||||
parser.add_argument('--password',
|
||||
default=env('OS_PASSWORD'),
|
||||
parser.add_argument('--os-password',
|
||||
default=utils.env('OS_PASSWORD'),
|
||||
help='Defaults to env[OS_PASSWORD]')
|
||||
|
||||
parser.add_argument('--tenant_name',
|
||||
default=env('OS_TENANT_NAME'),
|
||||
help='Defaults to env[OS_TENANT_NAME]')
|
||||
|
||||
parser.add_argument('--tenant_id',
|
||||
default=env('OS_TENANT_ID'), dest='os_tenant_id',
|
||||
parser.add_argument('--os-tenant-id',
|
||||
default=utils.env('OS_TENANT_ID'),
|
||||
help='Defaults to env[OS_TENANT_ID]')
|
||||
|
||||
parser.add_argument('--auth_url',
|
||||
default=env('OS_AUTH_URL'),
|
||||
parser.add_argument('--os-auth-url',
|
||||
default=utils.env('OS_AUTH_URL'),
|
||||
help='Defaults to env[OS_AUTH_URL]')
|
||||
|
||||
parser.add_argument('--region_name',
|
||||
default=env('OS_REGION_NAME'),
|
||||
parser.add_argument('--os-region-name',
|
||||
default=utils.env('OS_REGION_NAME'),
|
||||
help='Defaults to env[OS_REGION_NAME]')
|
||||
|
||||
parser.add_argument('--identity_api_version',
|
||||
default=env('OS_IDENTITY_API_VERSION', 'KEYSTONE_VERSION'),
|
||||
help='Defaults to env[OS_IDENTITY_API_VERSION] or 2.0')
|
||||
parser.add_argument('--os-auth-token',
|
||||
default=utils.env('OS_AUTH_TOKEN'),
|
||||
help='Defaults to env[OS_AUTH_TOKEN]')
|
||||
|
||||
parser.add_argument('--os-image-url',
|
||||
default=utils.env('OS_IMAGE_URL'),
|
||||
help='Defaults to env[OS_IMAGE_URL]')
|
||||
|
||||
return parser
|
||||
|
||||
|
@ -101,16 +87,7 @@ class OpenStackImagesShell(object):
|
|||
|
||||
self.subcommands = {}
|
||||
subparsers = parser.add_subparsers(metavar='<subcommand>')
|
||||
|
||||
try:
|
||||
actions_module = {
|
||||
'2.0': shell_v2_0,
|
||||
}[version]
|
||||
except KeyError:
|
||||
actions_module = shell_v2_0
|
||||
|
||||
self._find_actions(subparsers, actions_module)
|
||||
self._find_actions(subparsers, shell_generic)
|
||||
self._find_actions(subparsers, shell_v1)
|
||||
self._find_actions(subparsers, self)
|
||||
|
||||
return parser
|
||||
|
@ -128,7 +105,7 @@ class OpenStackImagesShell(object):
|
|||
help=help,
|
||||
description=desc,
|
||||
add_help=False,
|
||||
formatter_class=OpenStackHelpFormatter
|
||||
formatter_class=HelpFormatter
|
||||
)
|
||||
subparser.add_argument('-h', '--help',
|
||||
action='help',
|
||||
|
@ -139,19 +116,28 @@ class OpenStackImagesShell(object):
|
|||
subparser.add_argument(*args, **kwargs)
|
||||
subparser.set_defaults(func=callback)
|
||||
|
||||
def _authenticate(self, username, password, tenant_id, auth_url):
|
||||
_ksclient = ksclient.Client(username=username,
|
||||
password=password,
|
||||
tenant_id=tenant_id,
|
||||
auth_url=auth_url)
|
||||
endpoint = _ksclient.service_catalog.url_for(service_type='image',
|
||||
endpoint_type='publicURL')
|
||||
return (endpoint, _ksclient.auth_token)
|
||||
|
||||
def main(self, argv):
|
||||
# Parse args once to find version
|
||||
parser = self.get_base_parser()
|
||||
(options, args) = parser.parse_known_args(argv)
|
||||
|
||||
# build available subcommands based on version
|
||||
api_version = options.identity_api_version
|
||||
api_version = '1'
|
||||
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:
|
||||
if options.help or not argv:
|
||||
self.do_help(options)
|
||||
return 0
|
||||
|
||||
|
@ -167,51 +153,45 @@ class OpenStackImagesShell(object):
|
|||
self.do_help(args)
|
||||
return 0
|
||||
|
||||
#FIXME(usrleon): Here should be restrict for project id same as
|
||||
# for username or apikey but for compatibility it is not.
|
||||
auth_reqd = (utils.is_authentication_required(args.func) or
|
||||
not (args.os_auth_token and args.os_image_url))
|
||||
|
||||
if not utils.isunauthenticated(args.func):
|
||||
if not args.username:
|
||||
raise exc.CommandError("You must provide a username "
|
||||
"via either --username or env[OS_USERNAME]")
|
||||
|
||||
if not args.password:
|
||||
raise exc.CommandError("You must provide a password "
|
||||
"via either --password or env[OS_PASSWORD]")
|
||||
|
||||
if not args.auth_url:
|
||||
raise exc.CommandError("You must provide an auth url "
|
||||
"via either --auth_url or via env[OS_AUTH_URL]")
|
||||
|
||||
if utils.isunauthenticated(args.func):
|
||||
self.cs = shell_generic.CLIENT_CLASS(endpoint=args.auth_url)
|
||||
if not auth_reqd:
|
||||
endpoint = args.os_image_url
|
||||
token = args.os_auth_token
|
||||
else:
|
||||
api_version = options.identity_api_version
|
||||
self.cs = self.get_api_class(api_version)(
|
||||
username=args.username,
|
||||
tenant_name=args.tenant_name,
|
||||
tenant_id=args.os_tenant_id,
|
||||
password=args.password,
|
||||
auth_url=args.auth_url,
|
||||
region_name=args.region_name)
|
||||
if not args.os_username:
|
||||
raise exc.CommandError("You must provide a username via"
|
||||
" either --os-username or env[OS_USERNAME]")
|
||||
|
||||
if not args.os_password:
|
||||
raise exc.CommandError("You must provide a password via"
|
||||
" either --os-password or env[OS_PASSWORD]")
|
||||
|
||||
if not args.os_tenant_id:
|
||||
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]")
|
||||
|
||||
endpoint, token = self._authenticate(args.os_username,
|
||||
args.os_password,
|
||||
args.os_tenant_id,
|
||||
args.os_auth_url)
|
||||
|
||||
image_service = client_v1.Client(endpoint, token)
|
||||
|
||||
try:
|
||||
args.func(self.cs, args)
|
||||
args.func(image_service, args)
|
||||
except exc.Unauthorized:
|
||||
raise exc.CommandError("Invalid OpenStack Identity credentials.")
|
||||
except exc.AuthorizationFailure:
|
||||
raise exc.CommandError("Unable to authorize user")
|
||||
|
||||
def get_api_class(self, version):
|
||||
try:
|
||||
return {
|
||||
"2.0": shell_v2_0.CLIENT_CLASS,
|
||||
}[version]
|
||||
except KeyError:
|
||||
return shell_v2_0.CLIENT_CLASS
|
||||
|
||||
@utils.arg('command', metavar='<subcommand>', nargs='?',
|
||||
help='Display help for <subcommand>')
|
||||
help='Display help for <subcommand>')
|
||||
def do_help(self, args):
|
||||
"""
|
||||
Display help about this program or one of its subcommands.
|
||||
|
@ -226,12 +206,11 @@ class OpenStackImagesShell(object):
|
|||
self.parser.print_help()
|
||||
|
||||
|
||||
# I'm picky about my shell help.
|
||||
class OpenStackHelpFormatter(argparse.HelpFormatter):
|
||||
class HelpFormatter(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)
|
||||
super(HelpFormatter, self).start_section(heading)
|
||||
|
||||
|
||||
def main():
|
||||
|
@ -240,7 +219,7 @@ def main():
|
|||
|
||||
except Exception, e:
|
||||
if httplib2.debuglevel == 1:
|
||||
raise # dump stack.
|
||||
raise
|
||||
else:
|
||||
print >> sys.stderr, e
|
||||
sys.exit(1)
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
from glanceclient.v1.client import Client
|
|
@ -0,0 +1,38 @@
|
|||
# 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 logging
|
||||
|
||||
from glanceclient.common import http
|
||||
from glanceclient.v1 import images
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Client(http.HTTPClient):
|
||||
"""Client for the OpenStack Images v1 API.
|
||||
|
||||
:param string endpoint: A user-supplied endpoint URL for the glance
|
||||
service.
|
||||
:param string token: Token for authentication.
|
||||
:param integer timeout: Allows customization of the timeout for client
|
||||
http requests. (optional)
|
||||
"""
|
||||
|
||||
def __init__(self, endpoint, token=None, timeout=600):
|
||||
""" Initialize a new client for the Images v1 API. """
|
||||
super(Client, self).__init__(endpoint, token=token, timeout=timeout)
|
||||
self.images = images.ImageManager(self)
|
|
@ -0,0 +1,70 @@
|
|||
# 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 urllib
|
||||
|
||||
from glanceclient.common import base
|
||||
|
||||
|
||||
class Image(base.Resource):
|
||||
def __repr__(self):
|
||||
return "<Image %s>" % self._info
|
||||
|
||||
def delete(self):
|
||||
return self.manager.delete(self)
|
||||
|
||||
|
||||
class ImageManager(base.Manager):
|
||||
resource_class = Image
|
||||
|
||||
def get(self, image):
|
||||
"""Get the metadata for a specific image.
|
||||
|
||||
:param image: image object or id to look up
|
||||
:rtype: :class:`Image`
|
||||
"""
|
||||
resp, body = self.api.head("/images/%s" % base.getid(image))
|
||||
meta = {'properties': {}}
|
||||
for key, value in resp.iteritems():
|
||||
if key.startswith('x-image-meta-property-'):
|
||||
_key = key[22:]
|
||||
meta['properties'][_key] = value
|
||||
elif key.startswith('x-image-meta-'):
|
||||
_key = key[13:]
|
||||
meta[_key] = value
|
||||
return Image(self, meta)
|
||||
|
||||
def list(self, limit=None, marker=None):
|
||||
"""Get a list of images.
|
||||
|
||||
:param limit: maximum number of images to return. Used for pagination.
|
||||
:param marker: id of image last seen by caller. Used for pagination.
|
||||
:rtype: list of :class:`Image`
|
||||
"""
|
||||
params = {}
|
||||
if limit:
|
||||
params['limit'] = int(limit)
|
||||
if marker:
|
||||
params['marker'] = int(marker)
|
||||
|
||||
query = ""
|
||||
if params:
|
||||
query = "?" + urllib.urlencode(params)
|
||||
|
||||
return self._list("/images/detail%s" % query, "images")
|
||||
|
||||
def delete(self, image):
|
||||
"""Delete an image."""
|
||||
self._delete("/images/%s" % base.getid(image))
|
|
@ -0,0 +1,38 @@
|
|||
# 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
|
||||
|
||||
from glanceclient.common import utils
|
||||
|
||||
|
||||
def do_image_list(gc, args):
|
||||
"""List images."""
|
||||
images = gc.images.list()
|
||||
columns = ['ID', 'Name', 'Disk Format', 'Container Format', 'Size']
|
||||
utils.print_list(images, columns)
|
||||
|
||||
|
||||
@utils.arg('id', metavar='<IMAGE_ID>', help='ID of image to describe.')
|
||||
def do_image_show(gc, args):
|
||||
"""Describe a specific image."""
|
||||
image = gc.images.get(args.id)
|
||||
|
||||
# Flatten image properties dict
|
||||
info = copy.deepcopy(image._info)
|
||||
for (k, v) in info.pop('properties').iteritems():
|
||||
info['Property \'%s\'' % k] = v
|
||||
|
||||
utils.print_dict(info)
|
|
@ -1 +0,0 @@
|
|||
from keystoneclient.v2_0.client import Client
|
|
@ -1,113 +0,0 @@
|
|||
# Copyright 2011 Nebula, 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.
|
||||
import logging
|
||||
|
||||
from glanceclient import client
|
||||
from glanceclient import exceptions
|
||||
from glanceclient import service_catalog
|
||||
from glanceclient.v1_1 import images
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Client(client.HTTPClient):
|
||||
"""Client for the OpenStack Images v1.1 API.
|
||||
|
||||
:param string username: Username for authentication. (optional)
|
||||
:param string password: Password for authentication. (optional)
|
||||
:param string token: Token for authentication. (optional)
|
||||
:param string tenant_name: Tenant id. (optional)
|
||||
:param string tenant_id: Tenant name. (optional)
|
||||
:param string auth_url: Keystone service endpoint for authorization.
|
||||
:param string region_name: Name of a region to select when choosing an
|
||||
endpoint from the service catalog.
|
||||
:param string endpoint: A user-supplied endpoint URL for the glance
|
||||
service. Lazy-authentication is possible for API
|
||||
service calls if endpoint is set at
|
||||
instantiation.(optional)
|
||||
:param integer timeout: Allows customization of the timeout for client
|
||||
http requests. (optional)
|
||||
|
||||
Example::
|
||||
|
||||
>>> from glanceclient.v1_1 import client
|
||||
>>> glance = client.Client(username=USER,
|
||||
password=PASS,
|
||||
tenant_name=TENANT_NAME,
|
||||
auth_url=KEYSTONE_URL)
|
||||
>>> glance.images.list()
|
||||
...
|
||||
>>> image = glance.images.get(IMAGE_ID)
|
||||
>>> image.delete()
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, endpoint=None, **kwargs):
|
||||
""" Initialize a new client for the Images v1.1 API. """
|
||||
super(Client, self).__init__(endpoint=endpoint, **kwargs)
|
||||
self.images = images.ImageManager(self)
|
||||
# NOTE(gabriel): If we have a pre-defined endpoint then we can
|
||||
# get away with lazy auth. Otherwise auth immediately.
|
||||
if endpoint is None:
|
||||
self.authenticate()
|
||||
else:
|
||||
self.management_url = endpoint
|
||||
|
||||
def authenticate(self):
|
||||
""" Authenticate against the Keystone API.
|
||||
|
||||
Uses the data provided at instantiation to authenticate against
|
||||
the Keystone server. This may use either a username and password
|
||||
or token for authentication. If a tenant id was provided
|
||||