Initial Python client bindings and CLI
Change-Id: Iac4a73acfb515c1e213a1dd0865a62bc39e3ed0f
This commit is contained in:
parent
fd06cdfad0
commit
6f280a8693
4
doc/requirements.txt
Normal file
4
doc/requirements.txt
Normal file
@ -0,0 +1,4 @@
|
||||
cliff
|
||||
jsonschema>=0.7
|
||||
requests
|
||||
python-keystoneclient>=0.2.0
|
243
doc/source/conf.py
Normal file
243
doc/source/conf.py
Normal file
@ -0,0 +1,243 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# monikerclient documentation build configuration file, created by
|
||||
# sphinx-quickstart on Wed Oct 31 18:58:17 2012.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys, os
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'monikerclient'
|
||||
copyright = u'2012, Managed I.T.'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
from monikerclient.version import version_info as monikerclient_version
|
||||
version = monikerclient_version.canonical_version_string()
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = monikerclient_version.version_string_with_vcs()
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = []
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
||||
|
||||
# -- Options for HTML output ---------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'default'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'monikerclientdoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output --------------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'monikerclient.tex', u'Moniker Client Documentation',
|
||||
u'Managed I.T.', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output --------------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'monikerclient', u'Moniker Client Documentation',
|
||||
[u'Managed I.T.'], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output ------------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'monikerclient', u'Moniker Client Documentation',
|
||||
u'Managed I.T.', 'monikerclient', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#texinfo_show_urls = 'footnote'
|
21
doc/source/index.rst
Normal file
21
doc/source/index.rst
Normal file
@ -0,0 +1,21 @@
|
||||
.. moniker documentation master file, created by
|
||||
sphinx-quickstart on Wed Oct 31 18:58:17 2012.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
Welcome to monikerclients's documentation!
|
||||
===================================
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
|
94
monikerclient/auth.py
Normal file
94
monikerclient/auth.py
Normal file
@ -0,0 +1,94 @@
|
||||
# Copyright 2012 Managed I.T.
|
||||
#
|
||||
# Author: Kiall Mac Innes <kiall@managedit.ie>
|
||||
#
|
||||
# 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 urlparse import urlparse
|
||||
from requests.auth import AuthBase
|
||||
|
||||
from keystoneclient.v2_0.client import Client
|
||||
|
||||
|
||||
class KeystoneAuth(AuthBase):
|
||||
def __init__(self, auth_url, username=None, password=None, tenant_id=None,
|
||||
tenant_name=None, token=None, service_type=None,
|
||||
endpoint_type=None):
|
||||
self.auth_url = str(auth_url).rstrip('/')
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.tenant_id = tenant_id
|
||||
self.tenant_name = tenant_name
|
||||
self.token = token
|
||||
|
||||
if (not username and not password) and not token:
|
||||
raise ValueError('A username and password, or token is required')
|
||||
|
||||
if not service_type or not endpoint_type:
|
||||
raise ValueError("Need service_type and/or endpoint_type")
|
||||
|
||||
self.service_type = service_type
|
||||
self.endpoint_type = endpoint_type
|
||||
|
||||
self.refresh_auth()
|
||||
|
||||
def __call__(self, request):
|
||||
if not self.token:
|
||||
self.refresh_auth()
|
||||
|
||||
request.headers['X-Auth-Token'] = self.token
|
||||
|
||||
return request
|
||||
|
||||
def get_ksclient(self):
|
||||
insecure = urlparse(self.auth_url).scheme != 'https'
|
||||
|
||||
return Client(username=self.username,
|
||||
password=self.password,
|
||||
tenant_id=self.tenant_id,
|
||||
tenant_name=self.tenant_name,
|
||||
auth_url=self.auth_url,
|
||||
insecure=insecure)
|
||||
|
||||
def get_endpoints(self, service_type=None, endpoint_type=None):
|
||||
return self.service_catalog.get_endpoints(
|
||||
service_type=service_type,
|
||||
endpoint_type=endpoint_type)
|
||||
|
||||
def get_url(self, service_type=None, endpoint_type=None):
|
||||
service_type = service_type or self.service_type
|
||||
endpoint_type = endpoint_type or self.endpoint_type
|
||||
endpoints = self.get_endpoints(service_type, endpoint_type)
|
||||
|
||||
return endpoints[service_type][0][endpoint_type].rstrip('/')
|
||||
|
||||
def refresh_auth(self):
|
||||
ks = self.get_ksclient()
|
||||
self.token = ks.auth_token
|
||||
self.service_catalog = ks.service_catalog
|
||||
|
||||
def args_hook(self, args):
|
||||
url = urlparse(args['url'])
|
||||
|
||||
if str(url.scheme) == '':
|
||||
if not self.token:
|
||||
self.refresh_token()
|
||||
|
||||
endpoints = self.get_endpoints()
|
||||
|
||||
if url.netloc in endpoints.keys():
|
||||
|
||||
args['url'] = '%s/%s?%s' % (
|
||||
self.get_url(),
|
||||
url.path.lstrip('/'),
|
||||
url.query
|
||||
)
|
94
monikerclient/cli/base.py
Normal file
94
monikerclient/cli/base.py
Normal file
@ -0,0 +1,94 @@
|
||||
# Copyright 2012 Managed I.T.
|
||||
#
|
||||
# Author: Kiall Mac Innes <kiall@managedit.ie>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
import abc
|
||||
from cliff.command import Command as CliffCommand
|
||||
from cliff.lister import Lister
|
||||
from cliff.show import ShowOne
|
||||
from monikerclient.v1 import Client
|
||||
|
||||
|
||||
class Command(CliffCommand):
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
def run(self, parsed_args):
|
||||
client_args = {
|
||||
'endpoint': self.app.options.os_endpoint,
|
||||
'auth_url': self.app.options.os_auth_url,
|
||||
'username': self.app.options.os_username,
|
||||
'password': self.app.options.os_password,
|
||||
'tenant_id': self.app.options.os_tenant_id,
|
||||
'tenant_name': self.app.options.os_tenant_name,
|
||||
'token': self.app.options.os_token,
|
||||
'region_name': self.app.options.os_region_name,
|
||||
}
|
||||
|
||||
self.client = Client(**client_args)
|
||||
|
||||
return super(Command, self).run(parsed_args)
|
||||
|
||||
@abc.abstractmethod
|
||||
def execute(self, parsed_args):
|
||||
"""
|
||||
Execute something, this is since we overload self.take_action()
|
||||
in order to format the data
|
||||
|
||||
This method __NEEDS__ to be overloaded!
|
||||
|
||||
:param parsed_args: The parsed args that are given by take_action()
|
||||
"""
|
||||
|
||||
def post_execute(self, data):
|
||||
"""
|
||||
Format the results locally if needed, by default we just return data
|
||||
|
||||
:param data: Whatever is returned by self.execute()
|
||||
"""
|
||||
return data
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
# TODO: Common Exception Handling Here
|
||||
results = self.execute(parsed_args)
|
||||
return self.post_execute(results)
|
||||
|
||||
|
||||
class ListCommand(Command, Lister):
|
||||
def post_execute(self, results):
|
||||
if len(results) > 0:
|
||||
column_names = results[0].keys()
|
||||
data = [r.values() for r in results]
|
||||
|
||||
return column_names, data
|
||||
else:
|
||||
return [], ()
|
||||
|
||||
|
||||
class GetCommand(Command, ShowOne):
|
||||
def post_execute(self, results):
|
||||
return results.keys(), results.values()
|
||||
|
||||
|
||||
class CreateCommand(Command, ShowOne):
|
||||
def post_execute(self, results):
|
||||
return results.keys(), results.values()
|
||||
|
||||
|
||||
class UpdateCommand(Command, ShowOne):
|
||||
def post_execute(self, results):
|
||||
return results.keys(), results.values()
|
||||
|
||||
|
||||
class DeleteCommand(Command):
|
||||
pass
|
98
monikerclient/cli/domains.py
Normal file
98
monikerclient/cli/domains.py
Normal file
@ -0,0 +1,98 @@
|
||||
# Copyright 2012 Managed I.T.
|
||||
#
|
||||
# Author: Kiall Mac Innes <kiall@managedit.ie>
|
||||
#
|
||||
# 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 monikerclient.cli import base
|
||||
from monikerclient.v1.domains import Domain
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ListDomainsCommand(base.ListCommand):
|
||||
""" List Domains """
|
||||
|
||||
def execute(self, parsed_args):
|
||||
return self.client.domains.list()
|
||||
|
||||
|
||||
class GetDomainCommand(base.GetCommand):
|
||||
""" Get Domain """
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(GetDomainCommand, self).get_parser(prog_name)
|
||||
|
||||
parser.add_argument('--domain-id', help="Domain ID", required=True)
|
||||
|
||||
return parser
|
||||
|
||||
def execute(self, parsed_args):
|
||||
return self.client.domains.get(parsed_args.domain_id)
|
||||
|
||||
|
||||
class CreateDomainCommand(base.CreateCommand):
|
||||
""" Create Domain """
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(CreateDomainCommand, self).get_parser(prog_name)
|
||||
|
||||
parser.add_argument('--domain-name', help="Domain Name", required=True)
|
||||
parser.add_argument('--domain-email', help="Domain Email",
|
||||
required=True)
|
||||
|
||||
return parser
|
||||
|
||||
def execute(self, parsed_args):
|
||||
domain = Domain(
|
||||
name=parsed_args.domain_name,
|
||||
email=parsed_args.domain_email
|
||||
)
|
||||
|
||||
return self.client.domains.create(domain)
|
||||
|
||||
|
||||
class UpdateDomainCommand(base.UpdateCommand):
|
||||
""" Update Domain """
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(UpdateDomainCommand, self).get_parser(prog_name)
|
||||
|
||||
parser.add_argument('--domain-id', help="Domain ID", required=True)
|
||||
parser.add_argument('--domain-name', help="Domain Name")
|
||||
parser.add_argument('--domain-email', help="Domain Email")
|
||||
|
||||
return parser
|
||||
|
||||
def execute(self, parsed_args):
|
||||
# TODO: API needs updating.. this get is silly
|
||||
domain = self.client.domains.get(parsed_args.domain_id)
|
||||
|
||||
# TODO: How do we tell if an arg was supplied or intentionally set to
|
||||
# None?
|
||||
|
||||
return self.client.domains.update(domain)
|
||||
|
||||
|
||||
class DeleteDomainCommand(base.DeleteCommand):
|
||||
""" Delete Domain """
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(DeleteDomainCommand, self).get_parser(prog_name)
|
||||
|
||||
parser.add_argument('--domain-id', help="Domain ID")
|
||||
|
||||
return parser
|
||||
|
||||
def execute(self, parsed_args):
|
||||
return self.client.domains.delete(parsed_args.domain_id)
|
@ -21,3 +21,23 @@ class Base(Exception):
|
||||
|
||||
class ResourceNotFound(Base):
|
||||
pass
|
||||
|
||||
|
||||
class RemoteError(Base):
|
||||
pass
|
||||
|
||||
|
||||
class Unknown(RemoteError):
|
||||
pass
|
||||
|
||||
|
||||
class Forbidden(RemoteError):
|
||||
pass
|
||||
|
||||
|
||||
class Conflict(RemoteError):
|
||||
pass
|
||||
|
||||
|
||||
class NotFound(RemoteError):
|
||||
pass
|
||||
|
@ -13,7 +13,7 @@
|
||||
# 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
|
||||
from cliff.app import App
|
||||
from cliff.commandmanager import CommandManager
|
||||
|
||||
@ -25,3 +25,41 @@ class MonikerShell(App):
|
||||
version='0.1',
|
||||
command_manager=CommandManager('moniker.cli'),
|
||||
)
|
||||
|
||||
def build_option_parser(self, description, version, argparse_kwargs=None):
|
||||
parser = super(MonikerShell, self).build_option_parser(
|
||||
description, version, argparse_kwargs)
|
||||
|
||||
parser.add_argument('--os-endpoint',
|
||||
default=os.environ.get('OS_SERVICE_ENDPOINT'),
|
||||
help="Defaults to env[OS_SERVICE_ENDPOINT]")
|
||||
|
||||
parser.add_argument('--os-auth-url',
|
||||
default=os.environ.get('OS_AUTH_URL'),
|
||||
help="Defaults to env[OS_AUTH_URL]")
|
||||
|
||||
parser.add_argument('--os-username',
|
||||
default=os.environ.get('OS_USERNAME'),
|
||||
help="Defaults to env[OS_USERNAME]")
|
||||
|
||||
parser.add_argument('--os-password',
|
||||
default=os.environ.get('OS_PASSWORD'),
|
||||
help="Defaults to env[OS_PASSWORD]")
|
||||
|
||||
parser.add_argument('--os-tenant-id',
|
||||
default=os.environ.get('OS_TENANT_ID'),
|
||||
help="Defaults to env[OS_TENANT_ID]")
|
||||
|
||||
parser.add_argument('--os-tenant-name',
|
||||
default=os.environ.get('OS_TENANT_NAME'),
|
||||
help="Defaults to env[OS_TENANT_NAME]")
|
||||
|
||||
parser.add_argument('--os-token',
|
||||
default=os.environ.get('OS_SERVICE_TOKEN'),
|
||||
help="Defaults to env[OS_SERVICE_TOKEN]")
|
||||
|
||||
parser.add_argument('--os-region-name',
|
||||
default=os.environ.get('OS_REGION_NAME'),
|
||||
help="Defaults to env[OS_REGION_NAME]")
|
||||
|
||||
return parser
|
||||
|
0
monikerclient/tests/__init__.py
Normal file
0
monikerclient/tests/__init__.py
Normal file
@ -0,0 +1,98 @@
|
||||
# Copyright 2012 Managed I.T.
|
||||
#
|
||||
# Author: Kiall Mac Innes <kiall@managedit.ie>
|
||||
#
|
||||
# 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 requests
|
||||
from urlparse import urlparse
|
||||
from monikerclient import exceptions
|
||||
from monikerclient.auth import KeystoneAuth
|
||||
from monikerclient.v1 import domains
|
||||
from monikerclient.v1 import records
|
||||
from monikerclient.v1 import servers
|
||||
|
||||
|
||||
class Client(object):
|
||||
""" Client for the Moniker v1 API """
|
||||
|
||||
def __init__(self, endpoint=None, auth_url=None, username=None,
|
||||
password=None, tenant_id=None, tenant_name=None, token=None,
|
||||
region_name=None, endpoint_type='publicURL'):
|
||||
"""
|
||||
:param endpoint: Endpoint URL
|
||||
:param auth_url: Keystone auth_url
|
||||
:param username: The username to auth with
|
||||
:param password: The password to auth with
|
||||
:param tenant_id: The tenant ID
|
||||
:param tenant_name: The tenant name
|
||||
:param token: A token instead of username / password
|
||||
:param region_name: The region name
|
||||
:param endpoint_type: The endpoint type (publicURL for example)
|
||||
"""
|
||||
if auth_url:
|
||||
auth = KeystoneAuth(auth_url, username, password, tenant_id,
|
||||
tenant_name, token, 'dns', endpoint_type)
|
||||
endpoint = auth.get_url()
|
||||
elif endpoint:
|
||||
auth = None
|
||||
else:
|
||||
raise ValueError('Either an auth_url or endpoint must be supplied')
|
||||
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
|
||||
def _ensure_url_hook(args):
|
||||
url_ = urlparse(args['url'])
|
||||
if not url_.scheme:
|
||||
args['url'] = endpoint + url_.path
|
||||
|
||||
hooks = {'args': _ensure_url_hook}
|
||||
|
||||
self.requests = requests.session(
|
||||
auth=auth,
|
||||
headers=headers,
|
||||
hooks=hooks)
|
||||
|
||||
self.domains = domains.DomainsController(client=self)
|
||||
self.records = records.RecordsController(client=self)
|
||||
self.servers = servers.ServersController(client=self)
|
||||
|
||||
def wrap_api_call(self, func, *args, **kw):
|
||||
"""
|
||||
Wrap a self.<rest function> with exception handling
|
||||
|
||||
:param func: The function to wrap
|
||||
"""
|
||||
response = func(*args, **kw)
|
||||
|
||||
if response.status_code in (401, 403):
|
||||
raise exceptions.Forbidden()
|
||||
elif response.status_code == 404:
|
||||
raise exceptions.NotFound()
|
||||
elif response.status_code == 409:
|
||||
raise exceptions.Conflict()
|
||||
elif response.status_code == 500:
|
||||
raise exceptions.Unknown()
|
||||
else:
|
||||
return response
|
||||
|
||||
def get(self, path, **kw):
|
||||
return self.wrap_api_call(self.requests.get, path, **kw)
|
||||
|
||||
def post(self, path, **kw):
|
||||
return self.wrap_api_call(self.requests.post, path, **kw)
|
||||
|
||||
def put(self, path, **kw):
|
||||
return self.wrap_api_call(self.requests.put, path, **kw)
|
||||
|
||||
def delete(self, path, **kw):
|
||||
return self.wrap_api_call(self.requests.delete, path, **kw)
|
53
monikerclient/v1/base.py
Normal file
53
monikerclient/v1/base.py
Normal file
@ -0,0 +1,53 @@
|
||||
# Copyright 2012 Managed I.T.
|
||||
#
|
||||
# Author: Kiall Mac Innes <kiall@managedit.ie>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
import abc
|
||||
|
||||
|
||||
class Controller(object):
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
|
||||
def list(self, *args, **kw):
|
||||
"""
|
||||
List something
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get(self, *args, **kw):
|
||||
"""
|
||||
Get something
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def create(self, *args, **kw):
|
||||
"""
|
||||
Create something
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def update(self, *args, **kw):
|
||||
"""
|
||||
Update something
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def delete(self, *args, **kw):
|
||||
"""
|
||||
Delete something
|
||||
"""
|
||||
raise NotImplementedError
|
@ -1,27 +0,0 @@
|
||||
# Copyright 2012 Managed I.T.
|
||||
#
|
||||
# Author: Kiall Mac Innes <kiall@managedit.ie>
|
||||
#
|
||||
# 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 monikerclient.v1 import domains
|
||||
from monikerclient.v1 import records
|
||||
from monikerclient.v1 import servers
|
||||
|
||||
|
||||
class Client(object):
|
||||
""" Client for the Moniker v1 API """
|
||||
|
||||
def __init__(self):
|
||||
self.domains = domains.Controller()
|
||||
self.records = records.Controller()
|
||||
self.servers = servers.Controller()
|
@ -13,20 +13,25 @@
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
import json
|
||||
from monikerclient import warlock
|
||||
from monikerclient import utils
|
||||
from monikerclient.v1.base import Controller
|
||||
|
||||
|
||||
Domain = warlock.model_factory(utils.load_schema('v1', 'domain'))
|
||||
|
||||
|
||||
class Controller(object):
|
||||
class DomainsController(Controller):
|
||||
def list(self):
|
||||
"""
|
||||
Retrieve a list of domains
|
||||
|
||||
:returns: A list of :class:`Domain`s
|
||||
"""
|
||||
response = self.client.get('/domains')
|
||||
|
||||
return [Domain(i) for i in response.json['domains']]
|
||||
|
||||
def get(self, domain_id):
|
||||
"""
|
||||
@ -35,6 +40,9 @@ class Controller(object):
|
||||
:param domain_id: Domain Identifier
|
||||
:returns: :class:`Domain`
|
||||
"""
|
||||
response = self.client.get('/domains/%s' % domain_id)
|
||||
|
||||
return Domain(response.json)
|
||||
|
||||
def create(self, domain):
|
||||
"""
|
||||
@ -43,6 +51,9 @@ class Controller(object):
|
||||
:param domain: A :class:`Domain` to create
|
||||
:returns: :class:`Domain`
|
||||
"""
|
||||
response = self.client.post('/domains', data=json.dumps(domain))
|
||||
|
||||
return Domain(response.json)
|
||||
|
||||
def update(self, domain):
|
||||
"""
|
||||
@ -51,6 +62,10 @@ class Controller(object):
|
||||
:param domain: A :class:`Domain` to update
|
||||
:returns: :class:`Domain`
|
||||
"""
|
||||
response = self.client.put('/domains/%s' % domain.id,
|
||||
data=json.dumps(domain.changes))
|
||||
|
||||
return Domain(response.json)
|
||||
|
||||
def delete(self, domain):
|
||||
"""
|
||||
@ -58,3 +73,7 @@ class Controller(object):
|
||||
|
||||
:param domain: A :class:`Domain`, or Domain Identifier to delete
|
||||
"""
|
||||
if isinstance(domain, Domain):
|
||||
self.client.delete('/domains/%s' % domain.id)
|
||||
else:
|
||||
self.client.delete('/domains/%s' % domain)
|
||||
|
@ -13,20 +13,25 @@
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
import json
|
||||
from monikerclient import warlock
|
||||
from monikerclient import utils
|
||||
from monikerclient.v1.base import Controller
|
||||
|
||||
|
||||
Record = warlock.model_factory(utils.load_schema('v1', 'record'))
|
||||
|
||||
|
||||
class Controller(object):
|
||||
class RecordsController(Controller):
|
||||
def list(self):
|
||||
"""
|
||||
Retrieve a list of records
|
||||
|
||||
:returns: A list of :class:`Record`s
|
||||
"""
|
||||
response = self.client.get('/records')
|
||||
|
||||
return [Record(i) for i in response.json['records']]
|
||||
|
||||
def get(self, record_id):
|
||||
"""
|
||||
@ -35,6 +40,9 @@ class Controller(object):
|
||||
:param record_id: Record Identifier
|
||||
:returns: :class:`Record`
|
||||
"""
|
||||
response = self.client.get('/records/%s' % record_id)
|
||||
|
||||
return Record(response.json)
|
||||
|
||||
def create(self, record):
|
||||
"""
|
||||
@ -43,6 +51,9 @@ class Controller(object):
|
||||
:param record: A :class:`Record` to create
|
||||
:returns: :class:`Record`
|
||||
"""
|
||||
response = self.client.post('/records', data=json.dumps(record))
|
||||
|
||||
return record.update(response.json)
|
||||
|
||||
def update(self, record):
|
||||
"""
|
||||
@ -51,6 +62,10 @@ class Controller(object):
|
||||
:param record: A :class:`Record` to update
|
||||
:returns: :class:`Record`
|
||||
"""
|
||||
response = self.client.put('/records/%s' % record.id,
|
||||
data=json.dumps(record))
|
||||
|
||||
return record.update(response.json)
|
||||
|
||||
def delete(self, record):
|
||||
"""
|
||||
@ -58,3 +73,7 @@ class Controller(object):
|
||||
|
||||
:param record: A :class:`Record`, or Record Identifier to delete
|
||||
"""
|
||||
if isinstance(record, Record):
|
||||
self.client.delete('/records/%s' % record.id)
|
||||
else:
|
||||
self.client.delete('/records/%s' % record)
|
||||
|
@ -13,20 +13,25 @@
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
import json
|
||||
from monikerclient import warlock
|
||||
from monikerclient import utils
|
||||
from monikerclient.v1.base import Controller
|
||||
|
||||
|
||||
Server = warlock.model_factory(utils.load_schema('v1', 'server'))
|
||||
|
||||
|
||||
class Controller(object):
|
||||
class ServersController(Controller):
|
||||
def list(self):
|
||||
"""
|
||||
Retrieve a list of servers
|
||||
|
||||
:returns: A list of :class:`Server`s
|
||||
"""
|
||||
response = self.client.get('/servers')
|
||||
|
||||
return [Server(i) for i in response.json['servers']]
|
||||
|
||||
def get(self, server_id):
|
||||
"""
|
||||
@ -35,6 +40,9 @@ class Controller(object):
|
||||
:param server_id: Server Identifier
|
||||
:returns: :class:`Server`
|
||||
"""
|
||||
response = self.client.get('/servers/%s' % server_id)
|
||||
|
||||
return Server(response.json)
|
||||
|
||||
def create(self, server):
|
||||
"""
|
||||
@ -43,6 +51,9 @@ class Controller(object):
|
||||
:param server: A :class:`Server` to create
|
||||
:returns: :class:`Server`
|
||||
"""
|
||||
response = self.client.post('/servers', data=json.dumps(server))
|
||||
|
||||
return server.update(response.json)
|
||||
|
||||
def update(self, server):
|
||||
"""
|
||||
@ -51,6 +62,10 @@ class Controller(object):
|
||||
:param server: A :class:`Server` to update
|
||||
:returns: :class:`Server`
|
||||
"""
|
||||
response = self.client.put('/servers/%s' % server.id,
|
||||
data=json.dumps(server))
|
||||
|
||||
return server.update(response.json)
|
||||
|
||||
def delete(self, server):
|
||||
"""
|
||||
@ -58,3 +73,7 @@ class Controller(object):
|
||||
|
||||
:param server: A :class:`Server`, or Server Identifier to delete
|
||||
"""
|
||||
if isinstance(server, Server):
|
||||
self.client.delete('/servers/%s' % server.id)
|
||||
else:
|
||||
self.client.delete('/servers/%s' % server)
|
||||
|
@ -113,6 +113,9 @@ def model_factory(schema):
|
||||
def itervalues(self):
|
||||
return copy.deepcopy(dict(self)).itervalues()
|
||||
|
||||
def keys(self):
|
||||
return copy.deepcopy(dict(self)).keys()
|
||||
|
||||
def values(self):
|
||||
return copy.deepcopy(dict(self)).values()
|
||||
|
||||
|
@ -6,3 +6,11 @@ cover-inclusive=true
|
||||
verbosity=2
|
||||
detailed-errors=1
|
||||
where=monikerclient/tests
|
||||
|
||||
[build_sphinx]
|
||||
source-dir = doc/source
|
||||
build-dir = doc/build
|
||||
all_files = 1
|
||||
|
||||
[upload_docs]
|
||||
upload-dir = doc/build/html
|
||||
|
9
setup.py
9
setup.py
@ -14,6 +14,7 @@
|
||||
# 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 textwrap
|
||||
from setuptools import setup, find_packages
|
||||
from monikerclient.openstack.common import setup as common_setup
|
||||
from monikerclient.version import version_info as version
|
||||
@ -46,6 +47,14 @@ setup(
|
||||
'bin/moniker',
|
||||
],
|
||||
cmdclass=common_setup.get_cmdclass(),
|
||||
entry_points=textwrap.dedent("""
|
||||
[moniker.cli]
|
||||
domain-list = monikerclient.cli.domains:ListDomainsCommand
|
||||
domain-get = monikerclient.cli.domains:GetDomainCommand
|
||||
domain-create = monikerclient.cli.domains:CreateDomainCommand
|
||||
domain-update = monikerclient.cli.domains:UpdateDomainCommand
|
||||
domain-delete = monikerclient.cli.domains:DeleteDomainCommand
|
||||
"""),
|
||||
classifiers=[
|
||||
'Development Status :: 3 - Alpha',
|
||||
'Topic :: Internet :: Name Service (DNS)',
|
||||
|
@ -1,2 +1,4 @@
|
||||
cliff
|
||||
jsonschema>=0.7
|
||||
requests
|
||||
python-keystoneclient>=0.2.0
|
||||
|
@ -1,3 +1,4 @@
|
||||
nose
|
||||
mox
|
||||
openstack.nose_plugin
|
||||
sphinx
|
||||
|
Loading…
x
Reference in New Issue
Block a user